【kintone】クリスマスわくわくアプリを作ろう

楽しい・・・かどうかはわからないけど、クリスマスな12月になりました！

アドベントカレンダーの1日目に記事書かせていただきます、じゅりどんと申します。

kintoneで楽しいことや面白いことができないか？研究しています。

今回は、クリスマスぴったりなkintoneアプリを作ったので紹介したいと思います。

その名も

「クリスマスわくわくアプリ」


# クリスマスわくわくアプリとは

みなさんはクリスマスツリーを飾り付けましたか？
我が家はまだですが、心のクリスマスツリーは一年中キラキラしております。

さて、このアプリですが、クリスマスツリーに飾りつけて、保存できるアプリなのです。

使いみちは見つからないけど・・・、わくわくしますよね。

| アプリ| 保存した画像 |
|:-:|:-:|
| <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/536148/2bc76c50-c8a9-e985-8db0-f9622c1df417.gif" >  | <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/536148/54647bc0-bab0-70a7-cefe-ae5d7139e85c.png"> |


## クリスマスわくわくアプリの両親

クリスマスわくわくアプリは私がQiitaで紹介してきたアプリのソースをコピペして編集して作られています。

父：[【kintone】kintone上で座席表を作る](https://qiita.com/juri_don/items/1a2fb2dc01fb903d4ce3)

母：[【kintone】お絵描きして添付ファイルに保存する（改）](https://qiita.com/juri_don/items/7dbf37c78579e5a90be4)

父の機能を応用して「ツリー」の上に「オーナメント」を並べます。
母の機能で「飾り付け後のツリー」を画像として添付ファイルに保存します。

また、母の機能でオーナメントを自作するアプリも作りました。
好きな画像をアップロードして飾りを作ることもできます。


| オーナメント作るアプリ | オーナメント一覧 |
|:-:|:-:|
| <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/536148/98d420e6-95b7-ba14-f298-9638c41be0fe.png" width="300px"> |   ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/536148/a7cf6152-5777-4bf0-8a1b-7d6cedcb0d1a.png)|

## たのしいおんがくでワクワク感up

クリスマスと言えばクリスマスソング。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/536148/d8905257-6e36-fb25-f664-dec9305190ef.png)

「たのしいおんがく」ボタンでジングルベルをかけながら作業することができます。
楽しさ倍増です。

# クリスマスわくわくアプリの中身

ではアプリの中身をここに晒していきます。

1. オーナメントアプリ
2. わくわくアプリ

の2つのアプリで1セットな感じです。

## クリスマスのオーナメントアプリ

まずクリスマスのオーナメントアプリから。

### みため
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/536148/bd64ac5c-3027-2537-d835-960f870ccb27.png" width="500px">

### フィールド

| フィールドの種類 | フィールドコード | 備考 |
|:-:|:-:|:-:|
| ドロップダウン | カテゴリ  | 飾り／背景  |
|  文字列(1行) | タイトル  |   |
|  添付ファイル | 画像  |   |
| 文字列(1行)  | ファイルパス  |   |

### 一覧のカスタマイズビューHTML
```html
<div id="canvas"></div>
<div>
    <label>画像タイトル：<input type="text" id="title"></label>
    <button id="btn_save">保存</button>
</div>
```

### JavaScript
ソース・・・そのうちきれいにするかも

```js
(()=>{
    'user strict';
    //一度開いて保存し直す必要がある
    kintone.events.on(['app.record.edit.submit'], event => {
        const record = event.record;
        record.ファイルパス.value = $("div.plupload_file_thumbnail_preview_img img")[0].src;
        return event;
    });

    function draw_start(){
        const canvas = new DrawingBoard.Board('canvas',{background: false}); 
    }
 
     //canvasの画像を保存
     function saveCanvas(canvas_id)
     {
         const canvas_ary = document.getElementsByClassName("drawing-board-canvas");
         const canvas = canvas_ary[0];
 
         const title = document.getElementById("title").value;
         // Brobを取得
         canvas.toBlob( function ( blob ) {
             // FormDataにファイルを格納
             const formData = new FormData();
             formData.append('__REQUEST_TOKEN__', kintone.getRequestToken());
             formData.append('file', blob, title + '.png');    
 
             //ファイルをアップロード
             const xmlHttp = new XMLHttpRequest();
             xmlHttp.open('POST','https://サブドメイン.cybozu.com/k/v1/file.json');
             xmlHttp.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
             xmlHttp.send(formData);
 
             //200が帰ってきたらレコード登録
             xmlHttp.onload= function(){
                 if(xmlHttp.status===200){
                     const key = JSON.parse(xmlHttp.responseText).fileKey;
 
                     const body={
                         app:kintone.app.getId(),
                         record:{
                             "タイトル":{value:title},
                             "画像":{value:[{fileKey: key}]}
                         }
                     }
 
                     //fileKeyを設定
                     kintone.api("/k/v1/record","POST",body).then(function(resp){
                         location.reload();
                     });
                 }
             }
         });
     }
 
     kintone.events.on(['app.record.index.show'], event => {
         if(event.viewId == ビューID){
             draw_start();
             const btn_save = document.getElementById("btn_save");
             btn_save.onclick =()=>{saveCanvas('canvas');};
         }
         return event;
     });
})();
```

## クリスマスわくわくアプリ（本体）
こちらはツリーを飾り付けして保存するアプリです。

### みため
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/536148/7bfaf2fb-f495-384b-7870-c0d1d62fe91b.png" width="300px">

### フィールド

| フィールドの種類 | フィールドコード | 備考 |
|:-:|:-:|:-:|
|  文字列(1行) | タイトル  |   |
|  添付ファイル | 添付ファイル|   |

### 一覧のカスタマイズビューHTML

```html

<div>
    <button id="btnMusic">たのしいおんがく</button>
    <button id="btnStop">おんがくをとめる</button>
</div>

<div>
    <select id="selOrn"></select>
    <button id="btnOrn" > 飾り</button>
    <input type="text" id="title"></input>
    <button id="btnSrv" > 保存</button>
</div>

<div>
    <canvas width="699" height="850" id="xmasTree" style="background: #eee;"></canvas>
</div>
```

### JavaScript

Tone.jsを使います。ここからDownloadして、kintoneにupしておいてください。
[Tone.js](https://github.com/Tonejs/Tone.js/wiki/Installation)

背景として、オーナメントアプリに画像を保存しておくと良いかも。
ツリーの画像のURLをベタ書きで書く感じで表示します。

```js
(()=>{
    'user strict';
    const KT_CONST = {
        MUSIC: [
            {//ジングルベル
            ID: 0,
            TITLE:"ジングルベル",
            GAKUFU: [
                [//1ch
                    'E5', 'E5', 'E5', null, 'E5', 'E5', 'E5', null,
                    'E5', 'G5', 'C5', 'D5', 'E5', null, null, null,
                    'F5', 'F5', 'F5', 'F5', 'F5', 'E5', 'E5', 'E5',
                    'G5', 'G5', 'F5', 'D5', 'C5', null, null, null 
                ],
                [//2ch
                    'C4', 'G4', 'C4', 'G4', 'C4', 'G4', 'C4', 'G4',
                    'C4', 'G4', 'C4', 'G4', 'C4', 'G4', 'C4', 'G4',
                    'B3', 'G4', 'B3', 'G4', 'C4', 'G4', 'C4', 'G4',
                    'B3', 'G4', 'B3', 'G4', 'C4', 'G4', 'C4', 'G4',
                ]
            ],
            BPM: 160
            },
        ]
    };

    //一覧表示
    kintone.events.on([
        'app.record.index.show'        
    ], async event => {
        //背景設定 ※座席表アプリの背景のところと同じように画像パスをゲットする
        $('canvas').drawImage({
            source: 'https://サブドメイン.cybozu.com/ツリーの画像パス.png',
            x: 349, y: 450,
            layer: true,
            draggable: false,
            bringToFront: false,
            type:"tree",
        });

        const getOrnament = {
            app: 146,
            fields:['タイトル','ファイルパス'],
            query:'カテゴリ in ("飾り")'
        };
        const getResp = await kintone.api('/k/v1/records', 'GET', getOrnament );

        getResp.records.forEach(r=>{
            $('#selOrn').append($("<option>", {value:r.ファイルパス.value, text:r.タイトル.value}));
        })

        //ボタン効果
        $('#btnOrn').on('click', onClickBtnOrn);
        $('#btnSrv').on('click', saveCanvas);

        // 再生ボタンクリック
        $('#btnMusic').on('click', startMusic);
        //Stopボタンクリック
        $('#btnStop').on('click', stopMusic);

        return event;
    });

    const onClickBtnOrn =()=>{
        const src = $('#selOrn').val();
        $('canvas').drawImage({
            source: src,
            x: 150, y: 150,
            layer: true,
            draggable: true,
            type:"orn",
        });
    }

    //canvasの画像を保存
    const saveCanvas = ()=>{
        const canvas = document.getElementById("xmasTree");
        const title = document.getElementById("title").value;
        // Brobを取得
        canvas.toBlob(blob => {
            // FormDataにファイルを格納
            const formData = new FormData();
            formData.append('__REQUEST_TOKEN__', kintone.getRequestToken());
            formData.append('file', blob, title+'.png');    

            //ファイルをアップロード
            const xmlHttp = new XMLHttpRequest();
            xmlHttp.open('POST','https://サブドメイン.cybozu.com/k/v1/file.json');
            xmlHttp.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
            xmlHttp.send(formData);

            //200が帰ってきたらレコード登録
            xmlHttp.onload= function(){
                if(xmlHttp.status===200){
                    const key = JSON.parse(xmlHttp.responseText).fileKey;
                    const body={
                        app:kintone.app.getId(),
                        record:{
                            "タイトル":{value:title},
                            "添付ファイル":{value:[
                                {fileKey: key}
                            ]}
                        }
                    }
                    //fileKeyを設定
                    kintone.api("/k/v1/record","POST",body).then(function(resp){
                        console.log(resp);
                        location.reload();
                    },(err)=>{
                        console.log(err,record.$id.value);
                        alert(err);
                    });
                }
            }
        });
    }

    const startMusic =()=> {
        Tone.Transport.stop();  
        Tone.Transport.cancel(); 

        const nes1ch_synth = new Tone.Synth().toMaster();
        const nes2ch_synth = new Tone.Synth().toMaster();

        //楽譜取得
        console.log(KT_CONST.MUSIC);
        const nesNch = KT_CONST.MUSIC[0].GAKUFU;

        const addNes1ch = (time, note) => {
            nes1ch_synth.triggerAttackRelease(note, '8n', time);
        }
        const addNes2ch = (time, note) => {
            nes2ch_synth.triggerAttackRelease(note, '8n', time);
        }

        const melody_nes1ch = new Tone.Sequence(addNes1ch, nesNch[0]).start();
        const melody_nes2ch = new Tone.Sequence(addNes2ch, nesNch[1]).start();

        Tone.Transport.bpm.value = KT_CONST.MUSIC[0].BPM;

        // 複数のSequenceを同期再生させる
        Tone.Transport.start();        
    }

    const stopMusic =()=> {
        // 停止
        Tone.Transport.stop();  
        Tone.Transport.cancel(); 
    }

})();
```

# まとめ

過去に作ったアプリをかけ合わせてできた、たぶんクリスマスらしいアプリでしたが、面白かったでしょうか？

ソースをコピペして編集して突貫工事的に作ったこともありますので、色々とツッコミどころも満載なコードになっているかと思います。
なにか見つけたら教えて下さいm(_ _)m

では今年もkintoneアドベントカレンダー、楽しみましょう＾０＾！！