CRUD Lab. Tech Blog

ギークス株式会社の沖縄開発拠点CRUDLab. (クラッドラボ)のIT技術(テック)ブログ。kintoneやAngularJSによる開発を行っています。

【kintone】期間を指定して月別で条件絞り込み表示する方法

0. 目次

1. 概要

暑いですね、沖縄!ギークス株式会社CRUD Lab.のKUNISADAです。

期間を指定して月別で条件絞り込み表示する方法

アプリの種類によっては、期間(開始〜終了)を指定する場合も多いと思います。 レコード数が多くなり、指定する期間が長くなればなるほど、月別で絞り込んで確認することができたらいいのに!という場合もあるかと思います。

今回は、図書貸し出しアプリを想定して、貸し出し開始日と終了日から貸し出し期間を取得し、特定の月別で条件絞り込み表示をしたいと思います。

2. イメージ図

▼ 図書貸し出しアプリ(借りた本と借りる期間を記録する)

例)登録データの一例

本タイトル:「沖縄移住するには」

貸し出し期間:2016-04-23(開始)〜2016-05-05(終了)

▼ イメージ図のフロー説明

  1. 貸し出しの本(すべて)の一覧画面
  2. 本貸し出し期間(検索用)で次のキーワードを含む検索で「2016年06月」を入力
  3. 2016年06月に貸し出された本の一覧を表示

f:id:crud_lab_editor:20160401163755g:plain

3. 準備

3-1 必要ファイルの読み込み

https://js.cybozu.com/momentjs/2.12.0/moment.min.js(参照:Cybozu CDN一覧)を利用。

f:id:crud_lab_editor:20160401165947p:plain

3-2 フィールド

f:id:crud_lab_editor:20160401171139p:plain

フィールド名 フィールドタイプ フィールドコード 初期値 その他
本タイトル 複数選択 -- -- --
借りた人 ユーザー選択 -- ※ここでは「KUNISADA」をテスト設定 --
貸し出し期間(開始) 日付 rental_start -- --
貸し出し期間(終了) 日付 rental_end -- --
本貸し出し期間(検索用) 文字列(1行) rental_months -- 隠したい項目グループに内包

※本貸し出し期間(検索用)フィールドは、 隠したい項目グループに入れて、ユーザーに入力させないように設定します。TIPSで後述。

※ 実装に必要なフィールドコードのみ記載しています。

4. 実装

(function () {
    'use strict';

    //期間計算の関数
    function list_all_months(params_start,params_end){

        var start = moment(params_start).startOf('month'),
        end = moment(params_end).startOf('month'),
        contract_ary   = [];

        start.subtract(1, 'months');

        while((start.diff(end) +1) <= 0){ 
            start.add(1, 'months');
            contract_ary.push(start.format('YYYY年MM月'));
        }

        var contract_months_str = contract_ary.join(',');
        return contract_months_str;

    }

    //新規レコード追加画面表示のイベントハンドラー
    kintone.events.on('app.record.create.show', function(event){
        var rec   = event.record;
        //本貸し出し期間(検索用)をdisabled処理で記入できないようにする
        rec['rental_months']['disabled'] = true;
        return event;
    });

    //新規レコード追加のイベントハンドラー
    kintone.events.on('app.record.create.submit', function(event){

        var rec   = event.record;

        /*-月別検索の処理-*/
        var rent_start  = rec['rental_start']['value'],
            rent_end    = rec['rental_end']['value'];

        return new kintone.Promise(function(resolve, reject) {
            //関数使用 
            rec['rental_months'].value = list_all_months(rent_start,rent_end);
            resolve(event);
        }, function(error){
            reject('データの取得に失敗しました。');
            return;
        });

        return event;
    });
})();

5. 注意点

▼ 検索時の注意点

今回のjsカスタマイズでは「YYYY年MM月」で日付登録する設定ですので、 条件絞り込みの際には「2016年06月」のように月には「0」を入れて検索してください。

6. TIPS

▼ 日付の条件絞り込みの現状

f:id:crud_lab_editor:20160401162752p:plain

このように、日付のフィールドでは「今日」「今月」「先月」「今年」の絞り込みはできますが、開始〜終了の期間の場合は今回のカスタムが便利です!

▼ グループにアクセス制限

また、アプリの設定でフィールドのアクセス権を以下のように制限することで、 f:id:crud_lab_editor:20160401180948p:plain

「KUNISADA」以外のユーザーにグループ自体を非表示にすることができるので、 入力時の混乱を避ける意味でも、用途に合わせて設定してみるといいかもしれません。

f:id:crud_lab_editor:20160401180955p:plain

6. まとめ

開始日と終了日があるようなアプリで、月別絞り込みをしたい場合は、 このようにカスタムすることで、月別の集計データを取ることも可能です。 今回は図書貸し出しアプリを想定しましたが、契約関係のアプリなどで、契約期間の絞り込みをする場合などにも使えるので、やり方次第ではとても便利なカスタマイズです!

【kintone】Webhookを使って、新規レコード追加をSlackに通知しよう

0. 目次

1. 概要

ギークス株式会社CRUD Lab.のKUNISADAです。

Webhookを使って、新規レコード追加をSlackに通知しよう

SlackのIncoming Webhookのインテグレーションを使って、kintoneとSlackを連携します。

Incoming Webhookとは、外部のアプリケーションからPOSTするとSlackにメッセージが投稿される仕組みのことです。Slackが発行するIncominng Webhook のURLを通して、JSON形式でメッセージをPOSTします。

新規レコード追加をSlackに通知したい時に便利です! 条件も自由にカスタムできるので、今回は確認フラグを設置して簡単な通知設定を行います。

2. イメージ図

▼新規レコード追加

※ 確認フラグを「ON」に設定

f:id:crud_lab_editor:20160401091750p:plain

▼Slackに通知

※ 確認フラグ「ON」の場合のみ通知する

f:id:crud_lab_editor:20160401091813p:plain

3. 準備

3-1 Incoming Webhookの設定(URLを取得/Slackと連携)

STEP1:incoming Webhookのインテグレーションを設定する

以下のURLにアクセスして、連携したいSlackのチームでログインします。 https://www.slack.com/services/new/incoming-webhook

STEP2:通知したいSlackのチャンネルを選択

f:id:crud_lab_editor:20160401091926p:plain

「Add Incoming WebHooks integration」ボタンを押して次の画面へ

STEP3:WebhookのURLを取得

f:id:crud_lab_editor:20160401091944p:plain

「Save Settings」で変更を保存

3-2 kintoneの設定(フィールド)

フィールド名 フィールドコード 初期値 その他
タイトル タイトル -- --
確認フラグ 確認フラグ 初期値はOFF --

場合に応じて、その他必要項目を設置してください。

※ 実装に必要なフィールドのみ記載しています。

4. 実装

const WEBHOOK_URL = 'https://hooks.slack.com/services/XXXXXXXXXXXXXX'; //ここにコピーしてきたWebhookのURLを挿入

(function () {
    'use strict';

    //新規レコード追加のイベントハンドラー
    kintone.events.on('app.record.create.submit', function(event){

        var rec   = event.record;

        //確認フラグがONの場合に以下の処理を実行
        if(rec['確認フラグ'].value == 'ON'){
            var payload = {
                'text': "新規レコードが追加されました。:「" + rec['タイトル'].value + "」"
            };
            return new kintone.Promise(function(resolve, reject) {        
                kintone.proxy(WEBHOOK_URL, 'POST', {}, payload, function(body, status, headers) {
                    console.log(status, body);            
                    resolve(event);
                });
            }, function(err) {
                reject('データの取得に失敗しました。');
                return;
            });
        }

        return event;
    });
})();

5. 参考URL

こちらのサイトを参考にしました! cybozudev.zendesk.com

参考サイトは、プロセスのステータスが変更になった際に、Slackに通知される仕組みになっています。

cybozudev.zendesk.com

逆に、Slackの「Outgoing Webhooks」を利用することで、Slackからkintoneへレコードを登録することもできるので、こちらも要チェックですね。

6. まとめ

Incoming Webhookを使えば、様々なタイミングでSlackに通知することが可能です。 実装も極めてシンプルなので、新規レコード追加以外の場合も柔軟にカスタムすることができるので、とても便利です。

【kintone】エラーメッセージをまとめてわかりやすく一括表示しよう

0. 目次

1. 概要

沖縄はもうすぐ海開きですね、ギークス株式会社CRUD Lab.のKUNISADAです。

エラーメッセージをまとめてわかりやすく一括表示しよう

アプリが複雑になればなるほど、フィールドの数も膨大になり、必須項目も埋もれてしまいがちです。 submitしたあとに、スクロールしながら一つずつ必須項目を確認しなくてはならないのは面倒ですよね! そこで、今回もシンプルな実装でエラーを分かりやすく全部表示してみます。

2.イメージ図

2-0 普段のエラー

f:id:crud_lab_editor:20160323145612p:plain

2-1 エラー表示カスタム

  • エラー一括表示

  • フィールドのエラーメッセージを詳細化

f:id:crud_lab_editor:20160324182621p:plain

3. 準備

3-1 フィールド

フィールド名 フィールドコード 初期値 その他
必須項目1 required_1 -- --
必須項目2 required_2 -- --
必須項目3 required_3 -- --

f:id:crud_lab:20160323143659p:plain

3-2 必要ファイルの読み込み

f:id:crud_lab_editor:20160303153525p:plain

4. 実装

(function () {

    'use strict';

    //新規作成、編集、一覧編集時のイベントハンドラー群
    var submit = ['app.record.create.submit',
                  'app.record.edit.submit',
                  'app.record.index.edit.submit'];

    kintone.events.on(submit, function(event){

        var rec        = event.record,
            required_1 = rec['required_1'],
            required_2 = rec['required_2'],
            required_3 = rec['required_3'],
            msg_ary    = [];

        //エラーメッセージの内容を登録
        if(required_1['value'] == undefined){
            required_1['error'] = '必須項目1には名前を入力してください';
            msg_ary.push('必要項目1');
        }
        if(required_2['value'] == undefined){
            required_2['error'] = '必須項目2には企業名を入力してください';
            msg_ary.push('必要項目2');
        }
        if(required_3['value'] == undefined){
            required_3['error'] = '必須項目3にはメールアドレスを入力してください';
            msg_ary.push('必要項目3');
        }

        //エラーメッセージの配列を半角スペース入りの文字列に
        var msg_str = msg_ary.join(' ');
        event.error = msg_str;

        return event;

    });


})();

5. まとめ

エラーメッセージを少しカスタムすることで、使い勝手の向上と入力時のストレス軽減に役立ちそうですね! フィールド数が多くなりがちな複雑なアプリでは、このようなカスタムが便利です。

【kintone】Highchartsを利用して他アプリのデータをグラフ化しよう

0. 目次

f:id:crud_lab:20160317162650p:plain

1. 概要

ギークス株式会社CRUD Lab.のKUNISADAです!

Highchartsというjavascriptのグラフライブラリを利用してkintoneをカスタマイズし、 他アプリデータをグラフ化してみます。

javascriptのグラフライブラリはchart.jsなど多数ありますが、 Highchartsには画像のexporting機能(画像としてダウンロードする機能)が備わっていたり、 二軸のグラフの作成がオプションから簡単に設定できたりと非常に便利なライブラリです。

※商用の場合は有料です。

もちろんkintone自体のグラフ機能もとても使いやすいので、 より高度なグラフ描画をしたい時だけHighchartsでカスタマイズするのがおすすめです。

今回は、気象庁が公表している

「沖縄県那覇市の年月別最高気温・最低気温・降水量」

のデータをグラフ化してみようと思います。

2. イメージ図

データアプリとグラフ描画アプリを作成し、グラフ描画アプリからデータをグラフ化します。

f:id:crud_lab:20160317155920g:plain

3. 準備

3-1 データアプリの作成

今回はcsvでデータを整理した後、csvからアプリ作成を行いました。 アプリ名はcsvのファイル名から生成されており、

naha_climate_2010.1-2016.3

となっています。 f:id:crud_lab:20160317153526p:plain

アプリ作成後、フィールド名を変更していきます。

アプリIDは「11」です。

フィールド名 フィールドコード 初期値 その他
year -- --
month -- --
降水量・合計(mm) sum_precipitation -- --
最高気温(℃) highest_temperature -- --
最低気温(℃) lowest_temperature -- --

3-2 グラフ描画アプリの作成

次にグラフを描画するためだけのアプリを作成します。

アプリ名は「sample_graph」としました。

フィールド名 フィールドコード 初期値 その他
年選択 selected_year -- --
[スペース] graphArea -- --

3-3 必要ファイルの読み込み

グラフ描画アプリ「sample_graph」に必要ファイルを読み込みます。

Highchartsに必要な

と最後にjavascriptカスタマイズする

  • sample.js

を読み込みます。

f:id:crud_lab:20160317155029p:plain

4. 実装

sample.js

(function() {
    "use strict";
    kintone.events.on(["app.record.edit.show", "app.record.create.show", "app.record.edit.change.selected_year", "app.record.create.change.selected_year"], function(event){

        var record = event.record,
            selectedYear = record['selected_year']['value'],
            //データ元のアプリIDとクエリの設定
            dataAppId = 11,
            url = kintone.api.url('/k/v1/records'),
            params = {
                'app'   : dataAppId,
                'query' : 'year = ' + selectedYear + 'order by month asc'
            },
            //取得データ用の配列を初期化。グラフ用の<div>をスペースの中に作成
            data = [],
            elGraph = kintone.app.record.getSpaceElement('graphArea'),
            elDiv = document.createElement('div');
            elDiv.setAttribute("id", "graph");
            elDiv.style.cssText ="width: 600px; height: 400px;";
            elGraph.appendChild(elDiv);

        kintone.api(url, 'GET', params,
            function(resp){
                var monthData = [],
                    highestPrecipitationData = [],
                    highestTemperatureData = [],
                    lowestTemperatureData = [];
                data = resp.records;

                //取得したデータからグラフ用の配列を作成
                for (var recordId in data) {
                    console.log(data[recordId]);
                    monthData.push(data[recordId]['month'].value);
                    highestPrecipitationData.push(parseInt(data[recordId]['sum_precipitation'].value));
                    highestTemperatureData.push(parseInt(data[recordId]['highest_temperature'].value));
                    lowestTemperatureData.push(parseInt(data[recordId]['lowest_temperature'].value));
                }

                //データからグラフを生成
                $(function () {
                    $('#graph').highcharts({
                        exporting: {
                            chartOptions: {
                                plotOptions: {
                                    series: {
                                        dataLabels: {
                                            enabled: true
                                        }
                                    }
                                }
                            },
                            scale: 3,
                            fallbackToExportServer: false
                        },
                        chart: {
                            zoomType: 'xy'
                        },
                        title: {
                            text: '那覇月別降水量・気温グラフ'
                        },
                        xAxis: [{
                            categories: monthData,
                            crosshair: true
                        }],
                        yAxis: [{ // Primary yAxis
                            labels: {
                                format: '{value}°C',
                                style: {
                                    color: Highcharts.getOptions().colors[1]
                                }
                            },
                            title: {
                                text: '気温',
                                style: {
                                    color: Highcharts.getOptions().colors[1]
                                }
                            }
                        }, { // Secondary yAxis
                            title: {
                                text: '降水量',
                                style: {
                                    color: Highcharts.getOptions().colors[0]
                                }
                            },
                            labels: {
                                format: '{value} mm',
                                style: {
                                    color: Highcharts.getOptions().colors[0]
                                }
                            },
                            opposite: true
                        }],
                        tooltip: {
                            shared: true
                        },
                        legend: {
                            layout: 'vertical',
                            align: 'left',
                            x: 60,
                            verticalAlign: 'top',
                            y: 30,
                            floating: true,
                            backgroundColor: (Highcharts.theme && Highcharts.theme.legendBackgroundColor) || '#FFFFFF'
                        },
                        series: [{
                            name: '降水量',
                            type: 'column',
                            yAxis: 1,
                            data: highestPrecipitationData,
                            tooltip: {
                                valueSuffix: ' mm'
                            }
                        },
                        {
                            name: '最高気温',
                            type: 'spline',
                            data: highestTemperatureData,
                            tooltip: {
                                valueSuffix: '°C'
                            }
                        },
                        {
                            name: '最低気温',
                            type: 'spline',
                            data: lowestTemperatureData,
                            tooltip: {
                                valueSuffix: '°C'
                            }
                        }]
                    });
                });
            }, function(resp) {
                alert('エラーが発生しました。管理者に問い合わせてください');
        });
    });

})();

5. まとめ

今回の実装ではサンプルということで、 kintone.events.on("app.record.create.change.selected_year")を利用し、毎度APIでの情報取得を行っています。

他アプリの情報を取得する際のAPIリクエスト上限が10000となっているため、

"app.record.create.show"と"app.record.edit.show"で情報を一括で取得し、

"app.record.create.change.selected_year"でselected_yearが変更された時グラフデータを変更する

という仕様に変えるとAPIリクエストの節約になります。

kintone上で、どうしてもグラフを画像としてダウンロードしたい、より高度なグラフをkintoneのデータを利用して作成したいというときの助けになれば幸いです。

6. 参考

www.highcharts.com

www.data.jma.go.jp

【kintone】kintoneでステップシーケンサーを作ってみた - 続編

0. 目次

1. 概要

ギークス株式会社CRUD Lab.の、ATSUSHIです。 先日投稿した「(kintoneでステップシーケンサーを作ってみた)http://crud-lab.geechs.com/entry/2016/03/15/090000」の続編です。

kintoneのUIを変更してより楽器らしくしていこうと思います。 ステップシーケンサーって何?という方は前回の記事に簡単に説明と参考リンクを掲載しているので見てみてください。

f:id:crud_lab_editor:20160317165223g:plain

1-1 前回のスペック

  • loop機能なし
  • 音源はピアノ、1オクターブのみ
  • BPM変更可能

1-2 追加機能

  • ステップ上でサウンド再生しない(OFF)機能の追加
  • オクターブを変更できる機能
  • ステップを自由に追加できる機能

1-3 今回フィーチャーするkintoneの技術

2. 実装

2-1 作業の進め方

kintoneでは、作成済みのアプリを再利用することができます。 似たような性質のアプリであれば、効率よく作業をすすめられる便利な機能です。

アプリを新規作成する際に「ほかのアプリを再利用」を選択することで利用可能です。 アプリ名を 「Sequencer Advance」として保存します。

2-2 アプリの設定

複製されたアプリのフィールドコードの編集を以下のポイントで行います。

  • 8つある音階選択のラジオボタンを削除
  • 横レイアウトで音階選択のラジオボタンを1個、新規追加
  • オクターブのラジオボタンを1個、新規追加
  • 上記のパーツをテーブル化

f:id:crud_lab_editor:20160317174159j:plain

... 前回は8ステップ固定でしたが、 テーブル化することで、長さをいくらでも伸ばすことができるようになります。 ループ機能を入れるともっとシーケンサーらしさが出るのですが、前回とのロジック変更が多くなるので、対応しません(苦)

フィールド名 フィールドコード 初期値 フィールドコードタイプ/その他 変更
タイトル title -- -- 流用
BPM bpm 120 -- 流用
- play_button -- 変更なし JavaScritpで buttonタグを挿入するためのスペース 流用
- samples -- 変更なし。JavaScriptで audioタグを挿入するためのスペース 流用
note note OFF ラジオボタン(横) / 1オクターブ、OFFの情報を保持 > テーブル化 新規*
oct oct 0 -1, 0, +1 > テーブル化 新規*

noteoct のフィールドをテーブル化して、レコードを複製できるようにします。 今回は、テーブルの上から下に向かって音声が再生されます。

2-3 JavaScriptのコーディング

アプリ設定「詳細設定」タブを開き、「JavaScript / CSSでカスタマイズ」>「スマートフォン用のJavaScriptファイル」に以下のファイルをリンクするようにしてください。

'use strict';

// 必要なjsライブラリをインクルード
document.write("<script type='text/javascript' src='https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js'></script>");

// レコードの新規作成、編集時に処理を実行する
kintone.events.on(['app.record.create.show','app.record.edit.show','app.record.detail.show'], function(event){

    // 音階のサウンドファイルをインクルード
    var samples = document.createElement('div');
    samples.id   = "samples";
    kintone.app.record.getSpaceElement("samples").appendChild(samples);
    
    var base_url = "https://kintone-dev-test-xxxx.c9users.io/";
    var audio_tags = '';
    audio_tags += '<audio src="'+base_url+'do.mp3" preload="auto" id="key_C" class="note"></audio>';
    audio_tags += '<audio src="'+base_url+'re.mp3" preload="auto" id="key_D" class="note"></audio>';
    audio_tags += '<audio src="'+base_url+'mi.mp3" preload="auto" id="key_E" class="note"></audio>';
    audio_tags += '<audio src="'+base_url+'fa.mp3" preload="auto" id="key_F" class="note"></audio>';
    audio_tags += '<audio src="'+base_url+'so.mp3" preload="auto" id="key_G" class="note"></audio>';
    audio_tags += '<audio src="'+base_url+'ra.mp3" preload="auto" id="key_A" class="note"></audio>';
    audio_tags += '<audio src="'+base_url+'si.mp3" preload="auto" id="key_B" class="note"></audio>';
    audio_tags += '<audio src="'+base_url+'do.mp3" preload="auto" id="key_C" class="note"></audio>';
    
    audio_tags += '<audio src="'+base_url+'do_minus.mp3" preload="auto" id="key_C_minus" class="note"></audio>';
    audio_tags += '<audio src="'+base_url+'re_minus.mp3" preload="auto" id="key_D_minus" class="note"></audio>';
    audio_tags += '<audio src="'+base_url+'mi_minus.mp3" preload="auto" id="key_E_minus" class="note"></audio>';
    audio_tags += '<audio src="'+base_url+'fa_minus.mp3" preload="auto" id="key_F_minus" class="note"></audio>';
    audio_tags += '<audio src="'+base_url+'so_minus.mp3" preload="auto" id="key_G_minus" class="note"></audio>';
    audio_tags += '<audio src="'+base_url+'ra_minus.mp3" preload="auto" id="key_A_minus" class="note"></audio>';
    audio_tags += '<audio src="'+base_url+'si_minus.mp3" preload="auto" id="key_B_minus" class="note"></audio>';
    audio_tags += '<audio src="'+base_url+'do_minus.mp3" preload="auto" id="key_C_minus" class="note"></audio>';
    
    audio_tags += '<audio src="'+base_url+'do_plus.mp3" preload="auto" id="key_C_plus" class="note"></audio>';
    audio_tags += '<audio src="'+base_url+'re_plus.mp3" preload="auto" id="key_D_plus" class="note"></audio>';
    audio_tags += '<audio src="'+base_url+'mi_plus.mp3" preload="auto" id="key_E_plus" class="note"></audio>';
    audio_tags += '<audio src="'+base_url+'fa_plus.mp3" preload="auto" id="key_F_plus" class="note"></audio>';
    audio_tags += '<audio src="'+base_url+'so_plus.mp3" preload="auto" id="key_G_plus" class="note"></audio>';
    audio_tags += '<audio src="'+base_url+'ra_plus.mp3" preload="auto" id="key_A_plus" class="note"></audio>';
    audio_tags += '<audio src="'+base_url+'si_plus.mp3" preload="auto" id="key_B_plus" class="note"></audio>';
    audio_tags += '<audio src="'+base_url+'do_plus.mp3" preload="auto" id="key_C_plus" class="note"></audio>';
    
    $(audio_tags).appendTo('#samples');
    $('#samples').css({visiblity:'hidden'});
    
    // ボタン
    var play_button      = document.createElement('div'), 
        button_tags      = '<button id="play_button">再生</button>';

    play_button.id   = "play_button";
    kintone.app.record.getSpaceElement("play_button").appendChild(play_button);
    
    $(button_tags).appendTo('#play_button');
    $('#play_button').css({margin:'16px'});

    $('#play_button').on('click',function() {
        
        var record = kintone.app.record.get().record;
        var bpm    = parseInt(record["bpm"].value,10), tempo_ms = (60/bpm)*1000;
        var steps  = record.Table.value, notes=[], octaves=[];
        
        $.each(steps,function(idx, step){
           notes.push(step.value.note.value);
           octaves.push(step.value.oct.value);
        });
        
        
        var interval_id = setInterval(function(){
    
            var id_name = 'key_', note = notes[0], octave = octaves[0];
            
            if(note != 'OFF'){
                id_name += note;
                if(octave == -1) id_name += '_minus';
                if(octave == +1) id_name += '_plus';
                
                var sound_obj = document.getElementById(id_name);
                sound_obj.currentTime=0;
                sound_obj.play();
            }
            notes.shift();
            octaves.shift();
            
        },tempo_ms);
        
        setTimeout(function () {
            clearInterval(interval_id);
            console.log('loop will stop after ' + (tempo_ms) + ' msec!');
        }, tempo_ms*steps.length);
    
    });
});
    
    

2-4 補足説明

テーブルの件数をカウントを元にステップの再生回数を指定しています。 JavaScriptでテーブルのデータを操作する場合は、objectの階層が深いので、適宜変数に入れるなどして 情報を整理しながら、コーディングすることをお勧めします。

オクターブ音の再現は、1オクターブ上/下のサンプルをmp3で作成して読み込んでいますが、 (Web Audio API)https://www.w3.org/TR/webaudio/ を活用すると音源をピッチシフトさせるなどして再生できるのかと思います。(勉強中です)

3. まとめ

kintone x 音楽 はシリーズ化したいところですが、ステップシーケンサーの回は終わりにします。 kintoneから広がるファストシステムの可能性をもっと探っていきたいと思います。

【kintone】シンプルなレコード内検索小窓を作ってみよう

0. 目次

1. 概要

ギークス株式会社CRUD Lab.のKUNISADAです!

シンプルなレコード内検索小窓を作ってみよう

アプリ内検索のように大規模じゃなくてもいい、レコード一覧画面でわざわざ条件絞り込みをするのも面倒くさい! そんな時に役立つ検索小窓を作ってみました。

2. イメージ図

例) 沖縄県◯◯市のレコード一覧の中で、「那覇市」を検索

f:id:crud_lab_editor:20160310154432g:plain

3. 準備

3-1 フィールド

今回は、「タイトル」フィールドを絞り込み検索の対象とするシンプルな設定です。

フィールド名 フィールドコード 初期値 その他
タイトル title -- --

f:id:crud_lab_editor:20160310151331p:plain

3-2 必要ファイルの読み込み

f:id:crud_lab_editor:20160303153525p:plain

4. 実装

//検索したいフィールドの設定値
const FIELD_CODE = 'title';

//レコード一覧表示のイベントハンドラー
(function () {
  'use strict';

    kintone.events.on("app.record.index.show", function (event) {

        //GET引数に格納された直前の検索キーワードを取得して再表示
        var result = {};
        //クエリから、URL固定部分(?query=)を無視して取り出す
        var query = window.location.search.substring(7);  
        //フィールドコード名と検索キーワードに分割する
        for(var i = 0;i < query.length;i++){
            var element = query[i].split('like');
            var param_field_code = encodeURIComponent(element[0]);
            var param_search_word = encodeURIComponent(element[1]);

            //空白スペースを取り除いて、配列に格納
            result[param_field_code.replace(/^\s+|\s+$/g, "")] = param_search_word.replace(/^[\s|\"]+|[\s|\"]+$/g, "");
        }

        //検索キーワード
        var search_word = document.createElement('input');
        search_word.type = 'text';

        //検索ボタン
        var search_button = document.createElement('input');
        search_button.type = 'submit';
        search_button.value = 'search';
        search_button.onclick = function () {
            keyword_search();
        };

        //キーワード検索の関数
        function keyword_search(){
            var keyword = search_word.value;
            var str_query = '?query='+ FIELD_CODE +' like "' + keyword;

            if(keyword == ""){
                str_query = "";
            }else if(keyword != ""){
                str_query = '?query='+ FIELD_CODE +' like "' + keyword + '"';
            }

            //検索結果のURLへ
            document.location = location.origin + location.pathname + str_query
        }

        //重複を避けるため要素をあらかじめクリアしておく
        var node_space = kintone.app.getHeaderMenuSpaceElement()
        for (var i =node_space.childNodes.length-1; i>=0; i--) {
            node_space.removeChild(node_space.childNodes[i]);
        }
        var label = document.createElement('label');
        label.appendChild(document.createTextNode('レコード内検索'));
        label.appendChild(document.createTextNode('  '));  
        label.appendChild(search_word);
        label.appendChild(document.createTextNode('  '));    
        label.appendChild(search_button);     
        kintone.app.getHeaderMenuSpaceElement().appendChild(label);

        return event;
    });
})();

5. kintoneでの検索の注意点

1. 1文字では検索不可/ 最低2文字以上が検索に必要

例)「なは」

  • OK :「なは」と検索 → 「なは」
  • NG:「な」と検索 → データがありません。

2. 英数字は単語単位でしか検索できない

例)「KUNISADA」

  • OK:「KUNISADA」と検索 → 「KUNISADA」
  • NG:「KUNI」と検索 → データがありません。

参考URL

データを検索する | kintone ユーザーヘルプ

6. まとめ

レコード一覧画面のメニュー右側の空白部分*1を有効活用するシリーズです。 頻繁に検索する必要がある場合は、とても便利なカスタムです。 社内用のwikiなど、カテゴリー別に整理してあるアプリなどで、探したい項目が簡単に見つかるのでおすすめです!

*1:レコード一覧画面のメニュー右側の空白部分の要素は、「kintone.app.getHeaderMenuSpaceElement()」で取得することができます。

kintoneでステップシーケンサーを作ってみた

0. 目次


1. 概要

ギークス株式会社CRUD Lab.の、ATSUSHIです。 今回はkintoneとJavaScriptカスタマイズを組み合わせて楽器をつくってしまおうと思います。

業務効率化とかそういった仕事に使える仕組みではないですが、 kintoneでどこまでできるか試していきたいので、今後もこういった記事を定期的にUPしていければと思います。

2. ステップシーケンサーの仕組み

ステップシーケンサーとは一般的に、一定間隔(ステップ)で音符を配置してそれらを元に音源を自動再生する音楽機器のことを指します。

この仕組みでドラムやベースを実装しリズムパートとして利用することが多いです。

f:id:crud_lab_editor:20160310001133p:plain

今回はシンプルに以下の仕様で実装を行います。

  • loop機能なし
  • 音源はピアノ、1オクターブのみ
  • BPM変更可能

<参考>

3. html5でのサウンドプログラミング

html5の仕様でaudio/videoタグが追加されました。 それまではembedタグを利用して外部プレーヤー(ex. flashプレイヤー,Quicktimeプレーヤー)で音源を再生させていました。

現在では、ブラウザ互換の課題はあるものの、JavaScript APIである、「Web Audio API」「Web MIDI API」などをタグと併用してかなり高レベルの音楽表現ができるように なっています。

音楽関連のJavaScriptライブラリとして有名なのが timbre.jsですが、 今回はシンプルに音源を読み込み、kintoneのUIで設定されたステップ情報をよみ、対応した音源を再生させる仕組みをつくっていきます。

3. 実装

それでは、実装します。 音声が流れないので、イメージしずらいかも知れませんが以下のようなプレイヤーを構築します。

f:id:crud_lab_editor:20160310001024g:plain

アプリの設定

スペースを利用して再生ボタンと、再生される音源のaudioタグを貼り付ける領域を定義します。
ステップの部分はラジオボタンを縦レイアウトで8個作成します。(2小節分のステップ情報)
BPMの設定はドロップダウンを利用します。

フィールド名 フィールドコード 初期値 フィールドコードタイプ/その他
タイトル title -- 文字列(1行) /
BPM bpm 120 ドロップダウン /60,80,120,150,240を設定
- play_button -- スペース / 再生ボタンを配置するためのスペース
- samples -- スペース / audioタグを配置するためのスペース
1〜8 seq_1〜seq_2 C ラジオボタン(縦) / 1オクターブの情報を保持

JavaScriptのコーディング

アプリ設定「詳細設定」タブを開き、「JavaScript / CSSでカスタマイズ」>「スマートフォン用のJavaScriptファイル」に以下のファイルをリンクするようにしてください。

'use strict';

// 必要なjsライブラリをインクルード
document.write("<script type='text/JavaScript' src='https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js'></script>");

// レコードの新規作成、編集時に処理を実行する
kintone.events.on(['app.record.create.show','app.record.edit.show','app.record.detail.show'], function(event){

    // 音階のサウンドファイルをインクルード
    var samples = document.createElement('div');
    samples.id   = "samples";
    kintone.app.record.getSpaceElement("samples").appendChild(samples);
    
    var base_url = "https://kintone-dev-test-xxxxxx.io/"; // ファイルパス
    var audio_tags = '';
    audio_tags += '<audio src="'+base_url+'do.mp3" preload="auto" id="key_C" class="note"></audio>';
    audio_tags += '<audio src="'+base_url+'re.mp3" preload="auto" id="key_D" class="note"></audio>';
    audio_tags += '<audio src="'+base_url+'mi.mp3" preload="auto" id="key_E" class="note"></audio>';
    audio_tags += '<audio src="'+base_url+'fa.mp3" preload="auto" id="key_F" class="note"></audio>';
    audio_tags += '<audio src="'+base_url+'so.mp3" preload="auto" id="key_G" class="note"></audio>';
    audio_tags += '<audio src="'+base_url+'ra.mp3" preload="auto" id="key_A" class="note"></audio>';
    audio_tags += '<audio src="'+base_url+'si.mp3" preload="auto" id="key_B" class="note"></audio>';
    audio_tags += '<audio src="'+base_url+'do.mp3" preload="auto" id="key_C" class="note"></audio>';
    $(audio_tags).appendTo('#samples');
    $('#samples').css({visiblity:'hidden'});
    
    // ボタン
    var play_button = document.createElement('div');
    play_button.id   = "play_button";
    kintone.app.record.getSpaceElement("play_button").appendChild(play_button);
    var button_tags = '<button id="play_button">再生</button>';
    $(button_tags).appendTo('#play_button');
    $('#play_button').css({margin:'16px'});

    $('#play_button').on('click',function() {
        
        var  record = kintone.app.record.get().record;
        
        var bpm = parseInt(record["bpm"].value,10); // 入力値
        var tempo_ms = (60/bpm)*1000;
        console.log(tempo_ms);
        
        var sequence  = [];
        
        for(var i=1; i<=8; i++){
            sequence.push(record["seq_"+i].value);
        }
        console.log(sequence);
        
        var sound_obj = document.getElementsByClassName('note');
        var tmp_sound_obj = null;
        var interval_id = setInterval(function(){
    
            var note = sequence[0];
            var sound_obj = document.getElementById('key_'+note);
            sound_obj.currentTime=0;
            sound_obj.play();
            tmp_sound_obj = sound_obj;
            
            sequence.shift();
            console.log(sequence);
            
        },tempo_ms);
        
        setTimeout(function () {
            clearInterval(interval_id);
            console.log('loop has stopped after ' + (tempo_ms) + ' msec!');
        }, tempo_ms*sequence.length);
    
    });
});
    

補足説明

アプリの詳細表示/新規作成/編集のタイミングで再生が可能です。 audioタグはpreloadを設定しており、JavaScriptが読み込まれた際に音源のプリロードが発生します。UI上は非表示にしてあります。

BPMの計算とシーケンサーの仕組み

BPM(Beat Per Minuite)は分間に4分音符がいくつおけるかで示すテンポを表す単位です。setInterval()を利用して音源を順々に再生させていきますので以下の計算で音と音の間隔を設定しています。

setIntervalの間隔 = 60(sec) / bpm * 1000

4. まとめ

今回作成したステップシーケンサーの仕組みはとてもシンプルなものです。 前述のとおりWeb Audio APIやtimbre.jsを利用することでさらに複雑な処理が実装できます。kintoneを活用してこういった面白い仕組みを構築することができるのは楽しいですね。

kintone x {something} の可能性を感じて頂ければ嬉しいです。