20200806のJavaScriptに関する記事は26件です。

ブラウザ上にカメラプレビューを表示する

概要

  • 以下のようにブラウザ上にカメラデバイスを利用してプレビューを表示します

カメラプレビューサンプル

使うもの

  • navigator.mediaDevices.getUserMedia
  • カメラデバイス(Facetime HDカメラ、iPhoneバックカメラ、フロントカメラ等)
  • ブラウザ(Chrome)
  • videoタグ
  • HTTPSでアクセスできる環境(これ重要)
    • ※ iPhone, Androidではssl環境下でないとカメラデバイスを利用できなくなっています
    • スマホで見る場合は必ずHTTPSで確認してください
    • APIリファレンスサイト上でもSecure context requiredの対象になっています

流れ

  1. まずコード
  2. 解説
  3. まとめ

まずコード

全貌なので多いです

<!doctype html>
<html lang="ja">
  <head>
    <!-- Required meta tags -->
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

    <!-- Bootstrap CSS -->
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css" integrity="sha384-9aIt2nRpC12Uk9gS9baDl411NQApFmC26EwAOH8WgZl5MYYxFfc+NcPb1dKGj7Sk" crossorigin="anonymous">

    <title>Camera Preview!</title>
  </head>
  <body>
    <nav class="navbar navbar-expand-lg navbar-light bg-light">
      <a class="navbar-brand" href="#">Camera Preview</a>
    </nav>

    <div class="container">
      <div class="row">
        <div class="col-lg-12">
          <h1>Hello Camera!</h1>
        </div>
      </div>
      <div class="row">
        <div class="col-lg-12">
          <!-- このvideoタグにカメラの映像を差し込みます -->
          <video class="w-100" src="" autoplay playsinline allowfullscreen></video>
        </div>
      </div>
    </div>

    <script type="text/javascript">
      const constraints = { audio: false, video: {
        // 任意の値を入れます。記述方法は多々あるので詳しくはドキュメントを参照してください
        // Facetime HDカメラはHD画質までなので、1270に設定しています
        // iPhoneであれば4032まで可能です
        width: 1270,
        // iPhoneなどの場合にenvironmentだとバックカメラ、userだとフロントカメラになります
        facingMode: "environment"
      }}

      navigator.mediaDevices.getUserMedia(constraints)
        .then(function(stream) {
          const video = document.querySelector('video');
          video.srcObject = stream
          video.onloadedmetadata = function(e) {
            video.play();
          };
        })
        .catch(function(err) {
          console.log(err.name + ": " + err.message);
        });
      </script>
  </body>

</html>

2. 解説

html側

かなり簡単シンプルです。

<!-- このvideoタグにカメラの映像を差し込みます -->
<video class="w-100" src="" autoplay playsinline allowfullscreen></video>

id属性をつけたほうが固いですね。
autoplayやplaysinlineなどのオプションはお好みで。
このvideoタグを元にさらにcanvasに書き出してopenCVなどで画像加工もできます。

javascript側

javascriptはたったこれだけの記述でカメラのプレビューが取得できます。

<script type="text/javascript">
  const constraints = { audio: false, video: {
    // 任意の値を入れます。記述方法は多々あるので詳しくはドキュメントを参照してください
    // Facetime HDカメラはHD画質までなので、1270に設定しています
    // iPhoneであれば4032まで可能です
    width: 1270,
    // iPhoneなどの場合にenvironmentだとバックカメラ、userだとフロントカメラになります
    facingMode: "environment"
  }}

  navigator.mediaDevices.getUserMedia(constraints)
    .then(function(stream) {
      const video = document.querySelector('video');
      video.srcObject = stream
      video.onloadedmetadata = function(e) {
        video.play();
      };
    })
    .catch(function(err) {
      console.log(err.name + ": " + err.message);
    });
</script>

最近は色々なブラウザでgetUserMediaがサポートされています。
以前は色々なベンダープレフィックスや実装方法で対応していました。IE以外の最新バージョンであればPC, スマホのほとんどのブラウザで利用可能です。
navigator.getUserMedia()は非推奨メソッドになっているので注意。

まとめ

たったこれだけで簡単にプレビューが作成できると思います。
これを元にcanvasに書き出して写真をとっても良いですし、AudioとともにWebRTCに使っても良いかと思います。
ブラウザ上で使える便利な機能の基本なので抑えておいて損はないと思います。

素敵なブラウザライフを!

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

JavaScriptの基本文法

JavaScriptの基本文法について書いていきます。
rubyと比較しながら勉強していくといいと思います!!

・window.alert()
 ブラウザでアラートを表示させるメソッド。
 例)window.alert('こんにちは');

・console.log()
 ブラウザのコンソールにテキストを表示させるメソッド。
 デバックとしても使用。
 例)console.log('こんにちは');

・let, const
 変数宣言の際に使用する。
 letは、後で書き換えることができる変数宣言であり、
 constは、後で書き換えることができない変数宣言ある。
 例) let name = 'yamada';
   console.log(name + 'さん、こんにちは今日');

・配列 [ ]
 例)let list = ['ruby','javascript','html'];

 ①配列の要素の取得(今回はjavascript)
 console.log(list[1]);

 ②配列の要素数の取得
 console.log(list.length);
 この場合コンソールに3と表示される。

 ③要素の追加
 上記の配列に
 list.push('css');
 今回は4番目の要素としてcssが追加される。

 ④要素の削除
 先頭の要素を削除する場合shiftメソッドを使用する。
 最後の要素を削除する場合popメソッドを使用する。

・オブジェクト {}
例)let obj = { name: 'yamada', age: 25, address: 'tokyo' };
  console.log(obj);

・条件式
 if (条件式1) {
 // 条件式1がtrueのときの処理
 } else if (条件式2) {
 // 条件式1がfalseで条件式2がtrueのときの処理
 } else {
 // 条件式1も条件式2もfalseのときの処理
 }

※条件式は()でくくること
※条件式の後に続く波括弧{}内の処理が実行されること
※複数条件を指定する場合は、elseのあとに続けてif文を記述すること

・繰り返し処理
 for (let i = 0; i < 繰り返す回数; i = i + 1) {
 // 繰り返す処理の内容
 }

・関数定義
 関数宣言と関数式の二種類ある。
 例)// 関数宣言
 function hello(){
 console.log('hello');
 }

// 関数式(無名関数)
 let hello = function(){
 console.log('hello');
 }

javascriptの基礎中の基礎ですが定着しきるまで復習したいと思います。
次回はもっと発展的な内容を書いていきたいと思います。

 

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

JavaScriptとRust(WebAssembly)でグラフの深さ優先探索のベンチマーク

はじめに

前々からRust + WebAssemblyでネットワーク可視化のライブラリを作っていましたが、使い勝手を良くするためのFFI(Foreign Function Interface)、つまりJavaScript側とRust側のどちらでデータを持つのか、そして他方にどのようなインタフェースを提供するのかの設計には悩まされていました。そこで今回は、グラフ処理の基本の一つである深さ優先探索(DFS)でベンチマークをとり、性能面での比較を行いました。私のアプリケーションでは、RustとJavaScript双方でアルゴリズムを書くこともあるため、グラフデータ構造の隣接リストとDFSのそれぞれをJavaScriptとRustの両方で実装し、4通りの組み合わせを比較します。

実装

それぞれの実装の一部を記載します。全体のソースコードは GitHubのリポジトリ をご覧ください。

隣接リストのJavaScriptによる実装

今回のベンチマークに必要最低限の以下の4つのメソッドを実装します。

  • addNode(u) :頂点 $u$ を追加する
  • addEdge(u, v) :辺 $(u, v)$ を追加する
  • neighbors(u) :頂点 $u$ の隣接頂点を返す
  • nodeCount() :グラフの頂点数を返す

実プロジェクトで使用していたJavaScript実装の隣接リストから必要な機能のみを取り出し以下のような実装にしました。

export class GraphJs {
  constructor() {
    this.nodes = new Map();
  }

  addNode(u, obj = {}) {
    this.nodes.set(u, {
      neighbors: new Map(),
      data: obj,
    });
    return this;
  }

  addEdge(u, v, obj = {}) {
    this.nodes.get(u).neighbors.set(v, obj);
    this.nodes.get(v).neighbors.set(u, obj);
    return this;
  }

  neighbors(u) {
    return this.nodes.get(u).neighbors.keys();
  }

  nodeCount() {
    return this.nodes.size;
  }
}

頂点や辺の削除を考慮しなければ配列の配列などにしてしまえばより高速化ができますが、ある程度実用的なデータ構造を前提として Map を使用しました。

隣接リストのRustによる実装

隣接リスト自体はpetgraphを使用します。JavaScript側からアクセスするために以下のようにWebAssemblyのインタフェースを実装します。JavaScript側へ提供する関数はJavaScriptによる実装と同様です。

#[wasm_bindgen]
impl GraphRust {
    #[wasm_bindgen(constructor)]
    pub fn new() -> GraphRust {
        GraphRust {
            graph: GraphType::with_capacity(0, 0),
        }
    }

    #[wasm_bindgen(js_name = addNode)]
    pub fn add_node(&mut self, value: JsValue) -> usize {
        let value = if value.is_null() || value.is_undefined() {
            Object::new().into()
        } else {
            value
        };
        self.graph.add_node(value.into()).index()
    }

    #[wasm_bindgen(js_name = addEdge)]
    pub fn add_edge(&mut self, u: usize, v: usize, value: JsValue) -> usize {
        let value = if value.is_null() || value.is_undefined() {
            Object::new().into()
        } else {
            value
        };
        let u = node_index(u);
        let v = node_index(v);
        self.graph.add_edge(u, v, value.into()).index()
    }

    #[wasm_bindgen(js_name = neighbors)]
    pub fn neighbors(&self, a: usize) -> Array {
        self.graph
            .neighbors(node_index(a))
            .map(|u| JsValue::from_f64(u.index() as f64))
            .collect::<Array>()
    }

    #[wasm_bindgen(js_name = nodeCount)]
    pub fn node_count(&self) -> usize {
        self.graph.node_count()
    }
}

なお、petgraphの neighbors は隣接ノードのイテレータを返しますが、WebAssemblyのレイヤーでは Array を返すようにしました。これは、JavaScriptのイテレータに変換した場合、頻繁にRust側からJavaScript側への変換が生じるため、Array に変換して返した方が処理速度が速かったためです。また、Rust側からイテレータを返した場合に、手動でイテレータのメモリ解放が必要で使い勝手も良くありませんでした。

DFSのJavaScriptによる実装

隣接リストは、RustとJavaScriptの両方で同じインタフェースを持つため、JavaScriptによるDFSの実装は一つにまとめて以下のように実装しました。

const rec = (graph, u, depth) => {
  for (const v of graph.neighbors(u)) {
    if (depth[v] === 0) {
      depth[v] = depth[u] + 1;
      rec(graph, v, depth);
    }
  }
};

const dfsJs = (graph) => {
  const depth = new Array(graph.nodeCount());
  depth.fill(0);
  depth[0] = 1;
  return rec(graph, 0, depth);
};

DFSのRustによる実装

RustによるDFSの実装は、入力のグラフがJavaScriptの場合とRustの場合でそれぞれ別の関数を用意します。

以下はJavaScript実装の隣接リスト用です。

fn dfs_rec(graph: &GraphJs, u: usize, depth: &mut Vec<usize>) {
    for v in graph
        .neighbors(u)
        .into_iter()
        .map(|v| v.ok().unwrap().as_f64().unwrap() as usize)
    {
        if depth[v] == 0 {
            depth[v] = depth[u] + 1;
            dfs_rec(graph, v, depth);
        }
    }
}

fn dfs(graph: &GraphJs) -> Vec<usize> {
    let mut depth = vec![0; graph.node_count()];
    depth.insert(0, 1);
    dfs_rec(graph, 0, &mut depth);
    depth
}

#[wasm_bindgen(js_name = dfsRustWithJs)]
pub fn dfs_js(graph: GraphJs) -> Array {
    dfs(&graph)
        .into_iter()
        .map(|u| JsValue::from_f64(u as f64))
        .collect::<Array>()
}

続いて、以下はRust実装の隣接リスト用です。

fn dfs_rec(graph: &GraphType, u: NodeIndex, depth: &mut Vec<usize>) {
    for v in graph.neighbors(u) {
        if depth[v.index()] == 0 {
            depth[v.index()] = depth[u.index()] + 1;
            dfs_rec(graph, v, depth);
        }
    }
}

fn dfs(graph: &GraphType) -> Vec<usize> {
    let mut depth = vec![0; graph.node_count()];
    depth[0] = 1;
    dfs_rec(graph, node_index(0), &mut depth);
    depth
}

#[wasm_bindgen(js_name = dfsRustWithRust)]
pub fn dfs_rust(graph: &GraphRust) -> Array {
    dfs(&graph.graph)
        .into_iter()
        .map(|u| JsValue::from_f64(u as f64))
        .collect::<Array>()
}

traitで両方の実装をまとめられるとカッコ良いので試したのですが、neighbors のシグネチャを fn neighbors<'a>(&'a self, u: NodeIndex) -> Box<dyn Iterator<Item = NodeIndex> + 'a>; としたところ、Box<dyn trait> の影響か、JavaScriptとRust両方で処理速度が落ちたので断念しました。

グラフ生成

$n = |V|$ 頂点のグラフ $G = (V, E)$ の各頂点ペアについて、$p$ の確率で辺を生成するランダムグラフを使用します。いわゆるErdős–Rényiモデルです。

なお、DFSの計算量のオーダーは $O(|E|)$ で、辺数 $|E|$ の期待値は $p n(n - 1) / 2$ となります。

実行結果

$n$ を 100 から 1000 まで 100 毎に、$p=0.1$ として 10 個のグラフを生成してベンチマークを取りました。実行時間の測定には benchmark.js を使用しています。以下のページで実際に実行することができます。

https://likr-sandbox.github.io/dfs-bench/

横軸に辺数 $|E|$、縦軸にbenchmark.jsによって得られた平均実行時間をとったグラフを次に表します。

dfs.png

実装の種類を 言語1-言語2 の形式で表していて、言語1が隣接リストの実装、言語2がDFSの実装を表しています。両方ともRustによる実装が最も速く、そこから2倍程度の時間で両方ともJavaScriptによる実装が続きます。JavaScriptとRustを組み合わせた場合は、最速の場合からおよそ一桁遅くなる結果となりました。

実行環境は以下の通りです。

Mac mini (2018)
プロセッサ:3.2 GHz 6コア Intel Core i7
メモリ:16GB 2667 MHz DDR4

Google Chrome:バージョン: 84.0.4147.89

まとめ

今回のベンチマークの結果、調査した範囲ではグラフのサイズによらず、Rustによる隣接リストの実装の方がトータルの計算時間は短くなることが確認できました。私のユースケースでは、安心してRustによる実装で進められそうです。

その他、当たり前のことですが、言語の境界をまたいだデータのやり取りが高コストであることが確認できました。頻繁に境界をまたぐ場合は、WebAssemblyの恩恵が十分に得られない可能性があるので、データの受け渡し方法を検討する必要があります。

今回の単純なベンチマークでは、RustとJavaScriptの性能差は2倍程度となりました。単純なプログラムだとJavaScriptは 気持ち悪いぐらい 非常に速いことがあります。しかし、数千行にわたる複雑なアルゴリズムを、性能を保ちながらJavaScriptで実装するのは少々難しくなります。また、将来的にWebAssemblyでSIMDやThreadsの恩恵を受けられるようになることを考えると、今回の結果で十分なアドバンテージを示すことができたと思います。

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

Expressでの環境変数の設定

環境変数を使う意義

①コード内のテキストをプレーンテキストから環境変数にすることでセキュリティ向上
②環境を変数に格納することでカスタマイズしやすくなる

①セキュリティについて

APIキーなどの重要な情報をそのままコード内に書いてしまっていると、GitHubにアップした時に、丸見えになってしまう。

②カスタマイズ性について

例)MongooseをMongoDBに接続する時

const mongoose = require('mongoose')


mongoose.connect('mongodb=//localhost:27017/task-manager-api', {
  useNewUrlParser: true, 
  useUnifiedTopology: true,
  useFindAndModify: false
});


このままだと、「'mongodb=//localhost:27017/task-manager-api'」という、ローカル環境のMongoDBに接続し続けることになる

→ローカル以外の環境にデプロイしても、ローカルのMongoDBに接続したままになってしまう。

環境変数作成の準備

└── App
    ├── config           //環境を保存するフォルダ
    |      └── dev.env  //環境変数として認識されるファイル名     
    |

※gitにpushする時に、configファイルは.gitignoreしておく

・「dev.env」の設定

npmパッケージ「env-cmd」を利用して環境変数を利用できるようにする。

npm i env-cmd
 

でインストール。

・「package.json」の変更
"scripts"に追記。

"scripts": {
    "start": "node src/index.js",    //メインプログラムが書いているファイルへのパス
    "dev": "env-cmd -f ./config/dev.env nodemon src/index.js"  //環境を書いたファイルへのパスと、メインプログラムを動かす時のコード
  }, 

※これで、ターミナルに「npm run dev」と入力するとアプリをスタートさせることができるようになる。

環境変数の設定

・「dev.env」の設定

①Port番号
②MongoDBとの接続
③秘密にしたいAPIのキー

の3つを環境変数に設定

PORT=3010
MONGODB_URL=mongodb://127.0.0.1:27017/task-manager-api
JWT_SECRET=thisisaseacretformyapp
・コード側での設定

※環境変数の呼び出しは「process.env.(環境変数)」

①Port番号
元々「port = 3010」と記述していていた箇所を「port = process.env.PORT」へ変更

②MongoDBとの接続
元々「mongoose.connect('mongodb=//localhost:27017/task-manager-api', {...})」と記述していた箇所を「mongoose.connect(process.env.MONGODB_URL, {...})」へ変更

③秘密にしたいAPIのキー
元々「thisisaseacretformyapp」と記述していた箇所を「process.env.JWT_SECRET」へ変更

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

【Vue.js】vue-headの導入について

フロント開発をする中、head要素をvueで管理する方法として、
vue-headというライブラリを導入する機会があったので、備忘録としてまとめておく。

前提

vue-routerを導入している事。
※この記事ではvue-routerについての解説は行いません。

手順

導入自体はとても簡単。
まずはnpmコマンドでパッケージをインストール。

npm install vue-head --save

今回はmain.jsで読み込みます。

main.js
import Vue from 'vue'
import VueRouter from 'vue-router'
import VueHead from 'vue-head'

Vue.use(VueHead)
Vue.use(VueRouter)

あとは各ページのコンポーネント毎にタイトルとmetaタグ設定すればOK。

home.vue
export default {
  head: {
    title() {
      return {
        inner: 'App',
        separator: '|',
        complement: 'page',
      }
    },
    meta: [
      { name: 'description', content: 'My description', id: 'desc' }
    ]
  }
    ...
  }
}

router.jsで表示するページ毎のタイトルを定義しておいて、
複数ページで共有するコンポーネントがあれば、簡単にタイトルやmetaの切り替えが出来ます。

更に共通の内容があれば、Mixinする事でよりシンプルに書く事が出来ます。

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

Mongooseでタイムスタンプを利用する

タイムスタンプとは

ある値が作られた時間を記録するもの。
サーバー側からもクライアント側からも使用することができる。

Mongooseにタイムスタンプを設定

Mongooseの設定から、オプションでタイムスタンプを自動作成させることができる。
<公式ドキュメント> https://mongoosejs.com/docs/guide.html#timestamps

スクリーンショット 2020-08-06 21.16.41.png

const mongoose = require('mongoose')

const userSchema = new mongoose.Schema({
  {..},
  {timestamps: true}

})


新しくuserShemaを用いてuserを作成するたびに、

「createdDt:"...(時刻)"」と「updatedAt:"...(時刻)"」

が作成される。

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

JQueryでのDOM操作まとめ ~Javascriptでテストを自動化したい~

僕が最初にやったプログラミングは求人サイトに載っている求人の自給を自動取得だったのですが、この時の経験が今やっている開発でも生きているので記事にしてみました。
バイトの時給なんて大体同じなのでデータを集めてもあまり意味はなかったのですが、その代わりにWebのデータを集めるためにDOM要素について調べたおかげでかなりWebのことを知ることができました。

その時読んだ記事:Python Webスクレイピング テクニック集「取得できない値は無い」JavaScript対応@追記あり6/12

DOMのことが知れればWeb開発・RPA・スクレイピング・テスト自動化と色々なことができます。

DOM操作のやりかた~的な良記事は他にたくさんあるため、ここでは入力フォームの操作に焦点を当てて記事を書いていこうと思います。

テキストボックス

<input type="text" name="hoge" value="入力値" data-target="fuga" id="input-1" class="target">

画面上の表示はこんな感じ。
input-pre.png

このテキストボックスへの入力をプログラムでやりたい時は以下のようにする。

image.png
↑ Javascriptなのでブラウザで動かせる

$('input[name="hoge"]').val("変更後")

上のコードは、$('input[name$="hoge"]')でName属性で要素を指定してに入力フォームのオブジェクトを取得し、.val("変更後")でそのオブジェクトのValueに文字列を入れています。

DOMオブジェクト(入力フォーム)の取得方法は属性指定以外にも色々あります。
↓これが全部
CSSの基本~セレクタの種類~

セレクトボックス

以下のようなセレクトボックスがあるとします。

<select class="form-control" name="WorkingShift">
 <option value=""></option>
 <option value="0">朝登板</option>
 <option value="1">昼登板</option>
 <option value="2">夜登板</option>
</select>
$('select[name$="WorkingShift"]').val(2)

と打ち込むと。入力フォームの値が「夜登板」に変わるはずです。

チェックボックス

<input class=​"form-control" name=​"SampleCheckbox" type=​"checkbox" value=​"さんぷるでーた">

チェックボックス・ラジオボタンはDOMの構造がテキストボックスと違うためコードも少し違う。
disabled, hidden属性の追加も同じやり方でできる。

$('input[name$="SampleCheckbox"]').val("さんぷるでーた").prop('checked', true)

以上Javascriptを使ったデータの入力方法でした。

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

文字列のURLからドメイン名を取得する

https://example.com/piyo/?chu=peroこういう文字列からドメイン名を取得したいとき

車輪の再発明にならないようにと思ったけどこれでいいのかな?

function getDomainFromUrl(url) {
    const dummy = document.createElement('a');
    dummy.setAttribute('href', url);

    return dummy.protocol + '//' + dummy.hostname;
}

const url = 'https://example.com/?chu=pero';
const domain = getDomainFromUrl(url);

console.log(domain); // https://example.com

追記

@il9437 さんからコメントでもっといい方法を教えていただきました!

メソッド作る必要なかった

const url = 'https://example.com/?chu=pero';
const domain = new URL(url).origin;

console.log(domain); // https://example.com

表示中のページURLから

console.log(location.origin); // https://example.com
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

文字列のURLからドメイン名とか取得するとき

https://example.com/piyo/?chu=peroこういう文字列からドメイン名を取得したいとき

これでいいのか?
よくなかった模様

function getDomainFromUrl(url) {
    const dummy = document.createElement('a')
    dummy.setAttribute('href', url)

    return dummy.protocol + '//' + dummy.hostname
}

const url = 'https://example.com/?chu=pero'
const domain = getDomainFromUrl(url)

console.log(domain) // https://example.com

追記

@il9437 さんからコメントでもっといい方法を教えていただきました!

メソッド作る必要なかった

const url = 'https://example.com/?chu=pero'
const domain = new URL(url).origin

console.log(domain) // https://example.com

表示中のページURLからの場合

console.log(location.origin) // https://example.com

ちなみに

const url = new URL('https://example.com/piyo/puni.js?chu=pero')

console.log(url.host)     // "example.com"
console.log(url.hostname) // "example.com"
console.log(url.href)     // "https://example.com/piyo/puni.js?chu=pero"
console.log(url.origin)   // "https://example.com"
console.log(url.pathname) // "/piyo/puni.js"
console.log(url.protocol) // "https:"
console.log(url.search)   // "?chu=pero"

// もうパラメータを取得するためにこんなことしなくていいんだね?
const url = 'https://example.com/piyo/puni.js?chu=pero'
console.log(url.substring(url.indexOf('?'))) // "?chu=pero"

となるようです
便利!!

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

VeeValidate に電話番号のバリデーションルールを追加する

先週投稿した VeeValidate で非同期的なバリデーションを行う記事に続いて、今回も VeeValidate ネタです。

VeeValidate には電話番号のバリデーションルールはデフォルトで用意されていません。そのため google-libphonenumber を使ってカスタムルールを実装してみたので、そのメモです。

ちなみに google-libphonenumber は電話番号のパースやバリデーションのための国際対応ライブラリです。非常に便利なので是非使ってみてください!

TL;DR

  • google-libphonenumber を使えば国内・国外どちらでも(あるいは両方について)電話番号のバリデーションが実装できる。
  • 入力文字列のフィルタは watch() 関数を使えばリアクティブに実装できる。

環境

実装

以下では VeeValidate のモジュール import は省略しています。

カスタムルール

まずは VeeValidate のカスタムルールを extend() で追加します。

import { PhoneNumberUtil } from 'google-libphonenumber'

extend('phone', {
  message: 'The {_field_} field format is invalid',
  validate(value) {
    const util = PhoneNumberUtil.getInstance()
    try {
      const phoneNumber = util.parseAndKeepRawInput(value, 'JP')
      return util.isValidNumber(phoneNumber)
    } catch (err) {
      return false
    }
  }
})

パースやバリデーションの機能は基本的に PhoneNumberUtil から利用できます。
入力値が短すぎるなど、場合によっては parseAndKeepRawInput でうまくパースできずに例外がスローされるので、その場合は false となるように try-catch で囲んでいます。

ちなみに国コードを指定せずにバリデーションするメソッドは(探した限りでは)用意されていないようです。
そのため日本の電話番号フォーマットにヒットしなかった場合は国外のフォーマットもチェックしたいといったケースでは以下のように全ての国コードを取得してループ処理する必要がありそうです。

validate(value) {
  const util = PhoneNumberUtil.getInstance()
  const jpFirstRegions = ['JP'].concat(
    util.getSupportedRegions().filter(regionCode => regionCode !== 'JP')
  )
  const validRegionCode = jpFirstRegions.find((regionCode) => {
    try {
      const phoneNumber = util.parseAndKeepRawInput(value, regionCode)
      return util.isValidNumber(phoneNumber)
    } catch (err) {
      return false
    }
  })
  return validRegionCode !== undefined
}

コンポーネント

次にコンポーネントです。
これは単に ValidationProvider の rules に phone を指定するだけです。

<template>
  <ValidationProvider
    v-slot="{ errors }"
    ref="provider"
    name="Phone Number"
    rules="phone"
  >
    <input
      v-model="value"
      type="text"
    />
    <span>{{ errors[0] }}</span>
  </ValidationProvider>
</template>

<script lang="ts">
import { createComponent, ref } from '@vue/composition-api'

export default createComponent({
  setup() {
    const value = ref<string | null>(null)
    return {
      value
    }
  }
})
</script>

ただこれだと数字以外の文字も入力できてしまうため、よりユーザーフレンドリーになるように数字のみ許可する場合は以下のようになります。

<script lang="ts">
import { createComponent, ref, watch } from '@vue/composition-api'

export default createComponent({
  setup() {
    const value = ref<string | null>(null)
    watch(() => {
      value.value = value.value.replace(/[0-9]/g, (s) => {
        // 全角から半角への変換
        return String.fromCharCode(s.charCodeAt(0) - 0xFEE0)
      }).replace(/[^0-9]/g, '')
    })
    return {
      value
    }
  }
</script>

watch() メソッドを用いて value の更新があった際に数字以外の文字をフィルターアウトするようにしており、ついでに全角数字は半角数字に変換しています1

ちなみに google-libphonenumber 自体は +1 202-456-1414 のような記号付きの電話番号フォーマットにも対応しているので、 + や - などを除外対象から外すように上記のルールを書き換えれば記号付きのケースにも対応できます。

input タグの type で制御すれば良いのでは?

input タグの type に numbertel を指定するアプローチだと幾つか問題があります。

まず number ですが、こちらはユーザーがコピペで +1 202-456-1414 のような記号を含む番号を入力した場合に value が空で評価されてしまいます!またデフォルトではスピンボタンが表示されますが、電話番号の数値の増減は不要であるため消去するのが結構手間です。

次に tel ですが、こちらは pattern を指定しても入力値をフィルターする機能はないため、許可していない文字も入力できてしまいます。

このような事情もあり、最終的に Vue 側で入力値をコントロールするアプローチに落ち着きました。


  1. この変換方法は YoheiM.NET さんの記事から拝借しました。 

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

JavaScript/node.jsでsleep

ちょっとしたことだけど、JavaScriptにはsleepがないので。

async () => {
    // 前の処理
    await new Promise(resolve => setTimeout(resolve, 1000));
    // 1秒待った後の処理...
}

JavaScriptに馴染みのないサーバーサイドの方向けに説明

setTimeout

setTimeout(コールバック, ミリ秒)でミリ秒後にコールバックを実行します。JSのタイマー処理の基本。

Promise

new Promise()でPromiseオブジェクトを取得します。Promiseオブジェクトは非同期処理が完了したら結果がもらえるやつです。

const 結果のPromise = new Promise(resolve => {
    // 任意の処理
    resolve(結果);
});

await

await Promiseオブジェクトで結果を取り出せます。async関数の中で使う必要があります。

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

ウェブサイト作成用備忘録・5号:1カラムWEBデザインの自作サンプルその1【コピペでプレビュー】

日々の学習のアウトプットの為、自主学習の際に工夫した内容を記録していきます。

今回は 自分が作成した1カラムWEBデザインのオリジナルサンプルを紹介します。

(※練習で作成したデザインなので、機能性は乏しいかもしれません…)

テーマ:javascript の動的制御で1カラムの背景ウインドウに複数の animation プロパティを適用し、複数の役割を与える。

記述サンプル

今回はHTMLファイルに全ての記述を行うので、コードをそのままコピペするだけで、実際の動作を確認出来るようにしました。

HTML

<!doctype html>
<html>
  <head>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
  </head>
  <style>

    * {
      margin: 0;
    }

    #screen {
      background-color: rgba(15,15,15,0.9);
      border-radius: 20px;
      min-height: 95%;
      max-width: 750px;
      position: fixed;
      top: 50%;
      right: 50%;
      z-index: 0;
      transform: translate(50%, -50%);
      -webkit-transform: translate(50%, -50%);
      animation: start 1s ease 1s 1 normal forwards running;
      -webkit-animation: start 1s ease 1s 1 normal forwards running;
    }

    @keyframes start {
      0% {
        width: 0;
        box-shadow: 0px 0px 0px 0px rgba(0,0,0,0.4);
      }
      100% {
        width: 90%;
        box-shadow: 25px 50px 20px 10px rgba(0,0,0,0.4);
      }
    }

    @-webkit-keyframes start {
      0% {
        width: 0;
        box-shadow: 0px 0px 0px 0px rgba(0,0,0,0.4);
      }
      100% {
        width: 90%;
        box-shadow: 25px 50px 20px 10px rgba(0,0,0,0.4);
      }
    }

    #hide_start {
      color: #fff;
      pointer-events: none;
      margin: auto;
      opacity: 0;
      width: 90%;
      max-width: 750px;
      position: relative;
      top: 40px;
      animation: show 1s 2s forwards;
      -webkit-animation: show 1s 2s forwards;
    }

    @keyframes show {
      100% {
        opacity: 1;
        pointer-events: unset;
      }
    }

    @-webkit-keyframes show {
      100% {
        opacity: 1;
        pointer-events: unset;
      }
    }

    #button_1 {
      cursor: pointer;
      position: fixed;
      right: calc(5% + 20px);
      z-index: 2;
      border-radius: 10px;
      transition: all 1s 0s ease;
      -webkit-transition: all 1s 0s ease;
    }

    #button_1.menu {
      right: calc(2% + 20px);
    }

    #screen.open {
      box-shadow: 25px 50px 20px 10px rgba(0,0,0,0.4);
      animation: menu_o 1s ease 0s 1 normal forwards running;
      -webkit-animation: menu_o 1s ease 0s 1 normal forwards running;
    }

    #screen.close {
      box-shadow: 25px 50px 20px 10px rgba(0,0,0,0.4);
      animation: menu_c 1s ease 0s 1 normal forwards running;
      -webkit-animation: menu_c 1s ease 0s 1 normal forwards running;
    }

    @keyframes menu_o {
      0% {
        width: 90%;
      }    
      100% {
        width: 60%;
        right: 20px;
        transform: translate(0, -50%);
        -webkit-transform: translate(0, -50%);
      }
    }

    @-webkit-keyframes menu_o {
      0% {
        width: 90%;
      }    
      100% {
        width: 60%;
        right: 20px;
        transform: translate(0, -50%);
        -webkit-transform: translate(0, -50%);
      }
    }

    @keyframes menu_c {
      0% {
        width: 60%;
        right: 20px;
        transform: translate(0%, -50%);
        -webkit-transform: translate(0%, -50%);
      }
      100% {
        width: 90%;
        right: 50%;
        transform: translate(50%, -50%);
        -webkit-transform: translate(50%, -50%);
      }
    }

    @-webkit-keyframes menu_c {
      0% {
        width: 60%;
        right: 20px;
        transform: translate(0%, -50%);
        -webkit-transform: translate(0%, -50%);
      }
      100% {
        width: 90%;
        right: 50%;
        transform: translate(50%, -50%);
        -webkit-transform: translate(50%, -50%);
      }
    }

    #menu {
      box-sizing: border-box;
      opacity: 1;
      padding-left: 1em;
      position: fixed;
      right: 20px;
      width: 60%;
      max-width: 750px;
      transition: all .5s;
      -webkit-transition: all .5s;
    }

    #menu.none {
      opacity: 0;
      pointer-events: none;
    }

    #content {
      box-sizing: border-box;
      padding: 0 1em;
      margin: auto;
      position: relative;
      transition: all .5s;
      -webkit-transition: all .5s;
    }

    #content.none {
      opacity: 0;
      pointer-events: none;
    }

  @media(min-width: 850px) {
  /* PC設定 */

    #screen {
      animation: start 1.5s ease 1s 1 normal forwards running;
      -webkit-animation: start 1.5s ease 1s 1 normal forwards running;    
    }

    #button_1 {
      right: calc(50% - 360px);
    } 

  }
  </style>
  <body id="" class="">
    <div id="screen" class=""></div>
    <div id="hide_start">
      <button type="button" id="button_1" class="">〇</button>
      <nav id="menu" class="none">
        <h2>MENU</h2>
      </nav>
      <main id="content">
        <h1>CONTENTS</h1>
      </main>
    </div>
  </body>
  <script type="text/javascript">
    jQuery(document).ready(function(){

      $("#button_1").click(function() {
        if($("#menu").hasClass("none")) {
          $("#screen").removeClass("close");
          $("#screen").addClass("open");
        } else {
          $("#screen").removeClass("open");
          $("#screen").addClass("close");
        }
        $("#button_1").toggleClass("menu");
        $("#menu").toggleClass("none");
        $("#content").toggleClass("none");
      });

    });
  </script>
</html>

解説

1・サイトコンテンツの背景となるウインドウ部分を空の div タグ #screen で記述。

2・javascript の動的制御で #screen にクラスを付け替える事で、transition や複数の animation プロパティを付け替え、メインコンテンツやサイドメニューの背景ウインドウに可変される。

3・後はサイト全体の表示が整うように色々書き加えていく…

文章としては以上になるのですが、実際に記述してみようとすると、最低限の情報量でも想像していた以上に実装に苦労しました。

実際の動作が気になる方は、テキストデータを丸ごとコピペして確かめてみましょう…!
(簡素な作りですが、感想、アドバイスなどあれば頂けると幸いです)

今回はこれで以上になります。

あくまで自分用の備忘録ですが、他の方の参考になれば幸いです。

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

勉強ノート:DOM操作

要素ノードの操作の手順

  1. Objectの取得
  2. Objectプロパティ値の変更

個別要素へのアクセス

  • document.getElementById(id): idに対応するオブジェクトを返す
  • document.getElementsByTagName(tag): p tagなど一致するオブジェクトのリストを返す
  • document.getElementsByName(name): name属性に一致するオブジェクトのリストを返す
  • document.getElementsByClassName(name): Class属性に一致するオブジェクトのリストを返す

セレクタ式を使ったアクセス

  • document.querySelector(selector): 1つ
  • document.querySelectorAll(selector):

相対パスでのアクセス(getxxx, queryxxxの後に使う)

  • obj.firstChild
  • obj.nextSibling
  • obj.firstElementChild
  • obj.nextElementSibling

Tagで修飾されたテキストへのアクセス

getxxxなどでオブジェクトを取得した後、以下の方法でアクセスできる。
- obj.innerHTML: 文書を埋め込むのに使う、テキストだけ返す
- obj.contentText: HTMLを埋め込むのに使う、タグを含んだ形で返す

Event-Driven

イベントハンドラを登録するには以下の方法がある。
1. タグ内の属性として宣言:
2. 要素オブジェクトのプロパティとして宣言: JS内でObjectを取得し、onclickなどのイベントプロパティに関数オブジェクトを設定する。複数のハンドラは紐付けられない。
3. addEventListenerメソッドを使う: elem.addEventListener(type, listener, capture)

ノードの作成、削除

  • createElement(element)
  • createAttribute(attr)
  • createTextNode(text)
  • elem.appendChild(node)

Ref

山田祥寛,JavaScript本格入門[改訂新版]

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

Vue.jsのライフサクルを丁寧に図解し直してみた

そもそもVue.jsのライフサイクルとは?

Vueインスタンスが生成されていから破棄されるまでの流れのこと。
基本的にはページが遷移するたびに破棄されデータなどが初期化され新たに生成される。

なぜいちいち破棄しなければならないのか?

ページごとに値が残っていては、Vueとして、サービスとして機能しないから。

Vue公式のライフサイクルの説明

Vueのライフサイクルは公式で以下のように図解してある。
Vue.js公式・ライフサイクルダイアグラム
FireShot Capture 003 - Vue インスタンス — Vue.js - jp.vuejs.org.png

だが英語だし、初見だと少し分かりにくかったので自分なりに補足などしてまとめてみた。

用語確認

初期化
最初の状態に戻すこと。もしくは最初の状態を整えること。
(https://wa3.i-3-i.info/word12961.html)

マウント
Vueの形式で書かれたコンポーネントなどの仮想表現を最終的なUI表現に出力するプロセス。

リアクティブデータ
リアクティブとは値が監視され、値の変更があった場合検知すること。
つまり、リアクティブデータとはVue.jsに値の変更などを逐一監視されているデータのこと。

自分なりに詳細に図解

Vue-10-2.jpg

Vue-11.jpg

Vue-12.jpg

Vue-13.jpg

Vue-14.jpg

Vue-15 2.jpg

参考

Vueのライフサイクル
Vue.js ライフサイクルフック概要

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

【node.js】 node.jsインストール 芋っていたけど、簡単だった件...

【ゴール】

node.jsを自身のPCインストール

【目次】

■node.jsとは

■インストール

■確認

■実際にファイルを作成,node起動

【環境】

■ Mac OS catalina
■ node v12.18.3
■ npm v6.14.6

【開始】

node.jsとは

■サーバーサイドで動くjavascriptのライブラリ
■動きが早い
■チャットや同時に多数の接続があっても耐える
■npmも同梱とお得。

インストール

下記の公式ページへ。
https://nodejs.org/ja/download/

*OSによって異なるので注意。
*確認事項ありますが、全て確認し、進めてください。

確認

実際にPCにインストールされたか確認

■node.jsのバージョンを確認

mac.terminal
$ node -v
v12.18.3

■npmのバージョンを確認
*npmとは「node package manager」の略。nodeのマネージャーさんですね。

mac.terminal
$ npm -v
6.14.6

実際にファイルを作成,node起動

■Descktopに適当にディレクトリ作成
■その中にファイル「index.html」「index.js」を用意

mac.terminal
$ cd Descktop
$ mkdir JS
$ cd JS 
$ touch index.html
$ touch index.js

■node.jsのローカル環境を起動

mac.terminal
$ npx @js-primer/local-server

■html/jsファイルに追記

index.html
<p>hello.</p>
<script src="index.js"></script>![スクリーンショット 2020-08-06 12.10.24.png]
index.js
conole.log('hello,world');

すると....

スクリーンショット 2020-08-06 12.12.20.png

以上。
あとはフレームワーク等使用すればいいですね。

【合わせて読みたい】

■ 【comandLine】 一言で コマンドライン 各種コマンド ターミナル
https://qiita.com/tanaka-yu3/items/b32e353bd6d7c9ebd4fb

■ 【javascript】 テンプレートリテラル とは 一言で。
https://qiita.com/tanaka-yu3/items/9b07bd9fc4126291be28

■アプリケーション開発の準備 · JavaScript Primer #jsprimer
https://jsprimer.net/use-case/setup-local-env/

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

「お父さん、アルゴリズムってなぁに?」

※この記事は無職やめ太郎(@Yametaro)氏の「ワイ口調」記法をパクり...リスペクトしました。
※また、ネットミームを含みます。苦手な方は頑張って読んでください

前書き

8歳娘「なんか、学校のプログラミングの授業で中村君がアルゴリズムはいいぞとかいうけど、意味が分からない」

父「ほ~ん。で、中村君、何言ってたん?」

娘「なんかアルゴリズムを使ったら、めんどくさい計算がすぐにできる!とか、クソコードを撲滅できる!って言ってた」

父「ほ~ん、じゃアルゴリズムについて素数表示を例に教えるわ」

娘「わ〜い!」
ワイ「ワ〜イ!」
父「誰だこいつ」

分かりやすい例を出してみる~素数表示

父「娘ちゃん、素数ってわかるやろ?」

娘「うん、2,3,5...とかの1と自分自身以外に約数を持たない数でしょ」

父「そうそう。それじゃ、素数の条件ってわかるか?」

娘「えーと、偶数の素数は2のみであることと、、、」

ワイ「あとは、3以上では奇数であることやな

父「(だからこいつ誰だよ)そうやな、じゃあ2~100までの素数をだしてみてみ」

娘「えーと、2,3,5,7,11...めんどくさいね!」

父「そうやろ、そのめんどくさい事をパッて解を出す手順のことをアルゴリズムというんや」

ワイ「そうだよ(便乗)」

父「...んで今回はエラトステネスの篩というアルゴリズムを使って素数を出していくで」

エラトステネスの篩を使用したコードを作ってみる

父「まず、最小値2と最大値100の変数と3つの配列を作っていくで」

let min = 2;
let max = 100;
let oddList = [];
let searchList = [];
let PrimeNimList = [];

娘「この配列たちってどういう意味?」

父「それは順次、解説していくで」

父「で、まず素数は2以外の奇数が必要条件のためminからmaxまでの奇数をoddListに格納したいんやけど」
父「娘ちゃんどう書く?」

娘「えーと」

  for(let i = min; max >= i; i++){
    if((( i % 2 ) != 0)){
      oddList.push(i);
    }
  }

父「そうやな!それで奇数を格納していくわけやな」

娘「oddListは奇数を格納する配列なんだね!」

父「そうや。でも、これだと素数である2が仲間外れになるから...」

  if (min <= 2){
    PrimeNimList.push(2);
  }

父「ここで、minが2以下の場合、後々素数として格納したいPrimeNimListに渡すんや」

娘「そうなんだね!」

ワイ「ちなみにここまでエラトステネスの篩の仕組みの理解を含めて、1.5時間かかりました」

父「何の話だよ」

父「...で、ここからがこのアルゴリズムのミソなんやけど」
父「1つ娘ちゃんにクイズをだすわ」
父「以下の手順をJSでコードを書いてみ」

  1,oddListの先頭の値の倍数を消去=ふるいをかけてsearchListに値を渡す
  2,oddListの先頭の値をPrimeNimListに入れる
  3,oddListの中身を空にし、searchListに値渡しをしてもらう
  4,searchListを空にする
  5,oddListの先頭の値が、maxの平方根(変数stopNum)まで1~4を繰り返す

父「ちなみに変数stopNumはこれや」

let stopNum = Math.sqrt(max);

父「最大値100の平方根、ここではstopNum=10やな」

娘「うーん、まずoddListの先頭の値を定義して」
娘「その値の倍数を消去、残った数をsearchListに渡したいから...」

    let leadNum = (oddList[0])
    oddList.forEach(element => {
      if((element % leadNum) != 0){
        searchList.push(element)
      }
    });

娘「で、oddListの先頭の値をPrimeNimListに入れるのは...」

PrimeNimList.push(leadNum)

娘「次に、oddListの中身を空にし、searchListに値渡しをしてもらうから...」

    oddList = []
    oddList = searchList.slice(0, searchList.length)

娘「searchListを空にするならば...」

    searchList = []

娘「う~ん、最後の5番は繰り返すからどのfor文を使うのだろう...」

ワイ「ホワイルゥ...(小声)」

娘「while文か!で、条件分岐の指定するならば、、、」

  let n = 0;
  let stopNum = Math.sqrt(max);
  while (n < stopNum){
    let leadNum = (oddList[0])
    oddList.forEach(element => {
      if((element % leadNum) != 0){
        searchList.push(element)
      }
    });

    PrimeNimList.push(leadNum)
    oddList = []
    oddList = searchList.slice(0, searchList.length)
    searchList = []

    n = oddList[0]
  }

娘「できた!」

ワイ「やったぜ」

父「さすがワイの娘や!」
父「で、最後にループから抜けたら残りの奇数をPrimeNimListに入れるんや」

  oddList.forEach(element => {
    PrimeNimList.push(element)
  });

父「んで、これを関数にまとめてコンソール出力を付け加えた完成形やで」

SieveOfEratosthenes.js
let x = 2;
let y = 100

processing(x,y)

function processing (min,max) {
  let oddList = [];
  let searchList = [];
  let PrimeNimList = [];
  let stopNum = Math.sqrt(max);

  // 2は奇数の例外のため、min <= 2ならば2をPrimeNimListに入れておく
  if (min <= 2){
    PrimeNimList.push(2);
  }

  // 素数は2以外の奇数が必要条件のため
  // minからmaxまでの奇数をoddListに格納
  for(let i = min; max >= i; i++){
    if((( i % 2 ) != 0)){
      oddList.push(i);
    }
  }

  let n = 0;
  // oddListの先頭の値の倍数を消去=篩をかけてsearchListに値を渡す
  // oddListの先頭の値をPrimeNimListに入れる
  // oddListの中身を空にし、searchListに値渡しをしてもらう
  // searchListを空にする
  // oddListの先頭の値が、maxの平方根(stopNum)まで繰り返す
  while (n < stopNum){
    let leadNum = (oddList[0])
    oddList.forEach(element => {
      if((element % leadNum) != 0){
        searchList.push(element)
      }
    });

    PrimeNimList.push(leadNum)
    oddList = []
    oddList = searchList.slice(0, searchList.length)
    searchList = []

    n = oddList[0]
  }

  // ループから抜けたら残りの奇数をPrimeNimListに入れる
  oddList.forEach(element => {
    PrimeNimList.push(element)
  });

  console.log(PrimeNimList);
  console.log(PrimeNimList.length);
}

父「このコンソールは、素数の配列(PrimeNimList)と素数配列の数(PrimeNimList.length)を表しているで」
父「んで、結果は...」
無題.png
娘「できた!やったぁ!」

ワイ「くぅ~疲れましたw これにて完結です!それでは完走した感想(激うまギャグ)ですか...」

父「そういうのいいから」

結局アルゴリズムとは

父「このようにめんどくさい問題をいかに効率よく手順を踏んで解を出すことなんや」

娘「そうなんだ!じゃあ中村君がいってためんどくさい計算がすぐにできる!とか、クソコードを撲滅できる!って」

父「そうそう、無駄な変数などを書かずに済むわけや」

娘「わぁ、すごい!無駄なメモリリソースとかが省けるね!

父「おお!そうや!娘ちゃんもきちんとPCメモリの仕組みも理解してるんやな!」

父「よ~し!今日は娘ちゃんが1つ賢くなったからご褒美にジョイ〇ルでも行くか!」

娘「わ~い!」
ワイ「ワ~イ!」
父「だから、お前誰だよ」
ワイ「あ、娘さんと仲良くしてる中村です」
父「()」

おしまい

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

Cognos Analytics 11.1.7 新機能紹介

Cognos Analytics 11.1.7 新機能紹介

Cognos Analytics 11.1.7が2020/7/30にリリースされました。

このバージョンは、長期サポートバージョン(LTSR)となりますので、他のバージョンと違い、基本2年、延長1年のサポートが受けられるバージョンとなります。
Cognos Release 11.1.7 is a Long-Term Support Release
https://community.ibm.com/community/user/businessanalytics/blogs/nickolus-plowden/2020/07/30/ltsr

11.1.7に追加された新機能を紹介します。

全般

新しい開発者向けAPI
Cognosでの開発者向けAPIと言えば、Cognos SDKというJavaベースのものでしたが、JavaScriptベースで動作するREST APIが追加されたようです。
・レポート/ダッシュボード/フォルダ、などの各種コンテンツの一覧や情報取得
・各種コンテンツのプロパティの変更
・各種コンテンツの削除
・ダッシュボードの作成や編集
・ユーザー/グループ/役割の管理
等々をこのAPIで行う事ができるようです。
SDKより便利なのか、学習が必要ですね。

全般的なUIデザイン変更
CA11.1.6でもポータル画面等のUIデザインが変更になっていて、カーボンデザインなるデザインのアイコン等に変わっていましたが、11.1.7でもさらにカーボンデザインを追求する方向でデザインが変わっているようです。

アセットの検索
Cognos Analyticsで再利用可能なアセット類は、以下のサイトで見つける事ができますが、CAのポータル画面の検索機能からでも検索可能になりました。
IBM Data and AI Accelerators
https://community.ibm.com/accelerators/?context=analytics&product=Cognos%20Analytics
001.PNG

ダッシュボード

軸ラベルのの改善
この辺りの軸のラベルが斜めに表示されたり、間隔が最適になるよう、表示上の改善が組み込まれているようです。
002.PNG

数値の軸ラベルには、対数表現が使用できるようになりました。
また、数値間隔、最小値、最大値の設定も可能になったようです。
003.PNG

パイチャートのラベル
パイチャートに、データアイテムのラベル表示、数値表示、その両方の表示を選択可能になりました。
画像は両方を選択しているイメージです。
004.PNG

凡例
凡例が多すぎる場合、画像のように省略で表現されるようになりました。
005.PNG

ウォーターフォールでのサブカテゴリー
画像のように「年」でウォーターフォールを表現するだけでなく、サブカテゴリーとして「四半期」を追加する事で、年をさらに四半期で分割して表現できるようになりました。
006.PNG

ツリーマップの表現
ツリーマップの表現を、画像のように縦方向に揃える、横方向に揃える、などの選択が可能になりました。
007.PNG

テンプレートのカスタマイズ
自作のダッシュボードをテンプレートとして保存時、新規作成時にカスタムテンプレートとして呼び出せるようになりました。
008.PNG

データアイテムの検索
データアイテムを選択時に、打ち込んだ途中の文字に応じて選択候補が画像のように表示されるようになりました。
009.PNG

データをCSV形式でエクスポート
データをCSV形式でエクスポート可能となりました。
010.PNG

クロス集計での条件付きの色指定
クロス集計で、条件付きの色指定が可能となりました。画像のように、指定された条件でセル背景と文字の色が変わります。
011.PNG

ブレットチャートでの範囲による背景色指定
ブレットチャートで、最小範囲、中範囲、最大範囲に分けて背景色の指定が可能となりました。
013.PNG

チャートタイトル
チャートのタイトルを、「データアイテム名」、「カスタム指定」、「なし」から選択可能となりました。
014.PNG

レポート

以下のダッシュボードで紹介した新機能については、レポートでも同様に組み込まれていますので、再度の説明は割愛します。
軸ラベルのの改善
凡例
ウォーターフォールでのサブカテゴリー
ツリーマップの表現

基準線
視覚化の棒グラフで、この様な基準線を表示する事ができるようになりました。
015.PNG

複数数値アイテムを配置しての比較表現サポート
こういう数値アイテムを複数置いての比較表現が、多くのチャートで使用可能になりました。
016.PNG
このバージョンで使用可能となったチャートタイプは以下となります。
Area
Bar
Column
Hierarchical packed bubble
Packed bubble
Point
Clustered combination
Stacked combination
Line
Tiled map
Pie
Radar
Tree map
Waterfall
Word cloud

組み合わせグラフ
この様な、棒グラフと折れ線グラフを組み合わせた、組み合わせグラフが視覚化で使用可能になりました。
017.PNG

パイチャートラベルの位置指定
パイチャートでのラベルの位置が、画像の様なパイの外側だったり、内側に配置したりと、配置位置の指定が可能になりました。
018.PNG

データ・セット
パッケージやデータモジュールから、特定のデータアイテムを集めて「データ・セット」というものを作成できますが、データ・セットのパフォーマンスがこのバージョンで向上しているようです。

データ表のフィルターの改善
CA11.1.6の新機能として紹介した「データ表」のフィルター機能で、「Deselect all」が使用可能になるなど改善がされているようです。
019.PNG

特定のページのみの実行時のフォーマット選択
レポートの開発時に、作成イメージを都度チェックすると思いますが、ページ単位での実行時に、画像のように出力フォーマットを指定して実行可能となりました。
020.PNG

閲覧

以下のダッシュボードで紹介した新機能については、閲覧の機能でも同様に組み込まれていますので、再度の説明は割愛します。
クロス集計での条件付きの色指定
データアイテムの検索
チャートタイトル
軸ラベルのの改善

データモデリング

行列式が使用可能に
マニュアル読んでもパッとわからない記述になっていますが、Framework Managerで粒度違いのファクト表をつなぐ時に使用していた「行列式」と同じ役割のものが、データ・モジュールでも使用可能となったようです。
https://www.ibm.com/support/knowledgecenter/SSEP7J_11.1.0/com.ibm.swg.ba.cognos.ca_mdlg.doc/c_ca_mdlg_col_dpndcy.html

サンプルのカレンダー
サンプルのカレンダーデータ・モジュールが利用可能となっています。
https://www.ibm.com/support/knowledgecenter/SSEP7J_11.1.0/com.ibm.swg.ba.cognos.ca_mdlg.doc/c_ca_rel_date_samples.html
また、以下のような日付を操作するフィルターもサンプルに組み込まれているようです。
Current week
Prior week
WTD (week to date)
Prior WTD
Same week last year

Framework ManagerでのBooleanデータアイテム認識
久しぶりにFramework Managerに新機能が組み込まれましたね。
Booleanの列を取り込んだ際に、以前はUnknownになっていましたが、本バージョンからは「Boolean」タイプとして取り込まれるようです。
※これはデータソース側のDBベンダーにより動作が依存するとのこと
021.PNG

管理

テーマの切り替え
CA11.1.7のデフォルトのテーマ(見た目)は、IBM Carbon Xですが、古いバージョンからのアップグレードの場合、テーマはIBM Classicが設定されます。管理の「カスタマイズ」の「テーマ」の中で切り替えが可能です。
022.PNG

スケジュール設定画面の変更
レポートのPDF定期保存などの際に設定する、スケジュールの設定画面が変更になっています。
023.PNG

外部データソース接続
データサーバーでSalesforceの接続が可能になりました。
データサーバーでMicrosoft Analysis Services 2019の接続が可能になりました。
Cognos Analyticsで接続のテストを行っているデータベースベンダーのリストが更新されています。
Vendor-supported client driver versions that were tested for each Cognos Analytics 11.1.x release
https://www.ibm.com/support/pages/node/1106607

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

Google Functions & Node.js: bunyanで高レベルなロギングをする方法

本稿では、Google Could Platform(GCP)のGoogle Cloud Functions(GCF)で、Node.jsのロギングライブラリbunyanを使ったログの記録方法を説明します。

この投稿で学べること

  1. bunyanをGoogle Cloud Functionsの関数に組み込む方法
  2. bunyanのメソッドの呼び出し方
  3. bunyanでオブジェクトの値をロギングする方法
  4. bunyanで記録したログの読み方

この投稿では説明しないこと

  • bunyanを使ってStackDriverにロギングする方法
  • bunyanを使ってStackDriverに「重要度」をつけながらロギングする方法

bunyanとStackDriverを組み合わせると、より自由度が高く分析しやすい構造でロギングすることが可能なりますが、この投稿ではそれについては説明しません。bunyanを単純にCloud Functionsで使うだけでも高レベルなロギングができて便利です。本稿はそこにフォーカスして説明することにします。

Cloud Functionsにてbunyanでロギングする方法

bunyanはロギングを抽象化したライブラリで、GCPに限ったものではなく、Node.jsのアプリケーションのロギングを手助けするものです。ログの出力先を、コンソールやファイルなど指定できたり、ログのレベル(INFO, WARN, ERROR)などを指定しながらロギングできるAPIが生えていて便利です。

GCFではconsole.logでもロギングできますが、低レベルなロギングしかできません。bunyanを使うことでより、高レベルのロギングができます。

:bulb: console.logで行う低レベルなロギングについて知りたい方はこちら↓
Google Functions & Node.js: console.logを使った最低限のロギング - Qiita

bunyanをインストールする

まずロギングライブラリのbunyanをインストールします:

yarn add bunyan

bunyanを関数に組み込む

次にbunyanを関数に組み込みます。

bunyanの設定コード

"bunyan"モジュールからbunyanオブジェクトを取り出し、createLoggerメソッドでロガーを作ります。

index.js
const bunyan = require('bunyan')

const logger = bunyan.createLogger({ name: 'my-function' })

createLoggerメソッドにはロガーの設定を渡します。いろいろな設定が可能ですが、必須設定のnameフィールドだけ指定しておきます。

ロギングするコード

bunyanの設定コードを書いたら、今度は関数実行時にロギングするコードを書きます。先程作成したloggerオブジェクトのメソッドを呼び出すことで、ロギングができます。

index.js
exports.loggingWithBunyan = async (req, res) => {
  logger.fatal('fatalのメッセージ')
  logger.error('errorのメッセージ')
  logger.warn('warnのメッセージ')
  logger.info('infoのメッセージ')
  logger.debug('debugのメッセージ')
  logger.trace('traceのメッセージ')
}

関数の全体像

以上のbunyanの設定コードとロギングするコードを組み合わせてると関数が完成します。次が完成形のコードです:

index.js
const bunyan = require('bunyan')

const logger = bunyan.createLogger({ name: 'my-function' })

exports.loggingWithBunyan = async (req, res) => {
  logger.fatal('fatalのメッセージ')
  logger.error('errorのメッセージ')
  logger.warn('warnのメッセージ')
  logger.info('infoのメッセージ')
  logger.debug('debugのメッセージ')
  logger.trace('traceのメッセージ')
  res.send('OK')
}

関数をデプロイして試す

実装ができたので、関数をデプロイします:

gcloud functions deploy loggingWithBunyan --runtime=nodejs12 --trigger-http

デプロイが完了したら、関数を呼び出してみます:

curl https://asia-northeast1-${PROJECT}.cloudfunctions.net/loggingWithBunyan

関数のログビューアを開き、ちゃんとログが出ているか見てみます:

CleanShot 2020-08-06 at 10.35.05@2x.png

JSONらしきものが記録されていることが分かります。ログエントリを開いて詳細を見てみます:

これは、logger.fatal()で記録したログエントリです。これを見ると、メッセージがしっかり記録されていることが分かります。

bunyanのログレベルの情報はどこ?

今回記録したログはログビューア上の「重大度」がどれもDEFAULTになっています。これは、bunyanが内部でconsole.logなどを使っているためです。

:bulb: console.logとGCPの「重大度」の関係性についての詳細はGoogle Functions: console.infoやconsole.errorなどとログビューアの「重大度」の関係性をご覧ください。

では、どこにbunyanのログレベルが残っているのでしょうか? 先程のログエントリ詳細を見返してみましょう。

このログはlogger.fatal()で記録したものですが、jsonPayload.level60とあるのが見て取れます。これが、bunyanのログレベルfatalを指す値です。

ちなみに、ログビューアではjsonPayload.levelの値でフィルタリングすることができます。値をクリックするとメニューが出てくるので、その中から「一致エントリを表示」をクリックします:

CleanShot 2020-08-06 at 10.43.55@2x.png

すると、クエリが更新され、値に一致するログエントリのみに絞り込まれます:

CleanShot 2020-08-06 at 10.47.19@2x.png

オブジェクトの値をログに出す

bunyanのロギングメソッドに渡せるのは、文字列に限ったものではありません。オブジェクトを渡すこともできます。

下の例では、ログメッセージと一緒に記録したいオブジェクトをロギングするものです:

index.js
const bunyan = require('bunyan')

const logger = bunyan.createLogger({ name: 'my-function' })

exports.loggingWithBunyan = async (req, res) => {
  const object = {
    boolean: true,
    number: 1,
    string: 'string',
    array: [true, 1, 'string'],
    object: { a: true, b: 1, c: 'string' },
    set: new Set([true, 1, 'string']),
    date: new Date(),
  }

  logger.fatal({ message: 'fatalのメッセージ+object', object })
  logger.error({ message: 'errorのメッセージ+object', object })
  logger.warn({ message: 'warnのメッセージ+object', object })
  logger.info({ message: 'infoのメッセージ+object', object })
  logger.debug({ message: 'debugのメッセージ+object', object })
  logger.trace({ message: 'traceのメッセージ+object', object })

  res.send('OK')
}

この関数をデプロイして、呼び出してみるとログビューア上でオブジェクトの値を確認できます:

20200806105656@2x.png

オブジェクトはjsonPayloadのプロパティで参照できます。これにより運用時にログの状況を確認したり、ログのフィルタリングや分析に活用することができます。

注意点として、記録できるオブジェクトはJSON.stringifyできる値のみという点です。上の例ではSetオブジェクトをオブジェクトに含めていますが、SetはJSON化すると{}になるのでログからは情報が欠けてしまっています。また、DateオブジェクトはJSON化すると文字列になったりしています。

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

typing_gameを作成しました。

what

シンプルなタイピングゲームを作成。

why

HTML,CSS,JavaScriptの学習、理解を深めるため

issue

・半角英数小文字のみ対応なので、全角日本語入力への対応。
・ミスタイプした際ライフポイントゲージが減り、0になるとゲームオーバーの機能。

index.html
<!DOCTYPE html>
<html lang=ja>
  <head>
    <meta charset="utf-8">
    <title>Typing Game</title>
    <link rel ="stylesheet" href="css/styles.css">
  </head>
  <body>
    <p class="bg">
      <img src="bg.png" alt="背景"title="背景">
    </p>

    <div class="title">
      <p id="title">Typing Game</p>
    </div>

    <div class="target">
      <p id="target">click to start</p>
    </div>

    <p class="info">
      Letter count:<span id="score">0</span>
      Time left:<span id="timer">0.00</span>

      Miss type:<span id="miss">0</span>
    </p>

    <script src ="js/main.js"></script>
  </body>
</html>
styles.css
body{
  font-family: 'Courier New',monospace;
  text-align: center;
  background-color: #b3b3b3;
  display: flex;
  flex-direction: column;
  align-items: center;
  position: relative;
}

p.bg img{
  width: 100vw;
  height: 100vh;
  position: absolute;
  left: 0;
  top: 0;
  z-index: -1;
  -ms-filter: blur(6px);
  filter: blur(6px);
}

.title{
  font-size: 50px;
  margin-top: 50px;
  display        : inline-block;
  color          : #ffffff;            /* 文字の色 */
  font-size      : 36pt;               /* 文字のサイズ */
  letter-spacing : 4px;                /* 文字間 */
  text-shadow    : 
        2px  2px 9px #003366,
      -2px  2px 9px #003366,
        2px -2px 9px #003366,
      -2px -2px 9px #003366,
        2px  0px 9px #003366,
        0px  2px 9px #003366,
      -2px  0px 9px #003366,
        0px -2px 9px #003366;        /* 文字の影 */
}

.target{
  font-size: 48px;
  letter-spacing: 3px;
  background-color: rgb(161, 134, 255);
  width: 700px;
  padding: 40px;
  border-radius: 20px;

}

.info{
  display: flex;
  flex-direction: column;
}
main.js
'use strict';
{
  const words = [
    'strawberry trapper',
    'guilty night,guilty kiss',
    'jumping heart',
    'hand in hand',
    'dreamer',
    'mirai ticket',
    'self control',
    'daydream worrior',
    'lonely tuning',
    'guilty eyes fever',
    'happy party train',
    'sky journey',
    'galaxy hide and seek',
    'innocent bird',
    'shadow gate to love',
    'landing action yeah',
    'my list to you',
    'miracle wave',
    'awaken the power',
    'crash mind',
    'drop out',
    'one more sunshine story',
    'water blue new world',
    'in this unstable world',
    'pianoforte monologue',
    'beginners sailing',
    'red gem wink',
    'white first love',
    'new winding road',
    'guilty farewell party',
    'thank you friends',
    'marine border parasol',
    'next sparkling',
    'hop stop nonstop',
    'believe again',
    'brightest melody',
    'jump up high',
    'deep resonance',
    'dance with minotaurus',
    'kokoro magic a to z',
    'wake up challenger',
    'new romantic sailors',
    'love pulsar',
    'changeless',
    'never giving up',
  ];

  let word;
  let loc;
  let score;
  let miss;
  const timeLimit = 30 * 1000;
  let startTime;
  let isPlaying = false;
  var type = new Audio('gun.mp3');
  var gameover = new Audio('correct.mp3');
  var misstyp = new Audio('ready.mp3');


  const target = document.getElementById('target');
  const scoreLabel = document.getElementById('score');
  const missLabel = document.getElementById('miss');
  const timerLabel = document.getElementById('timer');

  function updateTarget(){
    let placeholder = '';
    for (let i = 0; i< loc; i++){
      placeholder += '_';
    }
    target.textContent = placeholder + word.substring(loc);
  }

  function updateTimer(){
    const timeLeft = startTime + timeLimit - Date.now();
    timerLabel.textContent = (timeLeft / 1000).toFixed(2);

    const timeoutId = setTimeout(() =>{
      updateTimer();
    },10);

    if(timeLeft < 0){
      isPlaying = false;
      clearTimeout(timeoutId);
      timerLabel.textContent = '0.00';
      gameover.play(); 
      gameover.volume = 1.0;
      setTimeout(() =>{
        showResult();
      },100)
      target.textContent = 'click to replay';
    }
  }

  function showResult(){
    const accuracy = score + miss === 0 ? 0 :score/ (score + miss) * 100;
    alert(`${score} lettters.${miss}misses. ${accuracy.toFixed(2)}% accuracy!`);

  }

  window.addEventListener('click',() => {
    if(isPlaying === true){
      return;
    }
    isPlaying = true;

    loc = 0;
    score = 0;
    miss = 0;
    scoreLabel.textContent = score;
    missLabel.textContent = miss;
    word = words[Math.floor(Math.random() * words.length)];

    target.textContent = word;
    startTime = Date.now();
    updateTimer();
  });

  window.addEventListener('keydown', e => {
    if(isPlaying !== true){
      return;
    }

    if(e.key === word[loc]){
      type.play(); 
      type.currentTime = 0;
      type.volume = 1.0;

      console.log('score');
      loc ++;
      if(loc === word.length){
        word = words[Math.floor(Math.random() * words.length)];
        loc = 0 ;
      }
      updateTarget();
      score++;
      scoreLabel.textContent = score;
    }else{
      misstyp.play(); 
      misstyp.currentTime = 0;
      misstyp.volume = 1.0;
      console.log('miss');
      miss++;
      missLabel.textContent = score;
    }
  });
}

GitHubはこちらです。
https://github.com/izn303/TypingGame

ぜひ遊んでみてください!
http://izn5656.stars.ne.jp/typing_game/

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

Google Functions & Node.js: winstonでロギングする

本稿では、Google Could Platform(GCP)のGoogle Cloud Functions(GCF)で、Node.jsのロギングライブラリwinstonを使ったログの記録方法を説明します。

この投稿で学べること

  1. winstonをGoogle Cloud Functionsの関数に組み込む方法
  2. winstonのメソッドの呼び出し方
  3. winstonでオブジェクトの値をロギングする方法
  4. winstonで記録したログの読み方

この投稿では説明しないこと

  • winstonを使ってStackDriverにロギングする方法
  • winstonを使ってStackDriverに「重要度」をつけながらロギングする方法

Cloud Functionsにてwinstonでロギングする方法

winstonはロギングを抽象化したライブラリで、GCPに限ったものではなく、Node.jsのアプリケーションのロギングを手助けするものです。ログの出力先を、コンソールやファイルなど指定できたり、ログのレベル(INFO, WARN, ERROR)などを指定しながらロギングできるAPIが生えていて便利です。

GCFではconsole.logでもロギングできますが、低レベルなロギングしかできません。winstonを使うことでより、高レベルのロギングができます。

:bulb: console.logで行う低レベルなロギングについて知りたい方はこちら↓
Google Functions & Node.js: console.logを使った最低限のロギング - Qiita

winstonをインストールする

まずロギングライブラリのwinstonをインストールします:

yarn add winston

winstonを関数に組み込む

次にwinstonを関数に組み込みます。

winstonの設定コード

"winston"モジュールからwinstonオブジェクトを取り出し、createLoggerメソッドでロガーを作ります。

index.js
const winston = require('winston')

const logger = winston.createLogger({
  level: 'silly', // 記録するログレベルの設定
  transports: [
    new winston.transports.Console(), // どこにログ出しするかの設定
  ],
})

createLoggerメソッドにはロガーの設定を渡します。levelは記録するログレベルで、次の7段階があります:

レベル 優先度(小さいほうが重要)
error 0
warn 1
info 2
http 3
verbose 4
debug 5
silly 6

levelに指定したログレベル以下のログが記録されるようになります。例えば、infoを設定すると、errorからinfoまでが記録され、http以降のログは記録されません。上のサンプルコードではsillyを設定しているので、全レベルが記録されます。

transportsの設定は、どこにログを出すかの設定です。winston.transports.Consoleconsole.logなどと同等と考えてください。Console以外にもファイルに出したりもできますが、Google Cloud Functionsではファイルにログ出しするという運用は通常行いません。

ロギングするコード

winstonの設定コードを書いたら、今度は関数実行時にロギングするコードを書きます。先程作成したloggerオブジェクトのメソッドを呼び出すことで、ロギングができます。

index.js
exports.loggingWithWinston = async (req, res) => {
  logger.error('errorのメッセージ')
  logger.warn('warnのメッセージ')
  logger.info('infoのメッセージ')
  logger.verbose('verboseのメッセージ')
  logger.debug('debugのメッセージ')
  logger.silly('sillyのメッセージ')
}

関数の全体像

以上のwinstonの設定コードとロギングするコードを組み合わせてると関数が完成します。次が完成形のコードです:

index.js
const winston = require('winston')

const logger = winston.createLogger({
  level: 'silly', // 記録するログレベルの設定
  transports: [
    new winston.transports.Console(), // どこにログ出しするかの設定
  ],
})

exports.loggingWithWinston = async (req, res) => {
  logger.error('errorのメッセージ')
  logger.warn('warnのメッセージ')
  logger.info('infoのメッセージ')
  logger.verbose('verboseのメッセージ')
  logger.debug('debugのメッセージ')
  logger.silly('sillyのメッセージ')

  res.send('OK')
}

関数をデプロイして試す

実装ができたので、関数をデプロイします:

gcloud functions deploy loggingWithWinston --runtime=nodejs12 --trigger-http

デプロイが完了したら、関数を呼び出してみます:

curl https://asia-northeast1-${PROJECT}.cloudfunctions.net/loggingWithWinston

関数のログビューアを開き、ちゃんとログが出ているか見てみます:

CleanShot 2020-08-06 at 09.37.28@2x.png

ちゃんとログがでているのが分かります。

winstonのログレベルの情報はどこ?

今回記録したログはどれも「重大度」はDEFAULTになっています。これは、winston.transports.Consoleが内部でconsole.logなどを使っているためです。

:bulb: console.logとGCPの「重大度」の関係性についての詳細はGoogle Functions: console.infoやconsole.errorなどとログビューアの「重大度」の関係性をご覧ください。

では、どこにwinstonのログレベルが残っているのでしょうか? ログエントリの詳細を開くと分かります。

CleanShot 2020-08-06 at 09.41.58@2x.png

このログはlogger.info()で記録したものですが、jsonPayload.level"info"とあるのが見て取れます。これが、winstonのログレベルです。

ちなみに、ログビューアではjsonPayload.levelの値でフィルタリングすることができます。値をクリックするとメニューが出てくるので、その中から「一致エントリを表示」をクリックします:

CleanShot 2020-08-06 at 09.44.55@2x.png

すると、クエリが更新され、値に一致するログエントリのみに絞り込まれます:

CleanShot 2020-08-06 at 09.48.30@2x.png

オブジェクトの値をログに出す

winstonのロギングメソッドは、第二引数にメタ情報を渡すことができます。メタ情報とは、ログメッセージに関連するデータのことです。ログメッセージがどういう状況で出たのかを後から分かるように、それに関連するデータを一緒に記録できるわけです。

下の例では、ログメッセージと一緒に記録したいオブジェクトをロギングするものです:

index.js
const winston = require('winston')

const logger = winston.createLogger({
  level: 'silly', // 記録するログレベルの設定
  transports: [
    new winston.transports.Console(), // どこにログ出しするかの設定
  ],
})

exports.loggingWithWinston = async (req, res) => {
  // 一緒に記録したいデータ
  const object = {
    boolean: true,
    number: 1,
    string: 'string',
    array: [true, 1, 'string'],
    object: { a: true, b: 1, c: 'string' },
    set: new Set([true, 1, 'string']),
    date: new Date(),
  }
  logger.error('errorのメッセージ+object', object)
  logger.warn('warnのメッセージ+object', object)
  logger.info('infoのメッセージ+object', object)
  logger.verbose('verboseのメッセージ+object', object)
  logger.debug('debugのメッセージ+object', object)
  logger.silly('sillyのメッセージ+object', object)

  res.send('OK')
}

この関数をデプロイして、呼び出してみるとログビューア上でオブジェクトの値を確認できます:

CleanShot 2020-08-06 at 09.59.53@2x.png

メタ情報はjsonPayloadのプロパティ組み込まれます。これにより運用時にログの状況を確認したり、ログのフィルタリングや分析に活用することができます。

注意点として、記録できるメタ情報はJSON.stringifyできる値のみという点です。上の例ではSetオブジェクトをメタ情報に含めていますが、SetはJSON化すると{}になるのでログからは情報が欠けてしまっています。また、DateオブジェクトはJSON化すると文字列になったりしています。

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

Google Functions & Node.js: winstonで高レベルなロギングをする方法

本稿では、Google Could Platform(GCP)のGoogle Cloud Functions(GCF)で、Node.jsのロギングライブラリwinstonを使ったログの記録方法を説明します。

この投稿で学べること

  1. winstonをGoogle Cloud Functionsの関数に組み込む方法
  2. winstonのメソッドの呼び出し方
  3. winstonでオブジェクトの値をロギングする方法
  4. winstonで記録したログの読み方

この投稿では説明しないこと

  • winstonを使ってStackDriverにロギングする方法
  • winstonを使ってStackDriverに「重要度」をつけながらロギングする方法

winstonとStackDriverを組み合わせると、より自由度が高く分析しやすい構造でロギングすることが可能なりますが、この投稿ではそれについては説明しません。winstonを単純にCloud Functionsで使うだけでも高レベルなロギングができて便利です。本稿はそこにフォーカスして説明することにします。

Cloud Functionsにてwinstonでロギングする方法

winstonはロギングを抽象化したライブラリで、GCPに限ったものではなく、Node.jsのアプリケーションのロギングを手助けするものです。ログの出力先を、コンソールやファイルなど指定できたり、ログのレベル(INFO, WARN, ERROR)などを指定しながらロギングできるAPIが生えていて便利です。

GCFではconsole.logでもロギングできますが、低レベルなロギングしかできません。winstonを使うことでより、高レベルのロギングができます。

:bulb: console.logで行う低レベルなロギングについて知りたい方はこちら↓
Google Functions & Node.js: console.logを使った最低限のロギング - Qiita

winstonをインストールする

まずロギングライブラリのwinstonをインストールします:

yarn add winston

winstonを関数に組み込む

次にwinstonを関数に組み込みます。

winstonの設定コード

"winston"モジュールからwinstonオブジェクトを取り出し、createLoggerメソッドでロガーを作ります。

index.js
const winston = require('winston')

const logger = winston.createLogger({
  level: 'silly', // 記録するログレベルの設定
  transports: [
    new winston.transports.Console(), // どこにログ出しするかの設定
  ],
})

createLoggerメソッドにはロガーの設定を渡します。levelは記録するログレベルで、次の7段階があります:

レベル 優先度(小さいほうが重要)
error 0
warn 1
info 2
http 3
verbose 4
debug 5
silly 6

levelに指定したログレベル以下のログが記録されるようになります。例えば、infoを設定すると、errorからinfoまでが記録され、http以降のログは記録されません。上のサンプルコードではsillyを設定しているので、全レベルが記録されます。

transportsの設定は、どこにログを出すかの設定です。winston.transports.Consoleconsole.logなどと同等と考えてください。Console以外にもファイルに出したりもできますが、Google Cloud Functionsではファイルにログ出しするという運用は通常行いません。

ロギングするコード

winstonの設定コードを書いたら、今度は関数実行時にロギングするコードを書きます。先程作成したloggerオブジェクトのメソッドを呼び出すことで、ロギングができます。

index.js
exports.loggingWithWinston = async (req, res) => {
  logger.error('errorのメッセージ')
  logger.warn('warnのメッセージ')
  logger.info('infoのメッセージ')
  logger.verbose('verboseのメッセージ')
  logger.debug('debugのメッセージ')
  logger.silly('sillyのメッセージ')
}

関数の全体像

以上のwinstonの設定コードとロギングするコードを組み合わせてると関数が完成します。次が完成形のコードです:

index.js
const winston = require('winston')

const logger = winston.createLogger({
  level: 'silly', // 記録するログレベルの設定
  transports: [
    new winston.transports.Console(), // どこにログ出しするかの設定
  ],
})

exports.loggingWithWinston = async (req, res) => {
  logger.error('errorのメッセージ')
  logger.warn('warnのメッセージ')
  logger.info('infoのメッセージ')
  logger.verbose('verboseのメッセージ')
  logger.debug('debugのメッセージ')
  logger.silly('sillyのメッセージ')

  res.send('OK')
}

関数をデプロイして試す

実装ができたので、関数をデプロイします:

gcloud functions deploy loggingWithWinston --runtime=nodejs12 --trigger-http

デプロイが完了したら、関数を呼び出してみます:

curl https://asia-northeast1-${PROJECT}.cloudfunctions.net/loggingWithWinston

関数のログビューアを開き、ちゃんとログが出ているか見てみます:

CleanShot 2020-08-06 at 09.37.28@2x.png

ちゃんとログがでているのが分かります。

winstonのログレベルの情報はどこ?

今回記録したログはどれも「重大度」はDEFAULTになっています。これは、winston.transports.Consoleが内部でconsole.logなどを使っているためです。

:bulb: console.logとGCPの「重大度」の関係性についての詳細はGoogle Functions: console.infoやconsole.errorなどとログビューアの「重大度」の関係性をご覧ください。

では、どこにwinstonのログレベルが残っているのでしょうか? ログエントリの詳細を開くと分かります。

CleanShot 2020-08-06 at 09.41.58@2x.png

このログはlogger.info()で記録したものですが、jsonPayload.level"info"とあるのが見て取れます。これが、winstonのログレベルです。

ちなみに、ログビューアではjsonPayload.levelの値でフィルタリングすることができます。値をクリックするとメニューが出てくるので、その中から「一致エントリを表示」をクリックします:

CleanShot 2020-08-06 at 09.44.55@2x.png

すると、クエリが更新され、値に一致するログエントリのみに絞り込まれます:

CleanShot 2020-08-06 at 09.48.30@2x.png

オブジェクトの値をログに出す

winstonのロギングメソッドは、第二引数にメタ情報を渡すことができます。メタ情報とは、ログメッセージに関連するデータのことです。ログメッセージがどういう状況で出たのかを後から分かるように、それに関連するデータを一緒に記録できるわけです。

下の例では、ログメッセージと一緒に記録したいオブジェクトをロギングするものです:

index.js
const winston = require('winston')

const logger = winston.createLogger({
  level: 'silly', // 記録するログレベルの設定
  transports: [
    new winston.transports.Console(), // どこにログ出しするかの設定
  ],
})

exports.loggingWithWinston = async (req, res) => {
  // 一緒に記録したいデータ
  const object = {
    boolean: true,
    number: 1,
    string: 'string',
    array: [true, 1, 'string'],
    object: { a: true, b: 1, c: 'string' },
    set: new Set([true, 1, 'string']),
    date: new Date(),
  }
  logger.error('errorのメッセージ+object', object)
  logger.warn('warnのメッセージ+object', object)
  logger.info('infoのメッセージ+object', object)
  logger.verbose('verboseのメッセージ+object', object)
  logger.debug('debugのメッセージ+object', object)
  logger.silly('sillyのメッセージ+object', object)

  res.send('OK')
}

この関数をデプロイして、呼び出してみるとログビューア上でオブジェクトの値を確認できます:

CleanShot 2020-08-06 at 09.59.53@2x.png

メタ情報はjsonPayloadのプロパティ組み込まれます。これにより運用時にログの状況を確認したり、ログのフィルタリングや分析に活用することができます。

注意点として、記録できるメタ情報はJSON.stringifyできる値のみという点です。上の例ではSetオブジェクトをメタ情報に含めていますが、SetはJSON化すると{}になるのでログからは情報が欠けてしまっています。また、DateオブジェクトはJSON化すると文字列になったりしています。

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

VisualforceでQRコード作成してみた

 LWCでQRコードリーダのコンポーネントを作成できること(Beta版)を知りました。
そちらを試す前にVisualforce上でQRコードを作成する機能を作ってみました。

LWCの開発者ガイド(Scan Barcodes)

成果物

ダウンロード (1).gif

※表示しているレコードはDevEditionに初期保存されている取引先責任者のレコードです。

オブジェクトのアクションをVisualforceページで作成しております。
Visualforce内のJavaScriptで現在のURLのQRコードを生成しています。
(オブジェクト固有のLightning Actionにするために拡張コントローラを指定していますが、コントローラ側では特に処理を行っていません。)
このページでは取引先責任者のレコードページのURLのQRコードを表示しています。

CreateQRcode.vfp
<apex:page standardcontroller="Contact" extensions="CreateQRcodeController" docType="html-5.0" standardStylesheets="false"
  showheader="false" sidebar="false">
  <apex:includeScript value="https://code.jquery.com/jquery-3.4.1.min.js" />
  <apex:includeScript value="http://jeromeetienne.github.io/jquery-qrcode/src/jquery.qrcode.js" />
  <apex:includeScript value="http://jeromeetienne.github.io/jquery-qrcode/src/qrcode.js" />

  <h1>Create QR Code</h1>
  <div id="qrcode"></div>
  <script>

    $('#qrcode').qrcode(location.href);
  </script>
</apex:page>

 Visualforceで行いましたが、AuraでもLWCでも同様の機能を作成できると思います。
また、本番組織での実装の際は、jQueryのファイルを静的リソースにアップロードする方が良いと思われます。

まとめ

 今回はVisualforceでQRコードを表示するだけの機能を作成しました。余裕があれば、AuraやLWCでの表示や、作成したQRコードを画像ファイルとしてレコードの添付ファイルに保存する機能を作成してみたいと思います。

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

JS 配列の一番最後以外の要素を返す

lodashのinitial関数を作ってみた

lodashのinitial関数

const initial = (array) => {
  const initialArray = []
  for (let i = 0; i < array.length - 1; i++) {
    initialArray.push(array[i])
  }
  return initialArray
}

console.log(initial([1, 2, 3, 4]))
// => [ 1, 2, 3, 4, 5 ]

▼POPメゾッドを使うともっと簡潔に書けるみたいだった

const initial = (array) => {
  const copedArray = [...array]

  //popメゾッドは末尾の要素を取り除く
  copedArray.pop()
  return copedArray
}

console.log(initial([1, 2, 3, 4]))
// => [ 1, 2, 3]
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

HTML MediaElement で NightCore する?

TL;DR

(function()
{
  const media = document.getElementsByTagName("video")[0];
  media.mozPreservesPitch = false;
  media.playbackRate = 1.2;
})()

動機

昨今は、Youtubeなど大抵の動画サイトでは再生速度を調節する機能がついています。
しかしながら、これらの再生速度を変更すると、ピッチが自動で修正(見かけ上?…聴きかけ上?変わらないように)されてしまいます。
普通の動画であれば、この挙動の方がありがたいのですが、聞き慣れた音楽をピッチを維持って聞きたい時があります。
そうですNightCoreです。

対応ブラウザについて

Firefoxで検証しました。
MDNのドキュメントを見る限りSafari(Webkit)でもサポートされているようです。この場合、Prefixがmozではなくwebkitになります。
ChromeではPrefixのつかないプロパティが実装されている的なことが書いてあったのですが、Prefix版でもそうでない版でも定義されていませんでした。?

参考

ナイトコア(Wikipedia)
HTMLMediaElement(MDN)
HTMLMediaElement.preservesPitch(Chrome Platform Status)

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

JavaScriptで現在のURLを取得 条件分岐

URL全体の取得

var url = location.href ;
console.log(url);
// http://hogehoge.com/fuga.html
var URL = document.URL;
console.log(URL);
// http://hogehoge.com/fuga.html

プロトコルのみの取得

var protocol = location.protocol ;
console.log(protocol);
// http:

ドメインのみの取得

var host = location.hostname ;
console.log(host);
// hogehoge.com

パスのみの取得

var path = location.pathname ;
console.log(path);
// /fuga.html

URLにhogeが含まれていたら処理を実行する

if(document.URL.match("/hoge/")) {
  $('body').addClass('hoge');
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【JavaScript】Blackjack - ブラックジャック

はじめに

JavaScriptの勉強としてブラックジャックを制作してみました。
今までは基礎としてはES6以前のコードで記述していましたが、最近ES6以降のコードを学んだのでそれらを意識しながら記述してみました。
まだまだのコードだとは思いますが、ブラックジャックのコードが他にあまりなかったので後学者の参考になればと思います。

キャプチャ

image.png

ソースコード

html

html
<div class="container">

  <p>ディーラー</p>
  <p id="dealer-result"></p>
  <div id="dealer" class="stage">
    <div id="p3" class="card-back"></div>
    <div id="p4" class="card-front"></div>
  </div>

  <p>プレイヤー</p>
  <p id="result"></p>
  <div id="player" class="stage">
    <div id="p1" class="card-front"></div>
    <div id="p2" class="card-front"></div>
  </div>

  <div class="btn">
    <input type="button" value="スタンド" id="stand">
    <input type="button" value="ヒット" id="hit">
  </div>

</div>

css

css
body {
  background-color: lemonchiffon;
  font-family: Times New Roman;
  font-size: 35px;
  font-weight: bold;
}

.container {
  width: 1000px;
  margin: 40px auto;
  padding: 0 10px;
  background-color: white;
}

p {
  margin: 10px;
}

.stage {
  display: flex;
}

.card-front,
.card-back {
  width: 100px;
  height: 150px;
  margin: 10px;
  text-align: center;
  line-height: 150px;
  border-radius: 5px;
  border: solid 1px lightskyblue;
}

.card-front {
  background-color: white;
  color: lightskyblue;
}

.card-back {
  background-color: lightskyblue;
  color: lightskyblue;
}

.btn {
  width: 500px;
  margin: 0 auto;
  text-align: center;
}

#stand, #hit {
  width: 120px;
  height: 30px;
  margin: 20px;
  border: solid 1px black;
  outline: none;
  cursor: pointer;
  background-color: lightsteelblue;
}

JavaScript

js
'use strict';

const deck = [];
const p1 = document.getElementById("p1");
const p2 = document.getElementById("p2");
const d1 = document.getElementById("d1");
const d2 = document.getElementById("d2");
const hit = document.getElementById("hit");
const stand = document.getElementById("stand");
const result = document.getElementById("result");
const dealerResult = document.getElementById("dealer-result");




  //マークを受け取り数字を付けて配列(deckに追加)
  const cards = mark => {
    for(let i =1; i <14; i++) {
      let card = (mark + i);
      deck.push(card);
    }
  }

  // マークを作成し関数cardsに渡す
  for(let m = 1; m < 5; m++) {
    if (m === 1) {
      // let mark = '♠️';
      cards("♠️");
    } else if (m === 2) {
      // let mark = '♣️';
      cards("♣️");
    } else if (m === 3) {
      // let mark = '❤︎';
      cards("❤︎")
    } else if (m === 4) {
      // let mark = '♦︎';
      cards("♦︎")
    } 
  }


  // 各手札を配る
  function deal(hand1, hand2) {
    //配列(deck)からランダムでカード切りとる
    const tramp1 = deck.splice(Math.floor(Math.random() * deck.length),1)[0];
    const tramp2 = deck.splice(Math.floor(Math.random() * deck.length),1)[0];


    // カードを表示
    hand1.textContent = tramp1;
    hand2.textContent = tramp2;

    // 数字のみを切り取り、文字列から数値へ変換
    let hand1Num = Number(tramp1.replace(/[^0-9^\.]/g,""));
    let hand2Num = Number(tramp2.replace(/[^0-9^\.]/g,""));


    // hand1の数字をチェック
    if(hand1Num >= 10) {
      hand1Num = 10;
    } else if (hand1Num === 1) {
      hand1Num = 11;
    } 

    // hand2の数字をチェック
    if(hand2Num >= 10) {
      hand2Num = 10;
    } else if (hand2Num === 1 && hand1Num !== 11) {
      hand2Num = 11;
    } 

    // 配列で管理
    return [hand1Num, hand2Num];

  }

  // 関数呼び出し
  deal(p1, p2);
  deal(d1,d2)

  // 配列を定数に代入
  const hands1 = deal(p1, p2)
  const hands2 = deal(d1, d2)

  //配列内の合計(手札合計)
  const sumHand = hands => {
    let sum = 0;
    for(let i = 0, len = hands.length; i < len; i++) {
      sum += hands[i];
    }
    return sum;
  };

  result.textContent = sumHand(hands1);

// カードを引く処理
function drowCard(who, hands) {

  const drow = document.createElement("div");

  // 引くカードをランダムで作成して表示させる
  drow.classList.add("card-front");
  drow.textContent = deck.splice(Math.floor(Math.random() * deck.length),1)[0];
  who.appendChild(drow);

  // 引いたカードを数値化
  let drowNum = (Number(drow.textContent.replace(/[^0-9^\.]/g,"")));

  //10以上か1かを判定
  if (drowNum >= 10) {
    drowNum = 10;
    return drowNum;
  } else if (drowNum === 1 && sumHand(hands) <= 10) {
    drowNum = 11;
    return drowNum;
  }
  return drowNum;
};

// ヒットボタンを押した時
hit.addEventListener("click", () => {

  const player = document.getElementById('player');

  hands1.push(drowCard(player, hands1));
  result.textContent = sumHand(hands1);

  // 21以上かを判定
  isBurst(hands1, result, "プレイヤー");

});

  // 21以上かを判定
  function isBurst(hands, res, who) {
    if (sumHand(hands) > 21) {
      if (hands[0] === 11) {
        hands[0] = 1;
        res.textContent = sumHand(hands);
      } else if (hands[1] === 11) {
        hands[1] = 1;
        res.textContent = sumHand(hands);
      } else {
        res.textContent = `${sumHand(hands)}  : burst! ${who}の負けです`
        d1.className = "card-front"
        dealerResult.textContent = sumHand(hands2);
        NoneBtn()
      }
    }
  }

  function NoneBtn() {
    hit.style.display = "none";
    stand.style.display = "none";
  };

  //スタンドボタンを押した時
  stand.addEventListener("click", () => {
    d1.className = "card-front"
    dealerResult.textContent = sumHand(hands2);


    const dealer = document.getElementById('dealer');

    //手札合計が17以上になるまでカードを引く
    while(sumHand(hands2) <= 16) {
      hands2.push(drowCard(dealer, hands2));
      dealerResult.textContent = sumHand(hands2);
    }

    // 手札が21以上かを判定
    isBurst(hands2, dealerResult, "ディーラー");

    // 勝敗判定
    if (sumHand(hands2) <= 21) {
      if (sumHand(hands1) > sumHand(hands2)) {
        result.textContent = `${sumHand(hands1)} : プレイヤーWIN!!`
        dealerResult.textContent = `${sumHand(hands2)} : ディーラーLOSE...`
        NoneBtn()
      } else if (sumHand(hands2) > sumHand(hands1)) {
        result.textContent = `${sumHand(hands1)} : プレイヤーLOSE...`
        dealerResult.textContent = `${sumHand(hands2)} : ディーラーWIN!!`
        NoneBtn()
      } else {
        result.textContent = `${sumHand(hands1)} : DRAW..`
        dealerResult.textContent = `${sumHand(hands2)} : DRAW..`
        NoneBtn()
      }
    } else {
      result.textContent = `${sumHand(hands1)} : プレイヤーWIN!!`
      NoneBtn()
    };

  });

おわりに

コードについてはもちろん完璧ではないと思いますが、自分の記録用としても投稿しておきます。
少しでも参考になればと思います。

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