【kintone】今年の漢字1文字は決めましたか？

## こんにちは
ついに、12月になってしまいましたね〜。
とは言っても全然12月の実感が湧かず、ニュースで「今年の流行語大賞は〜〜」とか「年末は〜〜」といった話が増えてきて、「あゝ12月なのね」とやっと実感が湧いてくる私です。

そんな年末恒例のニュースといえば **今年の漢字** ってありますよね！
今年は何が選ばれるのでしょうか？[今年の漢字の発表は12月14日の漢字の日(12月12日いい字を一字)](https://www.kanken.or.jp/kanji2020/)らしいのですが、その前にkintoneで今年の漢字の予想をしたり、今年を1文字で振り返ってみませんか？

![無題のプレゼンテーション_-_Google_スライド.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/168550/d0a08590-5727-16e7-cd82-1a8f52a0aaf2.png)


HTML,CSS,JavaScriptの知識があれば簡単にkintoneで書き初めが楽しめるカスタマイズができるので、もし興味のある方は、是非手元のkintone環境で試してみてください！

※ kintone環境がないですという方は、
[kintone 開発者ライセンス（開発環境）作成](https://developer.cybozu.io/hc/ja/articles/200720464-kintone-%E9%96%8B%E7%99%BA%E8%80%85%E3%83%A9%E3%82%A4%E3%82%BB%E3%83%B3%E3%82%B9-%E9%96%8B%E7%99%BA%E7%92%B0%E5%A2%83-) よりkintoneの開発環境を作成することができます。


## kintoneを使って書き初めアプリを作ってみよう
#### 目次
1. 完成形の確認
2. 準備内容
3. ハンズオン
4. まとめ

## 完成形の確認
新規レコード作成画面
<img width="600" alt="書き初めアプリ" src="https://media.giphy.com/media/1Dky7CSepZGr2Ouc4B/giphy.gif">

レコード一覧画面
<img width="700" alt="書き初めアプリ一覧" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/168550/80baa00b-38ad-7bb0-687f-dfc4abfc7d27.png">


## 準備内容
準備するものは3つです。
1. kintoneアプリ
2. kintoneのjsカスタマイズ環境
3. processing.jsファイル(後ほどダウンロードします)

### 1.kintoneアプリ

|フィールド名|フィールドタイプ|フィールドコード|
|---|---|---|
|タイトル|文字列一行|title|
|添付ファイル|ファイル|image|
|-|スペース|hanshi|

### 2. kintoneのjsカスタマイズ環境
`kintoneシステム管理` > `JavaScript / CSSでカスタマイズ` が表示されているか確認してください。

![kintoneシステム管理.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/168550/8a805d9c-f9f7-b8af-a7f3-54927cc5b71e.png)

### 3. processing.jsファイル
[processing-js ダウンロード (Githubリンク)](https://github.com/processing-js/processing-js)
`DownloadZIP` > `解凍` > `processing.min.js` を使用します。

kintoneの `アプリの設定画面` > `設定` > `JavaScript/CSSでカスタマイズ` > `PC用のJavaScriptファイル` にprocessing.min.jsをアップロードします。

<img width="600" alt="jsファイルアップロード" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/168550/bb36db70-360b-cddb-38d3-bdfa38324524.png">


:::note warn
### Processing.jsとは？
Processing.jsは、ビジュアルアートとインタラクティブなグラフィックスのためのJavaScriptライブラリです。
https://processing.org/

※2018年12月でサポートが終了しています。今後はp5.jsをお使いください。
https://github.com/processing-js/processing-js
:::

## ハンズオン

いよいよ大事なJavaScriptを記述していきます。
今回の以下の要件でアプリを作成しました。

- kintoneの新規レコードの作成・レコードの編集画面を表示した際に、書き初めができるスペースを作成
- マウスクリックした際に書き始める
- マウスが押されている間は書き続ける
- マウスクリックが終わったタイミングで書き終わる
- スペースキーで半紙をリセット
- rキーで朱色（Red）
- bキーで黒色（Black）
- gキーで緑色（Green）
- 「保存」ボタンをクリックすると画像として保存される

コードを見ながら、内容を確認してみて下さい。

```javascript:index.js
(() => {
  "use strict";
  const show_events = ["app.record.create.show", "app.record.edit.show"];
  const success_events = ["app.record.create.submit.success", "app.record.edit.submit.success"];

  // Base64エンコードされた文字列をBlobオブジェクトに変換する関数
  const b64toBlob = (b64Data, contentType = "", sliceSize = 512) => {
    const byteCharacters = atob(b64Data);
    const byteArrays = [];

    for (let offset = 0; offset < byteCharacters.length; offset += sliceSize) {
      const slice = byteCharacters.slice(offset, offset + sliceSize);
      const byteNumbers = Array.from(slice, (char) => char.charCodeAt(0));
      const byteArray = new Uint8Array(byteNumbers);
      byteArrays.push(byteArray);
    }

    return new Blob(byteArrays, { type: contentType });
  };

  kintone.events.on(show_events, async () => {
    document.getElementById(
      "user-js-hanshi"
    ).innerHTML = `<canvas id="canvas" width="600" height="750"></canvas>`;

    const sketchProc = (processing) => {
      const radius = 20; //半径
      const A = 3;

      // 初期設定
      processing.setup = () => {
        processing.smooth(); //グラフィックスの滑らかさ
        processing.frameRate(60); //秒間60フレームのレートでアニメーションを描画
        processing.size(400, 600); //キャンバスのサイズを400x600ピクセル
        processing.background(250); //背景色
        processing.stroke(54, 54, 54); //描画する線の色
        processing.fill(54, 54, 54); //図形の塗りつぶし色
      };

      // マウスが押された時に呼ばれる
      processing.mousePressed = () => {
        processing.strokeWeight(2); //描画する線の太さ2px
        processing.pushMatrix(); //現在の変換行列
        processing.translate(processing.mouseX, processing.mouseY + 3); //描画の起点をマウスの位置に移動, Y座標は3ピクセルだけ下
        processing.rotate(processing.radians(-150)); //描画座標系を-150度回転
        processing.beginShape(); //形状描画
        // 角度thetaを10度から360度まで変化させながら形状を描画
        for (let theta = 10; theta < 361; theta++) {
          let r = 1 / (A * processing.sin(processing.radians(theta) / 2) + 1);
          // 頂点（x,y）
          processing.vertex(
            radius * r * processing.cos(processing.radians(theta)),
            radius * r * processing.sin(processing.radians(theta))
          );
        }
        processing.endShape(); //形状描画を終了
        processing.popMatrix(); //保存した変換行列に戻る
        processing.redraw(); //画面を再描画
      };

      // マウスをドラッグする際に呼ばれる
      // 筆で書いたのような幾何学的な設定をする
      processing.mouseDragged = () => { 
        for (let i = 0; i < 10; i++) {
          let x = processing.random(i); //xは0-1のランダムな値を取る
          let y = processing.sqrt(processing.sq(i) - processing.sq(x)); //xをもとに幾何学的関係になる値を取る
          // Lines for drawing
          processing.line(
            processing.pmouseX - x, //直前のマウスのX座標
            processing.pmouseY + y, //直前のマウスのY座標
            processing.mouseX, //現在のX座標
            processing.mouseY //現在のY座標
          ); //右下&左上
          processing.line(
            processing.pmouseX - x,
            processing.pmouseY - y,
            processing.mouseX,
            processing.mouseY
          ); //右上&左下
          processing.line(
            processing.pmouseX + x,
            processing.pmouseY + y,
            processing.mouseX + x,
            processing.mouseY + y
          ); //右下
          processing.line(
            processing.pmouseX - x,
            processing.pmouseY - y,
            processing.mouseX - x,
            processing.mouseY - y
          ); //左上
          processing.line(
            processing.pmouseX + x,
            processing.pmouseY - y,
            processing.mouseX + x,
            processing.mouseY - y
          ); //右上
          processing.line(
            processing.pmouseX - x,
            processing.pmouseY + y,
            processing.mouseX - x,
            processing.mouseY + y
          ); //左下
          processing.line(
            processing.pmouseX,
            processing.pmouseY,
            processing.mouseX,
            processing.mouseY
          );
          processing.line(
            processing.pmouseX,
            processing.pmouseY + y,
            processing.mouseX,
            processing.mouseY
          );
        }
      };

      // キーが押された時に呼ばれる
      processing.keyPressed = () => {
        switch (processing.keyCode) {
          case 32: // Space
            processing.background(255);
            break;
          case 82: // 'r'
            processing.stroke(235, 97, 1);
            processing.fill(235, 97, 1);
            break;
          case 66: // 'b'
            processing.stroke(54, 54, 54);
            processing.fill(54, 54, 54);
            break;
          case 71: // 'g'
            processing.stroke(50, 168, 56);
            processing.fill(50, 168, 56);
            break;
        }
      };
    };

    const canvas = document.getElementById("canvas");
    new Processing(canvas, sketchProc);
  });

  kintone.events.on([success_events], async (event) => {
    try {
      const imageData = canvas.toDataURL("image/png").split(",")[1];
      const blob = b64toBlob(imageData, "image/png");
      const formData = new FormData();
      formData.append("__REQUEST_TOKEN__", kintone.getRequestToken());
      formData.append("file", blob, "image.png");

      const headers = {
        "X-Requested-With": "XMLHttpRequest",
      };

      const fileUploadResponse = await fetch("/k/v1/file.json", {
        method: "POST",
        headers,
        body: formData,
      });

      const fileUploadData = await fileUploadResponse.json();
      if (!fileUploadResponse.ok) {
        throw new Error(`File upload failed: ${fileUploadResponse.status}`);
      }

      const fileKey = fileUploadData.fileKey;

      await kintone.api(kintone.api.url("/k/v1/record", true), "PUT", {
        app: kintone.app.getId(),
        id: event.record.$id.value,
        record: {
          title: {
            value: event.record.title.value,
          },
          image: {
            value: [
              {
                fileKey: fileKey,
              },
            ],
          },
        },
      });

      console.log("Record updated successfully");
    } catch (error) {
      console.error("Error:", error);
    }
  });
})();
```

上記のjsファイルを、kintoneのPC用のJavaScriptファイルに読み込ませたらアプリは完了です。

<img width="600" alt="jsファイルアップロード" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/168550/f3188972-3293-c455-8f5a-0dbadcfda8af.png">

アプリから新規コードを作成してみてください。

<img width="500" alt="試し書き" src="https://media.giphy.com/media/0JPazrXpqmjSHev7qd/giphy.gif">


## まとめ
いかがでしたか？kintoneはデータを溜めるDBとして利用するのも良いですし、jsライブラリと組み合わせてDOM操作をしながら見た目を変えてみるのも面白いですよね！
~~全然実用的なものではないですが~~ 社内のコミュニケーションアプリになったら嬉しいですし、今年を振り返る良い機会になれば幸いです！

それでは皆様、良い年の瀬をお過ごしください〜👋🍊
<img width="100" alt="試し書き" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/168550/015df757-51b4-1b23-e717-d3eed0dada51.png">



## 参考サイト
参考になりました。ありがとうございました！
[Processing.jsを使ってみる （２）](https://qiita.com/Souj/items/fe8aa34f1b1339a94427)
[It's 書time! [caravanより短いコードで"書き初め"してみた]](https://qiita.com/HirokiOhara/items/87bf989e6d2cd044207a)
[キーコードの取得](https://web-designer.cman.jp/javascript_ref/keyboard/keycode/)