Concrete5にVueCLIを使ってUIを構築する。3【編集画面と一覧画面】
技術スタック concrete5JavascriptVue.js

Concrete5にVueCLIを使ってUIを構築する。3【編集画面と一覧画面】

2020.10.04

こんにちはjunです。Concrete5にVueCLIを使ってUIを構築する 2の記事の続きを書いていきます。前回の記事ではフォームの作成・登録まで行いました。この記事では登録したデータの編集とファイルマネージャーのvueコンポーネント化を行っていきます。

編集画面は追加画面とコンポーネントを共有し、データベースからAjaxでデータを取得してコンポーネントに代入をします。そして登録したデータの操作が行える一覧画面を作成します。

編集画面のレンダーとAjax設定

まずは編集画面から作成していきます。編集は追加と違ってデータベースからデータを取得して、初期値として当てはめる必要があります。PHPであればvalue="<?php echo $data?>"みたいに挿入することで簡単に実現できますが、vueを使うとなれば一捻り必要です。

jsでフロントを構築する場合、基本的にDBのデータはAjaxを用いて再度サーバーにデータを要求します。concrete5でもAjaxとそのエントリーを実装することができます。まずエントリーの設定からやってみましょう。

Ajaxエントリー(REST API)の作成

今回はシングルページのコントローラーを用いてAjax用のエントリーを実装します。Ajaxでデータを取得する際は基本的に取得用のURLを作成して、そのURLに対してAjaxを飛ばしてJSONデータをレスポンスとして受け取るのが定石です。

vuetest
├── controller.php //これはパッケージのcontroller
├── controllers
│   └── single_page
│       └── dashboard
│           └── vuetest.php
└── single_pages
    └── dashboard
        └── vuetest
            ├── add.php   //追加画面シングルページ
            ├── edit.php  //編集画面シングルページ
            └── view.php

では vuetest/controllers/single_page/dashboard/vuetest.php に以下のように記述します。

vuetest.php
public function edit($id=null){
    if($id==null) return Redirect::to('/dashboard/vuetest/')->send();

    if($_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest'){
        $db = Database::connection();
        $db->Execute("START TRANSACTION");
        $result = $db->fetchAll('SELECT * FROM album WHERE ID = ?',array($id));
        $db->Execute("COMMIT");
        return Core::make('helper/ajax')->sendResult($result);
    }

    $this->render('/dashboard/vuetest/edit');
}

まずは編集画面を /dashboard/vuetest/edit というURLで表示できるようにします。このメソッドではパラメータがあるので /dashboard/vuetest/edit/2 のようなURLを送信できます。数字の部分はアルバムのDBでのIDとします。(アルバムID)

つまり/dashboard/vuetest/edit/ にアルバムIDを加えて送信することで、指定したIDのデータを表示できるようにします。そしてリクエストがXMLHttpRequestつまり、AjaxであればデータJSONで返し、そうでなければ404を返すようにします。

リクエストの種別を限定することで、ブラウザなどでAjax専用の /dashboard/vuetest/loadData というURLを叩いても404しか表示されません。

このようなAPIを作成するときは、データを差し出してもいいユーザーなのかをチェックする必要があります。しかし今回のような管理画面配下のページ(dashboard)でルーティングする場合は特に気にする必要はありません。

/dashboard/* 配下はリクエストからログインユーザーであるかをチェックしており、ログインユーザーでないリクエストの場合、ログインページのHTMLが返されます。実際にcurlで上記のURLを打ってみると

<!DOCTYPE html>
<html>
<head>
    <link rel="stylesheet" type="text/css" href="/concrete/themes/concrete/main.css" />
    
<title>ログイン :: c5test</title>

<meta http-equiv="content-type" content="text/html; charset=UTF-8"/>
<meta name="generator" content="concrete5 - 8.5.4"/>
<link rel="canonical" href="http://localhost:8888/login">
<script type="text/javascript">
    var CCM_DISPATCHER_FILENAME = "/index.php";
    var CCM_CID = 174;
    var CCM_EDIT_MODE = false;
    var CCM_ARRANGE_MODE = false;
    var CCM_IMAGE_PATH = "/concrete/images";
    var CCM_TOOLS_PATH = "/index.php/tools/required";
    var CCM_APPLICATION_URL = "http://localhost:8888";
    var CCM_REL = "";
    var CCM_ACTIVE_LOCALE = "ja_JP";
</script>
内部の生データが外部からアクセスされるのは非常にまずいのでdashbord配下にルーティングを置き、さらにリクエストの種類をXHRだけにしましょう。

コンポーネントにAjaxアクセスを実装

URLとデータの取得処理は書いたので、vue側でそこにAjaxを飛ばすようにします。前回作成したform.vue にAjaxの処理を書きます。

form.vue
created(){
    if(this.isEdit){
        $.ajax({
            url:location.href,
            type:'GET',
            success: (data)=> {
                let result = JSON.parse(data)[0]; // returns array
                this.title = result.title;
            },
            error: (xhr, textStatus, errorThrown)=>{
                console.error('Error! ' + textStatus + ' ' + errorThrown);
            }
        })
    }
}

form.vueは編集と新規追加を併用しているので、isEditというプロパティでAjaxを飛ばすかを制御しています。現在のURLに対してAjaxを飛ばすと、先ほどのコードで書かれているようにIDに紐づいたアルバム情報をDBから取ってきてくれます。

データはJSONで戻ってくるので JSON.praseを用いてJSで用いられるようにします。もし仮にAjaxが失敗した場合(400、500系のエラー)、コンソールでエラーが吐かれるようになっています。

編集用コンポーネントのレンダリング設定

シングルページそのものはエントリーポイントだけ。

edit.php
<?php
defined('C5_EXECUTE') or die('Access Denied.');
?>

<div id="edit"></div>

そしてvue側のeditコンポーネントはpropsを変えるだけでadd.vueとほぼ同じ

edit.vue
<template>
    <AlbumForm :isEdit="true"/>
</template>

<script>
import AlbumForm from './components/form';
export default {
    name:'add',
    components:{AlbumForm}
}
</script>

最後にレンダー用のmain.jsを以下のようにしておきます。

main.js
import Vue from 'vue'
import Add from './add.vue'
import Edit from './edit.vue'


Vue.config.productionTip = false

function renderIfidExits(id,vueRoot){
  if(document.getElementById(id) !== null){
    return new Vue({
      render: h => h(vueRoot),
    }).$mount('#'+id)
  }
}

renderIfidExits('add',Add);
renderIfidExits('edit',Edit);

前回はAddコンポーネントだけでしたが、今回はEditもあります。さらにこのmain.jsで全てのコンポーネントのレンダーを制御しているので、エントリーポイント のIDが存在すればそこにコンポーネントをレンダリングする。という方法を取っています。

本当はコンポーネントごとにレンダー用のjs(add.js,edit.jsみたいな)を作成するのですが、面倒だったのでこうしました。

そしてform.vueで編集の場合(isEdit = true)にcreated()でAjaxを飛ばしてデータを取得し、data()に代入するようします。

実際にレンダーしてみる

ビルドをして /dashboard/vuetest/edit/{id} へアクセスします。IDがあっていればそのデータを取ってきてくれます。

きちんと画面には前回入力した追加内容が表示されています。開発者ツールでNetworkでAjaxを確認してみましょう。Request Header を確認すると確かに、現在のURLにアクセスしておりさらにXMLHttpRequestで送信されています。Resonseをみてみると

[
    {
        "id":"1",
        "title":"\u30c6\u30b9\u30c8",
        "created":"2020-08-27 00:28:38",
        "modified":"2020-08-27 00:28:38"
    }
]

このようにデータベースから取ってきた内容がJSONとして渡されているのが確認できます。ちなみに日本語はエンコードされているので、js側でJSON praseを使うことで元の文章に戻すことができます。

内容を更新する

編集画面を表示した際の初期表示はできるようになったので、次は内容が書き換えてDBの内容を変更できるようにしましょう。しかし触るのはバックエンドの部分だけです。vuetest/controllers/single_page/dashboard/vuetest.phpを以下のように変更します。

vuetest.php
pupublic function edit($id=null){

    // パラメータがない場合は一覧画面へリダイレクト
    if($id==null) return Redirect::to('/dashboard/vuetest/')->send();

    // Ajax エンドポイント
    if($_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest'){
        $db = Database::connection();
        $db->Execute("START TRANSACTION");
        $result = $db->fetchAll('SELECT * FROM album ORDER BY ID DESC');
        $db->Execute("COMMIT");
        return Core::make('helper/ajax')->sendResult($result);
    }

    //post処理(ここを追記)
    if(Request::isPost() == true){
        $title = $this->post('title');

        if(empty($title)==false){
            $db = Database::connection();
            $db->executeQuery("START TRANSACTION");
            $db->executeQuery(
                'UPDATE album SET `title`=?, `modified`=now() WHERE `ID`=?',
                array($title,$id)
            );
            $db->executeQuery("COMMIT");
            Redirect::to('/dashboard/vuetest')->send();
        }else{
            Redirect::to('/dashboard/vuetest')->send();
        }

    }else{
        $this->render('/dashboard/vuetest/add');
    }
}

新規作成の際に使用されるpupublic function edit を一部変更したぐらいです。postがある場合、その値のバリデーションをして変更するIDを元に更新用のSQLを走らせるだけです。

実際に変更してみる

無事、タイトルと編集時間が更新されました。これでCRUDの「Update」が完成しました。

一覧画面を作成する

追加・編集の画面は完成しました。次は全ての登録データを一覧で見れる画面を作成していきます。/dashboard/vuetest/ にアクセスした際に一覧が表示されるようにします。

一覧用コンポーネントの作成

一覧コンポーネントをindex.vueとしておきます。一覧画面では以下のような構成にしています。

index.vue
<template>
    <div class="ccm-dashboard-content-inner">
        <h3>アルバム一覧</h3>
        <hr>

        <table class="p-package-index table table-hover">
            <thead>
                <tr>
                    <th class="c-dol-title">タイトル</th>
                    <th class="c-dol-publish-date">作成日</th>
                    <th class="c-dol-operation">操作</th>
                </tr>
            </thead>
            <tbody>
                <tr v-for="val in list" :key="val.id" tabindex="0">
                    <td style="vertical-align:middle;">{{val.title}}</td>
                    <td style="vertical-align:middle;">{{val.created}}</td>
                    <td style="vertical-align:middle;">
                        <a :href="'/dashboard/vuetest/edit/'+val.id" type="button" class="btn btn-success btn-sm" style="margin-right:10px;">編集</a>
                        <button type="button" class="btn btn-danger btn-sm" v-on:click="cofirmDelete(val)">削除</button>
                    </td>
                </tr>
            </tbody>
        </table>

        <div class="ccm-dashboard-form-actions-wrapper">
            <div class="ccm-dashboard-form-actions">
                <a href="/dashboard/vuetest/add" class="pull-right btn btn-primary">
                    新規追加
                </a>
            </div>
        </div>
    </div>
</template>

<script>
export default {
    name:'index',
    data(){
        return {
            list:[],
            isProcessing:false
        }
    },
    methods:{
        cofirmDelete(obj){
            let result = window.confirm('アルバム:「'+obj.title+'」を本当に削除しますか?この操作は取り消せません。');
            if(result) return this.deleteAlubm(obj.id);
        },
        deleteAlubm(id){
            if(this.isProcessing==false){
                this.isProcessing = true;

                $.ajax({
                    url:'/dashboard/vuetest/deleteData/',
                    type:'POST',
                    data:{target:id},
                    success: ()=> {
                        let targetIndex = this.list.findIndex((ele)=>{
                            return ele.ID == id;
                        });
                        this.list.splice(targetIndex,1);
                        this.isProcessing = false;
                    },
                    error: (xhr, textStatus, errorThrown)=>{
                        console.log('Error! ' + textStatus + ' ' + errorThrown);
                    }
                })
            }
        },
    },
    created(){
        $.ajax({
            url:'/dashboard/vuetest/loadData',
            type:'GET',
            success: (data)=> {
                this.list = JSON.parse(data); // returns array
            },
            error: (xhr, textStatus, errorThrown)=>{
                console.error('Error! ' + textStatus + ' ' + errorThrown);
            }
        })
    }
}
</script>

レンダリングされる際は以下のような処理で一覧画面が表示されます。

  1. createdでDB上の情報を取ってくるAjaxをエンドポイントに飛ばす。
  2. 取得したデータをdata()のlistに挿入。
  3. v-forでlist分 の行を表示する。

これをレンダーすると以下のようになります。

右側の「操作」で、「編集」でそのアルバムデータの編集画面へ移動、「削除」ではそのデータを削除するAjaxを飛ばします。また、「新規追加」では新規追加画面へ移動します。この画面があれば一通りのデータ操作がブラウザ画面からできるようになります。

ロード用・削除用エンドポイントを作成

vuetest/controllers/single_page/dashboard/vuetest.php コントローラーでは一覧データ・対象のIDを削除するAjaxエンドポイントを作成します。

vuetest.php
public function view() {
    $this->render('/dashboard/vuetest/view');
}

// ロード用のエンドポイント
public function loadData() {
    if ($_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest'){
        $db = Database::connection();
        $db->Execute("START TRANSACTION");
        $result = $db->fetchAll('SELECT * FROM album ORDER BY ID DESC');
        $db->Execute("COMMIT");
        return Core::make('helper/ajax')->sendResult($result);
    }else{
        $this->replace('/page_not_found');
    }
}

// 削除エンドポイント
public function deleteData(){
    $targetID=$this->post('target');
    if ($_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest' && !is_null($targetID)){
        $db = Database::connection();
        $db->Execute("START TRANSACTION");
        $db->executeQuery('DELETE FROM album WHERE `ID`=?',array($targetID));
        $db->Execute("COMMIT");
    }else{
        $this->replace('/page_not_found');
    }
}

エントリーポイント を作成

上記のコンポーネントをレンダリングするために編集・追加の時のように、一覧用ページにレンダーできるように設定をします。

packages/vuetest/single_pages/dashboard/vuetest/view.php に以下のようにエントリーポイントを記述します。

view.php
<?php
defined('C5_EXECUTE') or die('Access Denied.');
?>

<div id="index"></div>

そしてmain.jsでは

main.js
import Vue from 'vue'
import Index from './index.vue'
import Add from './add.vue'
import Edit from './edit.vue'


Vue.config.productionTip = false

function renderIfidExits(id,vueRoot){
  if(document.getElementById(id) !== null){
    return new Vue({
      render: h => h(vueRoot),
    }).$mount('#'+id)
  }
}

renderIfidExits('index',Index);
renderIfidExits('add',Add);
renderIfidExits('edit',Edit);

id='index'があれば一覧のコンポーネントが出力されるようにしました。

実際に操作してみる

新しくデータを入れてみる

右下の「新規追加」は /dashboard/vuetest/add へリンクしています。そのためクリックすると追加画面が現れて、追加ができます。新規に「テスト2」としておきましょう。

それで「登録」を押すとデータ挿入処理と共に一覧へリダイレクトされます。すると、

きちんと一覧に反映されました。

削除してみる

では削除を押すと、確認ダイアログがでてOKであれば削除APIが走って実行されます。

「変更テスト(id 1)」がDB上から削除されまた、一覧からも消えました。

編集してみる

緑色の「編集」は編集画面のURLへリンクしています。vueでは以下のようにv-for内で動的に作成できるようにしています。

<tr v-for="val in list" :key="val.id" tabindex="0">
...
    <td style="vertical-align:middle;">
        ...
        <a target="_blank" :href="'/dashboard/vuetest/edit/'+val.id">編集</a>
        ...
    </td>
...
</tr>

実際にリンクしてみると、選択したデータの編集画面が表示されました。

次回は..

今回の記事では編集画面と一覧画面が完成しました。これで「作成」「読み取り」「更新」「削除」の実装ができました。次の記事では「タイトル」だけでなく「画像データ」「リッチテキスト」といったデータを使えるようにします。

コメント

コメント読み込み中..

Copyright © 2021 jun. All rights reserved.