こんにちはjunです。Concrete5にVueCLIを使ってUIを構築する 2の記事の続きを書いていきます。前回の記事ではフォームの作成・登録まで行いました。この記事では登録したデータの編集とファイルマネージャーのvueコンポーネント化を行っていきます。
編集画面は追加画面とコンポーネントを共有し、データベースからAjaxでデータを取得してコンポーネントに代入をします。そして登録したデータの操作が行える一覧画面を作成します。
まずは編集画面から作成していきます。編集は追加と違ってデータベースからデータを取得して、初期値として当てはめる必要があります。PHPであればvalue="<?php echo $data?>"
みたいに挿入することで簡単に実現できますが、vueを使うとなれば一捻り必要です。
jsでフロントを構築する場合、基本的にDBのデータはAjaxを用いて再度サーバーにデータを要求します。concrete5でもAjaxとそのエントリーを実装することができます。まずエントリーの設定からやってみましょう。
今回はシングルページのコントローラーを用いて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
に以下のように記述します。
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しか表示されません。
/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>
URLとデータの取得処理は書いたので、vue側でそこにAjaxを飛ばすようにします。前回作成したform.vue
にAjaxの処理を書きます。
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系のエラー)、コンソールでエラーが吐かれるようになっています。
シングルページそのものはエントリーポイントだけ。
<?php
defined('C5_EXECUTE') or die('Access Denied.');
?>
<div id="edit"></div>
そしてvue側のeditコンポーネントはpropsを変えるだけでadd.vue
とほぼ同じ
<template>
<AlbumForm :isEdit="true"/>
</template>
<script>
import AlbumForm from './components/form';
export default {
name:'add',
components:{AlbumForm}
}
</script>
最後にレンダー用の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
を以下のように変更します。
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としておきます。一覧画面では以下のような構成にしています。
<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>
レンダリングされる際は以下のような処理で一覧画面が表示されます。
これをレンダーすると以下のようになります。
右側の「操作」で、「編集」でそのアルバムデータの編集画面へ移動、「削除」ではそのデータを削除するAjaxを飛ばします。また、「新規追加」では新規追加画面へ移動します。この画面があれば一通りのデータ操作がブラウザ画面からできるようになります。
vuetest/controllers/single_page/dashboard/vuetest.php
コントローラーでは一覧データ・対象のIDを削除するAjaxエンドポイントを作成します。
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
に以下のようにエントリーポイントを記述します。
<?php
defined('C5_EXECUTE') or die('Access Denied.');
?>
<div id="index"></div>
そして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>
実際にリンクしてみると、選択したデータの編集画面が表示されました。
今回の記事では編集画面と一覧画面が完成しました。これで「作成」「読み取り」「更新」「削除」の実装ができました。次の記事では「タイトル」だけでなく「画像データ」「リッチテキスト」といったデータを使えるようにします。
コメント
コメント読み込み中..