iT邦幫忙

2021 iThome 鐵人賽

DAY 13
0
Modern Web

Laravel 實務筆記系列 第 13

Eloquent ORM - 編輯資料

編輯單一資料

如果用 Route::resource 建立 API 的話,編輯單一資料的路由會是

請求方法 路由 控制器函式 路由名稱標籤
PUT/PATCH /todos/{id} update photos.update

在 TodoController 編輯 update 函式

     public function update(Request $request, Todo $todo)
     {
-        //
+        $data = $request->all(); // 取得請求中的 body 資料
+ 
+        $todo->name = $data['name'];
+    
+        $todo->save();
     }

update 函式中宣告了 Todo $todo 這個參數,這樣 Laravel 的 Service Container 就會自動根據路由中的 id 找出該筆資料,建立 Todo Model 的實例。

有了實例後剩下的就跟新增資料時一樣了,從 Request 中取出 name 值,更新實例的欄位後呼叫 save() 更新資料。

如果更新資料的邏輯不複雜的話,也可以用快捷式的 update 方法

     public function update(Request $request, Todo $todo)
     {
-        //
+        $data = $request->all(); // 取得請求中的 body 資料
+  
+        $todo->update([
+            'name' => $data['name']
+        ]);     
     }

跟 create 一樣, 要將想寫入的欄位在 Model 中加入 $fillable 才能夠更新。

編輯多筆資料

另外 update 可以用於一次性更新多筆資料

Flight::where('active', 1)
      ->where('destination', 'San Diego')
      ->update(['delayed' => 1]);

用 where 搜尋出來的多筆資料會被一併更新 delayed 欄位。

更新 Material UI

就在寫文的期間 Material UI 更新到 5.0.1 版了,套件名稱都不同,通通重裝一遍。

sail yarn remove @material-ui/core
sail yarn add @mui/material @emotion/react @emotion/styled 

接下來要加上修改之前建立好的 Todo 資料功能,會用到圖案按鈕所以先來加上圖案套件:

sail yarn add @mui/icons-material

Dashboard 畫面整個翻新

import React from "react";
import Authenticated from "@/Layouts/Authenticated";
import {
  Container,
  Button,
  List,
  ListItem,
  ListItemText,
  TextField,
  Grid,
  IconButton,
  Box,
} from "@mui/material";
import { Head, Link, useForm } from "@inertiajs/inertia-react";
import { Inertia } from "@inertiajs/inertia";

import DeleteIcon from "@mui/icons-material/Delete";
import CreateIcon from "@mui/icons-material/Create";

export default function Dashboard(props) {
  const { todos } = props; // 取出 todos
  const { data, setData, post, processing, errors, reset, transform } = useForm(
    {
      todo: "",
    }
  );

  const handleChange = (event) => {
    setData(
      event.target.name,
      event.target.type === "checkbox"
        ? event.target.checked
        : event.target.value
    );
  };

  const submit = (e) => {
    e.preventDefault();

    // 轉換欄位名稱
    transform((data) => ({
      name: data.todo,
    }));

    // 發送請求
    post(route("todo.store"), {
      // 新增資料結束後重載 todos
      onFinish: (visit) => {
        Inertia.reload({ only: ["todos"] });
        reset(); //另外順便清空輸入框
      },
    });
  };

  return (
    <Authenticated
      auth={props.auth}
      errors={props.errors}
      header={
        <h2 className='font-semibold text-xl text-gray-800 leading-tight'>
          Dashboard
        </h2>
      }>
      <Head title='Dashboard' />

      <div className='py-12'>
        <div className='max-w-7xl mx-auto sm:px-6 lg:px-8'>
          <div className='bg-white overflow-hidden shadow-sm sm:rounded-lg'>
            <div className='p-6 bg-white border-b border-gray-200'>
              You logged in!
            </div>
          </div>
        </div>
      </div>
      <Container>
        <form onSubmit={submit}>
          <Grid
            container
            direction='row'
            justifyContent='flex-start'
            alignItems='flex-end'>
            <TextField
              id='todo'
              label='Todo'
              name='todo'
              value={data.todo}
              onChange={handleChange}
              variant='standard'
              style={{ marginRight: 4 }}
            />
            <Button
              type='submit'
              variant='contained'
              color='primary'
              disabled={processing}>
              Add
            </Button>
          </Grid>
        </form>
        <Box sx={{ width: "100%", maxWidth: 360 }}>
          <List>
            {todos.map((item, index) => (
              <ListItem
                button
                key={index}
                secondaryAction={
                  <IconButton edge='end' aria-label='edit'>
                    <CreateIcon />
                  </IconButton>
                }>
                <ListItemText primary={item.name} />
              </ListItem>
            ))}
          </List>
        </Box>
      </Container>
    </Authenticated>
  );
}

準備更新功能

稍微修改一下畫面的功能,在點擊編輯按鈕後開始編輯 Todo ,按下 Enter 的話就準備送出更新請求。

@@ -1,6 +1,5 @@
-import React from "react";
+import React, { useState } from "react";
 import Authenticated from "@/Layouts/Authenticated";
-import clsx from "clsx";
 import {
   Container,
   Button,
@@ -26,6 +25,10 @@ export default function Dashboard(props) {
     }
   );
 
+  const [todoList, setTodoList] = useState(
+    todos.map((todo) => ({ ...todo, editing: false }))
+  );
+
   const handleChange = (event) => {
     setData(
       event.target.name,
@@ -55,6 +58,17 @@ export default function Dashboard(props) {
     });
   };
 
+  const edit = (todoId) => {
+    console.log(`edit ${todoId}`);
+
+    const newList = todoList.map((todo) =>
+      todo.id === todoId
+        ? { ...todo, editing: true }
+        : { ...todo, editing: false }
+    );
+    setTodoList(newList);
+  };
+
   return (
     <Authenticated
       auth={props.auth}
@@ -102,16 +116,41 @@ export default function Dashboard(props) {
         </form>
         <Box sx={{ width: "100%", maxWidth: 360 }}>
           <List>
-            {todos.map((item, index) => (
+            {todoList.map((item) => (
               <ListItem
                 button
-                key={index}
+                key={item.id}
                 secondaryAction={
-                  <IconButton edge='end' aria-label='edit'>
+                  <IconButton
+                    edge='end'
+                    aria-label='edit'
+                    onClick={() => {
+                      edit(item.id);
+                    }}>
                     <CreateIcon />
                   </IconButton>
                 }>
-                <ListItemText primary={item.name} />
+                {item.editing ? (
+                  <TextField
+                    autoFocus
+                    placeholder={item.name}
+                    variant='standard'
+                    onBlur={() => {
+                      setTodoList(
+                        todos.map((todo) => ({ ...todo, editing: false }))
+                      );
+                    }}
+                    onKeyDown={(e) => {
+                      if (e.key == "Enter") {
+                        console.log("value", e.target.value);
+
+                        // Update todo
+                      }
+                    }}
+                  />
+                ) : (
+                  <ListItemText primary={item.name} />
+                )}
               </ListItem>
             ))}
           </List>

建立畫面請求

用 Inertia.put 發送 put 請求給 todo.update 路由,記得指定路由中的 id 值。

然後一樣在請求完成後重新拉一次 todos 資料。

@@ -69,6 +69,20 @@ export default function Dashboard(props) {
     setTodoList(newList);
   };
 
+  const confirmEdit = (todo) => {
+    Inertia.put(
+      route("todo.update", { id: todo.id }),
+      {
+        name: todo.name,
+      },
+      {
+        onFinish: (visit) => {
+          Inertia.reload({ only: ["todos"] });
+        },
+      }
+    );
+  };
+
   return (
     <Authenticated
       auth={props.auth}
@@ -145,6 +159,7 @@ export default function Dashboard(props) {
                         console.log("value", e.target.value);
 
                         // Update todo
+                        confirmEdit({ id: item.id, name: e.target.value });
                       }
                     }}
                   />

上一篇
Eloquent ORM - 讀取資料
下一篇
Eloquent ORM - 刪除資料
系列文
Laravel 實務筆記30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言