【Laravel實戰】從零開始學會用Livewire寫出可編輯的表格

【Laravel實戰】從零開始學會用Livewire寫出可編輯的表格

你是否有個需求是希望能夠更便利的修改資料庫表格的資料,就如同編輯Excel檔案一般。那今天的這個實作教學就是為你所寫的

本實作是在說明如何達成當按下表格的某欄資料後,能夠以inline的方式立即進行修改,就如同下圖那樣

其中點資料進行編輯,也就是Editable的功能是透過X-editable項目來實現,至於Table的佈局是透過Tailwind CSS來實現,詳細資料請看參考文件

Editable實作

Step 1.編輯Livewire組件

建立好組件之後,加入等會視圖所需的公開屬性

/app/Http/Livewire/EditableLink.php

<?php

namespace App\Http\Livewire;

use Livewire\Component;

class EditableLink extends Component
{
    public $model; //模型實例
    public $pk; //主鍵
    public $pk2; //主鍵2,用於複合主鍵,非必須
    public $col_key; //要編輯的欄位
    public $type = 'text'; //欄位輸入項類型
    public $options = []; //選項
    public $title = '請填入..'; //popup標題
    public $pkObj //主鍵物件,X-editable要用;

    public function mount()
    {
        $data = [$this->pk => $this->model->{$this->pk}];
        if(isset($this->pk2)){
            $data[$this->pk2] = $this->model->{$this->pk2};
        }
        $data['table'] = $this->model->getTable();
        $this->pkObj = json_encode($data);
    }

    public function render()
    {
        return view('livewire.editable-link');
    }
}

這個視圖是為了組合出 X-editable 所需要的 Anchor 標籤

//resources/views/livewire/editable-link.blade.php

<a href="#" data-title="{{ $title }}" class="xedit" id="{{ $col_key }}" data-type="{{ $type }}"
    data-source="{{ json_encode($options) }}" data-pk="{{ $pkObj }}" >
    {{ $model->{$col_key} }}
</a>

Step 2.編輯表格視圖

在每個資料的部分使用先前所建立的組件

組件使用範例如下:

這是單一主鍵的寫法

@livewire('editable-link', ['pk'=>'id','col_key'=>'name','model'=>$member],key('name-'.$member->id))

這是複合主鍵的寫法,需再加上pk2

@livewire('editable-link',['pk'=>'id','pk2'=>'id2','col_key'=>'name','model'=>$member],key('name-'.$member->id))

下面給出一個完整的前端編輯表格呈現頁面範例

//resources/views/livewire/member-table.blade.php

<div>
    <div class="flex flex-col">
        <div class="-my-2 overflow-x-auto sm:-mx-6 lg:-mx-8">
            <div class="py-2 align-middle inline-block min-w-full sm:px-6 lg:px-8">
                <div class="shadow overflow-hidden border-b border-gray-200 sm:rounded-lg">
                    <table class="min-w-full divide-y divide-gray-200">
                        <thead class="bg-gray-50">
                            <tr>
                                <th scope="col"
                                    class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
                                    姓名
                                </th>
                                <th scope="col"
                                    class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
                                    地址&電話
                                </th>
                                <th scope="col"
                                    class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
                                    手機
                                </th>
                                <th scope="col"
                                    class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
                                    狀態
                                </th>
                                <th scope="col" class="relative px-6 py-3">
                                    <span class="sr-only">刪除</span>
                                </th>
                            </tr>
                        </thead>
                        <tbody class="bg-white divide-y divide-gray-200">
                            @foreach ($members as $member)
                            <tr>
                                <td class="px-6 py-4 whitespace-nowrap">
                                    <div class="flex items-center">
                                        <div class="flex-shrink-0 h-10 w-10">
                                            <img class="h-10 w-10 rounded-full"
                                                src="https://images.unsplash.com/photo-1494790108377-be9c29b29330?ixlib=rb-1.2.1&amp;ixid=eyJhcHBfaWQiOjEyMDd9&amp;auto=format&amp;fit=facearea&amp;facepad=4&amp;w=256&amp;h=256&amp;q=60"
                                                alt="">
                                        </div>
                                        <div class="ml-4">
                                            <div class="text-sm font-medium text-gray-900">
                                                @livewire('editable-link',
                                                ['pk'=>'id'
                                                ,'col_key'=>'name','model'=>$member],key('name-'.$member->id))
                                            </div>
                                            <div class="text-sm text-gray-500">
                                                {{ $member->id }}
                                            </div>
                                        </div>
                                    </div>
                                </td>
                                <td class="px-6 py-4 whitespace-nowrap">
                                    <div class="text-sm text-gray-900">
                                        @livewire('editable-link',
                                        ['pk'=>'id','col_key'=>'addr','type'=>'textarea','model'=>$member],key('addr-'.$member->id))
                                    </div>
                                    <div class="text-sm text-gray-500">
                                        @livewire('editable-link',
                                        ['pk'=>'id','col_key'=>'tel','model'=>$member],key('tel-'.$member->id))
                                    </div>
                                </td>
                                <td class="px-6 py-4 whitespace-nowrap">
                                    @livewire('editable-link',
                                    ['pk'=>'id','col_key'=>'mobile','model'=>$member],key('mobile-'.$member->id))
                                </td>
                                <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
                                    @livewire('editable-link',
                                    ['pk'=>'id','col_key'=>'status','type'=>'select','model'=>$member,'options'=>$statuses],key('status-'.$member->id))
                                </td>
                                <td class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
                                    <a href="#" class="text-indigo-600 hover:text-indigo-900">刪除</a>
                                </td>
                            </tr>
                            @endforeach
                        </tbody>
                    </table>
                </div>
            </div>
        </div>
    </div>

</div>

<!-- 請確保app.blade.php裡頭有css這個Stack-->
@push('css')
<link href="//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap.min.css" rel="stylesheet">
<link href="//cdnjs.cloudflare.com/ajax/libs/x-editable/1.5.0/bootstrap3-editable/css/bootstrap-editable.css"
    rel="stylesheet" />

@endpush

<!-- 請確保app.blade.php裡頭有js這個Stack-->
@push('js')
<script src="http://code.jquery.com/jquery-2.0.3.min.js"></script>
<script src="//netdna.bootstrapcdn.com/bootstrap/3.0.0/js/bootstrap.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/x-editable/1.5.0/bootstrap3-editable/js/bootstrap-editable.min.js">
</script>
<script>
    //設定編輯模式為inline,預設為popup
    $.fn.editable.defaults.mode = 'inline';

   function initXedit()
    {
        $('.xedit').editable({
            url: '{{url("api/table/update")}}',
            showbuttons: true, //是否要出現確認按鈕
            toggle: 'click', //互動方式,其他選項有dbclick.mouseenter
            success: function (response, newValue) {
            console.log('Updated', response)
            }
        });
    }

    //為Editable輸入項加入編輯功能
    $(document).ready(function () {
        $.ajaxSetup({
            headers: {
                'X-CSRF-TOKEN': '{{csrf_token()}}'
            }
        });

        initXedit();

    });

</script>
@endpush

編輯表格控制器


<?php

namespace App\Http\Livewire;

use Livewire\Component;
use App\Models\Member;
class MemberTable extends Component
{
    //select選單要用的選項
    public $statuses = ['1' => '開啟', '2' => '關閉'];

    public function render()
    {
        $members = Member::take(5)->get();
        return view('livewire.member-table',compact('members'))->layout('layouts.app');
    }

}

編輯Update API

用於等會前端程式呼叫之用

//app/Http/Controllers/ApiController.php

use DB;
use Log;

//支持 X-editable 功能的 API
    public function updateTable(Request $request)
    {
        $pk = $request->input('pk');
        //Log::debug($pk);
        $pk_keys = array_keys($pk);
        $pk_keys = array_diff($pk_keys,['table']);
        //Log::debug($pk_keys);
        if ($request->ajax()) {
            $query = DB::table($pk['table']);
            //支持複合主鍵
            foreach($pk_keys as $key){
                $query = $query->where($key,$pk[$key]);
            }
            $query->update([$request->input('name') => $request->input('value')]);
            return response()->json(['success' => true]);
        }
    }

編輯API路由

//routes/api.php

Route::post('/table/update', 'App\Http\Controllers\ApiController@updateTable');

分頁實作

安裝 Tailwind CSS

因為 Laravel8 預設的分頁是透過 Tailwind CSS 來實現,所以需要先安裝好 Tailwind CSS

Step 1.安裝前端套件

npm install

Step 2.透過npm來安裝 Tailwind CSS

npm install -D tailwindcss@latest postcss@latest autoprefixer@latest

Step 3.建立 Tailwind CSS 設定檔案

npx tailwindcss init

生成好的檔案內容如下:

// tailwind.config.js

module.exports = {
  purge: [],
  darkMode: false, // or 'media' or 'class'
  theme: {
    extend: {},
  },
  variants: {
    extend: {},
  },
  plugins: [],
}
Step 4.修改設定檔內的purge設定

將之改成如下:

// tailwind.config.js

purge: [
     './resources/**/*.blade.php',
     './resources/**/*.js',
     './resources/**/*.vue',
   ],
Step 5.在Laravel Mix 設置檔中加入 tailwind CSS
//webpack.mix.js

mix.js("resources/js/app.js", "public/js")
    .postCss("resources/css/app.css", "public/css", [
     require("tailwindcss"),
    ]);
Step 6.編輯 app.css 素材檔

在 app.css 加入以下程式碼

//resources/css/app.css 

@tailwind base;
@tailwind components;
@tailwind utilities;
Step 7.在佈局視圖載入 app.css

在 resources/views/layouts/app.blade.php 加入以下內容

<meta charset="UTF-8" />
   <meta name="viewport" content="width=device-width, initial-scale=1.0" />
   <link href="{{ asset('css/app.css') }}" rel="stylesheet">
Step 8.編譯 css/app.css

執行以下指令

npm run dev (開發環境)
npm run production (正式環境)

修改表格視圖

在要呈現分頁連結的位置加入以下程式碼

{{ $hcusts->links() }}

在最後的 script 區域加入以下程式碼

//每次翻頁都要再做initXedit
window.addEventListener('turn-page', event => {
//alert('Name updated to: ' + event.detail.newName);
    initXedit()
})

修改表格控制器

在控制器或組件類別裡頭加入分頁相關的程式碼,以下用全頁組件作示範,列出需修改的部分

//app\Http\Livewire\MemberTable.php

use Livewire\WithPagination;

class MemberTable extends Component
{
    //啟動分頁功能,預設為Tailwind,需要先安裝
    use WithPagination;
    ...

    //每次換頁時都會呼叫這個方法,用來發出事件
    public function hydrate()
    {
        $this->dispatchBrowserEvent('turn-page');
    }

    //render方法要微調
    public function render()
    {
        //分頁查詢要改呼叫paginate,5為一頁的筆數
        $hcusts = Hcust::paginate(5);
        return view('livewire.hcust-table',compact('hcusts'))->layout('layouts.app');
    }
}

參考文件


分享這篇文章:

關聯文章:

訂閱電子報,索取 Laravel 學習手冊

價值超過 3000 元,包含常用 Laravel 語法與指令!

Laravel 百萬年薪特訓營

從最基礎的 PHP 語法開始,包含所有你該知道的網頁基礎知識,連同 Laravel 從零開始一直到實戰,最後還將告訴你如何找好工作,讓你及早擁有百萬年薪