Shoeisha Technology Media

CodeZine(コードジン)

記事種別から探す

Elixir+PhoenixとSpread.Viewsでリアルタイムな出勤管理アプリを作ろう

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

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

目次

Web側の実装(1)

 それでは実装に入っていきます。今回はWeb側の実装、次回記事でデバイス側の実装を解説していきます。まずは、ベースとなるPhoenixフレームワークでの開発から始めていきます。

開発環境

 参考までに、筆者の環境を記載します。

  • Phoenix v1.3.0
  • Erlang v9.1.5
  • Elixir v1.5.2
  • Node.js v9.2.0(PhoenixではフロントエンドのビルドツールでNode.jsが使われています)

 ちなみに本番サーバーはUbuntu 16.04、ローカル環境はmacOS high Sierraとなっています。

 Ubuntu上でPhoenixが動くようになるまでの手順は、こちらの記事「Elixir/PhoenixをUbuntu(16.04 on Azure)で動かしてPM2で永続化まで」にまとめていますのでご参照下さい。

Webサービスの雛形を作る

 まずはWebサービスの雛形を作っていきます。

$ mix phx.new myapp --no-ecto

 これで雛形のファイルが作成されます。途中で「Y|n」の入力確認が出たらYをタイプして進みます。myappフォルダができるので移動します。

$ cd myapp

 次にサーバーを起動します。

$ mix phx.server

Compiling 12 files (.ex)
Generated myapp app
[info] Running MyappWeb.Endpoint with Cowboy using http://0.0.0.0:4000
21:49:28 - info: compiled 6 files into 2 files, copied 3 in 1.0 sec

 これでファイルのコンパイルと同時にサーバー起動もしてくれます。

Spread.Viewsのライブラリを読み込む

 Phoenixではフロントエンド開発にBrunchというビルドツールを利用しています。

 assets/jsフォルダとassets/css内にユーザーが作成したJSファイルとCSSファイルを追加し、jQueryや今回使うSpread.Viewsなどの外部ベンダーが作成したライブラリなどをassets/vendorフォルダ内に追加します。

 そうすることで、Brunchがビルドを行い、JSファイルはpriv/static/js/app.jsとしてビルドされ、CSSファイルはpriv/static/css/app.cssとしてビルドされます。

 実際の動作ではビルド後のapp.jsとapp.cssだけを読み込んで使います。

 今回のSpread.Viewsのライブラリはvendorフォルダ内にspreadというフォルダを作成し、その中にcssjsフォルダを作成して管理するようにしました。

 読み込むファイルはこちらの勤務表デモのソースコードを参照しましょう。

 また、jQuryも利用するのでjQueryファイルもvendorフォルダに設置して下さい。

ライブラリの読み込み順番

 Spread.Viewsでは共通のgc.spread.common.10.3.0.min.jsなどを先に読み込み、プラグインのplugins/gc.spread.views.cardlayout.10.3.0.min.jsなどは後に読み込むなどの、ライブラリの読み込み順番が大切です。

 scriptタグで読み込む際は読み込む順番は記述した通りになりますが、Brunchの場合はbrunch-config.jsを編集する必要があります。

 order>beforeに配列で順番を記載することができます。

brunch-config.js
//省略
      order: {
        before: [
          "vendor/spread/css/gc.spread.views.dataview.10.3.0.css",
          "vendor/spread/css/bootstrap-snippet.min.css",
          "vendor/spread/css/plugins/gc.spread.views.cardlayout.10.3.0.css",
          "vendor/spread/css/plugins/gc.spread.views.calendargrouping.10.3.0.css",
          "vendor/jquery.min.js",
          "vendor/spread/js/gc.spread.common.10.3.0.min.js",
          "vendor/spread/js/gc.spread.views.dataview.10.3.0.min.js",
          "vendor/spread/js/locale/gc.spread.views.dataview.locale.ja-JP.10.3.0.min.js",
          "vendor/spread/js/plugins/gc.spread.views.cardlayout.10.3.0.min.js",
          "vendor/spread/js/plugins/gc.spread.views.calendargrouping.10.3.0.min.js",
          "vendor/spread/js/zepto.min.js",
          "vendor/spread/js/license.js",
        ]
      }

//省略

サンプル画像の追加

 出勤表に使う画像をassets/static/imagesに追加しましょう。

 サンプルに合わせてPNGファイルを設置しました。

app.jsの編集

 assets/js/app.jsを編集します。

assets/js/app.js
import "phoenix_html"

// Import local files
//
// Local files can be imported directly using relative
// paths "./socket" or full ones "web/static/js/socket".

import socket from "./socket"

 と書いている部分の下に勤務表デモのJSファイルを追記します。

 サンプルコードの241行目のvar names = ['Troy', 'cvaldes', 'DeepakSharma14', 'hlkhan', 'Jeb', 'luculus', 'john'];部分の配列は先ほど追加したファイル名に当たるので、適宜変更しましょう。

assets/js/app.js
import "phoenix_html"

// Import local files
//
// Local files can be imported directly using relative
// paths "./socket" or full ones "web/static/js/socket".

import socket from "./socket"

var CalendarGrouping;
var dataView;
var monthEventTemplate = '<div><div data-column = "card" class="smallIcon"></div></div>';
var currentArry = [];
var presenter = '<img style ="width:100%;height:100%" src="{{=it.card}}"/>';
var columns = [{
    id: 'name',
    caption: 'name',
    dataField: 'name'
}, {
    id: 'card',
    caption: 'card',
    dataField: 'card',
    presenter: presenter
}, {
    id: 'date',
    caption: 'date',
    dataField: 'date'
}];

$('#btn-next').on('click',next);
$('#btn-previous').on('click',previous);

//after window resize, change the template back
$(window).resize(function() {
    if (dataView) {
        var options = dataView.options;
        var strategy = options.groupStrategy;
        var strategyOptions = strategy.options;
        if (strategyOptions.viewMode === "Month") {
            options.cardHeight = 56;
            options.cardWidth = 56;
            options.rowTemplate = monthEventTemplate;
        }
        dataView.invalidate();
    }
});

const render = () => {
    const sourceData = createData();
    console.log(sourceData)
    CalendarGrouping = new GC.Spread.Views.Plugins.CalendarGrouping({});
    CalendarGrouping.eventLimitClick.addHandler(function(sender, args) {
        var options = sender.options;
        options.cardHeight = 100;
        options.cardWidth = 100;
        options.rowTemplate = '<div><div data-column = "card" class="bigIcon"></div></div>';
    });
    CalendarGrouping.popoverClose.addHandler(function(sender, args) {
        var options = sender.options;
        options.cardHeight = 56;
        options.cardWidth = 56;
        options.rowTemplate = monthEventTemplate;
        sender.invalidate();
    });
    dataView = new GC.Spread.Views.DataView(document.getElementById('grid1'), sourceData, columns, new GC.Spread.Views.Plugins.CardLayout({
        cardHeight: 56,
        cardWidth: 56,
        grouping: {
            field: 'date',
            converter: function(field) {
                return field.toDateString();
            }
        },
        rowTemplate: monthEventTemplate,
        groupStrategy: CalendarGrouping
    }));
    updateTitle();
}

$(document).ready(render); //初期描画

function getMonth(currentDate, monthCount) {
    var year = currentDate.getFullYear();
    var month = currentDate.getMonth() + monthCount;
    var day = currentDate.getDate();

    if (month == 12) {
        month = 0;
        year += 1;
    } else if (month == -1) {
        month = 11;
        year -= 1;
    }

    return new Date(year, month, day, 0, 0, 0);
}

function updateTitle() {
    var options = CalendarGrouping.options;
    var date = options.startDate;
    var title = document.getElementById("title");
    var dateFormatter = new GC.Spread.Formatter.GeneralFormatter('mmmm yyyy');
    title.innerText = dateFormatter.format(date);
}

function previous() {
    var options = CalendarGrouping.options;
    var currentDate = options.startDate;
    currentDate = getMonth(currentDate, -1);
    CalendarGrouping.options.startDate = currentDate;
    updateTitle();
    dataView.invalidate();
}

function next() {
    var options = CalendarGrouping.options;
    var currentDate = options.startDate;
    currentDate = getMonth(currentDate, 1);
    CalendarGrouping.options.startDate = currentDate;
    updateTitle();
    dataView.invalidate();
}

function getDay(currentDate, daysCount) {
    var date = new Date(currentDate);
    var timeSpan = 1000 * 60 * 60 * 24 * (daysCount ? daysCount : 1);
    date.setTime(date.getTime() + timeSpan);

    return date;
}

function createData() {
    var names = ['n0bisuke', 'uko', 'chantoku', 'mao', 'wami', 'chachamaru', 'taka'];
    var data = [];
    var now = new Date();
    var name;
    var factor = 7;
    var date;
    for (var i = 0; i < names.length; i++) {
        for (var j = 0; j < factor; j++) {
            date = getDay(new Date(now.getFullYear(), now.getMonth(), 1), getRandomData(factor * 4));
            name = names[i];
            data.push({
                name: name,
                card: './images/' + name + '.png',
                date: date
            });
        }
        currentArry.length = 0;
    }

    return data;
}

function getRandomData(factor) {
    var dataRandom = Math.floor(Math.random() * factor + 1);
    while (currentArry.indexOf(dataRandom) !== -1) {
        dataRandom = Math.floor(Math.random() * factor + 1);
    }

    currentArry.push(dataRandom);
    return dataRandom;
}

 現時点だとJSコードが荒いので次回の記事で修正を入れますが、一旦このまま進みます。

calender.cssの追加

 先ほどのJSと同様に勤務表デモからCSSファイルを追加します。

 assets/css/calender.cssを追加します。先ほどの説明同様にassetsフォルダ内のCSSはBrunchによってビルドされます。

assets/css/calender.css
* {
    -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}

@font-face {
    font-family: 'spreadview-demo-icon';
    src: url(data:application/font-woff;base64,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) format('woff');
}

.demo-icon {
    font-family: "spreadview-demo-icon";
    font-style: normal;
    display: inline-block;
    text-align: center;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
    font-variant: normal;
    text-transform: none;
}

.icon-left-big:before {
    content: '\e802';
}
/* '' */

.icon-right-big:before {
    content: '\e803';
}
/* '' */

.wrapper {
    height: 1000px;
    position: relative;
    overflow: auto;
}

.gc-grid {
    border: 1px solid #CECECE;
}

.smallIcon {
    width: 50px;
    height: 50px;
    margin: 3px;
}

.smallIcon img {
    border-radius: 50px;
    border: solid 2px #e0e0e0;
}

.bigIcon {
    width: 80px;
    height: 80px;
    margin: 10px;
}

.bigIcon img {
    border-radius: 50px;
    border: solid 2px #e0e0e0;
}

.button-container {
    overflow: hidden;
    margin-bottom: 10px;
}

.dateTextTitle {
    display: inline-block;
    margin-left: 45%;
}

@media only screen and (max-width: 768px) {
    .wrapper {
        height: 820px;
    }
    .dateTextTitle {
        margin-left: 0;
    }
}

  • 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