20200327のJavaScriptに関する記事は30件です。

Spreadsheetでラクしたい Google Apps Script ソースコードテンプレート

以下ページの転載になります。ご了承ください。

Spreadsheetでラクしたい Google Apps Script ソースコードテンプレート - Yuto Hongo Portfolio


[ひとことで言うと、こんな記事]

SpreadSheetでの情報の【取得】【更新】の基本的なメソッドをあらかじめGASのソースとして、準備しました。


[こんな人におすすめ]

  • 表計算ソフトでのデータ処理の作業がつらいと感じている方
  • データを編集するような作業を自動化したいと感じている方

このテンプレートで少しでも楽になる方が増えればと思います。


[目次]

  • 定常作業がめんどくさいので、Google Apps Scriptで自動化したい
  • 準備したSpreadsheet編集のサンプルコードの概要
  • 実際のサンプルコード

定常作業がめんどくさいので、Google Apps Scriptで自動化したい

皆さんも、データ入力や編集の作業って普段から行ってたりしないでしょうか? 特になにかのデータのマスター管理とかをなさっていたりする方は多いと思います。

本当にめんどくさいです。

実は、Googleに準備されているサービスのいくつかは、Google Apps Scriptというものでなにかしらの処理をしてもらうことが可能です。

Apps Script | Google Developersが公式サイトとなります。

TOPページをみただけでも、様々なサービスに対して利用できますね。


- Spreadsheet
- Gmail
- Google Drive
- Google Form
- etc...

今回はSpreadSheetのデータ処理を行う際に、最初に準備しておくと作業が楽になりそうなソースコードのテンプレートを準備させていただきました。

準備したスプレッドシート編集のサンプルコード概要

GASの実行はV8ランタイムが前提

V8 Runtime Overview | Apps Script | Google Developers

少し前にアップデートされたそうです。

スプレッドシートの1行目はテーブルヘッダ

Column1 Column2 Column3 Column4
data1-1 data1-2 data1-3 data1-4
… 

各カラムにどんな情報が入っているのかの説明行としておいてある想定です。

1行のデータをモデルとして捉える

1行分のデータをモデルとして定義しています。今回は column1~4 + SpreadSheet用のデータをメンバとして定義しています。

必要最低限の情報取得、書込の機能だけを記述

以下のような機能のみを記述していたりします。

情報の取得

  • resolveAll … スプレッドシート内にすでにある情報をすべて取得し、 Modelの配列 として返却
  • resolveByRowNumber … スプレッドシート内で○○番目にある情報を取得し、 Model として返却

情報の書込

  • storeAll … Modelの配列 をすべてシートに書き込む
  • storeByRowNumber … Model をスプレッドシート内で○○番目に書き込む

実際のサンプルコード

以下に実際のサンプルコードを記述していきます。

main.gs

// Get Endpoint
function doGet(e) {

}
// Post Endpoint
function doPost(e) {

}

// Main 
function main() {
  // TODO:処理の記述
}

model.gs

/**
 * SpreadSheetの1行を表したModelの準備
 */
class _Model {
  // TODO:必要なカラムに応じて準備
  constructor(
    column1,
    column2,
    column3,
    column4
  ) {
    this.column1 = column1
    this.column2 = column2
    this.column3 = column3
    this.column4 = column4
    this.sheetRow = [
      column1,
      column2,
      column3,
      column4,
    ]
  }
}

modelRepository.gs

/**
 * SpreadSheetの情報を取得/編集する処理
 * (※ 必要に応じて準備してください)
 */

/**
 * 情報をすべてModelとして取得する
 */
function _modelRepository_resolveAll(sheet) {

  var startRow = 2 // 1行目がテーブルヘッダ
  var startCol = 1
  var lastRow = sheet.getLastRow()
  var numRows = lastRow - 1 // 1行目がテーブルヘッダ
  var lastCol = sheet.getLastColumn()

  // まだ情報がない場合
  if (numRows === 0) return []

  // TODO:必要なカラムに応じて準備
  var sheetDatas = sheet.getSheetValues(startRow, startCol, numRows, lastCol)
  return sheetDatas.map(sheetRow => new _Model(sheetRow[0], sheetRow[1], sheetRow[2], sheetRow[3]))
}

/**
 * 上から○○番目のデータをModelとして取得
 */
function _modelRepository_resolveByRowNumber(sheet, rowNumber) {

  var startRow = rowNumber + 1 // 1行目がテーブルヘッダ
  var startCol = 1
  var numRows = 1
  var lastCol = sheet.getLastColumn()

  // TODO:必要なカラムに応じて準備
  var sheetDatas = sheet.getSheetValues(startRow, startCol, numRows, lastCol);
  var models = sheetDatas.map(sheetRow => new _Model(sheetRow[0], sheetRow[1], sheetRow[2], sheetRow[3]))
  return models[0]

}

/**
 * すべてのModelをSpreadSheetに保存する
 */
function _modelRepository_storeAll(sheet, models) {

  // 既存データをすべて消去
  _truncateData(sheet)

  var startRow = 2 // 1行目がテーブルヘッダ
  var startCol = 1
  var numRows = models.length
  var lastCol = sheet.getLastColumn()

  // 更新情報がない場合
  if (numRows !== 0) {
    var updateSheetRange = sheet.getRange(startRow, startCol, numRows, lastCol)
    var sheetRows = models.map(model => { return model.sheetRow })
    updateSheetRange.setValues(sheetRows)
  }
}

/**
 * 上から○○番目にModelの情報を保存する
 */

function _modelRepository_storeByRowNumber(sheet, rowNumber, model) {

  var startRow = rowNumber + 1 // 1行目がテーブルヘッダ
  var startCol = 1
  var numRows = 1
  var lastCol = sheet.getLastColumn()

  var updateSheetRange = sheet.getRange(startRow, startCol, numRows, lastCol)
  var sheetRows = [model.sheetRow]
  updateSheetRange.setValues(sheetRows)

}

/**
 * シート情報をすべてクリアする
 */
function _truncateData(sheet) {

  var startRow = 2 // 1行目がテーブルヘッダ
  var startCol = 1

  var lastRow = sheet.getLastRow()
  var numRows = lastRow - 1 // 1行目がテーブルヘッダ

  var lastCol = sheet.getLastColumn()

  // まだ情報がない場合
  if (numRows !== 0) {
    var clearSheetRange = sheet.getRange(startRow, startCol, numRows, lastCol)
    clearSheetRange.clearContent()
  }
}

sheet.gs

/**
 * 編集対象のSpreadSheetの情報を取得する
 */
function _getSheet() {

  // TODO:利用するSpreadSheetに応じて準備
  var SHEET_URL = '[SpreadsheetのURL]';
  var spreadSheetPage = SpreadsheetApp.openByUrl(SHEET_URL);
  var sheet = spreadSheetPage.getSheetByName('[対象シートの名前]');

  return sheet
}

「もっとソースコードをこうしたらきれいに書けます」や「もっとこういう機能入れましょう」などございましたら、以下プルリクエスト等をよろしくお願いいたします。

google-apps-script-sample


最後までお読みいただき、誠にありがとうございます。様々なことを学んでいきたいと思っていますので、アドバイス等いただけると幸いです。

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

[JavaScript] 動的にinput[type=file]を組み立てて画像をアップロードしたらBase64が手に入るコード

画像アップロードする時に、以下のように display: none をつかってブラウザ本来の input[type=file] をみえないようにした上で独自レイアウトのボタンで画像アップロードするような実装を時々みかけます。

<label for="hoge">画像アップロード</label>
<input id="hoge" type="file" style="display: none">

で、 addEventListener でファイル変更検知を行ったりと。
けれど、上記のようにするとマークアップがある程度拘束されてしまいます。やりたいことはボタンをクリックしたら画像をアップロードしてBase64を手にいれるとかそういう話なので、クリックイベントを仕込んで以下のコードを実行すればOKです。

<div style="width: 0; height: 0; overflow: hidden;">
  <input id="browserPhotoUploader" type="file" accept="image/*" />
</div>
public getPictureFromBrowser() {
  /*
   * input[type=file] を組み立て
   */
  const inputFile: HTMLElement = document.querySelector('input#browserPhotoUploader');

  return new Promise(resolve => {
    /*
     * input[type=file] の変更(change)を検知したら、ファイルをアップロードしてresolveする
     */  
    inputFile.addEventListener(
        'change',
        (e) => {
          const file = e.target.files[0];
          const reader = new FileReader();

          reader.onload = (fileInput => {
            /*
             * アップロードされるファイルが画像かを簡易判定
             */  
            if (file.type.indexOf('image') < 0) {
              reject(null);
            }

            return (event) => {
              resolve(event.target.result);
            };
          })(file);

          reader.readAsDataURL(file);
        },
        false,
    );

    /*
     * input[type=file] をクリック
     * イベントリスナーよりあとに実行されるように
     */  
    inputFile.click();
  });
}

このコードだったらHTML側は

<button (click)="getPictureFromBrowser()">画像アップロード</button>

だけで済みますね。 display:none せずにすむのでシンプル!

それでは、また。

おまけ: TypeScript版

any 使ってるのちょっと何ですが・・・。

public getPictureFromBrowser(): Promise<string> {
    const inputFile: HTMLElement = document.querySelector('input#browserPhotoUploader');
    return new Promise(resolve => {
      inputFile.addEventListener(
        'change',
        (e: any) => {
          const file = e.target.files[0];
          const reader = new FileReader();

          reader.onload = (fileInput => {
            if (file.type.indexOf('image') < 0) {
              reject(null);
            }

            return (event) => {
              resolve(event.target.result);
            };
          })(file);

          reader.readAsDataURL(file);
        },
        false,
      );
      inputFile.click();
    });
  }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Kinx 実現技術 - Garbage Collection

Garbage Collection

はじめに

「見た目は JavaScript、頭脳(中身)は Ruby、(安定感は AC/DC)」 でお届けしているスクリプト言語 Kinx。作ったものの紹介だけではなく実現のために使った技術を紹介していくのも貢献。その道の人には当たり前でも、そうでない人にも興味をもって貰えるかもしれない。

前回のテーマは JIT。今回のテーマは Garbage Collection。

GC は特別なことはしていない、というよりむしろ複雑なことはせずに、シンプルにしました。一番の目的はリソース管理というのも勿論あるものの、フリー・オブジェクトのキャッシングにある。malloc/free の回数を減らす。これが結構パフォーマンスに影響する。

実現手法

マーク・アンド・スイープ

ストップ・ザ・ワールドのマーク・アンド・スイープで、一旦全ての処理を止めて GC する。(あえて Kinx の)疑似コードであらわすと次のような感じ。

function mark(p) {
    // Stop the recursive check
    return if (p.mark);
    p.mark = true;

    // Check the mark recursively
    p.innerObjects.each(mark);
}

function markAndSweep(stack, context) {
    // Initialization
    context.aliveList.each(&(p) => { p.mark = false; });

    // Mark
    stack.each(mark);

    // Sweep
    context.aliveList = context.aliveList.filter(&(p) => {
        if (!p.mark) {
            context.deadList.push(p);
        }
        return p.mark;
    });
}

マーク

まず、マークと呼ばれるフェーズで生きているオブジェクト全てにマークを付けていく。ここでマークを漏らすと生きているのに回収されてしまっておかしくなるので漏らさないように。例のレキシカル・スコープや、関数オブジェクトに括り付いたフレーム等も全て対象。

マークの際のルートとなるのはスタック。スタック上のオブジェクトをルートに参照を全て辿って到達するオブジェクトに対し、片っ端からマークしていく。オブジェクトは循環参照しているケースがあるので、一度マークを付けたら次にまた到達したときには何もしないようにガードしておく。

また、Kinx ではスタック以外のルートとして以下がある。

  • 独立して保持している例外オブジェクト。
  • 正規表現リテラルの管理リスト。

晴れて全てのオブジェクトにマークが付いたら、スイープのフェーズに入る。

スイープ

オブジェクトは全て alive リストと dead リストで管理されている。ただし、alive の方は線形リスト、dead の方はベクターである。

なぜなら、alive リストは死んだオブジェクトを途中から引っこ抜いていく形で使われるのに対し、dead リストは死んだオブジェクトの再利用に使われるだけなので途中から引っこ抜くことがないため。こんな感じ。

aliveList -> obj1 -> obj2 -> obj3 -> obj4 -> obj5 -> ... -> objN -> ...
                             ~~~~ dead!
                           ↓
                                ,---> To deadList
                               /
aliveList -> obj1 -> obj2    obj3    obj4 -> obj5 -> ... -> objN -> ...
                      |               ↑
                      `---------------'

Kinx ではオブジェクト・サイズごとに全て異なるアロケーターを持ち、別々の alive/dead リストで管理することにしてある。そうすることで、オブジェクト再利用時の処理を dead リストからの pop 一発でいけるようにしている。

スイープ・フェーズでは alive リスト全てチェックし、マークの付いていないオブジェクトをリストから外して dead リストに push。

gctick

回収オブジェクトが多くないと GC するのは無駄。なので、まずは一定間隔で GC するようになっている。そのカウンタとして gctick が用意してある。一定回数インストラクションを実行したら GC がキックされる。

今後 GC 中のオブジェクト状況をプロファイルして gctick の値をコントロールしようとは思っている。

雑感

本来管理すべきリソースはメモリだけではない。ファイル・ディスクリプタやソケット等 open/close が伴うものは適切にリソース管理しなくてはならない。

C++ では RAII という機構でほぼほぼ気にせず華麗にリソース管理できるようにしていたのが、メモリ以外は try-finally で自分で管理する必要性が出てきているのはもしかして退化なのでは?、といささか感じなくもない。

C++
std::unique_ptr<FILE, decltype(&fclose)> fp(fopen(filename, "r"), fclose);

に対して、

Kinx
function open(filename, mode, func) {
    var f;
    try {
        f = new File(filename, mode);
        return func(f);
    } finally {
        f.close();
    }
}

。。。C++ のほうが簡単に書けるな。

まあ、GC するときに close するようにはしてはいるもののタイミングは予測不能。その性質上、予測不能でも問題ないものにしか適用できない。また、迂闊にオブジェクトが残ったりするとシステム側でリソース数に上限があったりするから、ちゃんと解放してあげないといけない。これを GC 的アルゴリズムの(予測不可能な世界の)中で実現するのは難しい。参照カウンタくらいか。

実は Kinx でも XML ドキュメント等は GC の時に解放されるが、解放するかどうかの判断は参照カウンタで実現している。XML ドキュメントは自身が管理する XML ノードから参照されているため、全ての管理対象 XML ノードがフリー状態になっていないと解放できない。したがって参照カウンタ。以下がソース。

今後

今後、GC を改善するとしたら以下のような感じ。ただし、GC のような裏方は本来の仕事を邪魔しない程度に関わるのが筋。GC が明らかにボトルネックになっているといったケース、その機能が言語本来の機能要求を満たすのに必要、という以外、優先度を上げるべきではないだろう。

  • gctick コントロール
  • Force GC ... これは必要。
  • native 中の GC
  • インクリメンタル GC

おわりに

Garbage Collection は学術的にも奥が深く、ハマったらここだけでも相当なパワーが必要な領域。現実的な範囲で実用上の要求事項が満たせるレベルで動作させることを一番に考えてこういう実装に。

余裕ができたら高度な GC を取り入れても良いとは思うが、何事もプロファイルが重要。手を付けるならちゃんとボトルネックになってることを確認してからですね。

尚、GC 関連の処理は以下のソースにあります。

では、次は Fiber の予定。

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

HTML5 <input type="datetime-local"> に現在時刻を設定するには

HTML5 で使えるようになった、<input> タグの type="datetime-local"` についてです。
日付と時刻を設定できるフォームです。使いやすいとは言えないんですけど、カレンダーで入力できるのでちょっとしたテストをするのに便利です。

https://developer.mozilla.org/ja/docs/Web/HTML/Element/Input/datetime-local

Web ページを開いたときに、現在時刻を設定するコードは次のようになります。
toISOString() は UTC が返るので、タイムゾーンを考慮する必要があります。
そこで、getTimezoneOffset() のぶんを、現在時刻からずらします。単位は分です。

<input type="datetime-local" id="cal">
window.addEventListener('load', () => {
  const now = new Date();
  now.setMinutes(now.getMinutes() - now.getTimezoneOffset());
  document.getElementById('cal').value = now.toISOString().slice(0, -1);
});

なお、value として設定できるのは、次の形式のいずれかです。

  • yyyy-MM-ddThh:mm
  • yyyy-MM-ddThh:mm:ss
  • yyyy-MM-ddThh:mm:ss.SSS

この形式に従わないものを設定しようとすると、開発者コンソールに次のエラーが表示されます。

The specified value "XXX" does not conform to the required format.  The format is "yyyy-MM-ddThh:mm" followed by optional ":ss" or ":ss.SSS".

コーディングの例を https://codepen.io/takatama/pen/vYOvEQP に置きました。

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

Nuxt.js + Vue.js + Vuex によるカウンターのサンプルコード

概要

  • Nuxt.js + Vuex を使用して「+」「-」ボタンで数値が増減するカウンターを作成する

今回の環境

  • Node.js 13.12.0
  • Nuxt.js 2.12.1

Nuxt.js + Vuex によるカウンターのサンプルコード

ソースコード一覧

├── package.json
├── pages
│   └── counter.vue
└── store
    └── counter.js

package.json

{
  "name": "my-app",
  "dependencies": {
    "nuxt": "2.12.1"
  }
}

pages/counter.vue

<template>
  <div>
    <p>{{ count }}</p>
    <p>
      <button @click="increment">+</button>
      <button @click="decrement">-</button>
    </p>
  </div>
</template>

<script>
export default {
  computed: {
    count () {
      console.log('Call the computed count')
      return this.$store.state.counter.count
    }
  },
  methods: {
    // 「+」ボタンクリック時に呼ばれる
    increment () {
      console.log('Call the methods increment')
      this.$store.commit('counter/increment')
    },
    // 「-」ボタンクリック時に呼ばれる
    decrement () {
      console.log('Call the methods decrement')
      this.$store.commit('counter/decrement')
    }
  }
}
</script>

store/counter.js

// カウンターの値を管理するストア (Vuex.Store)

export const state = () => ({
  count: 0
})

export const mutations = {
  increment (state) {
    console.log('Call the mutations increment')
    state.count++
  },
  decrement (state) {
    console.log('Call the mutations decrement')
    state.count--
  }
}

Node.js サーバを起動

package.json に記述したライブラリをインストール。

$ npm install

Node.js サーバを起動。

$ ./node_modules/nuxt/bin/nuxt.js

   ╭─────────────────────────────────────────────╮
   │                                             │
   │   Nuxt.js v2.12.1                           │
   │   Running in development mode (universal)   │
   │                                             │
   │   Listening on: http://localhost:3000/      │
   │                                             │
   ╰─────────────────────────────────────────────╯

ℹ Preparing project for development
ℹ Initial build may take a while
✔ Builder initialized
✔ Nuxt files generated

✔ Client
  Compiled successfully in 5.69s

✔ Server
  Compiled successfully in 5.30s

ℹ Waiting for file changes
ℹ Memory usage: 121 MB (RSS: 200 MB)
ℹ Listening on: http://localhost:3000/

Web ブラウザで http://localhost:3000/counter にアクセスすると「+」「-」ボタンと数値が増減するカウンターが表示される。

参考資料

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

多次元リスト(配列)を一次元に直す

空のリスト(配列)を新しく作成し、元の要素を一つずつコピーする。

pythonの場合

test.py
# coding: utf-8

animal_list = ["イヌ", ["ペルシャ", "マンチカン", "スコティッシュフォールド"], "ヒト", ["ハムスター, カピバラ"]];
new_list = []

for animal in animal_list:
    if type(animal) == list:
        new_list.extend(animal)
    else:
        new_list.append(animal)

print(new_list)


js(gas)の場合

test.gs
function make_array() {

  var array = ["イヌ", ["ペルシャ", "マンチカン", "スコティッシュフォールド"], "ヒト", ["ハムスター, カピバラ"]];
  var new_array = [];

  for (var i = 0; i <= array.length - 1; i++) {
    if (typeof(array[i]) == "object") {
      array[i].map(function(text) { new_array.push(text) });
    } else {
      new_array.push(array[i]);
    }
  }

  Logger.log(new_array);

}


<メモ>
・リストは可変(大きさを決める必要がない)に対し、配列は不変。(宣言時に大きさを決める必要がある)
・jsでは配列の大きさを決める必要がない?

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

Google Places API のデータで営業中か判定するアルゴリズム

Places API を使えば、Google Map に登録されているデータを取得できます。
Places API は取得したいデータに応じて API が複数ありますが、Place Details で fieldsopening_hours を指定すれば、そのお店の開店・閉店時間を取得できます。

営業時間が次のお店なら、

  • 日: 11:30 AM – 2:00 PM, 5:00 – 10:00 PM
  • 月: 11:30 AM – 2:00 PM, 5:00 – 10:00 PM
  • 火: 11:30 AM – 2:00 PM, 5:00 – 10:00 PM
  • 水: 閉店
  • 木: 11:30 AM – 2:00 PM, 5:00 – 10:00 PM
  • 金: 11:30 AM – 2:00 PM, 5:00 – 10:00 PM
  • 土: 11:30 AM – 2:00 PM, 5:00 – 10:00 PM

API で取得できるデータは次の通りです。なかなか複雑ですね!

{
 "periods": [
    {
      "close": {
        "day": 0,
        "time": "1400"
      },
      "open": {
        "day": 0,
        "time": "1130"
      }
    },
    {
      "close": {
        "day": 0,
        "time": "2200"
      },
      "open": {
        "day": 0,
        "time": "1700"
      }
    },
    {
      "close": {
        "day": 1,
        "time": "1400"
      },
      "open": {
        "day": 1,
        "time": "1130"
      }
    },
    {
      "close": {
        "day": 1,
        "time": "2200"
      },
      "open": {
        "day": 1,
        "time": "1700"
      }
    },
    {
      "close": {
        "day": 2,
        "time": "1400"
      },
      "open": {
        "day": 2,
        "time": "1130"
      }
    },
    {
      "close": {
        "day": 2,
        "time": "2200"
      },
      "open": {
        "day": 2,
        "time": "1700"
      }
    },
    {
      "close": {
        "day": 4,
        "time": "1400"
      },
      "open": {
        "day": 4,
        "time": "1130"
      }
    },
    {
      "close": {
        "day": 4,
        "time": "2200"
      },
      "open": {
        "day": 4,
        "time": "1700"
      }
    },
    {
      "close": {
        "day": 5,
        "time": "1400"
      },
      "open": {
        "day": 5,
        "time": "1130"
      }
    },
    {
      "close": {
        "day": 5,
        "time": "2200"
      },
      "open": {
        "day": 5,
        "time": "1700"
      }
    },
    {
      "close": {
        "day": 6,
        "time": "1400"
      },
      "open": {
        "day": 6,
        "time": "1130"
      }
    },
    {
      "close": {
        "day": 6,
        "time": "2200"
      },
      "open": {
        "day": 6,
        "time": "1700"
      }
    }
  ]
}

このデータを使って、店舗が営業中かどうかを判定するアルゴリズムは次のようになります。

/**
 * 営業中か判定する
 * @param {Date} d 日付オブジェクト
 * @param {object} openingHours 営業時間のデータ
 * @return {boolean} 営業中なら true、閉店なら false
 */
const isOpen = (d, openingHours) => {
  const day = d.getDay();
  const h = String(d.getHours()).padStart(2, '0');
  const m = String(d.getMinutes()).padStart(2, '0');
  const time = h + m;
  return openingHours.periods.filter(p => p.open.day === day).some(p => {
    return p.open.time <= time && time <= p.close.time; 
  });
};

コーディング例を https://codepen.io/takatama/pen/vYOvEQP に置きました。

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

Vue.js + Vuex によるカウンターのサンプルコード

概要

  • Vue.js + Vuex を使用して「+」「-」ボタンで数値が増減するカウンターを作成する

今回の環境

  • Vue.js 2.6.1
  • Vuex 3.1.3

Vuex とは

Vuex とは何か? | Vuex

Vuex は Vue.js アプリケーションのための 状態管理パターン + ライブラリです。 これは予測可能な方法によってのみ状態の変異を行うというルールを保証し、アプリケーション内の全てのコンポーネントのための集中型のストアとして機能します。

ステート | Vuex

Vuex は 単一ステートツリー (single state tree) を使います。つまり、この単一なオブジェクトはアプリケーションレベルの状態が全て含まれており、"信頼できる唯一の情報源 (single source of truth)" として機能します。これは、通常、アプリケーションごとに1つしかストアは持たないことを意味します。

Vuex 入門 | Vuex

Vuex アプリケーションの中心にあるものはストアです。"ストア" は、基本的にアプリケーションの 状態(state) を保持するコンテナです。単純なグローバルオブジェクトとの違いが 2つあります。

  1. Vuex ストアはリアクティブです。Vue コンポーネントがストアから状態を取り出すとき、もしストアの状態が変化したら、ストアはリアクティブかつ効率的に更新を行います。

  2. ストアの状態を直接変更することはできません。明示的にミューテーションをコミットすることによってのみ、ストアの状態を変更します。これによって、全ての状態の変更について追跡可能な記録を残すことが保証され、ツールでのアプリケーションの動作の理解を助けます。

Vue.js + Vuex によるカウンターのサンプルコード

以下の HTML を Web ブラウザで読み込むと「+」「-」ボタンと数値が増減するカウンターが表示される。

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Vue.js + Vuex ぽちぽちカウンターサンプル</title>
</head>
<body>

<div id="app">
  <p>{{ count }}</p>
  <p>
    <button @click="increment">+</button>
    <button @click="decrement">-</button>
  </p>
</div>

<script src="https://cdn.jsdelivr.net/npm/vue@2.6.11"></script>
<script src="https://unpkg.com/vuex@3.1.3/dist/vuex.js"></script>

<script>

// カウンターの値を管理するストア
const store = new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    // increment: state => state.count++
    increment() {
      console.log('Call the mutations increment')
      this.state.count++
    },
    // decrement: state => state.count--
    decrement() {
      console.log('Call the mutations decrement')
      this.state.count--
    }
  }
})

new Vue({
  el: '#app',
  computed: {
    count () {
      return store.state.count
    }
  },
  methods: {
    // 「+」ボタンクリック時に呼ばれる
    increment() {
      console.log('Call the methods increment')
      store.commit('increment')
    },
    // 「-」ボタンクリック時に呼ばれる
    decrement() {
      console.log('Call the methods decrement')
      store.commit('decrement')
    }
  }
})
</script>

</body>
</html>

参考資料

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

Sign in with Apple JSのSafariとそれ以外のブラウザの挙動の違い

はじめに

Sign in with Apple JSの動作を試している中で気づいた、Safariとそれ以外のブラウザ(Chromeなど)の挙動の違いについて結構ハマったので記事に残します。
同じ問題で困っている人の助けになれば幸いです。
SafariはMacにしか搭載されていないはずなので、この記事はMacOSが対象になります。

知っておくべきこと

Safariは任意のパラメータの値が間違っていても、Safariが良い感じに調整してくれて動作するので、WebでSign in With Appleのボタンを設置する場合の動作確認は(動作対象であれば)Chromeでもやった方が良いです。

背景

Sign in with Appleのパラメータを確認するためにWebで簡易ログイン画面を作成し、動作確認をしていました。Safariだと成功しますが、Chromeだと invalid_request と以下のエラーメッセージが表示され、どのパラメータに問題があるのかがわからない状態で右往左往してました。
Application is not authorized to access the requested information.
スクリーンショット 2020-03-23 18.27.39.png

余談: WebでSign in with Appleを使うために必要な手順

以下のブログが一番わかりやすかったです。heckがどういう意味なのかはわかっていない
https://developer.okta.com/blog/2019/06/04/what-the-heck-is-sign-in-with-apple

https://developer.apple.com でSign in With Appleを使うためにServiceIDを登録する必要があるのですが、ServiceIDを登録するためにはAppIDを登録し、そこでSign in With Appleを有効化して、さらにKeyを登録する必要があります。
公式のドキュメントにもよく読むと記載があるのですが、別ページに分かれておりJSだけで実装したい人でもちゃんとアプリの方も読む必要があります。自分はハマりました。

Sign in With Apple for the webのドキュメント
https://help.apple.com/developer-account/?lang=en#/dev1c0e25352

実際には一つ上のこれも読むとちゃんと書いてある
https://help.apple.com/developer-account/?lang=en#/dev77c875b7e

ドキュメントは全部読みましょう

挙動の違い

Safari

Sign in With Apple ボタンをクリックするとポップアップが表示され、パスワードなりtouch barなりでログインを行います。任意のパラメータのvalidateは行われず、ログイン成功すると usePopup のパラメータに従いリダイレクトページに遷移するかそのままかの処理が行われます。
(失敗させて遷移させる方法は思いつきませんでした)
また、 usePopup の値にかかわらずログインはポップアップで行われます。なのでログインが完了するまでは別の画面に遷移することはありません。

Chrome

Sign in With Apple ボタンをクリックするとログインページにパラメータを全て付与して遷移します。その際、invalidなパラメータがあるとエラー画面が表示されます。
usePopup の値に従い、trueの場合は別ウィンドウを表示してログインを行い、falseの場合は元ページをログイン画面に遷移させ、ログイン処理を行います。ログイン完了後にリダイレクトするのはどちらも同じです。

ちなみにエラーの原因

単純なケアレスミスで、 scope パラメータで nameemail の両方を取得したかったのでスペースで区切って指定していましたが、HTMLで出力するときにエンコードされていて、そのパラメータを送っていたことが原因でした。

必須パラメータと任意パラメータ

https://developer.apple.com/documentation/signinwithapplejs/incorporating_sign_in_with_apple_into_other_platforms

最後に

パラメータミスっても動作するSafari(実際にはしてなかった)はさすがのサポートですね。
今ではもう scope パラメータを指定すれば nameemail は取得できるので、まだAppleが対応していないとは思わないようにしましょう。

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

画像を添付するとその画像を表示するプログラムの作成方法

今回は、画像を添付するとその添付画像を表示するプログラムの作成方法をまとめていきたいと思います。

今回制作するのは、以下のような機能です。

create_image_change.gif

事前準備

まず、以下のようにディレクトリーとファイルと適当な画像の入ったディレクトリー(この画像ではimage-changeという名前)を任意の場所に作成しておきます。

ツリー.png

HTMLの記述

index.html
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <script type="text/javascript" src="js/script.js"></script>
  <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
  <link rel="stylesheet" type="text/css" href="css/layout.css">
</head>

<body>
  <output id="output-image"></output>
  <p>※画像を選択してください</p><br>
  <input type="file" id="image-field" onchange="outputReadFile()">
</body>
</html>

CSSの記述

layout.css
img{
  width:200px;
}

Javascriptの記述

script.js
function outputReadFile() {
  var file = event.target.files[0]; // 添付ファイルのデータを変数fileに格納

if(file){ //ファイルが添付されている場合
  initializeFile(); //初期化
  var outputData = new FileReader; // FileReaderを作成
  outputData.readAsDataURL(file); // ファイルの読み込み

  outputData.onload = outputFile(); // ファイルの読み取りを終えたタイミングでoutputFile関数を実行

  function outputFile(){ // 添付画像のhtmlを作成
    return function(event) {
      var img = document.createElement('img'); //img要素を生成
      img.src = event.target.result; //添付画像の場所を指定
      var outputImage = document.getElementById('output-image'); // outputタグのidを読み込み
      outputImage.insertBefore(img, null); // outputタグの中に添付画像のimgタグを出力
      $('p').remove(); // p要素の"※画像を選択してください"を削除
    };
  };
}else{ //ファイルが添付されていない場合
      initializeFile(); //初期化
      var p = document.createElement('p'); // p要素生成
      p.innerHTML="※画像を選択してください" // p要素の内容を追加
      var outputImage = document.getElementById('output-image'); //outputタグのidを取得
      outputImage.after(p); //outputタグの後にp要素を追加
  }

  function initializeFile(){
    var outputImage = document.getElementById('output-image'); //outputタグのidを取得
    outputImage.innerHTML = ''; //outputタグの中身を削除
  };
};

最後に

HTMLとCSSを自分でカスタマイズすると、色々と(例えば、アプリ内のプロフィール画像の編集時や記事の投稿時などで)応用できるので便利です。
何か質問などあればコメントからどうぞ。

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

受託Webサイト制作の時の開発環境

今回の開発環境の備忘録と次点として生かそうと思い投稿しておきます。

前提

案件

ボリューム:20ページくらい
納期:2週間くらい
備考:レスポンシブ(SP版のデザインはなく良い感じに)

業務フロー

コーディング→ローカル確認→テスト環境アップ(WebサーバにFTPソフトで)

開発環境(マシン、サーバ)

MacOS
MAMP

開発リソース

php
css
js
assets(image, font, etc)

コンパイル

Macのシェルスクリプトでコピーやsassを走らせて成果物を作る。
具体的には、CPコマンドやインストールしたsassコマンドでsrcディレクトリからdistディレクトリに吐き出していく。watch機能もつけた。

ディレクトリ構成

site/
- dist/
  - index.php(home)
  - _section.php(home)
  - detail/(page)...etc
  - css
    - home.css...etc
  - js
    - home.js...etc
  - common
    - header.php
    - footer.php...etc
- src/
  - assets/
  - js/
    - common/
      - lib/
        - bxSlider.js...etc
    - page/
      - home
  - scss/
    - common/
    - page/
      - home
        - style.scss
        - _section.scss...etc
  - template/
    - common/
    - page/
      - home/
        - index.php
        - _section.php...etc
- compile.sh
- watch.sh

考察

まずコンパイルについては、
sass以外は、ほとんどコピーなので、コンパイルする必要性がなかった。
わかりやすくするためにcss以外のリソースも一緒にコンパイルしていたが、最初から必要はなかった。そのためコンパイルに余計な時間が取られたと思う。
ただ、個人的には開発リソースと成果物は分けるべきとは思っているので、今回のコンパイルは悪くないと思っている。
実際、jsの圧縮やphpテンプレートに自動でリソースパスを渡すなどの機能もつけやすくなると思っている。
一つ悪いポイントは、納期がパツパツ過ぎたため、余計なこと考える余裕もないし、実装する時間もないため、余計な機能になってしまったことだと思う。
実務ではそこも重要なポイントであるので、設計はほんとむずいと改めて思った。

phpは、受託制作に関しては優秀であると再認識した。
正直特にphpの機能を使いこなした訳ではないが、includeと変数だけで、テンプレートを細分化できるので良い。1ページでだいたい処理が完結する(ページ単位でのスコープで良い)ので十分と思っています。

sassはここで言うまでもなくとても良い。コンパイルで変更したファイルだけを見れるようにできたら良いと思った。なんかありそうな気はしてるが。

jsはほぼプラグイン。圧縮するほどの量もないが、がっつりした案件が来た時改め考えたい。もしできるなら、webpackを使ってcssModulesを使いたいのだが、そもそもWebサイト受託でそこまでやることあるかまだ疑問に思っているので保留。

次点

受託制作は、スピード重視かつ壊しやすい設計も必要なので、コンパイルはsassのみで、phpはそのまま編集が良いかなと思いました。
あと、スピード重視のため、cssはflexをめっちゃ多用すべきと思いました。
flexのポリフィルを入れればIE8までは対応できるので、今の時代なら大丈夫な範囲だと思ってます。
あとは、スライダーやスライドショーでbxSlider。

まとめ

  • コンパイルはsassだけで良い
  • phpとcssのflexはスピード開発に良い
  • bxSliderとflexibility.js

大して参考になるお話ではないと思いますが、アプリ開発とサイト制作の違いがあり、どちらにも効率的なやり方があるのだなーと思ったので、残した記事でした。

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

フロントでfirestoreのエラーを検知したい

とあるプロジェクトにて。
こんなかんじでfirestoreのエラー検知をしようとしているコードがありました。

try {
  connectFirestore();
} catch {
   window.alert('firebase接続失敗!');
}

try {
  watchFirestoreUpdate();
} catch {
   window.alert('firebase参照失敗!');
}

try {
  updateFirestore();
} catch {
   window.alert('firebase更新失敗!');
}

しかし、意図的にエラーを起こして動作確認したところ、、、
検知できてませんでした。



この記事は、firestoreのエラー検知をちゃんとやろうとした、格闘の記録です。
firestoreは使い慣れていないため、変なことをしていたらご指摘いただけるとありがたいです。




前提

  • 言語はtsです
  • フロントエンドです
  • firebaseのバージョンは7です
  • firestoreをつかって、別システムと連携をするページです
  • 監視や更新がひっそりと失敗するのは、要件上クリティカルにまずいです


アウトライン

やろうとしたことの一覧と、結果を記載します。

やろうとしたこと 結果 何をした?
初回接続エラーの検知 できた setTimeout()で接続タイムアウトを検知
途中切断の検知 一応できたけど挙動が怪しいので導入見送り setInterval()で定期的に接続状態をチェック
NW切断の検知 できた window.addEventListener('offline')を設定
参照エラーの検知 できた コールバックを使う
更新エラーの検知 できた コールバックを使う


エラー検知、その前に

以降のコードでは、↓を使います。

import firebase from 'firebase';

// 本記事の各所で使う汎用モジュール
let singletonApp: firebase.app.App | null = null;
export const getApp = (): firebase.app.App => {
  if (singletonApp === null) {
    // 初回だけinitialize (2回やるとduplicateエラーになる)
    singletonApp = firebase.initializeApp(/*引数省略*/);
  }
  return singletonApp;
};
export const getRef = (path?: string): firebase.database.Reference => {
  return getApp().database().ref(path);
};


初回接続エラーの検知

ページ読み込みの直後に行っている、認証や、firebase.database.Reference取得の失敗を検知できるようにしました。
認証エラーは素直にPromiseで検知できたのですが、reference取得エラーの検知が厄介でした。
接続失敗したときに各種イベントが発火するわけもないため、タイムアウトで検知するようにしました。

// ロード時に実行する関数
//  => 認証エラーと接続タイムアウトでalertを出す
export const onLoad = (): void => {
  getApp()
      .auth()
      .signInWithEmailAndPassword('e-mail@gmail.com', 'password1234')
      .then(() => {
        console.log('firebase: authentication ok');
      })
      .catch((err) => {
        console.log(err);
        window.alert('firebase認証失敗')
      });
  alertOnConnectionTimeout();
};

const alertOnConnectionTimeout = (): void => {
  // TIMEOUT_SEC秒経ってもfirebase接続ができなければalertを出す
  const TIMEOUT_SEC = 15;
  checkConnectionTimeout(TIMEOUT_SEC)
      .then(() => {
        console.log('firebase: connection ok');
      })
      .catch((err) => {
        console.log(err);
        window.alert('firebase接続失敗');
      });
};

const checkConnectionTimeout = (timeoutSec: number): Promise<void> => {
  return new Promise((resolve, reject) => {
    getRef('.info/connected').on('value', (snapshot: any) => {
      // 接続したらここに来る
      if (snapshot.val() == false) {
        return;
      }
      resolve();
    });
    window.setTimeout(() => {
      // タイムアウトしたらここに来る
      reject(new Error('timeout'));
    }, timeoutSec * 1000);
  });
};



なお、以下はダメでした。

// ダメだったケース
export const onLoad = (): void => {
  checkConnectionOnce();
};
const checkConnectionOnce = (): void => {
  getRef('.info/connected').once('value', (snapshot: any) => {
    // 接続失敗なら、そもそもここに来ない
    if (snapshot.val() == false) {
      // 接続後、1回目のvalueイベントでここにくる (=正常系でアラートが出てしまう)
      window.alert('Firebaseの接続に失敗しました');
      return;
    }
    // 接続後、2回目のvalueイベントでここにくる (=onceだとここに到達しない)
    // => よって、↑ではonceではなくonで見ることにした
    console.log('firebase: connection ok');
  });
};


途中切断の検知

自分 <-(a)-> firebase <-(b)-> 他人 のうち、(a)の途中切断を検知しようとしました。
一応、以下のコードで検知できるとは思うのですが、

  • devtoolで確認したところメモリリークしているっぽい挙動だった
  • 次項のNW切断エラーを入れれば大半のケースで検知できるし、シンプルになる

という理由から、導入はやめました。
(NW切断の検知だけだと、NW正常でfirebase側に障害があった場合は検知できません。とはいえ参照・更新のタイミングでエラー検知できるため、大過ないだろうと考えました)

// ロード時に実行する関数
export const onLoad = (): void => {
  // INTERVAL_SEC秒ごとに、firebaseの接続状態をチェック
  const INTERVAL_SEC = 30;
  setInterval(
    checkConnectionTimeout, // 中身は前項を参照
    INTERVAL_SEC * 1000
  );
};

なお、onDisconnectといういかにも今回のケースに使えそうな名前の関数がありましたが、これは使えませんでした。

// onDisconnect()を使うと、
// 切断時に、firebase側でfirestoreの値を更新してくれる。
// ただ、値を更新をするためのインターフェースしか用意されていないため、
// window.alert()を出す、みたいなことには使えない。
getRef('my/app/path/is_connected').onDisconnect().set(false);

/* 余談
 * onDisconnect()による値の更新には罠があります。
 * タブを閉じた場合は、即時値が反映されますが、
 * NW切断の場合は、3分程たたないと値が反映されません。
 * NW切断も含めてリアルタイムに値を反映したい場合は、何かしら別の手段が必要です。
 */


NW切断の検知

firebaseではなく、ブラウザの機能をつかいました。超簡単。

window.addEventListener('offline', () => {
  window.alert('ネットワーク切断');
}, false);


参照エラーの検知

Referenceを使う場合、promiseではなく、なつかしのコールバックを使ってエラーを検知します。この仕様に気づかずそこそこハマりました。
(改めて型定義を見返すまで気づけなかったです。。。盲点でした。)

const watchMyAppPath = () => {
  getRef('my/app/path').on('value', (snapshot) => {
    console.dir(snapshot.val());
  }, captureWatchError);
};

const captureWatchError = (err: any) => {
  if (err) {
    console.log(err);
    window.alert('firebase参照失敗');
  }
};

なお、試してませんが、以下を使えば普通にtry/catchできるかもしれません。

  • promisifyを使ってpromise化する
  • ThenableReferenceを使ってみる
    (名前から想像するに、thenやcatchを使えるようにしたReferenceと思われます。ドキュメントをみても使い方が分からなかったのと、変更の影響がでかそうだったため導入を見送りました)


更新エラーの検知

参照エラーと同じく、コールバックを使いました。

const updateMyAppPath = () => {
  getRef('my/app/path/boolean').set(true, captureWatchError);
};

const captureUpdateError = (err: any) => {
  if (err) {
    console.log(err);
    window.alert('firebase更新失敗');
  }
};


まとめ

  • 異常系もちゃんと動作確認をしよう
  • .info/connectedを見ればfirebaseの接続状態が分かる
  • window.addEventListener('offline') でNW切断を検知できる
  • onDisconnect()でfirebase切断時に値を更新できるが、NW切断だとラグがあるので注意
  • firestoreの参照、更新エラーにはコールバック関数を使う



以上です。

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

chromeで401レスポンスが返ってきたときの不具合

背景

https://www.nnn.ed.nico/questions/14833
n予備校の教材の通りに実装を進めていたところ、このリンクと同じ状況に遭遇

問題

chromeに限り、以下のコードで表示されるはずのログアウトしましたという文とリンクが表示されない。

 function handleLogout(req, res) {
   res.writeHead(401, {
    'Content-Type': 'text/html; charset=utf-8'
   });

   res.end('<!DOCTYPE html><html lang="ja"><body>' +
     '<h1>ログアウトしました</h1>' +
     '<a href="/posts">ログイン</a>' +
     '</body></html>'
   );
 }

原因

chromeのバグ
https://bugs.chromium.org/p/chromium/issues/detail?id=992639

解決

  • とりあえず別のステータスコードを変える
function handleLogout(req, res) {
  console.log('handleLogout');
  res.writeHead(400, {
    'Content-Type': 'text/html; charset=utf-8'
  });
  res.end('<!DOCTYPE html>'+
   '<html lang="ja"><body>' +
    '<h1>ログアウトしました</h1>' +
    '<a href="/posts">ログイン</a>' +
    '</body></html>');
}
  • (ブラウザを変える)

追記

教材にこのエラーについての解決方法が追記されていた。
解決方法は、ステータスコードを302に書き換えること。

https://developer.mozilla.org/ja/docs/Web/HTTP/Status
より、

302 Found
このレスポンスコードは、リクエストされたリソースの URI が一時的に変更されたことを示します。

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

【Nuxt.js】Components番外編: 親子間のやりとりを$emit, props, Vuexで見てみよう

前置き

コンポーネント間のやりとりについて、
やりたいことがある時に
パターンがいくつかあるよという例です?
近々Vuexに関する記事を投稿していたので
そのパターンも含めて書いてみました✍️
それぞれどんな時に使うかも
目次ごとに記載しています?
・emit, $event
・propsをfunc
・ Vuex

propsでfuncすることは
あまりないのですが…!
初めから誰もが見やすい
完璧なコードを目指すのではなく、
動かすだけならこんな方法もある!
と知ることは大事だと思います??

?それぞれ基礎編の記事はこちら
emit基礎編
https://note.com/aliz/n/nd6e771724cd7
props基礎編
https://note.com/aliz/n/n99144d4556b9
vuex基礎編
https://note.com/aliz/n/n497914c981c8

やりたいこと

name.gif

buttonで名前を変えるだけです笑
とにかくシンプルにわかりやすく!
がモットーです?
表示はこちらです。
・親ではname
・子ではmyName

file
components/
--| Name.vue

pages/
--| index.vue

store/
--| index.js

パターン1: $emit, $event

【どんな時に使う?】
今回のように構造がシンプルな時⭕️
というか基本はコレです!✨
親で渡しているpropsの値を変えます。
これができればモーダルウィンドウも簡単!
https://note.com/aliz/n/n2f0bc857defb

❓では子で値を変えると
 動作はしますがエラーになりますね。
error.gif

Name.vue
<template>
 <div class="button">
   <p>myName: {{ myName }}</p>
   <button @click="changeName">
     reset
   </button>
 </div>
</template>

<script>
export default {
 props: {
   myName: {
     type: String,
     required: false
   },
 },
 methods: {
   changeName() {
     this.myName = "Max"
     this.$emit('nameSwitch', this.myName)
   // または1行にまとめて
     this.$emit('nameSwitch', 'Max')
   }
 },
}
</script>

【index.vue】
@nameSwitch="name = $event"
親にあるdataのname: 'Bob'を
イベントで操作します?
@nameSwitch="name"にすると
通常methodsのname()になるけど
dataのnameをイベントで渡すことにより
子のmethods, resetNameを使用します!

$emitカスタムイベント
https://jp.vuejs.org/v2/guide/components-custom-events.html

index.vue
<template>
 <div class="page">
   <p>name: {{ name }}</p>
   <Name
     :myName="name"
     @nameSwitch="name = $event"
   />
 </div>
</template>

<script>
import Name from '~/components/Name.vue'

export default {
 components: {
   Name
 },
 data () {
   return {
     name: "Bob"
   }
 },
}
</script>

親にmethods追加して
名前を戻してみましょう。
ちゃんと戻りますね?
error2.gif

index.vue
<template>
 <div class="page">
   <p>name: {{ name }}</p>
   <Name
     :myName="name"
     @nameSwitch="name = $event"
   />
   <button @click="resetName">
     reset
   </button>
 </div>
</template>

<script>
import Name from '~/components/Name.vue'

export default {
 components: {
   Name
 },
 data () {
   return {
     name: "Bob"
   }
 },
 methods: {
   resetName () {
     this.name = "Bob"
   },
 },
}
</script>

エラー解消は
子で値を変えなければOKです!
基礎編をご覧ください??

パターン2: propsをfunc

【どんな時に使う?】
あんまり使いません。
前置きに書いたように
動かす手段の1つとしてご紹介。

【特徴】
コードは単純で分かりやすいです?
ただこれをやるなら
$emitやvueの方が良いですね⭕️
子と親でのやりとりを
理解するのには最もシンプルで
良いかもしれません?

こちらが参考になります!
https://kuroeveryday.blogspot.com/2017/09/vuejs-callback-vs-emit-events.html

Name.vue
<template>
 <div class="button">
   <p>myName: {{ myName }}</p>
   <button @click="resetFn">
     resetFn
   </button>
 </div>
</template>

<script>
export default {
 props: {
   myName: {
     type: String,
     required: false
   },
   resetFn: {
     type: Function
   },
 },
}
</script>
index.vue
<template>
 <div class="page">
   <p>name: {{ name }}</p>
   <Name
     :myName="name"
   :resetFn = "resetName"
   />
 </div>
</template>

<script>
import Name from '~/components/Name.vue'

export default {
 components: {
   Name
 },
 data () {
   return {
     name: "Bob"
   }
 },
 methods: {
   resetName () {
     this.name = "Max"
   },
 },
}
</script>

パターン3: vuex

【どんな時に使う?】
ネストが深すぎてpropsがどうなってるか
分かりにくい!!って時に使います。
あとはサーバーとの通信が基本なので
今回のシンプルかつ通信不要な際は使いません。

ただ導入が容易ではなく
気軽にオススメはしません?‍♂️?
本当に必要な時には⭕️

【特徴】
一見、コードもファイルも増えて
ごちゃついてそうな印象ですが☁️
何がどこにあって、が超絶見やすいですね✨?

Name.vue
<template>
 <div class="button">
   <p>myName: {{ myName }}</p>
 </div>
</template>

<script>
export default {
 props: {
   myName: {
     type: String,
     required: false
   },
 },
}
</script>
index.vue
<template>
 <div class="page">
   <p>name: {{ name }}</p>
   <Name
     :myName="name"
     @nameWasReset="name = $event"
   />
   <button @click="$store.commit('changeName')">
     change
   </button>
 </div>
</template>

<script>
import Name from '~/components/Name.vue'

export default {
 components: {
   Name
 },
 computed: {
   name () {
     return this.$store.getters.name
   },
 },
}
</script>
index.js
export const state = () => ({
 name: "Max",
})

export const getters = {
 name(state) {
   return state.name
 },
}

export const mutations = {
 changeName(state) {
   state.name = "Bob"
 },
}

記事が公開したときにわかる様に、
note・Twitterフォローをお願いします?

https://twitter.com/aLizlab

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

Chart.jsにいい感じの色を自動的に割り当てる

やりたいこと

簡単にカッコいいグラフやチャートを表示できるJavascriptライブラリ「Chart.js」を使う際、色を自分で指定するのが大変。更にデータの個数が可変な時、自動的にいい感じの色を割り当てたい。

実現方法

Chart.jsのプラグイン「chartjs-plugin-colorschemes」を使うとプリセットされたいい感じのカラースキームを使うことができます。

chartjs-plugin-colorschemes
https://nagix.github.io/chartjs-plugin-colorschemes/ja/

およそ500種類くらいのカラースキームが用意してあるので好みのものを選んでください。
キャプチャ.PNG

今回はReactを使った例で説明していきます。更に「Chart.js」をそのまま使うのではなくのReactラッパーである「react-chartjs-2」を使っていきます。データをpropsで渡すだけでコンポーネントのように簡単にグラフやチャートを追加できます。

手順

まずはnpmを使ってインストールしていきます。

$ npm install --save chart.js react-chartjs-2 chartjs-plugin-colorschemes

追加したいjsファイルの先頭でモジュールのインポートをしていきましょう

//今回はドーナッツグラフを作ります
import { Doughnut } from 'react-chartjs-2';

//一括でインポートしても...
import 'chartjs-plugin-colorschemes';
//ファイルサイズを減らしたい場合はカラースキームだけをインポートしても大丈夫です
//今回はbrewr.Paired12というカラースキームを使いたいので以下のように指定しました。
import { brewr } from 'chartjs-plugin-colorschemes/src/colorshemes/colorschemes.office';

キャプチャ.PNG

Reactのファイルはこんな感じになります。
家計簿アプリを例に説明すると、今回はAPIを叩いてカテゴリごとの金額の合計を取得してセットしています。

import React, { useState, useEffect } from 'react';
import { Doughnut } from 'react-chartjs-2';
import { brewer } from 'chartjs-plugin-colorschemes';
import axios from 'axios';

const DoughnutChart = (props) => {
  const [chartData, setChartData] = useState({});

  useEffect(() => {
    getChartData();
  }, []);

  const getChartData = () => {
    axios
      .get("/api/getChartData")
      .then(res => {
        const expenses = res.data;
        let labels = [];
        let data = [];
        expenses.forEach(expense => {
          labels.push(expense.category);
          data.push(expense.money);
      });

      setChartData({
          labels:labels,
          datasets: [
            {
              label: labels,
              data: data,
              //ここはfalseに設定
              fill: false,
            }
          ]
        }
      );
    });
  };

  const options = {
    plugin: {
      colorschemes: {
        scheme: 'brewr.Paired12'
      }
    }
  };

  return (
    <React.Fragment>
        <Doughnut data={chartData} options={options}/>
    </React.Fragment>
  );
};

export default DoughnutChart;

このコンポーネントを組み込んで表示するとこんな感じのおしゃれな配色のグラフが表示されます。
キャプチャ.PNG

もし新たなカテゴリのデータが登録され、表示するデータが増えた際もカラースキームから色を割り振って表示してくれます。今回使っている「brewr.Paired12」の場合は12色まで対応可能です。
image.png

参考にさせて頂いたサイト

Chart.js | Open source HTML5 Charts for your website
chartjs-plugin-colorschemes
jerairrest/react-chartjs-2: React wrapper for Chart.js - GitHub
react-chartjs-2とChart.jsを使ってグラフを作ってみた - Qiita

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

Javascript基本集(3)~関数~

Javascript基本集(3)~関数~

自分の学習用です

  
基本書式

function 関数名(引数) {
    内部処理
    return 戻り値;
}

例題

function hanbun(hikisuu) {
    var xxx = hikisuu / 2;
    return xxx;
}

関数名・引数

『hanbun』というのが『関数名』。『hanbun』のあとの括弧内に書かれた『hikisuu』が、『引数』(ひきすう)と呼ばれる変数になります。『引数』は、内部処理を行う際に、外部から値を受け取るための変数。

『引数』を書く括弧の中には、変数名を書く。
引数が必要ないのなら、何も書かなくてもOK。『引数』を複数使いたい場合は『hanbun(hikisuu1, hikisuu2)』のように『,』(カンマ)で区切って並べて書く。
宣言した『引数』は、『関数』内でのみ有効。

return 戻り値

最後の行の『return xxx;』について。
『return』は、「ここで『関数』を終了する」という宣言。
『関数』内で『return』の書かれた行が実行されると、その場所で『関数』の処理が終了する。
『関数』が終了すると、その『関数』が呼び出された場所まで処理が戻る。
『関数』は、『{}』の末尾まで来るか、『return』の行まで来ると、処理を終え、呼び出された場所に戻る。

今回はここまで:santa:

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

Vue.js・Nuxt.jsに入門したときにみたリンク集

はじめに

Nuxt.jsでとあるサイト(HPぐらいのレベル感で状態管理とかはほとんどないシンプルなアプリケーション)を構築することになりVueに入門しました。
Nuxt.jsでSPAでの構築、UIフレームワークはVuetifyです。
筆者は、Reactの経験はありますが、Vueの経験はありません。
そのため、公式系をざざっと読み、Nuxtでアプリを構築しながら必要なことを調べながら実装しました。
そのときに調べたことのリンク集です。

だいたいこれで理解した。

公式系

Nuxt

Vue

Deploy

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

javascript

innerHTML value

form部品の中の情報を取得する場合にはvalueを使う
HTMLタグを有効なタグとして扱うのがinnerHTML
内容を書き換えて出力する時にはinnerHTMLを使うのが良い

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

フロントエンドの速度改善でやったこと

最近、Vue.jsで作られたWebページの速度改善をやりました。
対応内容について書き残します。

前置き

  • ページ読み込み速度ではなく、読み込んだ後の速度を改善しました
  • 対応内容は、Vue.jsに限らず、フロント全般で有効な内容です
  • バックエンドの速度改善は含まれません

概要

No 何をした? どうやった?
1 API呼び出しの並列化 Promise.all()を使う
2 大量データの一覧表示が重くならないようにする virtual-scrollを使う

対応1: API呼び出しの並列化

before

async mounted() {
  // 4つのAPIを順次実行
  const response1 = await apiRequest1();
  const response2 = await apiRequest2();
  const response3 = await apiRequest3();
  const response4 = await apiRequest4();
},

mounted()内で複数のAPIを順次実行している箇所がありました。
当たり前の話ですが、この場合
処理時間 = api1の実行時間 + api2の実行時間 + api3の実行時間 + api4の実行時間
となるため、遅いです。
幸い今回はapiの実行順序を守る必要がなかったため、次のように並列化しました。

after

async mounted() {
  // 4つのAPIを並列実行
  const [
    response1,
    response2,
    response3,
    response4,
  ] = await Promise.all([
    apiRequest1(),
    apiRequest2(),
    apiRequest3(),
    apiRequest4(),
  ]);
},

これで、
処理時間 = api1〜4のうち一番遅いものの実行時間
となり、速くなりました。

なお、細かいエラー処理をしたい場合は次のようにもできます。

async mounted() {
  const [
    response1,
    response2,
    response3,
    response4,
  ] = await Promise.all([
    apiRequest1().catch((err) => {
      console.log(`apiRequest1() failed: err(${err})`);
    }),
    apiRequest2().catch((err) => {
      console.log(`apiRequest2() failed: err(${err})`);
    }),
    apiRequest3().catch((err) => {
      console.log(`apiRequest3() failed: err(${err})`);
    }),
    apiRequest4().catch((err) => {
      console.log(`apiRequest4() failed: err(${err})`);
    }),
  ]);
  console.log('Promise.all finished!');
  // 例えばapiRequest4()だけ失敗した場合は、
  //   apiRequest4() failed: err(エラー内容)
  //   Promise.all finished!
  // が出る。
  // response4はundefinedになる。
},

対応2: 大量データの一覧表示が重くならないようにする

about

データの一覧を表示している箇所について、データ数が多くなると、動作がもっさりする問題がありました。
対策として、virtual-scrollを導入しました。

virtual-scrollとは、ユーザーに見える部分だけDOMを生成することで、擬似的にスクロールを実現する仕組みのことを指します。
例えば1万件データがあった場合、
普通にv-forでlist表示をすると、1万個のDOMが描画されます。
一方、virtual-scrollでlist表示すると、ユーザに見える分+α(サイズにもよるけどせいぜい数十個)のDOM描画で済みます。

参考: https://kakkoyakakko2.hatenablog.com/entry/2018/10/23/003000
(ユーザに見える部分だけDOMを生成する様子がgifアニメになっており、わかりやすいです)

sample

各種フレームワークにvirtual-scroll用のライブラリがあるようですが、今回はvueを使っているため、
vue-virtual-scroll-listを使いました。
以下、サンプルコードと、実行結果です。

<template>
  <div class="container">
    <h1>Qiita Test</h1>

    <!-- 普通にv-forで回すと、データ量が多いときの描画コストが大きい -->
    <h2>Before: normal list</h2>
    <ul style="height: 200px; overflow: scroll;">
      <li
        style="list-style: none;"
        v-for="user in users"
        :key="user.id"
      >
        {{ user.name }}
      </li>
    </ul>

    <!-- virtual-scrollを使うと、表示される分だけ描写するため、描画コストがデータ量に依存しない -->
    <h2>After: virtual scroll list</h2>
    <ul style="height: 200px; overflow: scroll;">
      <vue-virtual-scroll-list
        :size="20"
        :remain="15"
      >
        <li
          v-for="user in users"
          :key="user.id"
        >
          {{ user.name }}
        </li>
      </vue-virtual-scroll-list>
    </ul>
  </div>
</template>

<script lang="ts">
import vue from 'vue';
import VueVirtualScrollList from 'vue-virtual-scroll-list';

export default vue.extend({
  components: {
    VueVirtualScrollList,
  },
  data() {
    return {
      users: [] as Array<{id: number, name: string}>,
    };
  },
  async mounted() {
    this.users = [
      {id: 1, name: 'user1'},
      {id: 2, name: 'user2'},
      {id: 3, name: 'user3'},
      {id: 4, name: 'user4'},
      // 後略。大量のユーザーがいるとします。
    ];
  },
});
</script>

1.png

最後に

virtual-scrollは同僚に教えてもらいました。
スクロールするとDOMがゴリゴリ変わっていくのは、なかなかにお洒落。
5, 6秒かかってた処理が一瞬になったときの気持ちよさったらなかったです。

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

ファイルを DATA URI に変換するフォーム

ファイルを DATA URI に変換するフォーム

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

<head>
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <title>DATA URI 変換</title>
  <meta name="viewport" content="width=device-width, initial-scale=1">
</head>

<body>
  <input type="file" id="fileform">
  <br>
  <br>
  <textarea id="datauri" cols="100" rows="30"></textarea>
  <br>
  <button type="button" id="copybutton">コピー</button>
  <script>
    const toBase64DataUri = file => new Promise(resolve => {
      const reader = new FileReader();
      reader.onload = () => resolve(reader.result);
      reader.readAsDataURL(file);
    });

    document.getElementById("fileform").addEventListener("input", async (e) => {
      const file = e.target.files[0];
      const datauri = await toBase64DataUri(file);
      document.getElementById("datauri").value = datauri;
    }, false);

    document.getElementById("copybutton").addEventListener("click", () => {
      document.getElementById("datauri").select();
      document.execCommand("copy");
    }, false);
  </script>
</body>

</html>

ファイルを DATA URI に変換してる部分

const toBase64DataUri = file => new Promise(resolve => {
  const reader = new FileReader();
  reader.onload = () => resolve(reader.result);
  reader.readAsDataURL(file);
});

ファイルを DATA URI に変換してる部分を呼び出している部分

const datauri = await toBase64DataUri(file);
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【随時更新】日本コロナMAPを作ります。【解説用】

最初に

最近プログラミングを始めたお医者さんの友達が、
コロナウイルスの患者数が県ごとにわかるMAPのWEBアプリを作りたい!
ということだったので、説明のために、挫折0を目指し、ソフトのダウンロードから説明していきます。
最小構成で考えるため、DBは使いません。
作業環境は、Windows10。

Visual Studio Codeのダウンロードとインストール

まずは、フロントエンドのコードを書くエディタが必要です。
フロントエンドとは、Html,Css,Javascriptで構成されるWEBページのことです。
・Htmlがページに表示される要素の構成するコード
・CSSがページデザインの設定するコード
・Javascriptが要素に動きをつけるコード
最初は、この3点からフロントをコード化して、
最後にPythonのFlaskからサーバー化→Herokuからサービス化という流れで行こうと思います。

さっそくVisualStudioCode(https://code.visualstudio.com/ )をダウンロード!
1.「Download for Windows」からダウンロードする。
2.ダウンロードした「VSCodeUserSetup-x64-1.43.2.exe」を実行
3.インストーラーが開くので「同意する」→「次へ」→「次へ」
 →追加タスクの選択では、一応デスクトップ上にアイコンを作成する にチェックつけときましょう。
 頻繁に使うんでね。最後、「インストール」を選択。終わったら「完了」でソフトが自動で起動します。
image.png

こんな画面になります。最後にメニューのFileからAutoSaveにチェックをいれておきましょう。
image.png

VisualStudioCodeからディレクトリ構成を構築する(動作GIFあり)

1.メニューバーのFileから、OpenFolderを選択。
2.好きなディレクトリに移動し、そこで右クリック→フォルダを新規作成(coronaMap)→そのフォルダを選択する
corona.gif
3.次に、map.htmlファイルの作成とjs/myscript.js, css/mystyle.cssフォルダの作成を行います。【下のGIF画像参照】
corona_dir.gif
ディレクトリ構成は、以下のようになっているはずです。

├── coronaMap
   └── css
    |    └── mystyle.css
    ├── js
    |    └── myscript.js
    map.html

4.次は、Map情報を描画するためにJQueryとJapanMapをダウンロードします!

まずは,JQueryから、VisualStudioCodeのjsディレクトリ内に、jquery.3.4.1.min.jsというファイルを作ります。
こちらからページをCtrl+Aで全選択しコピーし(https://code.jquery.com/jquery-3.4.1.min.js )、
貼り付けます。【GIF参照】
corona_jquery.gif

次に、JapanMapをダウンロードします。
このサイト(https://takemaru-hirai.github.io/japan-map/ )のZIPをクリックし、ダウンロードしてください。
ダウンロードしたZIPファイルを解凍し、中にある「jquery.japan-map.min.js」をコピーし、
今回のcoronaMap/jsフォルダの中に貼り付けてください。
corona_japanmap.gif

準備は整った。あとはゴリゴリとコードを書きます。(コピペOk)

まずは、map.htmlの記述から

map.html
<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8" />
        <title>日本MAP</title>

        <!-- JQueryとJapanMapの指定 -->
        <script src="js/jquery.3.4.1.min.js"></script>
        <script src="js/jquery.japan-map.min.js"></script>

        <!-- 自分で記述していくJavascriptファイルの指定 -->
        <script src="js/myscript.js"></script>

        <!-- 自分で記述していくCSSファイルの指定 -->
        <link href="css/mystyle.css" rel="stylesheet"/>
    </head>
    <body>
        <!-- MAPを記述していくコンテナ -->
        <div id="map-container"></div>
    </body>
</html>

次は、Javascriptの記述です。
基本的に自分が既出していくJavascriptは、myscript.jsのみです。

myscript.js
$(function(){        
    let areas = [
        {code : 1, name: "北海道", color: "#7f7eda", hoverColor: "#b3b2ee", prefectures: [1]},
        {code : 2, name: "青森", color: "#759ef4", hoverColor: "#98b9ff", prefectures: [2]},
        {code : 3, name: "岩手", color: "#759ef4", hoverColor: "#98b9ff", prefectures: [3]},
        {code : 4, name: "宮城", color: "#759ef4", hoverColor: "#98b9ff", prefectures: [4]},
        {code : 5, name: "秋田", color: "#759ef4", hoverColor: "#98b9ff", prefectures: [5]},
        {code : 6, name: "山形", color: "#759ef4", hoverColor: "#98b9ff", prefectures: [6]},
        {code : 7, name: "福島",   color: "#759ef4", hoverColor: "#98b9ff", prefectures: [7]},
        {code : 8, name: "茨城",   color: "#7ecfea", hoverColor: "#b7e5f4", prefectures: [8]},
        {code : 9, name: "栃木",   color: "#7ecfea", hoverColor: "#b7e5f4", prefectures: [9]},
        {code : 10, name: "群馬",   color: "#7ecfea", hoverColor: "#b7e5f4", prefectures: [10]},
        {code : 11, name: "埼玉",   color: "#7ecfea", hoverColor: "#b7e5f4", prefectures: [11]},
        {code : 12, name: "千葉",   color: "#7ecfea", hoverColor: "#b7e5f4", prefectures: [12]},
        {code : 13, name: "東京",   color: "#7ecfea", hoverColor: "#b7e5f4", prefectures: [13]},
        {code : 14, name: "神奈川",   color: "#7ecfea", hoverColor: "#b7e5f4", prefectures: [14]},
        {code : 15, name: "新潟",   color: "#7cdc92", hoverColor: "#aceebb", prefectures: [15]},
        {code : 16, name: "富山",   color: "#7cdc92", hoverColor: "#aceebb", prefectures: [16]},
        {code : 17, name: "石川",   color: "#7cdc92", hoverColor: "#aceebb", prefectures: [17]},
        {code : 18, name: "福井",   color: "#7cdc92", hoverColor: "#aceebb", prefectures: [18]},
        {code : 19, name: "山梨",   color: "#7cdc92", hoverColor: "#aceebb", prefectures: [19]},
        {code : 20, name: "長野",   color: "#7cdc92", hoverColor: "#aceebb", prefectures: [20]},
        {code : 21, name: "岐阜",   color: "#7cdc92", hoverColor: "#aceebb", prefectures: [21]},
        {code : 22, name: "静岡",   color: "#7cdc92", hoverColor: "#aceebb", prefectures: [22]},
        {code : 23, name: "愛知",   color: "#7cdc92", hoverColor: "#aceebb", prefectures: [23]},
        {code : 24, name: "三重",   color: "#ffe966", hoverColor: "#fff19c", prefectures: [24]},
        {code : 25, name: "滋賀",   color: "#ffe966", hoverColor: "#fff19c", prefectures: [25]},
        {code : 26, name: "京都",   color: "#ffe966", hoverColor: "#fff19c", prefectures: [26]},
        {code : 27, name: "大阪",   color: "#ffe966", hoverColor: "#fff19c", prefectures: [27]},
        {code : 28, name: "兵庫",   color: "#ffe966", hoverColor: "#fff19c", prefectures: [28]},
        {code : 29, name: "奈良",   color: "#ffe966", hoverColor: "#fff19c", prefectures: [29]},
        {code : 30, name: "和歌山",   color: "#ffe966", hoverColor: "#fff19c", prefectures: [30]},
        {code : 31, name: "鳥取",   color: "#ffcc66", hoverColor: "#ffe0a3", prefectures: [31]},
        {code : 32, name: "島根",   color: "#ffcc66", hoverColor: "#ffe0a3", prefectures: [32]},
        {code : 33, name: "岡山",   color: "#ffcc66", hoverColor: "#ffe0a3", prefectures: [33]},
        {code : 34, name: "広島",   color: "#ffcc66", hoverColor: "#ffe0a3", prefectures: [34]},
        {code : 35, name: "山口",   color: "#ffcc66", hoverColor: "#ffe0a3", prefectures: [35]},
        {code : 36, name: "徳島",   color: "#fb9466", hoverColor: "#ffbb9c", prefectures: [36]},
        {code : 37, name: "香川",   color: "#fb9466", hoverColor: "#ffbb9c", prefectures: [37]},
        {code : 38, name: "愛媛",   color: "#fb9466", hoverColor: "#ffbb9c", prefectures: [38]},
        {code : 39, name: "高知",   color: "#fb9466", hoverColor: "#ffbb9c", prefectures: [39]},
        {code : 40, name: "福岡",   color: "#ff9999", hoverColor: "#ffbdbd", prefectures: [40]},
        {code : 41, name: "佐賀",   color: "#ff9999", hoverColor: "#ffbdbd", prefectures: [41]},
        {code : 42, name: "長崎",   color: "#ff9999", hoverColor: "#ffbdbd", prefectures: [42]},
        {code : 43, name: "熊本",   color: "#ff9999", hoverColor: "#ffbdbd", prefectures: [43]},
        {code : 44, name: "大分",   color: "#ff9999", hoverColor: "#ffbdbd", prefectures: [44]},
        {code : 45, name: "宮崎",   color: "#ff9999", hoverColor: "#ffbdbd", prefectures: [45]},
        {code : 46, name: "鹿児島",   color: "#ff9999", hoverColor: "#ffbdbd", prefectures: [46]},
        {code : 47, name: "沖縄",   color: "#eb98ff", hoverColor: "#f5c9ff", prefectures: [47]},
    ];

    const getWidth = $(window).width();//画面の大きさ取得
    $("#map-container").japanMap({
        width: getWidth,//画面の大きさそのまま描画する
        selection: "area",
        areas: areas,
        backgroundColor : "#dcdcdc",
        borderLineColor: "#f2fcff",
        borderLineWidth : 0.25,
        lineColor : "#a0a0a0",
        lineWidth: 1,
        drawsBoxLine: true,
        showsPrefectureName: true,
        prefectureNameType: "short",
        movesIslands : true,
        fontSize : 11,
        fontShadowColor : "#fff",
        onSelect : function(data){
            alert(data.name);
        }
    });
});

ここまで出来たら、いったん確認してみます。
map.htmlファイルをドラッグ&ドロップでWEBブラウザ上で動かす
corona_startmap.gif

いったん休憩で、次は、いよいよコロナの情報を取得してMAPに反映させます。

Pythonを扱う準備

Pythonで行う役割は主に2つです。
サーバー化とスクレイピングです。
まずは最初に、サーバー化からはじめましょう。
今回は、小規模なのでFlaskを利用します。

1.pycharmで先ほどの作業ディレクトリを開きます。(File→Open→先ほどのcoronaMapを選択)
※Pycharmとanacondaはインストール済みで話を進めます。

corona_pycharm.gif

次に、作業用で利用するpython環境をanacondaで構築しましょう。
windowsアプリからanaconda promptを起動する。
この画面で、環境を作成

conda create -n corona python=3.6.8
conda activate corona

conda createの分でProceed ([y]/n)?とでたら、「y」と入力し、Enter

次は、Flaskのインストール

pip install flask

ここまでできたら、pycharmの設定にこのPython環境を以下の流れで適応させます。(GIFあり)

「File」→「Settings」→「Project:coronaMap」→「Project Interpreter」
→Project Interpreter欄の一番右にある歯車ボタン→Add→Existing environmentを選択し、
Interpreterの欄の一番右の「...」を選択する。
anaconda→envs→corona→python.exeを選択

corona_python_settings.gif

この手順で、先ほど作成したPython環境に適応できます。

Flaskでサーバー化

先ほど作成したmap.htmlとjs, cssファイルの構成を変えます。
新しくtemplatesフォルダとstaticフォルダを作成し、map.htmlをtemplatesフォルダの中に、
js,cssフォルダをstaticフォルダの中にぶち込みます。
そして、main.pyを作成する。

以下が変更されたディレクトリ構成

├── coronaMap
   └── templates
    |    └── map.html
    ├── static
    |    ├── jsフォルダ
    |    └── cssフォルダ
    main.py

一応、作成中のGIFが下の画像になります。念のため
corona_create_flask_dir.gif

main.pyにコードを記述

main.py
from flask import *

app = Flask(__name__)
server_mode = "local"

@app.route("/", methods=["GET", "POST"])
def corona_page():
    return render_template("map.html")

if __name__ == "__main__":
    if server_mode == "local":
        port = 5000
        app.run(debug=True, port=port)

これで、Runから起動してみると、http://127.0.0.1:5000/ にアクセスすると同様のMAPが表示されるはずです。
表示が正しくされない場合は、map.htmlのscriptのsrc先が"../static/js/jquery.3.4.1.min.js"のようにstaticのパスが通っているか確認してください。

とりあえずもう一回休憩。
次は、コロナウイルスの情報をスクレイピングしてきます。

pythonでスクレイピングしてMAPに反映

スクレイピングするサイトは、厚生労働省のページ!信用度高い
https://www.mhlw.go.jp/stf/newpage_10465.html

まずは、前回と同じようにanaconda promptで必要になるモジュールをインストール

pip install requests
pip install bs4

新しいPythonファイル(scraping_corona)を作成して記述します

scraping_corona.py
import requests
from bs4 import BeautifulSoup

#探すサイト( --https://www.mhlw.go.jp/stf/houdou/houdou_list_202003.html-- )

# 新型コロナウイルス感染症の現在の状況と厚生労働省の対応について(令和2年3月26日版)
url = 'https://www.mhlw.go.jp/stf/newpage_10465.html'

prefectures_directory = {
    '北海道':1,
    '青森県':2,
    '岩手県':3,
    '宮城県':4,
    '秋田県':5,
    '山形県':6,
    '福島県':7,
    '茨城県':8,
    '栃木県':9,
    '群馬県':10,
    '埼玉県':11,
    '千葉県':12,
    '東京都':13,
    '神奈川県':14,
    '新潟県':15,
    '富山県':16,
    '石川県':17,
    '福井県':18,
    '山梨県':19,
    '長野県':20,
    '岐阜県':21,
    '静岡県':22,
    '愛知県':23,
    '三重県':24,
    '滋賀県':25,
    '京都府':26,
    '大阪府':27,
    '兵庫県':28,
    '奈良県':29,
    '和歌山県':30,
    '鳥取県':31,
    '島根県':32,
    '岡山県':33,
    '広島県':34,
    '山口県':35,
    '徳島県':36,
    '香川県':37,
    '愛媛県':38,
    '高知県':39,
    '福岡県':40,
    '佐賀県':41,
    '長崎県':42,
    '熊本県':43,
    '大分県':44,
    '宮崎県':45,
    '鹿児島県':46,
    '沖縄県':47
}

def get_colona_data():
    r = requests.get(url)
    if r.status_code == requests.codes.ok:
        soup = BeautifulSoup(r.content, 'html.parser')
        data = [[[td.get_text(strip=True) for td in trs.select('th, td')]
                 for trs in tables.select('tr')]
                for tables in soup.select('table')]

        #1行目がアナウンス・対策情報
        announce_data = data[0]

        #2行目が国別の感染者数のデータ
        country_corona_data = data[1]

        #3行目が国内の感染者数データ ['都道府県名', '患者数', '現在は入院等', '退院者', '死亡者']
        japan_corona_data = data[2]

        japan_corona_data_true = []#都道府県のみのデータ
        max_patient_num = max([int(japan_data[1]) for japan_data in japan_corona_data[1:-1]])

        def hex_color_change(patient_num):
            ratio = patient_num / max_patient_num * 255
            hex_data = hex(int(ratio))[2:]
            if len(hex_data) == 1:
                str_hex_data = "#0"+str(hex_data) + "0000"
            else:
                str_hex_data = "#" + str(hex_data) + "0000"
            return str_hex_data

        for japan_data in japan_corona_data[1:-1]:
            prefectures = japan_data[0]
            code_num = prefectures_directory[prefectures]
            code_id = "pref_"+str(code_num)
            patient_num = int(japan_data[1])
            hospitalized_num = int(japan_data[2])
            end_hospitalized_num = int(japan_data[3])
            dead_num = int(japan_data[4])
            hex_data = hex_color_change(patient_num)
            jd = {
                "prefectures": prefectures,
                "code_num:": code_num,
                "code_id": code_id,
                "patient_num": patient_num,
                "hospitalized_num": hospitalized_num,
                "end_hospitalized_num": end_hospitalized_num,
                "dead_num": dead_num,
                "hex_data": hex_data
            }
            japan_corona_data_true.append(jd)

        return japan_corona_data_true

#確認用
#jcd = get_colona_data()
#print(jcd)

出力を確認する場合は、一番下のコメントアウトを消してください。

マップを表示する前に、
このコードを通して、スクレイピングして情報をmap.htmlに渡します。

それに伴い,main.pyも編集

main.py
from flask import *
from scraping_corona import get_colona_data#追加

app = Flask(__name__)
server_mode = "local"

@app.route("/", methods=["GET", "POST"])
def corona_page():
    japan_corona_data = get_colona_data()#追加
    return render_template("map.html", japan_corona_data=japan_corona_data)

if __name__ == "__main__":
    if server_mode == "local":
        port = 5000
        app.run(debug=True, port=port)

次に、HTMLを記述していきます。
HTMLにデータを格納→Javascriptに受け渡しをします。

map.html
<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8" />
        <title>日本MAP</title>

        <!-- JQueryとJapanMapの指定 -->
        <script src="../static/js/jquery.3.4.1.min.js"></script>
        <script src="../static/js/jquery.japan-map.min.js"></script>

        <!-- 自分で記述していくJavascriptファイルの指定 -->
        <script src="../static/js/myscript.js"></script>

        <!-- 自分で記述していくCSSファイルの指定 -->
        <link href="../static/css/mystyle.css" rel="stylesheet"/>
    </head>
    <body>
        <!-- MAPを記述していくコンテナ -->
        <div id="map-container"></div>

        <!-- データを格納エリア -->
        <div id="japan_corona_data">
            {% for data in japan_corona_data %}
            <div class="corona-data" id="{{ data.code_id }}" data-prefectures="{{ data.prefectures }}" data-code_num="{{ data.code_num }}" data-patient_num="{{ data.patient_num }}" data-hex_data="{{ data.hex_data }}"></div>
            {% endfor %}
        </div>
    </body>
</html>

次は、Javascriptでの受け取り、MAPに反映させます。

myscript.js
$(function(){
    let areas_data_list = [];
    for (let index=0; index < 47; index++){
        const select_code = index+1;
        const select_id = "#pref_"+select_code.toString();
        let areas_data;
        if ($(select_id).length){
            areas_data = {
                code: select_code,
                name: $(select_id).data('prefectures'),
                color: $(select_id).data('hex_data'),
                hoverColor: $(select_id).data('hex_data'),
                prefectures: [select_code],
            }
        }
        else{
            areas_data = {
                code: select_code,
                name: "",
                color: "#000000",
                hoverColor: "#000000",
                prefectures: [select_code],
            }
        }
        areas_data_list.push(areas_data);
    }
    const getWidth = $(window).width();//画面の大きさ取得
    $("#map-container").japanMap({
        width: getWidth,//画面の大きさそのまま描画する
        selection: "area",
        areas: areas_data_list,
        backgroundColor : "#222222",//背景を黒くする
        borderLineColor: "#f2fcff",
        borderLineWidth : 0.25,
        lineColor : "#a0a0a0",
        lineWidth: 1,
        drawsBoxLine: true,
        showsPrefectureName: false,//件名を非表示にする
        prefectureNameType: "short",
        movesIslands : true,
        fontSize : 11,
        fontShadowColor : "#fff",
        onSelect : function(data){
            alert(data.name);
        }
    });
});

再び、Runでmain.pyを実行し、http://127.0.0.1:5000/
こんな画像が表示されました。感染者数が多ければ多いほど赤くなります。
image.png

またまた少し休憩します。
次は、マウスを当てた状態で、グラフを表示するようにします。

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

【JavaScript】2次元のベクトルクラス【HTML5】

はじめに

自分用の2次元のベクトルクラスです。
ES6で書いています。静的クラスとして書いています。

ソース

// ベクトルクラス(ベクトルの計算に使用)
class Vector {
    // 足し算
    static add(v0, v1) {
        return {
            x: v0.x + v1.x,
            y: v0.y + v1.y,
        };
    }
    // 引き算
    static subtract(v0, v1) {
        return {
            x: v0.x - v1.x,
            y: v0.y - v1.y,
        };
    }
    // ベクトルの長さを返す
    static length(v) {
        return Math.sqrt(v.x * v.x + v.y * v.y);
    }
    // 単位ベクトルを返す(非破壊的)
    static unit(v) {
        const len = Vector.length(v);
        return {
            x: v.x / len,
            y: v.y / len
        };
    }
    // 内積
    static innerProduct(v0, v1) {
        return v0.x * v1.x + v0.y + v1.y;
    }
}

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

JavaScriptで特定の要素が配列に含まれるか判定する

JavaScriptにin演算子があったので、SQLみたいに以下のように書けるかと思ったのですが、うまくいきませんでした?

const arr = ['a', 'b', 'c'];
console.log('a' in arr); // falseになってしまった

どうやらJavaScriptのin演算子は「要素」が含まれるかどうかのチェックではなく、「プロパティ」が含まれるかどうかをチェックするみたいです?

ですので、以下のような感じが正しい使い方
(arr.lengthは要素の数を返すプロパティ)

const arr = ['a', 'b', 'c'];
console.log('length' in arr); // trueになる

今回求めたい、特定の要素が含まれるかどうか、というのはindexOfもしくはincludesで判定できます?

const arr = ['a', 'b', 'c'];
console.log(arr.indexOf('a') !== -1); // trueになる
console.log(arr.includes('a')); // trueになる
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【JavaScript】3x3の行列クラス【HTML5】

はじめに

自分用の3x3の行列クラスです。
ES6で書いています。静的クラスとして書いています。

ソース

// 行列クラス(行列の計算に使用)
class Matrix {
    // m0は行列、m1は行列又はベクトル
    // 行列は大きさ9の1次元配列であること。 ex. [ 1, 0, 0, 0, 1, 0, 0, 0, 1 ]
    // ベクトルはxとyをプロパティに持つ連想配列であること。 ex. { x: 2, y: 4 }
    // 左からベクトルをかけることは想定していない
    static multiply(m0, m1) {
        if(m1.length && m1.length === 9) {// m1は行列
            return [
                m0[0] * m1[0] + m0[1] * m1[3] + m0[2] * m1[6],
                m0[0] * m1[1] + m0[1] * m1[4] + m0[2] * m1[7],
                m0[0] * m1[2] + m0[1] * m1[5] + m0[2] * m1[8],
                m0[3] * m1[0] + m0[4] * m1[3] + m0[5] * m1[6],
                m0[3] * m1[1] + m0[4] * m1[4] + m0[5] * m1[7],
                m0[3] * m1[2] + m0[4] * m1[5] + m0[5] * m1[8],
                m0[6] * m1[0] + m0[7] * m1[3] + m0[8] * m1[6],
                m0[6] * m1[1] + m0[7] * m1[4] + m0[8] * m1[7],
                m0[6] * m1[2] + m0[7] * m1[5] + m0[8] * m1[8],
            ];
        } else {// m1はベクトル
            return {
                x: m0[0] * m1.x + m0[1] * m1.y + m0[2],
                y: m0[3] * m1.x + m0[4] * m1.y + m0[5],                
            };
        }
    }
    // 単位行列
    static identify() {
        return [1, 0, 0, 0, 1, 0, 0, 0, 1];
    }
    // 平行移動行列
    static translate(x, y) {
        return [1, 0, x, 0, 1, y, 0, 0, 1];
    }
    // 拡大縮小行列
    static scale(x, y) {
        return [x, 0, 0, 0, y, 0, 0, 0, 1];
    }
    // 回転行列
    static rotate(theta) {
        const cos = Math.cos(theta),
            sin = Math.sin(theta);
        return [cos, -sin, 0, sin, cos, 0, 0, 0, 1];
    }
    // 逆行列を求める
    static inverse(m) {
        const det = Matrix.determinant(m),
            inv = [
                m[4] * m[8] - m[5] * m[7],    -(m[1] * m[8] - m[2] * m[7]),   m[1] * m[5] - m[2] * m[4],
                -(m[3] * m[8] - m[5] * m[6]), m[0] * m[8] - m[2] * m[6],      -(m[0] * m[5] - m[2] * m[3]),
                m[3] * m[7] - m[4] * m[6],    -(m[0] * m[7] - m[1] * m[6]),   m[0] * m[4] - m[1] * m[3]
            ];
        return inv.map(elm => elm / det);
    }
    // 行列式を求める
    static determinant(m) {
        return m[0] * m[4] * m[8] 
        + m[1] * m[5] * m[6] 
        + m[2] * m[3] * m[7]
        - m[2] * m[4] * m[6]
        - m[1] * m[3] * m[8]
        - m[0] * m[5] * m[7];
    }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Jestの比較メソッドについて

リファクタリングを行ってる時にJavaScriptのテストの比較周りの知識が曖昧だったのでまとめます。

調べるきっかけとなったコード

is.test.ts
it("Jest's test", () => {
    ....
    assert.deepStrictEqual(value, [{ id: 1, name: "aaaaa" }]);
});

JestでAssertionがエラーとなった。

is.test.ts
assert.deepStrictEqual(received, expected)

Expected value to deeply and strictly equal to:
  [{"id": 1, "name": "aaaaa"}]
Received:
  [{"id": 1, "name": "aaaaa"}]

Difference:

Compared values have no visual difference.

assert.deepStrictEqualは値の完全一致と思い込んでいたので、なんで失敗するのだろう?と少しはまりました。

Jestの比較

Jestにも同様の関数が存在するので試した結果が以下。

is.test.ts
class LaCroix {
    public flavor: any;
    constructor(flavor: any) {
        this.flavor = flavor;
    }
}
it("同一の確認", () => {
    // "レモン" === "レモン" -> 成功
    expect("レモン").toBe("レモン");

    // true === 1 -> 失敗
    expect(true).toBe(1);

    // 1 == true -> 成功
    expect(1).toBeTruthy();

    // LaCroix {
    //     "flavor": "レモン",
    // }
    // Object {
    //     "flavor": "レモン",
    // }
    // 値が同じなので成功
    expect(new LaCroix("レモン")).toEqual({flavor: "レモン"});

    // LaCroix {
    //     "flavor": 1,
    // }
    // Object {
    //     "flavor": "1",
    // }
    // 値が違うので失敗
    expect(new LaCroix(1)).toEqual({flavor: '1'});

    // LaCroix {
    //     "flavor": "レモン",
    // }
    // Object {
    //     "flavor": "レモン",
    // }
    // 値は同じだがオブジェクトの型が違うので失敗
    expect(new LaCroix("レモン")).toStrictEqual({flavor: "レモン"});

    // LaCroix {
    //     "flavor": "レモン",
    // }
    // LaCroix {
    //     "flavor": "レモン",
    // }
    // 値もオブジェクトの型も同じなので成功
    expect(new LaCroix("レモン")).toStrictEqual(new LaCroix("レモン"));

    // LaCroix {
    //     "flavor": "レモン",
    // }
    // Object {
    //     "flavor": "レモン",
    // }
    // 値は同じだがオブジェクトの型が違うので失敗
    assert.deepStrictEqual(new LaCroix("レモン"), {flavor: "レモン"});

    // LaCroix {
    //     "flavor": 1,
    // }
    // LaCroix {
    //     "flavor": "1",
    // }
    // 値もオブジェクトの型も同じなので成功
    assert.deepStrictEqual(new LaCroix("レモン"), new LaCroix("レモン"));
});

名前的に=== (厳格な等価性比較)toStrictEqualが同じものと思っており、プリミティブ値に使ってたことがあります。
toBeTruthyよりtoBe(true)のほうが厳密なテストですね。

まとめ

プリミティブ値の比較を行う場合は toBe を使う。
オブジェクトの値の比較を行う場合は toEqual を使う。
オブジェクトの値、型の比較を行う場合は toStrictEqual を使う。(assert.deepStrictEqualでも可

参考

等価性の比較と同一性 - JavaScript | MDN

Object.is() - JavaScript | MDN

Expect · Jest

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

GAとGTMを駆使してA/Bテストする方法

こんな方向け

  • 検証期間を同じにして、A/Bテストを行いたい
  • 対象となるGoogleアナリティクスのビューでUser ID を有効にしているため、Google オプティマイズが使えない

やれること

  • ボタンのA/Bテスト
  • ファーストビューのA/Bテスト

など

前提条件

  • Googleアナリティクスが導入済みであること
  • GTMが導入済みであること

やりかた

手順は4つです。
1. A/Bテストしたい部分のHTMLを、テストパターンの個数ぶん書く
2. ランダムで出しわける部分、HTMLを書き替える部分をjQueryで書く
3. 書いたjQueryをGTMに登録する
4. GTMでGoogleアナリティクスにイベントを送信する

ここでは、ボタンのA/Bテストを想定して、例を紹介します

以降は、下記ボタンがページにある想定で説明を進めます。

<div id="abcd-button">
    <a target="_blank" href="ページURL">
        <img src="ボタン画像パス.png">
    </a>
</div>

1.A/Bテストしたい部分のHTMLを、テストパターン数分だけ書く

下記2つを書き分けたHTMLを用意してください

  • idの値
  • ボタン画像

テストパターンAのHTML

<div>
    <a target="_blank" href="ページURL" id="abcd-button-abtest-a" class="abcd-button-abtest">
        <img src="Aパターンのボタン画像パス.png">
    </a>
</div>

テストパターンBのHTML

<div>
    <a target="_blank" href="ページURL" id="abcd-button-abtest-b" class="abcd-button-abtest">
        <img src="Bパターンのボタン画像パス.png">
    </a>
</div>

2.ランダムで出しわける部分、HTMLを書き替える部分をjQueryで書く

<script>
    $(function() {
        //手順1で作成したテストパターンのHTMLを変数に代入します
        var htmlA = '';
        var htmlB = '';
        //テストパターンのHTMLを代入した変数を配列にします
        var testList = [htmlA,htmlB]
        //テストパターンの中からランダムで1つ選びます
        var quantity = testList.length;
        while (quantity) {
                var j = Math.floor( Math.random() * quantity );
                var t = testList[--quantity];
                testList[quantity] = testList[j];
                testList[j] = t;
            }
        //spliceの値は右の値をパターン数から1個引いた値を入れてください
        //今回は2パターンのテストなので、2-1=1となり、1になります
        testList.splice(1,1);
        var abtest = testList.join('');
        //オリジナルのボタンのID名を値に入れます
        var $content = $('#abcd-button');
        //ランダムで1つ選んだものを、オリジナルと差し替えます
        $content.replaceWith(abtest);
    });
</script>

3.書いたjQueryをGTMに登録する

下記手順で、GTMに新規タグを登録します。

  1. タグ新規作成
  2. タグタイプで「カスタムHTML」を選択
  3. HTMLの入力欄に、手順2で作成したスクリプトを貼り付ける
  4. トリガーを新規作成
  5. トリガータイプで「ページビュー」を選択
  6. トリガーの発生場所で「一部のページビュー」を選択
  7. 「Page URL」「等しい」を選択し、値はボタンがあるページのURLを入力
  8. トリガーを保存する
  9. タグを保存する

4.GTMでGoogleアナリティクスにイベントを送信する

下記手順で、GTMに新規タグを登録します。

  1. タグ新規作成
  2. タグタイプで「Google アナリティクス: ユニバーサル アナリティクス」を選択
  3. トラッキング タイプで「イベント」を選択
  4. カテゴリ、アクション、ラベルを任意で入力
  5. 値で「Click ID」を選択
  6. 詳細設定>タグの順位付け>●●が発効する前にタグを配信にチェックを入れる
  7. 手順3で作成したタグを選択
  8. トリガーを新規作成
  9. トリガータイプでクリック>「リンクのみ」を選択(「すべての要素」でも良い)
  10. 「Click Classes」「等しい」を選択し、値は手順1でリンク要素に記述したクラス名を入力(例ではabcd-button-abtest)
  11. トリガーを保存する
  12. タグを保存する

以上で設定終了です。
Googleアナリティクス>行動>イベント からクリック数を見て、A/Bどちらが良かったか、比較することができます。

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

jestでモックしてみたメモ

レガシーコードのテストをjestに移行していた時に、
jestだとどうやってモックしたり、追跡したりするんだ?となったのでその時のメモ

最初に

相当アホなところでハマったのだが、jest はnamespaceをそのまま読み込むため以下のようの読み込まないといけない。

import 'jest' // OK

import jest from "jest" // NG

jest.xxxx()

特定の関数をモックする

関数にもろもろについてモックの仕方です。

テスト用のモック関数の作成

const mockFunc = jest.fn().mockImplementation(() => return "mock func");
console.log(mockFunc()); // mock func

関数自体のモックは jest.fn()に集約されており、単純に何か値を返すのみの関数は mockImplemantionの引数で定義します。
また、mock関数の引数や、どういった値を返したかの検証も行えます。

関数の動作の検証

引数の検証

実際にモックした関数の引数を見たい場合はこんな感じでmock.calls を使います

const mockFunc = jest.fn().mockImplementation((message: string) => {
    return message;
});
mockFunc('test1');
mockFunc('test2');
console.log(mockFunc.mock.calls); // [ ['test1'], ['test2']] 

戻り値の検証

戻り値の場合はmock.resultsを使います。

const mockFunc = jest.fn().mockImplementation((message: string) => {
    return message;
});
mockFunc('test1');
mockFunc('test2');
console.log(mockFunc.mock.results);
/*
[
  {
    type: 'return',
    value: 'test1',
  },
  {
    type: 'return',
    value: 'test2',
  },
];

*/

value は実際に返した値で、
typeはその値が正常リターンか、エラーかを判定します。

正常の場合は return
エラーがスローされた場合は throw
となります。

また、 imcomplete というケースがありますが、これはよくわからないので省略します。

後処理

テストの後処理は2種類あり、 関数のモック自体を抹消する場合と、関数の引数などの追跡結果を抹消する場合があります。

const mockFunc = jest.fn().mockImplementation((message: string) => {
    return message;
});

mockFunc.mockClear() // どういった引数を受取、どういった値を返したかの計測結果をリセット
mockFunc.mockReset() // 実際に実装した関数や戻り値の設定をリセット

特定のオブジェクトに生えている関数を追跡、モックする

追跡対象を指定

const spy = jest.spyOn(targetObject , "do"); // オブジェクトの指定とそこから生えている関数を指定

// 関数実行処理

expect(spy).toHaveBeenCalled(); // 関数が実行されたかどうかを検証

指定した関数をモック関数にする

const spy = jest.spyOn(targetObject , "do")
    .mockImplemention(() => "spy");

↑のようにすると、指定したオブジェクトの関数は、mockImplementionの中で定義した関数に差し替えられます。
なお、mockImplementionの中には先程解説した jest.fnを渡すこともでき、その場合は引数などの検証も行えると思います(未検証)

オマケ 現在時刻のモック

特定の時刻でポイントが失効するかどうかをチェックしたい場合に現在時刻をモックする必要があったのでその時のことも書いておきます。

重要なことは、日付を扱うパッケージオブジェクト(moment, dayjsなど)はjavascriptのDateオブジェクトに依存しているため、Dateオブジェクトをモックすれば現在時刻系は騙せます。

なので、

  • jest で Dateオブジェクトをモックする
  • mockDate を使う
  • 自分でDateオブジェクトをモックする(レガシーコードはこれをやってた)

個人的に、mockDateを使うのが良いかなと思います。使いやすいしリストアなどもやりやすい他、
ドキュメントも充実しているので、情報共有もしやすいです。

最後に

時刻関連のモックをしらべるのに一番時間がかかりました。
Dateオブジェクトをいじればいいみたいな結果は出てくるのですが、なぜそれが必要なのかという理由が納得できず、
momentの初期化処理のコードを見に行ってDateオブジェクトに依存しているとわかりました。

それでは、良いjestライフを

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

Javascript基本集(2)~配列~

Javascript基本集(配列)

自分の学習用です

配列宣言

配列の宣言には2種類の方法がある。

① 配列コンストラクタ「Array」を使う方法
 ~ 「new」を使ってArray()の引数へ格納したい文字列を設定する ~

② 配列リテラル「[ ]」を使う方法
 ~ []を使ってそのまま文字列を設定する ~

//①配列コンストラクタの例
var array1 = new Array('いぬ', 'さる', 'きじ');

//②配列リテラルの例
var array2 = ['いぬ', 'さる', 'きじ'];

console.log( array1 );
console.log( array2 );

実行結果

["いぬ", "さる", "きじ"]

["いぬ", "さる", "きじ"]

  
  
コントラクタとリテラルでの相違点

var array1 = new Array(5);
var array2 = [5];

console.log( array1 );
console.log( array2 );

実行結果

1. [empty × 5] //コントラクタ
2. 
3. [5] //リテラル

配列コンストラクタで「new Array(5)」と記述すると「5つ分の配列要素を用意する」という意味になる。
配列リテラルで「[5]」と記述すると「5という数値を配列に格納する」という意味になる。

JavaScriptで配列を宣言(作成)する時は、特に理由がない限り「配列リテラル」を使うように

配列の初期化

var array = [];

console.log( array );

実行結果

[]

  

『配列』操作

例題

var a = ["aa","bb"];
var b = ["EE","FF"];
var c = [1,5,7,3];

よく使う命令

意味 使い方 説明
indexOf() 配列の要素を検索 a.indexOf(1); 2番目の要素が表示される
length 配列の要素の個数 x = a.length; 変数『x』に『2』が入る
push(array) 配列を末尾に追加 a.push(b); 変数『a』が『"aa","bb","EE","FF"』になる
push(値) 値を末尾に追加 a.push("cc"); 変数『a』が『"aa","bb","cc"』になる
unshift(array) 配列を先頭に挿入 a.unshift(b); 変数『a』が『"EE","FF","aa","bb"』になる
unshift(値) 値を先頭に挿入 a.unshift("cc"); 変数『a』が『"cc","aa","bb"』になる
pop() 末尾の要素を削除 a.pop() 変数『a』が『"aa"』になる
shift() 先頭の要素を削除 a.shift() 変数『a』が『"bb"』になる
concat(array) 連結した配列 x = a.concat(b); 変数『x』に『"aa","bb","EE","FF"』が入る
sort() 小さい順に並び替える c.sort(); 変数『c』が『1,3,5,7』になる
reverse() 逆向きに並び替える c.reverse(); 変数『c』が『3,7,5,1』になる
join("文字列") 配列を文字列で連結 x = a.join("/"); 変数『x』に『aa/bb』が入る
toString() 配列を文字列にする x = a.toString(); 変数『x』に『aa,bb』が入る
map 配列の要素一つ一つにコールバック関数を実行する c.map(function(value){return value * 2;}); 『2,10,14,6』となる
filter 対象となるデータに特定の条件を与えて、それに該当するデータだけを抽出 c.filter(function(value){return value > 6;}) 『7』が抽出される

mapについては、3つの引数を取得することができる

例えば、

配列データ.map( function( value, index, array ) {

});

・「value」は、配列の値
・「index」は、配列のインデックス番号
・「array」は、現在処理している配列

引数の「index」を使って、偶数の「Index番号」の時だけ値を2倍にするパターン

var items = [1,2,3,4,5,6,7,8,9];

var result = items.map( function( value, index, array ) {

    //「index番号」が偶数の時だけ2倍にする
    if( index % 2 !== 0 ) {
        return value * 2;
    }
    else {
        return value;
    }
});

console.log( result );

実行結果

[1, 4, 3, 8, 5, 12, 7, 16, 9]

「map」は元の配列データに一切変更を加えない特徴がありますが、3つ目の引数「array」を利用すれば、元のデータを変更することも可能

var items = [1,2,3,4,5,6,7,8,9];

items.map( function( value, index, array ) {

    //「array」と「index」を利用して、元の配列データを変更する
    array[index] = value * 2;

});

//元の配列データを出力する
console.log( items );

実行結果

[2, 4, 6, 8, 10, 12, 14, 16, 18]

第2引数のオブジェクトについて
第2引数に任意の「オブジェクト」を指定することが出来る

var array = [ 配列データ ];

//第2引数にオブジェクトを指定
array.map( コールバック関数, オブジェクト );

例題(オブジェクトから任意のキーワードを指定して「価格」を返す)

var foodList = {
  'オムライス': 450,
  '焼きそば': 500,
  'お好み焼き': 600,
  '焼き飯': 400
};

//任意のキーワードを指定する
var order = ['焼き飯', 'お好み焼き'];

var result = order.map( function( value, index, array ) {

//配列のキーワードを使ってオブジェクト内の値を取得する
    return this[value];

}, foodList );

console.log( result );

実行結果

[400, 600]

第2引数へ「foodList」と記述することで、コールバック関数内の「this」が「foodList」を参照できるようになる

今回はここまで:santa:

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

モーダル、SPのメニューが表示されたときだけ中の要素をスクロールさせる

モーダル、SPのメニューが表示されたときだけ中の要素をスクロールさせる

※覚書メモです

以前jQueryでモーダルやSPのメニューを表示したときに背景を固定する記事を載せたことがありました。
しかしそれから数年たって、そんなことをしなくても簡単に実装できちゃういいものを見つけることができました(教えてくださった同僚には感謝です)。

body-scroll-lock

▽github
https://github.com/willmcpo/body-scroll-lock

▽npmjs
https://www.npmjs.com/package/body-scroll-lock

指定した要素だけスクロールができるようになるというライブラリ。
androidもiosも難なくできました。
まさにそれがしたかったこと…!


以下使い方

今回もyarnを使います。
yarnじゃなくてもnpmでも大丈夫です(インストールするときのかき方が違うので気を付けてください)

yarn add body-scroll-lock

例)開くとき

import { disableBodyScroll, clearAllBodyScrollLocks } from 'body-scroll-lock'

// 例えばメニュー
const openBtn = document.getElementsByClassName('openBtn')[0]
const menuInner = document.getElementsByClassName('menu-inner')[0] // スクロールさせたい要素

openBtn.addEventListener('click' , () => {
    disableBodyScroll(menuInner)
})

分かりやすいように「開く」「閉じる」を分けましたが、上の例に続いて書いても大丈夫です。

例)閉じるとき

// 例えばメニュー
const closeBtn = document.getElementsByClassName('closeBtn')[0]

closeBtn.addEventListener('click' , () => {
    clearAllBodyScrollLocks()
})

この2つだけ。スクロールさせたい要素の指定を変更すればすぐに実装できます。
あんなに頭を悩ませていましたが、本当に簡単なので早く知りたかったです。。

以上

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

(箱だけドラフト)【最短・脳死】 画像認識AI LINE BOTの作り方

■目次
•はじめに
•Line Botアカウントを作ろう
•使いたAIを見つけよう
•Javascriptコード(フュージョン!!!)
•Line botへの接続
•(参考)AIの作り方

○はじめに
さて、できるだけ簡単に画像認識AI Botを作りましょう!
尚、AIと言ってもAIメーカーですので悪しからず。

①Line botアカウントを作ろう!
@n0bisukeさん記事
1時間でLINE BOTを作るハンズオン (資料+レポート) in Node学園祭2017 #nodefest - Qiita
-LINE developersのサイトにアクセスし、ログインしましょう。
-Messaging APIのチャンネルを作りましょう!
-Botと友達になろう(QRコード)

②AIメーカーで画像認識AIを見つけよう。
AIメーカーにアクセスしてLine BotにしたいAIを見つけましょう!
-見つけたAIから①id(3~4桁)②APIキー③AIに登録されているラベルをメモ

③Javascriptコード
鋭意作成中(すみません)

④Line botへ接続しよう
■再び@n0bisukeさん記事
1時間でLINE BOTを作るハンズオン (資料+レポート) in Node学園祭2017 #nodefest - Qiitaへ
-npm/3000
-web hookを更新

⑤実演
作成中

(余談)AI botの作り方
タオアリス作ってみた=土屋太鳳と広瀬アリス見分けbot

■終わりに
間に合わなくてすみません。この記事は捨てずに完成させます。
プロトアウト生の宿題を見て他のAPIを勉強します。

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