20190503のJavaScriptに関する記事は19件です。

Electronをさわってみた

どうも、はじめまして。
いつもはAndroidで動くMastodonのクライアントを作っています。

本題ですが少し前から興味があったElectronを触るお話です。
ダイアログが出るところまでやっていこうと思います。
ちなみに私はHTMLもJSも全然わかりません。

作る

Node.jsを入れるところは省きます。ごめんね。
node --versionは10.15.3、npm --versionは6.4.1です。
Windows 10で作業していきます。

ディレクトリ移動

お好きな場所に作業フォルダを作成して、
ターミナルを開きcd {作ったフォルダのパス}`で移動しておいてください。
Shift+右クリックで出るPowerShellをここで開くでもいいかもしれない。

package.json作成

ターミナルに
npm initと入力し、エンターで進みます。
もしくはオプションに-yをつけて飛ばしても良かったかもしれない。

生成されたpackage.jsonは以下の通りになってると思います。

{
  "name": "test_electron",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "electron": "^5.0.0"
  }
}

これのnameところは各自で変わってると思います。
これでオッケーです。

Electronをインストールする

フォルダ内で以下の文を入力します。

npm install --save-dev electron

好きなエディタを開いて

作業フォルダの中にsrcフォルダを作成します。
この中に

  • index.html
  • main.js
  • package.json

を作成してください。package.jsonは最初に作ったやつじゃないです。ややこしい。

SnapCrab_NoName_2019-5-3_22-11-58_No-00.png

ファイルの中身は
index.html

<!DOCTYPE html>
<html lang="jp">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>はじめてのElectron</title>
</head>

<body>
    はろーわーるど!
</body>

</html>

package.json

{
    "main": "main.js"
}

main.js

// Modules to control application life and create native browser window
const { app, BrowserWindow } = require('electron')

// Keep a global reference of the window object, if you don't, the window will
// be closed automatically when the JavaScript object is garbage collected.
let mainWindow

function createWindow() {
    // Create the browser window.
    mainWindow = new BrowserWindow({
        width: 800,
        height: 600,
        webPreferences: {
            nodeIntegration: true
        }
    })

    // and load the index.html of the app.
    mainWindow.loadFile('index.html')

    // Open the DevTools.
    // mainWindow.webContents.openDevTools()

    // Emitted when the window is closed.
    mainWindow.on('closed', function () {
        // Dereference the window object, usually you would store windows
        // in an array if your app supports multi windows, this is the time
        // when you should delete the corresponding element.
        mainWindow = null
    })
}

// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.on('ready', createWindow)

// Quit when all windows are closed.
app.on('window-all-closed', function () {
    // On macOS it is common for applications and their menu bar
    // to stay active until the user quits explicitly with Cmd + Q
    if (process.platform !== 'darwin') app.quit()
})

app.on('activate', function () {
    // On macOS it's common to re-create a window in the app when the
    // dock icon is clicked and there are no other windows open.
    if (mainWindow === null) createWindow()
})

// In this file you can include the rest of your app's specific main process
// code. You can also put them in separate files and require them here.

main.jsに関してはサンプルそのままです

実行してみる

以下の文をターミナルに入力

npx electron src

そしてこのように表示されたら成功です。888888
SnapCrab_はじめてのElectron_2019-5-3_20-47-36_No-00.png

ダイアログを出してみる

公式ドキュメントも見てみてね。
index.htmlを少し書き換えていきます。

<!DOCTYPE html>
<html lang="jp">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>はじめてのElectron</title>
</head>

<body>
    はろーわーるど!

    <script>

        const dialog = require('electron').remote.dialog;
        var options = {
            type: 'info',
            buttons: ['はい'],
            title: 'はじめてのElectron',
            message: 'ようこそ',
            detail: 'てすとだよ'
        };

        dialog.showMessageBox(null, options);

    </script>

</body>

</html>

はろーわーるど!のしたの<script>なんとか~</script>が増えたところです。

これで実行すると、

SnapCrab_NoName_2019-5-3_20-48-44_No-00.png

こうなります。alert('')より使えるね!

参考にしたドキュメント

https://electronjs.org/docs/tutorial/first-app#trying-this-example

以上です。おつー

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

プログラミング学習記録76〜おみくじゲーム〜

今日やったこと

  • ドットインストール「JavaScriptでおみくじを作ろう」でおみくじを作り、少しアレンジする
  • アレンジしたものをYouTubeに投稿する
  • ドットインストール「JavaScriptでタイピングゲームを作ろう」を途中までやる

前回と同じようにドットインストールで作ったものを少しアレンジして、動作している様子をYouTubeに投稿してみました。
ドットインストール「JavaScriptでおみくじを作ろう」を少しアレンジして作ったもの

おみくじの種類を増やして、確率を調整しました。




また、以下のようにjsとCSSを記述して、ボタンを作りました。

mousedownはマウスボタンが押された時、mouseupはマウスボタンを離した時に起こるイベントです。

以下のように書くと、押された時にbtnにpressedというクラスが追加されて、離した時にはpressedクラスが外されます。

btn.addEventListener('mousedown' , () => {
    btn.classList.add('pressed');
  });


btn.addEventListener('mouseup' , () => {
    btn.classList.remove('pressed');
  });
#btn.pressed{
  box-shadow: 0 5px 0 #d1483e;
  margin-top: 35px;
}


#05のMath.random() Math.floor()の解説がめちゃくちゃわかりやすかったです。

「はじめてのJavaScript」の途中に#05を差し込んで欲しい、と思いました。

明日も頑張ります。


おわり

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

プログラミング学習記録75〜ちょいアレンジして動画投稿〜

今日やったこと

  • Progate JavaScript1そのまま復習
  • ドットインストール「はじめてのJavaScript」で作った簡単なゲームをちょっとアレンジしてYouTubeに投稿
  • ドットインストール「JavaScriptでおみくじを作ろう」を視聴

Progateはそのまま復習だけしました。
実際にコードを書いて復習しなくてもよさそうです。

ドットインストールの「はじめてのJavaScript」で作ったミニゲームをちょっとだけアレンジしたものをYouTubeに投稿してみました↓

ドットインストール「はじめてのJavaScript」を少しアレンジして作ったもの

ただ動画を見て受動的に作るよりも「どこか変えられるところはないか?」と考えながらコードを書いた方が身になると思います。

なので、当たりの時の背景色と回転数だけですが、変えてみました。

こんな感じで作ったものを動画でアップロードしてみるのもモチベーションになっていい気がします。


あと、「JavaScriptでおみくじを作ろう」の動画を見ました。

今までは1動画見る→コード書く→1動画見る→コードを書く→…という感じだったのですが、今日は1回最後まで動画を見てみることにしました。

1回最後まで見てみた結果、全体像がわかってからコードを書いた方が理解しやすいのではないかと思いました。

続きはまた明日やってみます。

おわり

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScriptでボタンを螺旋状に配置する

ソース

test.html
<div id="div1"></div>

<script>
    var x = 0
    var y = 0
    var vec = 0//↑→↓←の順で0~3
    var co = 0
    var max = 1

    var num = 100//ボタンの数
    var margin = 40//ボタン間の距離
    var div1 = document.getElementById("div1")

    function next() {
        if (co == max) {
            co = 0
            if (vec == 1 || vec == 3) { max++ }
            vec++;
            if (vec > 3) { vec = 0 }
        }
        co++

        if (vec == 0) {
            y++
        } else if (vec == 1) {
            x++
        } else if (vec == 2) {
            y--
        } else if (vec == 3) {
            x--
        }
    }

    for (var i = 0; i < num; i++) {
        var b = document.createElement("button")
        b.innerText = "" + i
        b.style.position = "absolute"
        b.style.left = window.innerWidth / 2 + x * margin + "px"
        b.style.top = window.innerHeight / 2 + y * margin + "px"
        b.style.backgroundColor="rgb("+(250-i)+",250,100)"
        div1.appendChild(b)

        next()
    }
</script>

結果
aaキャプチャ.PNG

何に使えるかはわかりません

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

VueでKonva.jsとcanvasを使ってお絵描き(その5)

その4はこちら

直線を引けるようにする

モードで直線を選択したら直線を引けるようにしようと思います。

・描画開始(マウスダウン)時にその時の座標を取得
・描画中(マウスムーブ)は何もしない
・描画終了(マウスアップ)時にその時の座標を取得

...開始・終了の座標を結んだら直線引けるんじゃね?って思ってやってみたらできました。

親(CallCanvas.vue)のほうはすでに直線モードを子に渡せる状態になっているので変更はありません。

子(FreeDrawing.vue)を修正します。
以下のようなコメント行の間にあるソースが直線を引くために追加したソースコードです。

/** 追加 */
...
..
.
/** 追加ここまで */
FreeDrawing.vue
...
..
.

<script>
import Konva from 'konva'

export default {
  ...
  ..
  .
  methods: {
    mousedown: function () {
      this.isPaint = true

      // マウスダウン時の座標を取得しておく
      this.lastPointerPosition = this.stage.getPointerPosition()

      // 戻す配列に描画前のキャンバスの状態を保存
      this.undoDataStack.push(this.context.getImageData(0, 0, this.canvas.width, this.canvas.height))

      /** 追加 */
      // 直線モード時はマウスダウン時に描画開始座標を指定する
      if (this.isTargetMode('line')) {
        this.context.beginPath()

        this.localPos.x = this.lastPointerPosition.x - this.drawingScope.x()
        this.localPos.y = this.lastPointerPosition.y - this.drawingScope.y()

        // 描画開始座標を指定する
        this.context.moveTo(this.localPos.x, this.localPos.y)
      }
      /** 追加ここまで */
    },
    mouseup: function () {
      this.isPaint = false

      /** 追加 */
      // 直線モード時はマウスアップ時に描画する
      if (this.isTargetMode('line')) {
        this.pos = this.stage.getPointerPosition()
        this.localPos.x = this.pos.x - this.drawingScope.x()
        this.localPos.y = this.pos.y - this.drawingScope.y()

        // 描画開始座標から、lineToに指定された座標まで描画する
        this.context.lineTo(this.localPos.x, this.localPos.y)
        this.context.closePath()
        this.context.stroke()
        this.drawingLayer.draw()

        this.lastPointerPosition = this.pos
      }
      /** 追加ここまで */
    },
    mousemove: function () {
      if (!this.isPaint) {
        return
      }
      // ペンモード時
      if (this.isTargetMode('brush') || this.isTargetMode('line')) {
        this.context.globalCompositeOperation = 'source-over'
      }
      // 消しゴムモード時
      if (this.isTargetMode('eraser')) {
        this.context.globalCompositeOperation = 'destination-out'
      }

      /** 追加 */
      // 直線モード時は何もしない
      if (this.isTargetMode('line')) {
        return
      }
      /** 追加ここまで */

      ...
      ..
      .
    },
    ...
    ..
    .
}
</script>

引けました!ただ斜め線引いた時に少しギザギザなってしまうところが難点。
(考えを安直にソースにしただけなので仕方ないかもしれないですが。)
image.png

備考

直線を引くときにガイドラインを表示してあげたりとかできるとさらに良くなりますね。
気が向いたらやってみます。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

長年欲しかったcanvasでくり抜きポリゴンを描画する方法

やりたいこと

こういう中身がくり抜かれたポリゴンをHTMLのcanvasで描画したい。
スクリーンショット 2019-05-03 19.29.39.png

真っ先に浮かぶ方法

ブレンドモードを指定してポリゴンの重なり部分を切り取る方法。

ctx.globalCompositeOperation = 'xor'

https://www.yoheim.net/blog.php?q=20121206

しかしこの方法は、「今のcanvasに描画されている部分」と「これから描画するポリゴン」の重なり部分が切り取られてしまう。

つまりこのような描画をしておいて、

  ctx.fillStyle = 'red'
  ctx.beginPath()
  ctx.moveTo(10, 40)
  ctx.lineTo(150, 40)
  ctx.lineTo(150, 100)
  ctx.lineTo(10, 100)
  ctx.closePath()
  ctx.fill()

  ctx.fillStyle = 'blue'
  ctx.beginPath()
  ctx.moveTo(50, 10)
  ctx.lineTo(120, 10)
  ctx.lineTo(120, 150)
  ctx.lineTo(50, 150)
  ctx.closePath()
  ctx.fill()

スクリーンショット 2019-05-03 19.24.43.png

くり抜き描画を追加で行うと、

  ctx.globalCompositeOperation = 'xor'
  ctx.beginPath()
  ctx.moveTo(70, 30)
  ctx.lineTo(100, 30)
  ctx.lineTo(100, 120)
  ctx.lineTo(70, 120)
  ctx.closePath()
  ctx.fill()

こうなる。
スクリーンショット 2019-05-03 19.26.26.png

違うんだ、こうなって欲しいんだという願いは叶わない。
スクリーンショット 2019-05-03 19.29.39.png

欲しかった方法

SVGの挙動を調べていたらこういう仕様を見つけた。

path要素においてはfill-ruleがnonzeroであっても,白抜きとなるケースが発生する.複数のパスから構成されたpash図形であった場合,互いにパスの向きが逆向きであった場合は,内側と外側とが打ち消し合い,fill対象の領域から除外される.

出展サイト:http://defghi1977.html.xdomain.jp/tech/svgMemo/svgMemo_03.htm

そしてこれは、canvasでも成り立つ。
つまりこう書けば、

  ctx.fillStyle = 'red'
  ctx.beginPath()
  ctx.moveTo(10, 40)
  ctx.lineTo(150, 40)
  ctx.lineTo(150, 100)
  ctx.lineTo(10, 100)
  ctx.closePath()
  ctx.fill()

  ctx.fillStyle = 'blue'
  ctx.beginPath()
  ctx.moveTo(50, 10)
  ctx.lineTo(120, 10)
  ctx.lineTo(120, 150)
  ctx.lineTo(50, 150)
  ctx.closePath()

  ctx.moveTo(70, 30)
  ctx.lineTo(70, 120)
  ctx.lineTo(100, 120)
  ctx.lineTo(100, 30)
  ctx.closePath()
  ctx.fill()

これが手に入る。
スクリーンショット 2019-05-03 19.29.39.png

ポイントはここ。
くり抜かれる側の矩形と、くり抜き範囲となる側の矩形で座標定義の回転方向が逆向きになっている。そして同じbeginPath()の中でパスを作ってから一気にfill()する。
要するにSVGでの仕様と同じ。SVGというよりは、もっと根底にある描画システム的な共通仕様なのかもしれない。

  ctx.fillStyle = 'blue'
  ctx.beginPath()
  ctx.moveTo(50, 10)
  ctx.lineTo(120, 10)
  ctx.lineTo(120, 150)
  ctx.lineTo(50, 150)
  ctx.closePath()

  ctx.moveTo(70, 30)
  ctx.lineTo(70, 120)
  ctx.lineTo(100, 120)
  ctx.lineTo(100, 30)
  ctx.closePath()
  ctx.fill()

回転方向を交互に変えていけばさらにくり抜きをネストしていくこともできてしまう。楽しい。
スクリーンショット 2019-05-03 19.44.15.png

もちろん包含関係になくてもxorな描画として使うことができるので、今まで頑張って座標をポチポチ作っていた場面でもスパッとxorを決めることができてしまう。

昔仕事でこういうくり抜きが欲しくて調べまくったのに結局わからず諦めたことがあったが、canvasではなくSVGを調べることで思わぬリベンジを果たすことができた。
これからは思う存分くり抜きまくろう。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ソートアルゴリズムの計算量について、復習してみた

GWいかがお過ごしでしょうか。
私は転職して、本格的に開発側へシフトしたこともあって
いろいろなアルゴリズムに触れる機会が増えています。
また、JS系のライブラリやフレームワークを業務で使う事が多いので
今回は計算量を題にして、JavaScriptのプログラミングにも触れながらまとめてみようと思います。

計算量って?

情報科学の処理量を表し、アルゴリズムの処理時間を比較するために用いられます。
計算量は、オーダー記法を用いて表現します。

例えば処理するデータ数をnとしたとき、O(1)O(n)といった書き方をします。
O(1)といった表記は、定数オーダーともいい、データ量に寄らずに処理時間が一定であることを表しています。
O(n)は、処理時間がデータn個に比例することを表しています。

また、例えば処理時間がデータn個の2倍かかるとした場合、計算量をO(2n)と書きたくなりますが
この定数2は無視をしてO(n)と書きます。

どう使われる?

O(1)とO(n)では、データ量が増えるとO(1)のほうが早そうですよね。
計算量を出すことで、処理時間を予測することは、アルゴリズムやデータ構造の選定に役立つわけです。

ソートアルゴリズムの計算量

今回は代表的なソートのアルゴリズムの計算量を見ていきます。

バブルソート

配列の要素の前から順番に比較してソートをします。
計算量は、O(n^2)になります。

クイックソート

基準値(ピボット)を決めてそれよりも大きい値小さい値を集めます。
この処理を再帰的に行ってソートをします。
計算量は、ピボットの選び方によって異なりますが、平均するとO(nlogn)になります。
(最悪計算量はO(n^2))

マージソート

配列を最小単位まで分割し、それぞれをマージしながらソートをする。
計算量はO(nlogn)になります。

計算量(処理時間)を確かめてみる

せっかくなので、今学習中のJavaScriptでモジュールを作って確認してみます。

バブルソート

bublesort.js
'use strict';
// バブルソート
function bubbleSort(array) {
  let result = Array.from(array);
  for(let e = result.length - 1; e > 0; e--){
    for(let i = 0; i < e; i++){
      if(result[i] > result[i + 1]){
        [result[i], result[i+1]] = [result[i+1], result[i]];
      }
    }
  }
  return result;
}

module.exports = bubbleSort;

クイックソート

quicksort.js
'use strict';

function quickSort(array, start = 0, end = array.length - 1){
// クイックソート
  if(end <= start){
    return;
  }

  const pivot = selectPivot(array, start, end);

  let left  = start;
  let right = end;
  while(true){
    while(array[left]  < pivot) { left++; }
    while(array[right] > pivot) { right--; }
    if(left >= right) { break; } // leftとrightが逆転 = 探索終了
    [array[left], array[right]] = [array[right], array[left]];
    left++;
    right--;
  }

  // 前半後半で、再起処理
  quickSort(array, start, left - 1);
  quickSort(array, right + 1, end);

    // ピボットを選ぶ
  function selectPivot(array, start , end){
    // 配列の先頭、中央、末尾の中央値をピボットとする。
    const [x, y, z]  = [ array[start],
                         array[Math.floor((start + end) / 2.0)],
                         array[end] ];
    if(x > y){
      return y < z ? y :
             z < x ? x : z;
    } else {
      return z < y ? y :
             x < z ? x : z;
    }
  }
}

module.exports = quickSort;

マージソート

margesort.js
'use strict';
// マージソート
function margeSort(array){
  if( array.length < 2 ){ return array; }
  const middle = Math.floor(array.length / 2.0);
  let left  = array.slice(0, middle);
  let right = array.slice(middle);

  return marge(margeSort(left), margeSort(right));

  // 配列をソートしながら一つに統合する
  function marge(left, right){
    let result = [];
    let leftIndex  = 0;
    let rightIndex = 0;

    while(leftIndex < left.length && rightIndex < right.length){
      if(left[leftIndex] < right[rightIndex]){
        result.push(left[leftIndex++]);
      }else{
        result.push(right[rightIndex++]);
      }
    }

    return result.concat(left.slice(leftIndex)).concat(right.slice(rightIndex));
  }
}

module.exports = margeSort;

処理時間を計測するためのモジュール

引数で与えた関数の処理時間を計測します。
(ブラウザ上で動かすなら、console.time系ではなくてperformance.now()とか使うといい)

measureTime
'use strict';

function measureElapseTime(func, dataNum, targetName){
  let measureTarget = `${targetName} times elapse time`;

  // ランダムな配列データを作成
  const array = [];
  for(let i = 0; i < dataNum; i++) {
    const value = Math.random();
    array.push(value);
  }

  console.time(measureTarget);
  func(array);
  console.timeEnd(measureTarget);
}

module.exports = measureElapseTime;

仕様例

今回はサンプルデータを10000として計測してみます。

execute.js
const measureElapsetime = require('./measureTime');
const bubleSort = require('./bublesort');
const quickSort = require('./quicksort');
const margeSort = require('./margesort');

const dataNum = 10000;

measureElapsetime(bubleSort, dataNum, 'BubleSort');
measureElapsetime(quickSort, dataNum, 'QuickSort');
measureElapsetime(margeSort, dataNum, 'margeSort');

実行結果

BubleSort times elapse time: 315.860ms
QuickSort times elapse time: 18.161ms
margeSort times elapse time: 17.550ms

確かに、クイックソートや、マージソートのほうがバブルソートのよりも約20倍早いことがわかります。
(もう少し早くなっても良さそうですが…)
また、クイックソートとマージソートの平均計算量が同等(O(nlogn))なので、近いこともわかります。

終わりに

他にもヒープソートやバケットソートなど、ソートアルゴリズムはまだまだあります。
これらをソースコードにして、処理時間を確かめてみるのもいいかもしれません。

参考

「Subterranean Flower Blog」 【連載記事】JavaScriptでプログラミングを学ぶ その5:データ構造とアルゴリズム

「Wiki Pedia」クイックソート

「Wiki Pedia」マージソート

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

vue cliでプロジェクト作ったら(メモ)

前記事のURL

https://qiita.com/s12ac098/items/f24ac76167b3d846064e

プロジェクトのディレクトリで「run npm serve」

npmrundev.jpg

localhost:8080にアクセス

runserve.jpg

デフォルトのプロジェクトができます

default.jpg

デフォルトをいじる Part1~HelloWorld.vueのリネーム~

まずcomponentsのHelloWorld.vueをリネームしましょう(ダサいから)
helloworld.jpg

安心してください。ページが死ぬだけです。
compile.jpg

App.vueにHelloWorldがいっぱいいるので、全部indexに変えましょう。
changealloccurences.jpg

App.vue
<template>
  <div id="app">
    <img alt="Vue logo" src="./assets/logo.png">
    <index msg="Welcome to Your Vue.js App"/>
  </div>
</template>

<script>
import index from './components/index.vue'

export default {
  name: 'app',
  components: {
    index
  }
}
</script>

<style>
#app {
  font-family: 'Avenir', Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>

デフォルトをいじる Part2~ページをまっさらに~

つづいて、こいつを真っ白に染めましょう
default.jpg

divタグとstyleタグの中を消しましょう。

App.vue
<template>
  <div id="app">
   <h1>vue cliがまっさらに・・・</h1>
  </div>
</template>

<script>
import index from './components/index.vue'

export default {
  name: 'app',
  components: {
    index
  }
}
</script>

<style>

</style>

index.vueの中も同様にですね。※divタグのクラスはご自由に

index.vue
<template>
  <div class="index container">

  </div>
</template>

<script>
export default {
  name: 'index',
  props: {

  }
}
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style>

</style>

white.jpg

つづく

次回はcomponentの追加における3ステップについて!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

React + ReduxでTodoアプリを作ってみよう!『Toggle Todo編』

概要

前回の記事ではTodoアプリにTodoを追加する『Add Todo』機能を実装しました。今回は、追加されたTodoをクリックした際に斜線を引けるようにし、Todoの未・済を判別出来る機能を実装したいと思います!

前回の記事
React + ReduxでTodoアプリを作ってみよう!『Add Todo編』

completed属性を付与する

completed属性を付与することで、Todoが完了済みなのか未完了なのかを判別できるようにしましょう!Todoを追加する際にcompleted属性にfalseをデフォルトとして設定します!

src/reducers/todos.js
import {ADD_TODO, TOGGLE_TODO} from '../actions';

const todos = (state = [], action) => {
  switch (action.type) {
    case ADD_TODO:
      return [...state, {id: action.id, text: action.text, completed: false}];
    default:
      return state;
  }
};

export default todos;

completed属性によってTodoのstyleを変更する

completedの属性がtrueかfalseかによってTodoに斜線を引けるようにstyleを反映させましょう!

src/components/Todo.js
import React from 'react';
import PropTypes from 'prop-types';

const Todo = ({onClick, completed, text}) => {
  return (
    <li
      onClick={onClick}
      style={{textDecoration: completed ? 'line-through' : 'none'}}>
      {text}
    </li>
  );
};

Todo.propTypes = {
  completed: PropTypes.bool.isRequired,
  text: PropTypes.string.isRequired,
};

export default Todo;

Action経由でcompleted属性を操作する

Action経由でcompleted属性を操作するために、まずはAction Creatorを作成しましょう。横線を引くTodoを判別するために、idを取得します。

src/actions/index.js
export const ADD_TODO = 'ADD_TODO';
export const TOGGLE_TODO = 'TOGGLE_TODO';

let nextTodoId = 0;
export const addTodo = text => {
  return {
    type: ADD_TODO,
    id: nextTodoId++,
    text,
    //text: text,
  };
};

export const toggleTodo = id => {
  return {
    type: TOGGLE_TODO,
    id,   
    //id: id
  };
};

またreducersに戻りaction.typeTOGGLE_TODOの際の状態遷移の方法を記述しましょう。この中では、stateに保管されているTodoのidと横線を引きたいTodoのidを比較し、お互いに一致するようであれば、一致したidのTodoが持っているcompleted属性を逆転させます。

src/reducers/todo.js
import {ADD_TODO, TOGGLE_TODO} from '../actions';

const todos = (state = [], action) => {
  switch (action.type) {
    case ADD_TODO:
      return [...state, {id: action.id, text: action.text, completed: false}];
    case TOGGLE_TODO:
      return state.map(todo =>
        todo.id === action.id ? {...todo, completed: !todo.completed} : todo
      );
    default:
      return state;
  }
};

export default todos;

Todoをクリックしてcompleted属性を変更する

まずは、mapDispatchToPropsを作成してdispatchを
propsとして使えるようにしましょう。toggleTodoという名前でdispatchをstoreに格納します。

src/containers/VisibleTodoList.js
import {connect} from 'react-redux';
import TodoList from '../components/TodoList';
import {toggleTodo} from '../actions';
const mapStateToPorops = state => {
  return {todos: state.todos};
};

const mapDispatchToProps = dispatch => {
  return {
    toggleTodo: id => {
      dispatch(toggleTodo(id));
    },
  };
};

const VisibleTodoList = connect(
  mapStateToPorops,
  mapDispatchToProps
)(TodoList);

export default VisibleTodoList;

これでTodoListでtoggleTodoが使えるようになったので、TodoコンポーネントのonClick属性にtodoのidを引数としてtoggleTodoを渡します。

src/components/TodoList.js
import React from 'react';
import PropTypes from 'prop-types';
import Todo from './Todo';

const TodoList = ({todos, toggleTodo}) => {
  return (
    <ul>
      {todos.map(todo => (
        <Todo key={todo.id} {...todo} onClick={() => toggleTodo(todo.id)} />
      ))}
    </ul>
  );

  TodoList.propTypes = {
    todos: PropTypes.arrayOF(
      PropTypes.shape({
        id: PropTypes.number.isRequired,
        text: PropTypes.string.isRequired,
      }).isRequired
    ).isRequired,
  };
};

export default TodoList;

Todoコンポーネント内でpropsとして渡されたtoggleTodoをonClick属性に付与します。

src/components/Todo.js
import React from 'react';
import PropTypes from 'prop-types';

const Todo = ({onClick, completed, text}) => {
  return (
    <li
      onClick={onClick}
      style={{textDecoration: completed ? 'line-through' : 'none'}}>
      {text}
    </li>
  );
};

Todo.propTypes = {
  completed: PropTypes.bool.isRequired,
  text: PropTypes.string.isRequired,
};

export default Todo;

以上でTodoをクリックした際に取り消し線を引く機能を実装できました!

ezgif.com-optimize (2).gif

次回は表示するTodoを完了・未完了によって分ける「Filter Todo」機能を実装したいと思います。

リファレンス

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

htmを使ってMithrilのコンポーネントをJSXライクに記述する

htmというpreactの作者が開発したライブラリがあり、

これを使うと、Mithrilのコンポーネントを以下のように記述できます。

簡易的なUIを素早く作りたいときに便利そうです。

import m from "https://raw.githack.com/MithrilJS/mithril.js/next/mithril.mjs";
import htm from "https://unpkg.com/htm?module";

const html = htm.bind(m);

const SearchInput = {
  view(vnode) {
    return html`
      <div class="search-input"> 
        <input class="search-input-text" type="search"></input>
        <button class="search-input-button" onclick=${vnode.attrs.search}>?</button>
      </div>
    `;
  }
};

const App = {
  view(vnode) {
    return html`
      <${SearchInput} search=${() => alert('hoge')}><//> 
    `;
  }
};

m.mount(document.body, App);

babel-plugin-htm

babel-plugin-htmを使うと、

htmのシンタックスをhyperscriptの呼び出しに置き換えることができます。

Mithrilで利用する際は、以下のように設定を行っておく必要があります。

.babelrc
{
  "plugins": [
    ["htm", {
      "pragma": "m"
    }]
  ]
}

先程のコードの場合は、以下のようにコンパイルされます。

import m from "https://raw.githack.com/MithrilJS/mithril.js/next/mithril.mjs";
import htm from "https://unpkg.com/htm?module";
const html = htm.bind(m);

const SearchInput = {
  view(vnode) {
    return m("div", {
      class: "search-input"
    }, m("input", {
      class: "search-input-text",
      type: "search"
    }), m("button", {
      class: "search-input-button",
      onclick: vnode.attrs.search
    }, "\uD83D\uDD0D"));
  }

};
const App = {
  view(vnode) {
    return m(SearchInput, {
      search: () => alert('hoge')
    });
  }

};
m.mount(document.body, App);

備考

以下のバージョンで動作確認を行いました。

  • htm: v2.1.1
  • mithril: v2.0.0-rc4

参考

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Redux入門

Reduxってなに?

ReduxとはReactと相性が良いフレームワークのことです。
Redux単体で利用することも可能ですが、ReactとReduxの組み合わせは鉄板でしょう。
stateを容易に管理することの出来るReduxですが、大規模なアプリケーションになればなるほど効果を発揮してくれそうです。

Reduxアプリを構成する機能ってなに?

Reduxを使ったアプリはAction,Reducer、Storeによって構成されています。

Actionとは

何がおきたのかという情報を持つオブジェクトです。

ActionをStoreへ送信(dispatch)すると、Storeのstateが変更されます。
stateの変更ではActionが必ず必要となります。
stateへ通じるルートを攻略する第一段階、まるで門番のような立ち位置の機能ですね。

const action = {
  type: 'SET_wanko',
  text: 'トイプードル'
};

Actionではどういうタイプのアクションなのかを明示するためtypeプロパティが必要となります、他と区別できないと何がなんだかわからなくなりますもんね。

逆に言えばこのActionはその程度の情報しか持っておらず、どのようにstateを変更するのか知らない存在なのです。

Reducerとは

上記したSET_wankoというタイプのアクション受けて、storeから受け取ったstateを変更して返す純粋関数です。
Reducer内では引数変更したり、API呼び出したり、Math.random()等の純粋関数以外の関数を呼び出してはいけません。
結果が毎回同一になるような操作しか扱えないのです。

stateをどう変更するのかactionでは決めれなかったことを指定しています。

function triming(state = [], action) {
  switch (action.type) {
    case 'SET_wanko':
      return state.concat([{ text: action.text, completed: false }]);

    default:
      return state;
  }
}

Storeとは

Storeとはアプリケーションの全てのstateを保持するオブジェクトです。

dispatchされたActionと保持するstateをreducerに渡してstate変更に一役買う立場の存在で、ボスのような風格ですね。
Storeの複製はダメです、ボスは一人だけなのです。
又、stateの変更は必ずActionを経由してください、バグの特定が困難になるのを防ぐためです。
ボスに会うためにはまず名乗って(Action type)からが礼儀ってもんです。

// Action
const action = {
  type: 'SET_wanko',
  text: 'トイプードル'
};

// Reducer
function triming(state = [], action) {
  switch (action.type) {
    case 'SET_wanko':
      return state.concat([{ text: action.text, completed: false }]);

    default:
      return state;
  }
}

// Store
const store = Redux.createStore(triming);

// Actionをdispatchする
// Reducerであるtodosが実行され、Storeが保持しているstateが変更される。
store.dispatch(action);

// stateを取得する
console.log(store.getState()); 
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JSエンジニア必見!アプリ開発の際のReact Nativeことはじめ

開発環境や前提の知識など

事前環境

• Android, iOS のクロスプラットフォーム・アプリ開発フレームワークです。
画面デザインとロジックのコードを共通で利用することができます。
JavaScript でコーディングし、React で画面をデザインできます。

・Node.jsやライブラリ管理にnpmなどが活用されます。
・Homebrew
・*watchmanをインストール(よく、忘れがちです)

brew install watchman

メリット

Android、iOSでコードが共通化できるので、両方開発する場合は開発効率が良い
JavaScript で開発するため、、Web開発者の学習コストが低い
Facebook, Instagram, CookPad, メルカリ など大手で実績がある。

デメリット

アップデート、保守管理が大変
業務で、少し触れていますが、React Native はソースの管理が難しく、
バージョンをあげたりすると、コマンドが通らなくなったりして、意外と神経を使います

おすすめツール

Visual Studio Code

PowerShell (Windowsのみ)

Android Studio (Android アプリのビルドに必須)
Android アプリの IDE
Xcode (iOS アプリのビルドに必須)
iOS や macOS アプリ の IDE
Node.js (必須)
React Native アプリのビルドやデバッグに必要。
yarn (必須)
Node.js のパッケージ・マネージャ。npm の改良版で、置き換えが可能。
必須、と書いたが、npm でも代替は可能。ただしメリットはない。

参考URL

React Native開発のすすめ
https://qiita.com/janus_wel/items/787732d2bf03ed53a3ba

ReactNativeを使ってみる
https://qiita.com/kurohune538/items/fb9f5ff0b005a39fcc27

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

VueでバリデーションをVuelidateで実装してみた

VueでバリデーションをVuelidateで実装してみた

まずはコマンドラインでnpmを使ってVuelidateをインストールします。

npm install vuelidate --save

インストールが終了したらmain.jsにVuelidateをimportしてください。

main.js
import Vuelidate from 'vuelidate'

次にmain.jsに次の一文も追加に記述してください。

main.js
Vue.use(Vuelidate);

これで準備が整いました。

Vuelidateを使っていこう

使用したいコンポーネントにimportしていきます。

import { required } from 'vuelidate/lib/validators'

{}のなかに使いたいバリデータを記述しています。
※バリデータとはバリデーションを行うための機能の一種です。

これにより2つの機能が使えるようになります。

  • validations コンポーネントオプション - 検証の定義
  • $v 構造体 - 検証状態を保持するビューモデル内のオブジェクト

です。

validationsはVue インスタンスのコンポーネントオプションとして使用できバリデーションをかけるデータに対してどのようなバリデータをかけるかをオブジェクトで設定できます。

$vはビューモデル内で使用できるオブジェクトでこのオブジェクトにバリデーションの

結果や色々な状態の示すプロパティが入っています。このオブジェクトを見て判断をしていきます。

次に要素をv-modelディレクティブでデータバインディングしたものに設定していきます。

 <input type="email" id="email" v-model="email">
export default {
  data () {
    return {
      email: ''
    }
  },
}

上記にvalidationsコンポーネントオプションを設定します。

export default {
  data () {
    return {
      email: ''
    }
  },
  validations: {
      email: {
        required,
      }
  }
}

validationsオプションを記述してその中で今回はdataオプションの所でemailと設定しているので

同じ名前のemailと記述してその中にかけたいバリデータを記述します。

いまimportしているのはrequiredだけなのでrequiredを記述します。

記述できたら次は要素の方に新たに@blur="$v.email.$touch()"と記述していきます。

 <input type="email" id="email" v-model="email" @blur="$v.email.$touch()">

@blurはVue.jsのイベントでフォーカスがなくなった時に発火します。

$v.emailはvalidationsで設定したemailの状態のプロパティが入ったオブジェクトを呼び出してます。

$v.emailのemailの部分を変更してvalidationsで設定した違うkeyを呼び出すことも可能です。

例えば

export default {
  data () {
    return {
      email: ''
    }
  },
  validations: {
      email2: {
        required,
      }
  }
}
 <input type="email" id="email" v-model="email" @blur="$v.email2.$touch()">

と変更したらemail2の状態のプロパティが入ったオブジェクトを呼び出してます。

$touch()は$vモデルのメッソドで$v.emailの状態のプロパティを再帰的にtrueにします。

では、次に$v.emailには状態のプロパティが入ったオブジェクトだと言いましたが

どのような状態のプロパティが入っているかをテキスト展開を追加して確認してみましょう。

<input type="email" id="email" v-model="email" @blur="$v.email.$touch()">
<pre>{{$v.email}}</pre>

スクリーンショット 2019-04-29 12.15.24.png

スクリーンショット 2019-04-29 12.25.17.png

他の値も変化していますが、特に見てもらいたいのが赤線の2つです。

requiredはvalidationsオプションでemailに設定したrequiredのことを示しており

今は要素に何かしら文字が入っているからtrueになっているが

何も入っていなくてフォーカスを外すとfalseになる

$modelは検証しているモデルへの参照。Vueモデルを直に参照したときと同じ値が得られます。

なのでthis.$v.email.$modelとthis.emailは同じ値になります。

なので$modelを書き換えるとVueモデルも一緒に書き換えられます。

Styleをつけていく

validationsでバリデーションをつけることができましたがあくまで中だけのバリデーションなので

要素に入力している人は全く気づきません。

なのでこちらで$v.emailの値を参照しながらエラーになっているかどうかを示してあげないといけません。

.invalid {
  border: 1px solid red;
  background-color: #ffc9aa;
}

cssに上記を追加して要素にも動的にclassを追加します。

<input type="email" id="email" v-model="email" @blur="$v.email.$touch()" :class="{invalid: $v.email.$error}">

$v.email.$errorの値がtrueになればinvalidのclassが追加されます。

これで確認してみましょう。
スクリーンショット 2019-04-29 13.04.46.png

一度フォーカスをして何も入力しないでフォーカスを外すと$errorがtrueになっているため

class名が追加され要素が赤く表示がされています。

これでバリデーションエラーぽくなりましたね。

次はなんでエラーになっているかをテキストで表示してあげたら親切ですね。

テキストでエラー内容を表示する

要素の下にテキストを追加していきます。

<input type="email" id="email" v-model="email" @blur="$v.email.$touch()" :class="{invalid: $v.email.$error}">
<p style="color:red;" v-if="$v.email.$error">メールアドレスは必須項目です。</p>

v-if="$v.email.$error"で先程のclass名のときと同じで$v.email.$errorがtrueのときのみ

表示させるようにしています。

スクリーンショット 2019-04-29 14.20.16.png

このようにエラーしていることが分かりやすくなりました。

その他のバリデータをご紹介

email

有効なメールアドレスを受け入れます。

確認メールを送信せずにアドレスが本物であるかどうかを判断することは不可能であるため、

サーバー上で注意深く確認する必要があります。

maxLength、minLength

入力された文字の長さをバリデーションします。

numeric

数字のみを受け入れます。

alphaNum

英数字のみを受け入れます。

などなどがあります。
まだまだバリデータはあるのでそれは公式サイトをご確認ください。

まとめ

いかかでしたでしょうか?

これであなたも簡単にバリデーションをVueで実装することができます。

是非ともやってみてください!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

スプレッドシートのグラフをChatworkに画像として自動投稿する

はじめに

ビジネスにおける予算管理、どうしていますか?
月次レポートや週次レポートならまだしも、日次のレポートを手作業で報告・共有するのって手間ですよね。
BIツールや社内システムで見える化しても、見る習慣が付いていないとメンバーによっては毎日追えていなかったりします。

そこで今回は、導入していれば毎日欠かさず読むであろうChatツール、Chatworkへの自動通知化を紹介します。
テキスト自動投稿だけだと状況が把握しづらいので、今回はスプレッドシートで作成した棒グラフを画像として投稿するシステムとしています。

もしChatworkじゃなくSlack等の別ツールでも、画像投稿APIが存在すればほぼ同じ作りでイケるはずです。

完成図

スプレッドシートを準備

まずはGoogle Driveでスプレッドシートを新規作成。
通知したいデータと、自動通知させたいグラフを作成します。例えばこんな感じ。

スプレッドシートスクショ

GASのコードを書く

ツール→スクリプトエディタを選びます。
そして以下コードを入力し保存してください。

var TOKEN   = INSERT_YOUR_TOKEN;
var ROOM_ID = INSERT_YOUR_ROOM_ID;

function myFunction() {
  var sheet = SpreadsheetApp.getActiveSheet();
  var charts = sheet.getCharts();
  var chartTitle = Utilities.formatDate(new Date(), 'Asia/Tokyo', 'yyyy/M/d') + '販売レポート';
  var fileName   = 'daily_report_' + Utilities.formatDate(new Date(), 'Asia/Tokyo', 'yyyy-MM-dd') + '.png';

  for (var i = 0; i < charts.length; i++) {
    var oldChart = charts[i];
    var newChart = oldChart.modify().setOption('title', chartTitle).build();
    sheet.updateChart(newChart); // タイトル変更したグラフをスプレッドシートに反映
    var newChartImage = newChart.getBlob().getAs('image/png').setName(fileName);
    try {
      postToChatworkFile(newChartImage);
    } catch (e) {
      Logger.log(e.getMessage());
    }
  }
}

function postToChatworkFile(file) {
  var payload = {
    'file': file,
    'message': '今日のレポートです'
  }
  var headers = {
    'X-ChatWorkToken': TOKEN
  }
  var options = {
    'method' : 'POST',
    'payload' : payload,
    'headers' : headers
  }
  var url = 'https://api.chatwork.com/v2/rooms/' + ROOM_ID + '/files';
  UrlFetchApp.fetch(url, options);
}

var TOKEN   = INSERT_YOUR_TOKEN;
var ROOM_ID = INSERT_YOUR_ROOM_ID;

INSERT_YOUR_XXXX部分には、トークンと部屋IDをそれぞれ入れてください。
Chatwork APIトークンの取得、部屋IDの取得方法が分からない場合はこちらの過去記事をご参照ください。

入力し終えたらスクリプト実行。
実行時は承認ダイアログが出てくるので、許可の仕方が不明な場合は、こちらも過去記事をご参照ください。

うまくいくと、このように自動投稿されます。
完成図

定期実行する

手動でスクリプト実行するとChatworkに通知されるようになりました。
あとは、これを毎日自動実行するだけ。

スクリプトエディタの時計マークをクリックすると、定期実行の設定ができます。
右下の「+トリガーを追加」をクリックし、以下の通り設定すると、毎日9〜10時の間に1回実行されます。

定期実行

数値を毎日自動反映させる

このままのスプレッドシートだと毎日同じ数値だけが報告されるので意味無いですね。

スプレッドシートに自動的に数値を入力させるには、以前書いた以下記事を参照してください。
【Google Apps Script】その15 Execution APIを使い、外部からAPIとして叩く

最後に

いかがでしょうか。
視覚的に分かりやすくなることで数値に対する苦手意識も減るかもしれません。
ぜひ試してみてください。

ちなみに今回利用した、ファイルを投稿するChatwork APIの公式ドキュメントはこちら。
http://developer.chatwork.com/ja/endpoint_rooms.html#POST-rooms-room_id-files

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScript: 時計の短針と長針はいつ重なるか

報われないのは鐘のあと

時計ってすごくおもしろくて。
毎時1回は重なるようにできてるんですけど、11時台だけは重ならないの。11時台だけは短針が先に逃げ切っちゃって、ふたつの針って重ならないんですよ。
伝えたいメッセージが何かというと『鐘が鳴る前は報われない時間があるということ』

ここで感動した人は、科学的に考えるくせをつけた方がよいでしょう。鐘がなったのちの0時から1時までの間に、ふたつの針が重なることはありません1。長針が短針に追いつくまでに要する時間は、約65.455分だからです。この話でいうなら、鐘が鳴ったあとこそ報われないことになります。話を1時から始めているのがトリックです。

この計算は、算数で「旅人算」または「追越算」と呼ばれます。先に外出した弟の忘れ物を、兄が追いかけて渡そうとしたりするアレです(下はイメージ動画)。

英進館CM:「歩く男」

英進館CM:「歩く男」 60秒ver
>> YouTubube動画

分単位の近似値

正確には、1時から12時まで場合分けして計算することになります。でも、面倒なので近似値をJavaScriptコードで求めてみましょう。知りたいのは時刻ですから、角度は使いません。長針が60分で1周するトラックを、短針の1周にかかる12時間で、長針はいつどこで追い越すか考えればよいのです。

for (let t = 0; t < 60 * 12; t += 0.1) {
    const hours = t / 12;
    const minutes = t % 60;
    if (Math.floor(hours * 10) === Math.floor(minutes * 10)) {
        console.log(Math.floor(hours / 5), Math.round(minutes));
    }
}

コンソールの出力はつぎのとおりです。分未満は丸めてあります。11時台は60分に追いつくとみなせます。

0 0
1 5
2 11
3 16
4 22
5 27
6 33
7 38
8 44
9 49
10 55
11 60

秒まで測ってみる

もう少し正確に、秒まで測ってみましょう。今度は、トラックを12時の単位で刻むことにします。時速は、長針が$12メモリ/h$、短針は$1メモリ/h$ということです。$1:00$から初めて重なるまでにかかる時間を$t$として、方程式を使わせてもらいます。

12t = 1 + t\\
t = \frac{1}{11}

よって、$0:00$から数えれば、長針が短針に追いつくのに要する時間は約$65.455$分ということです。

60 + \frac{1}{11} = \frac{60 \times 12}{11} \approx 65.455

時刻表示に直すのは、JavaScriptに任せましょう。Dateオブジェクトの日時メソッドは、引数に大きな値を渡すと、繰り上げて設定してくれます。ですから、ミリ秒に換算して足し込みました。

const interval = 60 * 12 / 11;
const time = new Date(2019, 4, 3);
for (let hours = 0; hours < 12; hours++) {
    console.log(time.toString().split(' ')[4]);
    time.setMilliseconds(time.getMilliseconds() + interval * 60 * 1000);
}
00:00:00
01:05:27
02:10:54
03:16:21
04:21:49
05:27:16
06:32:43
07:38:10
08:43:38
09:49:05
10:54:32
11:59:59

  1. いや、0:00に重なって始まっている、という指摘があろうことは承知です。この重なり合いが11時台と重複する、それをどちらにカウントするかで「報われない時間」は変わります。でも、0:00から数えるなら、重なりは始まりです。そうすると、10:54の始まりは12:00で一巡して終わります。重なりを終わりで数えるのであれば、0:00は外して始め、11:60で11時台を締めなければならないはずです。 

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Javascriptでブランケット付きのURLパラメータをオブジェクトに

概要

railsで(たしかPHPも?)foo[bar][quz]=123&foo[bar][qux][]=1みたいなクエリーをつけると

{
  foo: {
    bar: {
      quz: 123,
      quz: [1]
    }
  }
}

というHashが得られますが、それをJavascript側でもオブジェクト化したくて、自分で実装するのも地味に大変そうな気がして探したけど見つけるのに手間取ったので載せておきます。

qs

https://github.com/ljharb/qs

これで期待通りの結果が得られました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

json(object) to form-encoded

ふとform-encodedなpostリクエストをしたくなったのですが、みんなjsonの事ばっかり考えていて困ったことがあったので備忘

やりたい事

javascriptのobjectをx-www-form-urlencodedなbodyに変えたい
↓こんなのを

objcect.js
var obj = {
    first: {
        firstA:"a",
        firstB:"b"
    },
    second: "seconds",
    third: {
        thirdA: {
            thirdAA: "aaa",
            thirdAB: "bbb"
        },
        thirdB: "bbb"
    }
}

↓こんなのに

string.js
first%5BfirstA%5D=a&first%5BfirstB%5D=b&second=seconds&third%5BthirdA%5D%5BthirdAA%5D=aaa,third%5BthirdA%5D%5BthirdAB%5D=bbb&third%5BthirdB%5D=bbb

こうした

色々パターン考えかったんですけど、脳みそパワー不足で1パターンのみなンだわ

result.js
// 中級者
function encodeObjToForm(obj) {
    var keys = [];
    var ret = [];
    for(key in obj){
        if(typeof obj[key] === "object"){
            keys.push(key);
            ret = ret.concat(encodedLoop(keys, obj[key]))
            keys = [];
        }else{
            ret.push(key + "=" + obj[key])
        }
    }
    return encodeURI(ret.join("&"));
}

function encodedLoop(keys, obj) {
    if (typeof obj === 'object') {
        var ret = [];
        for (key in obj) {
            keys.push(key)  
            ret.push(encodedLoop(keys, obj[key]));
            keys.pop()
        }
        return ret
    } else {
        str = ''
        for (k in keys) {
            if (k === '0') {
                str += keys[k];
            } else {
                str += "[" + keys[k] + "]";
            }
        }
        str += "=" + obj;
        return str;
    }
}

助けて

汎用的に書く意味は全く無く、普通jsonだしformでもエンドポイントごとに整形すればいいじゃん。

もっとかっこよくイテラブルにやったりしたい。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Nuxt.jsでAPIサーバーへのproxyをつくる(serverMiddlewareだけで)

概要

クライアントがNuxt、WEB+APIサーバーがその他(実際にはFlask)のアプリを作るにあたって、Nuxtはnpm run devで起動しておきながら、APIサーバーにも自由にアクセスをさせたいという場合があり、そのためのproxyをできるだけ簡単に構成します。
ここでWEB+APIサーバーと書いたサーバーは、本番ではNuxtのビルド済のファイルを返すWEBサーバーとAPIサーバーを兼ねるものです。

そのような事をしたい理由

要件としては、最終的に返却するドメインがNuxtのファイルもAPIも同一になるような環境を作る事です。(ポートも同じ)

このような構成のNuxtのアプリを開発するには、おそらく概念的には2通りの考え方があって、

  • Nuxtのビルドをwatchにする
  • Nuxtはdevで立ち上げて、そこからAPIサーバーの内容をproxyで返す(今回の構成)

このうち、もともとは前者の方法を考えていたのですが、この辺はnuxt自体の課題でもあるらしく、npm run generate --watchというのがあまりうまく機能しなかったのと、仮にできたとしてもなんだかんだビルドの時間等がかかるな?ということになり、後者の方法を検討して、実現した物の基礎的な部分について記述します。

なお、初版ではcookieを通していなかったのですが、cookieをそのまま素通しするようにしました。
その他のヘッダーも、同様にやればできると思います。

やること

nuxt.config.jsを編集して、以下のようなjsを作成します。

nuxt.config.js(の一部)
import bodyParser from 'body-parser'

export default {
  // 略
  serverMiddleware: [
    bodyParser.json(),
    { path: '/api/path/dayo', handler: '~/api/proxy.js' },
    { path: '/api/path/desu', handler: '~/api/proxy.js' }
  ],
  // 略
}

※apiフォルダを作って、api/proxy.jsとします。APIサーバーはlocalhost:5000で動いているものとします。
(ほかでも書き換えれば動きます)
やりとりはすべてJSONなので注意してください。

proxy.js
import axios from 'axios'

export default function (req, res, next) {
  axios({
    method: req.method,
    url: 'http://localhost:5000' + req.originalUrl,
    headers: req.headers,
    data: req.body
  }).then(otherRes => {
    if (otherRes.headers && otherRes.headers['set-cookie']) {
      res.setHeader('Set-Cookie', otherRes.headers['set-cookie'])
    }
    res.write(JSON.stringify(otherRes.data))
    res.end()
  })
  // next()
}

結果

APIのやりとり形式がJSONである前提で、メソッドとbodyを維持してproxyしてくれます。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

WebGLでマーチングキューブ法

マーチングキューブ法はボクセルデータからメッシュを作成する手法です。空間を格子上に分割することで作成された各直方体(セル)について、それを構成する8つの頂点の値に応じて三角形ポリゴンを生成することでメッシュを作成します。
マーチングキューブ法 - Wikipedia

WebGLで実装するにあたり、以下のサイトを参考にしました。このサイトの方法では、1つのセルに対して8つの頂点がとりうる値のパターンは256あるので、そのパターンすべてについて三角形ポリゴンの作成方法をルックップテーブルとしてあらかじめ格納しておきます。各セルについてルックアップテーブルを参照しながら三角形ポリゴンを必要な数だけ生成しています。
Polygonising a scalar field (Marching Cubes)

マーチングキューブ法は対象にするボクセルデータにより必要となる三角形ポリゴンの数が変わります。リアルタイムにメッシュを生成する場合はジオメトリシェーダーを使ってGPU側でポリゴンを作成するのが一般的だと思います。しかし、WebGLにはジオメトリシェーダーがないため、シェーダー側でポリゴンを作成する方法がありません。マーチングキューブ法では各セルで生成される三角形ポリゴンの最大数は5つです。そのため今回のWebGLの実装では、各セルについて5つの三角形を描画するようにドローコールを行い、不要な三角形はシェーダー側でピクセルを破棄して描画しないようにしています。

ソースコード全文はGithubに置いておきました。
aadebdeb/WebGL_MarchingCubes: Real time Marching Cubes in WebGL

まずはじめに、ルックアップテーブルを格納するテクスチャを作成しておきます。シェーダー側にルックアップテーブルを直接定義すると、シェーダーの定数制限に引っかかるのでテクスチャで渡しています。参考にしているサイトにはedgeTabletriTableの2種類がありますが、edgeTableは今回の実装では使用しなので、triTableに対応したテクスチャのみを作成しています。ArrayBufferViewからテクスチャを作成する方法については私が以前に書いた記事を参考にしてください。

const TRI_TABLE = new Uint32Array([
  -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
  0, 8, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
  // ... 長いので省略
  0, 3, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
  -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1
]);

function createTriTexture(gl) {
  const triTexture = gl.createTexture();
  gl.bindTexture(gl.TEXTURE_2D, triTexture);
  gl.texImage2D(gl.TEXTURE_2D, 0, gl.R32UI, 4096, 1, 0, gl.RED_INTEGER, gl.UNSIGNED_INT, TRI_TABLE);
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
  gl.bindTexture(gl.TEXTURE_2D, null);
  return triTexture;
}

マーチングキューブ法を実行するためのパラメータとドローコールは以下のようになります。marchingSpaceがマーチングキューブ法を実行する空間全体の大きさ、cellNumがセル数、cellSizeがセルの大きさを表しています。cellNum.x*cellNum.y*cellNum.zが総セル数であり、各セルについて三角形が最大5つ描画される(3*5=15)ので、頂点数vertexNumcellNum.x*cellNum.y*cellNum.z*15になります。

const marchingSpace = new Vector3(10.0, 10.0, 10.0);
const cellNum = new Vector3(50, 50, 50);
const cellSize = new Vector3(marchingSpace.x / cellNum.x, marchingSpace.y / cellNum.y, marchingSpace.z / cellNum.z);
const vertexNum = cellNum.x * cellNum.y * cellNum.z * 15;
...
gl.drawArrays(gl.TRIANGLES, 0, vertexNum);

マーチングキューブ法を実行する頂点シェーダーは以下のようになります。 アルゴリズム自体は参考にしているサイトと同じです。main関数では、まずgl_VertexIDをもとに現在どのセルのどの三角形の頂点を対象にしているかを決定します。セルの8つの頂点の値から、ルックアップテーブルを参照するためのインデックスを作成します。ルックアップテーブルから取得した値により現在対象にしている三角形の頂点がどの辺にあるかがわかるので、辺の2つの端点の位置と値から頂点位置を決定しています。ルックアップテーブルの値が-1のときは三角形を描画しないということなので、gl_Positionには適当な値を入れておき、フラグメントシェーダーでピクセルを破棄するためのフラグを立てておきます。

#version 300 es

precision highp usampler2D;

out vec3 v_normal; // ワールド座標系における法線
flat out int v_discard; // 1のときピクセルを破棄

uniform usampler2D u_triTexture; // triTableの値を格納するテクスチャ
uniform mat4 u_mvpMatrix;
uniform mat4 u_normalMatrix;
uniform ivec3 u_cellNum; // セル数
uniform vec3 u_cellSize; // セルの大きさ
uniform float u_time;

// 符号付き距離関数を返す関数
// サンプルで実際に使用しているものを載せるのは冗長なため、ここでは単純な球の距離関数を使用している
float getDistance(vec3 p) {
  return length(p) - 2.0;
}

// 法線を返す関数
vec3 getNormal(vec3 p) {
  float e = 0.01;
  return normalize(vec3(
    getDistance(p + vec3(e, 0.0, 0.0)) - getDistance(p - vec3(e, 0.0, 0.0)),
    getDistance(p + vec3(0.0, e, 0.0)) - getDistance(p - vec3(0.0, e, 0.0)),
    getDistance(p + vec3(0.0, 0.0, e)) - getDistance(p - vec3(0.0, 0.0, e))
  ));
}

// v0, v1の値をもとにp0, p1を補間した値を返す
vec3 interpolate(vec3 p0, vec3 p1, float v0, float v1) {
  return mix(p0, p1, -v0 / (v1 - v0));
}

void main(void) {
  int cellId = gl_VertexID / 15; // セルのID
  int vertexId = gl_VertexID % 15; // セル内での頂点のID

  ivec3 cellIdx = ivec3(
    cellId % u_cellNum.x, (cellId % (u_cellNum.x * u_cellNum.y)) / u_cellNum.x, cellId / (u_cellNum.x * u_cellNum.y));
  vec3 cellCorner = (0.5 * vec3(u_cellNum) - vec3(cellIdx)) * u_cellSize;

  // 現在のセルの各頂点位置を求める
  vec3 c0 = cellCorner;
  vec3 c1 = cellCorner + u_cellSize * vec3(1.0, 0.0, 0.0);
  vec3 c2 = cellCorner + u_cellSize * vec3(1.0, 1.0, 0.0);
  vec3 c3 = cellCorner + u_cellSize * vec3(0.0, 1.0, 0.0);
  vec3 c4 = cellCorner + u_cellSize * vec3(0.0, 0.0, 1.0);
  vec3 c5 = cellCorner + u_cellSize * vec3(1.0, 0.0, 1.0);
  vec3 c6 = cellCorner + u_cellSize * vec3(1.0, 1.0, 1.0);
  vec3 c7 = cellCorner + u_cellSize * vec3(0.0, 1.0, 1.0);

  // 現在のセルの各頂点の値を求める
  float v0 = getDistance(c0);
  float v1 = getDistance(c1);
  float v2 = getDistance(c2);
  float v3 = getDistance(c3);
  float v4 = getDistance(c4);
  float v5 = getDistance(c5);
  float v6 = getDistance(c6);
  float v7 = getDistance(c7);

  // セルの各頂点の値からルックアップテーブルを参照するためのインデックスを求める
  int cubeIdx = 0;
  if (v0 < 0.0) cubeIdx |= 1;
  if (v1 < 0.0) cubeIdx |= 2;
  if (v2 < 0.0) cubeIdx |= 4;
  if (v3 < 0.0) cubeIdx |= 8;
  if (v4 < 0.0) cubeIdx |= 16;
  if (v5 < 0.0) cubeIdx |= 32;
  if (v6 < 0.0) cubeIdx |= 64;
  if (v7 < 0.0) cubeIdx |= 128;

  int tri = int(texelFetch(u_triTexture, ivec2(cubeIdx * 16 + vertexId, 0), 0).x);
  if (tri == -1) {
    // -1のとき描画する三角形はないので、ピクセルを破棄するようにする
    gl_Position = vec4(vec3(0.0), 1.0);
    v_discard = 1;
  } else {
    // 描画する三角形があるときは、頂点位置を求める
    vec3 position;
    if (tri == 0) {
      position = interpolate(c0, c1, v0, v1);
    } else if (tri == 1) {
      position = interpolate(c1, c2, v1, v2);
    } else if (tri == 2) {
      position = interpolate(c2, c3, v2, v3);
    } else if (tri == 3) {
      position = interpolate(c3, c0, v3, v0);
    } else if (tri == 4) {
      position = interpolate(c4, c5, v4, v5);
    } else if (tri == 5) {
      position = interpolate(c5, c6, v5, v6);
    } else if (tri == 6) {
      position = interpolate(c6, c7, v6, v7);
    } else if (tri == 7) {
      position = interpolate(c7, c4, v7, v4);
    } else if (tri == 8) {
      position = interpolate(c0, c4, v0, v4);
    } else if (tri == 9) {
      position = interpolate(c1, c5, v1, v5);
    } else if (tri == 10) {
      position = interpolate(c2, c6, v2, v6);
    } else if (tri == 11) {
      position = interpolate(c3, c7, v3, v7);
    }
    gl_Position = u_mvpMatrix * vec4(position, 1.0);
    v_normal = (u_normalMatrix * vec4(getNormal(position), 0.0)).xyz;
    v_discard = 0;
  }
}

フラグメントシェーダーは以下のようになります。v_discard == 1のときはdiscardでピクセルを破棄し、それ以外の場合は普通にシェーディングしています。

#version 300 es

precision highp float;

in vec3 v_normal;
flat in int v_discard;

out vec4 o_color;

const vec3 LIGHT_DIR = normalize(vec3(0.5, 0.5, 1.0));

void main(void) {
  if (v_discard == 1) {
    discard;
  } else {
    vec3 normal = normalize(v_normal);
    vec3 color = vec3(0.3, 0.3, 1.0) * max(0.0, dot(LIGHT_DIR, normal));
    o_color = vec4(color, 1.0);
  }
}

実際に実行してみると以下のようになりました。球やトーラスなど曲面で構成されるオブジェクトの場合はきれいにレンダリングされますが、直方体の角部分では法線が正しく求まらないのかアーティファクトが目立つ結果となっています。セルの数を増やせば目立たなくなりますが、その分負荷も大きくなります。
marchingcubes.gif

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む