Shoeisha Technology Media

CodeZine(コードジン)

特集ページ一覧

Spread.ViewsとPayPalを連携して商品発送管理システムを作る

IoT時代の救世主! SpreadJSで作るデータ可視化アプリ 第4回

  • LINEで送る
  • このエントリーをはてなブックマークに追加

目次

4. ドラッグアンドドロップのイベントを取得する

 ここまでで、見た目の調整が出来たので、振る舞いを追加していきます。

 今回の仕組みの場合、ドラッグアンドドロップでカードを別の状態に動かしたときに、何かしら処理を行いたいです。

 Spread.ViewsのTrellisGroupingのドキュメントを見るとdragDroppingというイベントを取得できると書いてあります。これを使ってドラッグアンドドロップした際の挙動をコントロールします。イベントの取得に関してはグリッドイベントのデモが参考になります。

 まずはドラッグアンドドロップが行われた際に実行される関数dragDroppingFnを定義します。

 次にTrellisGrouping['dragDropping'].addHandler()のメソッドで定義した関数を紐付けます。

index.html
省略
      <script>
      	 省略

		//ドラッグアンドドロップ時に反応
       const dragDroppingFn = (sender,args) => {
            if(args.status !== 'beforeDropping') return; //ドロップ完了時のみ発火させる
            alert('ドロップ');
            console.log('----------');
            console.log(sender.dataSource_);
       };

       //ドラッグ&ドロップ時のイベントを追加
       TrellisGrouping['dragDropping'].addHandler(dragDroppingFn);

    </script>
</body>

</html>

 dragDroppingのイベントではドラッグ時(マウスで要素を動かし始めるタイミング)とドロップ時(マウスを離して要素を置いたタイミング)の二回のイベントが取得できます。args.statusの値がbeforeDraggingbeforeDroppingのどちらかになるので、どちらかを利用する際に条件分岐をさせます。

 今回でいうとif(args.status !== 'beforeDropping') return;の箇所でドラッグイベントを無視するようにして、ドロップ時のみ反応するようにしています。

 ページをリロードして確認するとドロップイベントとデータが確認できます。

5. サーバーサイドとデータ連動

 現状のデータはフロント側のJavaScriptに保存されていて、ページがリロードされると初期状態に戻ってしまいます。

 サーバーサイドでデータを保持して、ページがリロードされても状態が維持されるようにします。

サーバーを編集する

 Node.js側のコードを編集します。

 originDataという配列に、フロントエンドで利用していたデータを移行します。

server.js
'use strict';

const express = require('express');
const bodyParser = require('body-parser');
const app = express();

app.use(bodyParser.urlencoded({extended: false}));
app.use(express.static(__dirname + '/public'));

const PORT = process.env.PORT || 3000;

let originData = [
    {"work": "未発送", "description": "Nefry BT x 1", "title": "菅原のびすけ", "photo": "./images/ds.png", "progress": 0, "address": "東京都千代田区外神田2-9-3\nユニオンビル工新 8F", "address_zip":"101-0021"},
    {"work": "ラベル申込/決済_済", "description": "Nefry BT x 1", "title": "菅原のびすけ", "photo": "./images/ds.png", "progress": 20, "address": "東京都千代田区外神田2-9-3\nユニオンビル工新 8F", "address_zip":"101-0021"},
    {"work": "ラベル印字_済", "description": "Nefry BT x 1", "title": "菅原のびすけ", "photo": "./images/ds.png", "progress": 40, "address": "東京都千代田区外神田2-9-3\nユニオンビル工新 8F", "address_zip":"101-0021"},
    {"work": "パッケージング_済", "description": "Nefry BT x 1", "title": "菅原のびすけ", "photo": "./images/ds.png", "progress": 60, "address": "東京都千代田区外神田2-9-3\nユニオンビル工新 8F", "address_zip":"101-0021"},
    {"work": "発送完了", "description": "Nefry BT x 1", "title": "菅原のびすけ", "photo": "./images/ds.png", "progress": 80, "address": "東京都千代田区外神田2-9-3\nユニオンビル工新 8F", "address_zip":"101-0021"},
    {"work": "配送完了", "description": "Nefry BT x 1", "title": "菅原のびすけ", "photo": "./images/ds.png", "progress": 100, "address": "東京都千代田区外神田2-9-3\nユニオンビル工新 8F", "address_zip":"101-0021"},
];

app.get('/', (req, res) => res.sendFile(__dirname+'/index.html'));

//データ取得のAPI
app.get('/getData', (req, res) => res.json(originData));

//データ更新のAPI
app.put('/update', (req, res) => {
    const updateData = JSON.parse(req.body.data);
    originData = updateData; //元データを更新
    console.log(originData);
    res.json(originData);
});

app.listen(PORT);
console.log(`listening on *:${PORT}`);

 また/getDataにアクセスするとoriginDataがJSON形式で取得できるAPIを作成します。

 Node.jsを再起動してhttp://localhost:3000/getDataにアクセスしてJSONが表示されることを確認しましょう。

 また、/updateにアクセスするとoriginDataが更新されるAPIも追加しています。

 こちらについてはクライアント側の説明をする際に触れます。

クライアントを編集する

 次はクライアント側です。データを/getDataにHTTPリクエストして取得します。HTTPのクライアントライブラリにはaxiosを利用します。https://unpkg.com/axios/dist/axios.min.jsを既存のscriptタグの前に読み込ませます。

index.html
省略
    <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
    <script>
         //状態定義
        const workGroups = ['未発送','ラベル申込/決済_済','ラベル印字_済','パッケージング_済','発送完了','配送完了']; //ヘッダー情報
    	省略

 index.htmlは変更点が多いのでbodyタグ以下をまとめています。

index.html
省略

<body style="margin:0;position:absolute;top:0;bottom:0;left:0;right:0;font-size:14px;user-select:none;-webkit-user-select: none;overflow:hidden;">
    <div style="height: 100%; position: relative">
        <div style="height:90%;">
            <div id="grid1" style="height:100%;"></div>
        </div>
    </div>

    <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
    <script>
        //状態定義
        const workGroups = ['未発送','ラベル申込/決済_済','ラベル印字_済','パッケージング_済','発送完了','配送完了']; //ヘッダー情報

        //カードのテンプレート
        const rowTemplate = `
        <div class="group-item-container">
            <div class="group-item-container-inner {{? it.progress==100}}finish{{?? it.progress>=80}}eighty-per{{?? it.progress>=50}}fifty-per{{?? it.progress>=30}}thirty-per{{??}}start{{?}}">
                <div data-column="title" class="group-item-title  {{? it.progress==100}}finish-head{{?? it.progress>=80}}eighty-per-head{{?? it.progress>=50}}fifty-per-head{{?? it.progress>=30}}thirty-per-head{{??}}start-head{{?}}"></div>
                <div data-column="photo" class="group-photo-container"></div>
                <div data-column="description" class="group-item-description"></div>
                <div data-column="address_zip" class="group-item-description">〒</div>
                <div data-column="address" class="group-item-description"></div>
            </div>
        </div>`;
        const photoPresenter = '<img class="employee-photo" src={{=it.photo}} />';

        //データ定義
        const columns = [
            {
                id: 'title',
                name: 'title',
                dataField: 'title'
            }, {
                id: 'description',
                name: 'description',
                dataField: 'description'
            }, {
                id: 'photo',
                dataField: 'photo',
                presenter: photoPresenter
            }, {
                id: 'progress',
                dataField: 'progress'
            }, {
                id: 'address',
                name: 'address',
                dataField: 'address'
            }, {
                id: 'address_zip',
                name: 'address_zip',
                dataField: 'address_zip'
            }
        ];

        //(1)変数宣言
        let data = []; //商品管理データの雛形
        let dataView,TrellisGrouping;

        //(2)データの更新
        const dataPut = async (updateData) => {
            const params = new URLSearchParams();
            console.log(updateData);
            params.append('data', JSON.stringify(updateData));
            const res =  await axios.put('/update', params);
            data = res.data;
        }

        //(3)ドラッグアンドドロップ時に反応
        const dragDroppingFn = (sender,args) => {
            if(args.status !== 'beforeDropping') return; //ドロップ完了時のみ発火させる

            setTimeout(() => {
                //プログレスの値を更新
                for(let i = 0,len = workGroups.length; i < len; i++){
                    if(args.dataItem.work === workGroups[i]){
                        args.dataItem.progress = i * 20;
                    }
                }
                dataPut(sender.dataSource_);
            },500); //ディレイが無いとうまく動作しない模様
        };

        //(4)データと描画更新
        const renderer = async () => {
            const res = await axios.get('/getData');
            data = res.data;
            dataView = new GC.Spread.Views.DataView(document.getElementById('grid1'), data, columns, new GC.Spread.Views.Plugins.GridLayout({
                grouping: [{
                    field: 'work',
                    preDefinedGroups: workGroups,
                    header: {height: 24}
                }],
                rowTemplate: rowTemplate,
                rowHeight: 150,
                groupStrategy: TrellisGrouping
            }));
        };

        setInterval(renderer,3000); //3秒ごとにデータと描画を更新

        //(5)初期実行
        const init = async () => {
            TrellisGrouping = new GC.Spread.Views.Plugins.TrellisGrouping({panelUnitWidth: 190});
            renderer();

            //ドラッグ&ドロップ時のイベントを追加
            TrellisGrouping['dragDropping'].addHandler(dragDroppingFn);
        };
        init();
    </script>
</body>

</html>
(1)変数宣言

 まずは、使い回す変数を宣言します。

let data = []; //商品管理データの本体
let dataView,TrellisGrouping;
(2)データの更新メソッド

 データの更新があった際に/updateのAPIにアクセスする関数です。

        const dataPut = async (updateData) => {
            const params = new URLSearchParams();
            console.log(updateData);
            params.append('data', JSON.stringify(updateData));
            const res =  await axios.put('/update', params);
            data = res.data; //本体を更新 (B)
        }

 ブラウザの時にaxiosのPOSTの値が送信されない?そんなことはないの記事などにあるように、axiosでPOSTやPUTリクエストを送る際にはURLSearchParamsを利用すると想定通りにリクエストが投げられます。サーバー側では受け取ったデータをoriginDataに上書きして更新しています(A)。更新されたデータがレスポンスとして返って来るので、クライアント側でもデータの更新をします(B)。

server.js
省略

app.put('/update', (req, res) => {
    const updateData = JSON.parse(req.body.data);
    originData = updateData; //元データを更新 (A)
    console.log(originData);
    res.json(originData);
});

省略
(3)ドラッグアンドドロップしたときにデータ更新を行う

 先ほど紹介したドラッグアンドドロップの処理の内部で(2)で紹介したdataPut()を呼び出します。

        //(3)ドラッグアンドドロップ時に反応
        const dragDroppingFn = (sender,args) => {
            if(args.status !== 'beforeDropping') return; //ドロップ完了時のみ発火させる

            setTimeout(() => {
                //プログレスの値を更新
                for(let i = 0,len = workGroups.length; i < len; i++){
                    if(args.dataItem.work === workGroups[i]){
                        args.dataItem.progress = i * 20;
                    }
                }
                dataPut(sender.dataSource_);
            },500); //ディレイが無いとうまく動作しない模様
        };

 ドラッグアンドドロップのイベント取得の都合上、setTimeout()で僅かにディレイさせないとうまくデータが更新されません。

(4)データと描画更新

 ここからはデータが動的に変わっていくため、データの取得と描画は初期実行だけではないです。

 renderer()を作成し、その中でデータ取得と描画が行われるようにしましょう。

		//(4)データと描画更新
        const renderer = async () => {
            const res = await axios.get('/getData');
            data = res.data;
            dataView = new GC.Spread.Views.DataView(document.getElementById('grid1'), data, columns, new GC.Spread.Views.Plugins.GridLayout({
                grouping: [{
                    field: 'work',
                    preDefinedGroups: workGroups,
                    header: {height: 24}
                }],
                rowTemplate: rowTemplate,
                rowHeight: 150,
                groupStrategy: TrellisGrouping
            }));
        };

        setInterval(renderer,3000); //3秒ごとにデータと描画を更新

 またsetInterval(renderer,3000);で3秒ごとにデータ更新と再描画が行われるようになります。

(5)初期実行の処理

 いよいよ実行手前です。先ほどのrenderer()を内部で呼び出しつつ、最初の1回だけ呼び出せば良い処理をini()にまとめています。

        //(5)初期実行
        const init = async () => {
            TrellisGrouping = new GC.Spread.Views.Plugins.TrellisGrouping({panelUnitWidth: 190});
            renderer();

            //ドラッグ&ドロップ時のイベントを追加
            TrellisGrouping['dragDropping'].addHandler(dragDroppingFn);
        };
        init();

実行して確認

 これで内容を確認すると、初期状態にserver.jsで記述したサンプルデータが描画され、カードをドラッグアンドドロップで移動させると、状態が更新されサーバーサイドのoriginDataも更新されます。

 ページをリロードしても、そのままの状態が保持されるようになりました。


  • LINEで送る
  • このエントリーをはてなブックマークに追加

著者プロフィール

  • 菅原のびすけ(dotstudio株式会社)(スガワラ ノビスケ)

     日本最大規模のIoTコミュニティ「IoTLT」主催。岩手県立大学大学院ソフトウェア情報学研究科を卒業後、株式会社LIGでWebエンジニアとして入社し、Web開発に携わる。2016年にdotstudio株式会社を立ち上げ、今はIoT領域を中心に活動している。JavaScript Roboticsコミ...

バックナンバー

連載:IoT時代の救世主! SpreadJSで作るデータ可視化アプリ
All contents copyright © 2005-2018 Shoeisha Co., Ltd. All rights reserved. ver.1.5