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

regl 利用でアニメーション(+ React)

こんにちは。
regl (Functional WebGL, GitHub) + React を利用しランダムな点のアニメーションを描いてみました。今回、use-interval (GitHub) を用いてみました。
ただし、fragColor など未だに十分理解できていないので変な箇所があるかもしれません。

regl.jpg

App.js
import React from 'react';
import createRegl from 'regl';
import useInterval from 'use-interval';

const App = () => {

  const pointSize = 14;
  const pointRandom = () => 
          [2 * Math.random() - 1,  // x
           2 * Math.random() - 1,  // y
           Math.random(), Math.random(), 0];  // r, g, b
  const generatePoints = (nPoints) => Array(nPoints).fill(0).map(pointRandom);

  const regl = createRegl(document.querySelector('.canvas'));

  const frag = `
      precision mediump float;
      varying vec3 fragColor;
      void main() {
        gl_FragColor = vec4(fragColor, 1);
      }
  `;

  const vert = `
      varying vec3 fragColor;
      attribute vec2 pos;
      attribute vec3 color;
      uniform float pointSize;
      void main() {
        fragColor = color;
        gl_PointSize = pointSize;
        gl_Position = vec4(pos, 0, 1);
      }
  `;

  const drawPoints = regl({
    frag: frag,
    vert: vert,
    primitive: 'points',
    count: (context, props) => props.points.length,
    attributes: {
      pos: (context, props) => props.points.map(d => d.slice(0, 2)),
      color: (context, props) => props.points.map(d => d.slice(2, 5))
    },
    uniforms: {
      pointSize: pointSize,
      points: regl.prop('points')
    }
  });

  const clearRegl = () => regl.clear({
        color: [0, 0, 0, 0],
        depth: 1
      });

  let i = 0;
  useInterval(() => {
      clearRegl();
      drawPoints({points: generatePoints((++i%2)*4+2)});
  }, 1000);

  return <div className='canvas'></div>;
};

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

ランダムな点のアニメーション(regl + React 利用)

こんにちは。
regl (Functional WebGL, GitHub) + React を利用しランダムな点のアニメーションを描いてみました。今回、use-interval (GitHub) を用いてみました。

regl.jpg

App.js
import React from 'react';
import createRegl from 'regl';
import useInterval from 'use-interval';

const App = () => {

  const pointSize = 14;
  const pointRandom = () => 
          [2 * Math.random() - 1,  // x
           2 * Math.random() - 1,  // y
           Math.random(), Math.random(), 0];  // r, g, b
  const generatePoints = (nPoints) => Array(nPoints).fill(0).map(pointRandom);

  const regl = createRegl(document.querySelector('.canvas'));

  const frag = `
      precision mediump float;
      varying vec3 fragColor;
      void main() {
        gl_FragColor = vec4(fragColor, 1);
      }
  `;

  const vert = `
      varying vec3 fragColor;
      attribute vec2 pos;
      attribute vec3 color;
      uniform float pointSize;
      void main() {
        fragColor = color;
        gl_PointSize = pointSize;
        gl_Position = vec4(pos, 0, 1);
      }
  `;

  const drawPoints = regl({
    frag: frag,
    vert: vert,
    primitive: 'points',
    count: (context, props) => props.points.length,
    attributes: {
      pos: (context, props) => props.points.map(d => d.slice(0, 2)),
      color: (context, props) => props.points.map(d => d.slice(2, 5))
    },
    uniforms: {
      pointSize: pointSize,
      points: regl.prop('points')
    }
  });

  const clearRegl = () => regl.clear({
        color: [0, 0, 0, 0],
        depth: 1
      });

  let i = 0;
  useInterval(() => {
      clearRegl();
      drawPoints({points: generatePoints((++i%2)*4+2)});
  }, 1000);

  return <div className='canvas'></div>;
};

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

fetch template

phpでデータを取ってくる。

変数(parameter)と値(value)をsetDataに渡して、setDataToDB.phpでは例えばPDOでDBにデータをupdate(or insert)する。

setDataToDB.phpがもし戻り値としてデータセットを返す場合にはthen((json)の中で処理する。

fetchSample.js
function setData(paramater, value) {
    var res = fetch("./setDataToDB.php", {
        method: "POST",
        headers: {
            "Content-Type": "application/json",
        },
        body: JSON.stringify({
            "value": value
        })
     }).then((response) => {
        if(response.ok)
        return response.json(); // レスポンスをテキストとして変換する
    }).then((json) => {
        //console.log(json[0]["someParameter"]); //複数のデータセットが帰ってくる場合は0でない。
        //何らかの処理;
        return json[0]["otherParameter"]; //returnで返らない?
    });
}

fetchで取ってきた値を他の関数でも使いたい場合にはどうするの?

受信するphp

setDataToDB.php
<?php
$str_json = file_get_contents('php://input');
$json_obj = json_decode($str_json, true);

//データベース接続ーーーーーーーーーーーーーーーー
$dsn = 'mysql:dbname=データベース名;host=localhost';
$user = 'ユーザ名';
$password = 'パスワード';
$dbh = new PDO($dsn, $user, $password);

//SQL処理ーーーーーーーーーーーーーーーーーーーーー
$dbh->beginTransaction();
$stmt = $dbh->prepare("update テーブル名 set カラム名=:mode;");
$stmt->bindParam(':mode', $json_obj["value"], PDO::PARAM_STR);
$stmt->execute();
$dbh->commit();
?>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

現代のフロントエンド開発シリーズ(7)- フロントエンドでもエンジニアリング必要があるのはなぜですか?

ソフトウェアの品質を改善するために、定量化および標準化された方法でソフトウェアを開発する

では、なぜフロントエンドを体系的にする必要があるのでしょうか。私はいくつかの点があるかと思います:

  1. 以前、フロントエンドの複雑さはそれほど高くなく、静的なページを表示するだけで十分でしたが、技術の進歩に伴い、エンジニアリングに役立つツールを導入しないと、多くのアプリケーションがますます複雑になっています(googleドライブ、facebookなど)。エンジニアリングの手法を導入しないと、プログラムの開発は管理しにくくなるでしょう
  2. フロントエンドの開発が徐々に成熟しているため、フロントエンドエンジニアリング用のツール(gulp、webpackなど)を開発する余裕があります。

実際、フロントエンドエンジニアリングは、「ああ、エンジニアリングを今すぐやろう」みたいな流行語ではありませんが、開発中は「ああ、できるなら」のように感じられます。

今日は、フロントエンドエンジニアリングを行う際の一般的な概念と構築ツールをいくつか紹介します。

Webpack

大規模なフロントエンドプロジェクトの中で、webpackはほとんど不可欠なツールです。

JavaScript初期の頃は、モジュールを管理する適切な方法がなく、モジュール化にするには、いくつかの設計パターンとエンジニア自身の経験などにしか頼ることができませんでした。

例として、モジュールをファイルで分けると、すぐに次のような依存関係の問題が発生します。

<script type="text/javascript" src="a.js"></script>
<script type="text/javascript" src="b.js"></script>
<script type="text/javascript" src="c.js"></script>
<script type="text/javascript" src="d.js"></script>

仮にdはa、b、cに依存するとして、順番が変更されるとバグが爆発します。時間が経つにつれ、全員が同じ場所ですべてをモジュール化することを望まなくなります。

依存関係メソッドを定義するCommonJSやRequireJSなどのいくつかのソリューションがあり、ライブラリを使用して依存関係の問題を解決できました。

ただし、まだいくつかの問題があります。

  • 依存関係は自分で宣言する必要があります
  • 動的インポート(Dynamic Import)を実行する場合、あまり解析する方法はない

webpackが登場した後、非常に強力なので、すぐに開発者に好まれました。

JavaScriptファイルに加えて、ローダーが設定されている限り、画像、フォントファイル、CSSなどをロードすることもできます。

Tree Shaking

初めてこの言葉を見たときは、すごくかっこよくて、概念的に考えやすかったと思いますね。木を振ると枯れ葉が落ちてしまいました。

ここで、枯れ葉の実は不要なコードを指します。ツリーの揺れとは、プログラムを作成するときにモジュールコードの一部しか使用しないことがあるため、ファイル全体をバンドルする必要がないことを指します。バンドルサイズを小さくすることができます。

このコードがほかの部分も使用されていないことをどのように知るかのを問題です。

一般的な静的言語では、コンパイラーに依存してこれを行うことができますが、JavaScriptでは、自分自身にのみ依存できます。幸い、webpack2後、開発者のためにツリーシェーキング関数を追加しましたが、静的に分析できるようにするためにes6インポートの構文に依存しています。

デッドコードの排除(dead code elimination)

以下のような絶対に実行しないコードを削除することができ、略してDCEと呼ばれます。

`javascript
if(false){
// コード
}
`

条件はfalseのため、コードをここで実行することはできず、トランスパイルを実行するときにプラグを抜くことができます。

詳細については、Danの開発モードの仕組みを参照してください。

コード分割(Code Splitting)

コード分​​割の主な目的は、アプリケーションのコードとサードパーティライブラリのコードを2つのファイルに分割することです。

通常、使用するThird Partyのコードは、バージョン番号が変更のない限りほとんど変更されません。これらのコードをパッケージに分解し、CDNにアップロードしてキャッシュを有効にすることができるため、変更がある部分がリロードすることができます。アプリケーション自体のコードだけ、将来バージョン番号が変わったときに、ハッシュメカニズムで簡単に更新できます。

最小化

開発中は、目的を理解するために他の人(そして私たち自身)が理解できるように変数に明確な名前を付けますが、本番環境のビルドでは必要ありません。このとき、変数名を最小限に抑えることができます。

前述のように、パラメーターを渡す方法により、パラメーターで使用されているパラメーターを明確に把握できるだけでなく、最小化するのにも便利です。

動的なインポート(dynamic import)

ユーザーがホームページをロードするだけの場合、プロファイルページに入る必要がない場合があります。
したがってホームページに関連するコードのみをロードし、ユーザーがプロファイルページにアクセスしたときにコードをロードします。

まとめ

Webpack構成ファイルは、使いやすい構成ファイルを書き込むのは非常に困難で、テストとパラメーターの調整に多くの時間がかかります。

完璧な構築メカニズムが必要な場合は、さまざまなプラグインを研究するのに長い時間がかかることがあります。もちろん、時間をかけて作図ツールを勉強すれば異なる意見を持つでしょう。設定の時間を節約したい場合は、create-react-appまたはvue-cliやその他のエコシステム構築ツールを使用することもできます。

ほとんどの設定は実際には同じですが(フロントエンド開発セクションで)、ニーズと使用条件に応じて異なる調整が必要になる場合があります。

さらに、アップグレードは非常に面倒な場合があります。たとえば、webpackのアップグレード時に、重大な変更が頻繁に発生します(それほど重大ではありませんが)。構成ファイルを変更するための調査にさらに時間がかかります。

記事のタイトルはフロントエンドエンジニアリングに関するものですが、この記事では主に構築ツールとそれに対応する概念について説明しました。

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

レガシーなウェブシステムのSELECTを使いやすくするJavascript(ブックマークレット)

困りごと

うちの会社には、スーパーなレガシーシステムがありまして、、、
HTMLで作られているWebシステムなのですが、<select>がめっちゃ長いんですよ。選択肢が50とか100とかあります。管理者が選択肢を作れるようなシステムなので、増やす一方です。

その画面に入力する身としては、たまったもんじゃありません。目で見て選択するなんて無理です。

ということで、レガシーシステムに息を吹き込むブックマークレットを作りました。

使うもの

  • Javascript
  • jQuery

レガシーシステムが相手なので、jQueryで楽させてもらいます。

コード

こんな感じになりました。コメント入れて60行くらい。
さすがにレガシーといえど、jQueryを使ってコード書きました。

select-filter.js
void ((function (f) {
  if (window.jQuery && jQuery().jquery > '2.0') {
    console.log('use jquery');
    f(jQuery);
  } else {
    console.log('load jquery');
    var script = document.createElement('script');
    script.src = '//code.jquery.com/jquery-3.2.1.min.js';
    script.onload = function () {
      var $ = jQuery.noConflict(true);
      f($);
    };
    document.body.appendChild(script);
  }
})(
  function ($, undefined) {
    $('select').each(function (i, select) {
      //selectにidが無ければ、nameから作り出す
      if (!select.id) {
        select.id = select.name + '_' + i;
      }

      let options = $(select).children('option').length;
      let text = `<input type="text" data-selectid="${select.id}" class="__ft">`;
      let badge = `<span id="${select.id}_hit">${options}</span>/<span>${options}</span>`;

      //textboxを突っ込む
      $(select).before(`${text}  ${badge} <br>`);
    });

    //追加したテキストボックス全てに適用するイベントハンドラ
    $('input.__ft').on('input', function (ev) {

      let selector = '#' + $(this).data('selectid');
      let selectid = $(selector).attr('id');
      console.log(selector);

      let txt = this.value;
      console.log(txt);

      //フィルタした瞬間に選択肢を元に戻すならコメントはずす
      // $(selector).prop("selectedIndex", 0);

      let hit = 0;
      $(selector).children('option').each(function (i, e) {
        if (isDefaultOption(e) || matchOption(e, txt)) {
          $(e).show();
          hit++;
        } else {
          $(e).hide();
        }
      });
      $(`span#${selectid}_hit`).text(hit);
    });

    //デフォルトオプションの判定(システムに合わせて適当に書き換える必要があるかも)
    function isDefaultOption(element) {
      return $(element).val() == '' || $(element).hasClass('default');
    }
    //表示する条件(半角全角の調整など必要なことをやるべし)
    function matchOption(element, input) {
      return $(element).text().indexOf(input) != -1;
    }
  }
))

コードはここにも置いてあります。
https://github.com/kanaxx/kanaxx.github.io/tree/master/bookmarklet/select-filter

コードの説明

:sunflower: 説明するほどのコードじゃないですけど、一応。

$('select')なので、画面にある全ての<select>が対象です。

<select>にidが振られていないようなレガシーシステムの場合のことも考えて、nameの値を元に<select>にidを追加しています。

テキストボックスの真下にある<select>のidをテキストボックスのdata-selectidとして仕込むことで関連を持たせ、class__ftクラスを割り当てる

__ftクラス全体にinputイベントハンドラを登録する

イベントハンドラでselectからoptionを探して、部分一致したものだけshow()する

実行

選択肢が多いページが必要だったので、自分で作りました。
https://kanaxx.github.io/bookmarklet/select-filter/index.html

F12でDevToolを開いて、Consoleにスクリプトを投げ込んでください。

image.png

初期表示はセレクトがあるだけ
image.png

長くてうんざり
image.png

ブックマークレット(スクリプト)を実行
画面内にあるセレクトの上に、テキストボックスが生まれる!
image.png

フィルタ実行
テキストに「都」を入れると「東京都」と「京都府」で2つの選択肢だけになるので、選びやすい。3/48表記は左がマッチした選択肢数、右が選択肢の総数です。テキストボックスから消すと全部元通りです。
image.png

まとめ

程よく汎用的に作ったので、たいていのサイトの選択肢<select>をハックできるはずです。一番上の選択肢を上手に判定する方法が見つからなかったけど。
気に入ったら、ブラウザのブックマークバーに登録してみてください。

参考資料

国の一覧はここを一覧を使わせていただきました。5年前の国名らいし2020年だと変わってるかもしれません。
https://qiita.com/tao_s/items/32b90a2751bfbdd585ea

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

レガシーなウェブシステムのSELECTタグを使いやすくするJavascript(ブックマークレット)

久しぶりにブックマークレット

困りごと

うちの会社には、スーパーなレガシーシステムがありまして、、、
HTMLで作られているWebシステムなのですが、<select>がめっちゃ長いんですよ。選択肢が50とか100とかあります。管理者が選択肢を作れるようなシステムなので、増やす一方です。

その画面に入力する身としては、たまったもんじゃありません。目で見て選択するなんて無理です。

ということで、レガシーシステムに息を吹き込むブックマークレットを作りました。

使うもの

  • Javascript
  • jQuery

レガシーシステムが相手なので、jQueryで楽させてもらいます。

コード

こんな感じになりました。コメント入れて60行くらい。
さすがにレガシーといえど、jQueryを使ってコード書きました。

select-filter.js
void ((function (f) {
  if (window.jQuery && jQuery().jquery > '2.0') {
    console.log('use jquery');
    f(jQuery);
  } else {
    console.log('load jquery');
    var script = document.createElement('script');
    script.src = '//code.jquery.com/jquery-3.5.1.slim.min.js';
    script.onload = function () {
      var $ = jQuery.noConflict(true);
      f($);
    };
    document.body.appendChild(script);
  }
})(
  function ($, undefined) {
    $('select').each(function (i, select) {
      //selectにidが無ければ、nameから作り出す
      if (!select.id) {
        select.id = select.name + '_' + i;
      }

      let options = $(select).children('option').length;
      let text = `<input type="text" data-selectid="${select.id}" class="__ft">`;
      let badge = `<span id="${select.id}_hit">${options}</span>/<span>${options}</span>`;

      //textboxを突っ込む
      $(select).before(`${text}  ${badge} <br>`);
    });

    //追加したテキストボックス全てに適用するイベントハンドラ
    $('input.__ft').on('input', function (ev) {

      let selector = '#' + $(this).data('selectid');
      let selectid = $(selector).attr('id');
      console.log(selector);

      let txt = this.value;
      console.log(txt);

      //フィルタした瞬間に選択肢を元に戻すならコメントはずす
      // $(selector).prop("selectedIndex", 0);

      let hit = 0;
      $(selector).children('option').each(function (i, e) {
        if (isDefaultOption(e) || matchOption(e, txt)) {
          $(e).show();
          hit++;
        } else {
          $(e).hide();
        }
      });
      $(`span#${selectid}_hit`).text(hit);
    });

    //デフォルトオプションの判定(システムに合わせて適当に書き換える必要があるかも)
    function isDefaultOption(element) {
      return $(element).val() == '' || $(element).hasClass('default');
    }
    //表示する条件(半角全角の調整など必要なことをやるべし)
    function matchOption(element, input) {
      return $(element).text().indexOf(input) != -1;
    }
  }
))

コードはここにも置いてあります。
https://github.com/kanaxx/kanaxx.github.io/tree/master/bookmarklet/select-filter

コードの説明

:sunflower: 説明するほどのコードじゃないですけど、一応。

$('select')なので、画面にある全ての<select>が対象です。

<select>にidが振られていないようなレガシーシステムの場合のことも考えて、nameの値を元に<select>にidを追加しています。

テキストボックスの真下にある<select>のidをテキストボックスのdata-selectidとして仕込むことで関連を持たせ、class__ftクラスを割り当てる

__ftクラス全体にinputイベントハンドラを登録する

イベントハンドラでselectからoptionを探して、部分一致したものだけshow()する

実行

選択肢が多いページが必要だったので、自分で作りました。
https://kanaxx.github.io/bookmarklet/select-filter/index.html

F12でDevToolを開いて、Consoleにスクリプトを投げ込んでください。

image.png

初期表示はセレクトがあるだけ
image.png

長くてうんざり
image.png

ブックマークレット(スクリプト)を実行
画面内にあるセレクトの上に、テキストボックスが生まれる!
image.png

フィルタ実行
テキストに「都」を入れると「東京都」と「京都府」で2つの選択肢だけになるので、選びやすい。3/48表記は左がマッチした選択肢数、右が選択肢の総数です。テキストボックスから消すと全部元通りです。
image.png

まとめ

程よく汎用的に作ったので、たいていのサイトの選択肢<select>をハックできるはずです。一番上の選択肢を上手に判定する方法が見つからなかったけど。
気に入ったら、ブラウザのブックマークバーに登録してみてください。

参考資料

国の一覧はここを一覧を使わせていただきました。5年前の国名らいし2020年だと変わってるかもしれません。
https://qiita.com/tao_s/items/32b90a2751bfbdd585ea

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

obniz を特定の人だけアクセスできるようにする

はじめに

obnizは、ハードウェアをあたかもWebサイトの要素の一つであるかのように制御することができるマイコン開発ボードです。
購入して電源を繋いで、Wi-Fiのパスワードを入力すれば、専用のobniz Cloudに接続され、あとはHTML+JavaScriptなどでプログラムを記述し、スマートフォンやパソコンのブラウザから制御することができます。
ファームウェアの書き換えが不要なので、I2C通信など、センサの制御をちょっと試したいときは、ブラウザで再読み込みするだけで良いのでとても便利に使っています。
obniz OS搭載のM5StickCなら、6軸センサや赤外線LEDも搭載されているので、マイコン開発がほぼ遠隔でできてしまいますね。

obniz Cloudのリポジトリでは、HTMLやJavaScriptのプログラムを書いて保存することができるようになっていて、公開または非公開から選べるようになっています。非公開の場合は、自分のobniz アカウントでログインする必要があります。

今回は、自分と、共有した相手だけobnizを制御できるようにしたいと思い、比較的簡単に実現する方法をまとめます。
今回の方法では、必ずしも安全というわけではなく、セキュリティは弱い方法です。あくまでも簡易的に実装してみた方法ですので、今回のコードを使用される場合は、その点を必ずご承知おきください。

obniz のアクセストークン

obniz 初回起動時の状態では、8桁のobniz IDを入力すれば、誰でもそのobnizを制御できる状態になっています。

https://docs.obniz.io/ja/guides/common/connection/
に記載されているように、obniz 開発者コンソールのデバイスから、アクセストークンを発行することができます。

アクセストークンを発行すれば、以下のように正しいアクセストークンを入力しなければ、obnizに接続することができなくなります。

index.html
new Obniz('1234-5678', {access_token: 'your token here'});

アクセストークンは、更新や削除もできるようになっています。
そこでこのアクセストークンを、HTMLの外から入力できるようにしてあげます。

プロンプトと Cookie による方法

window.prompt と Cookie を利用して、初回接続時にはプロンプトを表示し、アクセストークンの入力を促します。一度接続できれば、アクセストークンを Cookie に保存し、次回以降は Cookie から呼び出して自動的に接続します。
JavaScript Cookieを使用しています。js.cookie.js へのパスは適宜書き換えてください。

index.html
  <script src="/path/to/js.cookie.js"></script>
  <script>
    var access_token;    
    if(Cookies.get('obniz_access_token')){
      access_token=Cookies.get('obniz_access_token');
    }else{
      access_token=window.prompt("Input token", "");
    }

    var obniz = new Obniz("XXXX-XXXX", {access_token});

    obniz.onconnect = async function () {
      Cookies.set('obniz_access_token', access_token, { expires: 7 });

この方法であれば、URLとobnizのアクセストークンを共有し、URLにアクセスしてもらって、アクセストークンをコピペしてもらえば良いですね。

URLパラメータによる方法

ただURLにアクセスするだけで繋げられる方法です。共有するURLは、通常のobniz Cloudの実行URLの後に、?access_token=XXXXXXXXXXXXXXXのような形で、obniz のアクセストークンを繋げたものになります。

index.html
    <script>
      var queryStr = window.location.search;
      var queryObj = {};
      if(queryStr){
        queryStr = queryStr.substring(1);
        let paramArr = queryStr.split('&');
        paramArr.forEach(p=>{
          let elem=p.split('=');
          queryObj[decodeURIComponent(elem[0])]=decodeURIComponent(elem[1]);
        });
      }

      var access_token=queryObj.access_token;
      var obniz = new Obniz("OBNIZ_ID_HERE", {access_token});

http://obniz.io/users/XXX/repo/XXXXX.html?access_token=XXXXXXXXXXXXXXX
のようなURLとなります。

2つの方法を統合

2つの方法を統合し、一部修正しました。

URLパラメータにあればそれを使い、接続できればCookieに保存します。
URLパラメータになければ、Cookieを呼び出し、Cookieにもなければ、プロンプトで入力を促します。

index.html
  <script src="/path/to/js.cookie.js"></script>
  <script>
      var queryStr = window.location.search;
      var queryObj = {};
      if(queryStr){
        queryStr = queryStr.substring(1);
        let paramArr = queryStr.split('&');
        paramArr.forEach(p=>{
          let elem=p.split('=');
          queryObj[decodeURIComponent(elem[0])]=decodeURIComponent(elem[1]);
        });
      }

      var access_token=queryObj.access_token;
      if(!access_token){
        access_token=Cookies.get('obniz_access_token');
      }
      if(!access_token){
          access_token=window.prompt("Input token", "");
      }

      var obniz = new Obniz("XXXX-XXXX", {access_token});

      obniz.onconnect = async function () {
        Cookies.set('obniz_access_token', access_token, { expires: 7 });

最後に

この方法を使えば、obniz のプログラムを友だちに共有しやすくなりますね!
Let's みんなで obniz!

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

Javascriptの復習(5)

この記事について

 Javascript初学者がアウトプットの場として書いている記事なので、スルーしてもらっても大丈夫です。Rubyを学習した後なのでRubyと絡めて記事を書くこともあります。

ファイルの分割

 コードの量が増えてくるとコードの修正が大変になります。1つのファイルに書くのではなく、複数のファイルに分割することで管理しやすくなります。今回は分割する時のコードの書き方を説明します。
 ファイルを分割する時は、それぞれのファイルを関連付け必要な値を渡さなければなりません。
「export default クラス名や定数等」とクラスや定数の下に書く事で他のファイルへとエクスポートできます。
※因みにexportは英語で「輸出」を意味します。

animal.js
class Animal {

}

//クラス定義後に書く事で他のファイルで使うように準備できます。
export default Animal;

次に、上記のAnimalクラスを使いたいファイルで「import クラス名や定数 from "./ファイル名"と書きます。
これにより、そのファイル内でAnimalクラスを使用できます。ファイル名の拡張子である".js"は省略できます。
※因みにimportは英語で「輸入」を意味します。

cat.js
//この記述でcat.js内でanimal.jsで定義したAnimalクラスを使用できる。
import Animal from "./animal"

デフォルトエクスポート

これまで書いていたexport defaultですが、この書き方は「デフォルトエクスポート」と言います。importする時にfrom"./ファイル名"とファイルを指定すると思いますが、この記述を書くと自動的にexportに書かれているクラスや定数が読み込まれます。先程「import クラス名や定数 from "./ファイル名"」と書くと説明しましたが、ここにあるクラス名や定数名はexportに書いてあるものと一致しなくても実は大丈夫です。importでファイル名を指定した時点で、自動的にexport defaultに書かれているクラス名や値がインポートされます。

animal.js
class Animal {

}

export default Animal;

cat.js
//クラス名がAnimalじゃなくても問題ない。このCat.jsファイル内では、AnimalクラスはBeastクラスとして扱われる。
import Beast from "./animal"

ただし、エクスポートできるのは1つの値で、複数のクラスや定数をエクスポートしようとするとエラーが出ます。

名前付きエクスポート

名前付きエクスポートとはdefaultと書かずに名前を指定してエクスポートする方法で、名前は{}で囲います。

dog.js
const dog1 = "チワワ"

//定数名を指定してエクスポート
export {dog1};

インポートする時も名前を書きます(importとfromの間)

script.js
//インポートも定数名を書く。
import {dog1} from"./dog"

console.log(dog1);

名前付きエクスポートはデフォルトエクスポートと違い複数のクラス及び定数をエクスポートすることができます。

dog.js
const dog1 = "チワワ"
const dog2 = "チャウチャウ"

//{}の中にエクスポートしたい物を全て書く
export {dog1, dog2};

script.js
//インポートも同様に全て書く
import {dog1, dog2} from"./dog"

console.log(dog1);
console.log(dog2);

パッケージ

Javascriptには「パッケージ」と呼ばれる誰かが既に作ってそのまま支える物があります。自分のプログラムでも引用して使用できます。フレームワークと同じ物だと思います。今回は以下のパッケージを使用しました。

chalk : コンソールに出力する文字に色を付けられる。
redline-sync : コンソールに値を手入力できる(Rubyのgetsのような機能)

パッケージを使用する時は、importでパッケージをインポートします(exportはいらない)。

script.js
//定数はなんでも良いが、同じ名前の方がわかりやすい。
import chalk from "chalk";

インポートしたファイルではパッケージの機能を使えるようになります。

script.js
//文字列をchalk.yellow等で囲むと色が変化する。
import chalk from "chalk";
//文字がイエローになる
console.log(chalk.yellow("Hello World"));
//背景色がシアンになる(bgはback groundの略)
console.log(chalk.bgCyan("Hello World"));

redline-syncというパッケージを使用すると、コンソール上に値(文字)を入力することができ、その値をプログラム上で使用できるようになります。今回はredlineSyncという定数としてredline-syncを使用できるようにしていますが、readlineSync.question("質問文")というコードを書くことで、質問文が表示され、文字が入力できるようになります。
整数を入力したい時はquestionではなくquestionIntと書きます(integerのintだと思う。)

script.js
//redline-syncをredlineSyncとしてインポートする。
import redlineSync from "redline-sync";

//質問文が表示されて、文字を入力できるようになる。入力された文字は定数wordに代入される。
const word = readlineSync.question("文字を入力してください : ");
console.log(`${word}と入力されました。`)

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

あるだけで一寸嬉しいちょっとした簡易ブックマークレット(たち)

概要

最近自分で作ったブックマークレットの内、
利用頻度が高く、作って良かったと感じたものをいくつか記載。

※『作った』とかいう表現は大げさだけど

ブックマークレットたち

上へ

所謂『1つ上の階層』に移動する。

javascript:(function(){
  window.location.href = window.location.href.replace(/[^\/]+\/?$/g, '');
})();
☝ (ブックマーク登録用)
javascript:(function(){window.location.href = window.location.href.replace(/[^\/]+\/?$/g, '');})();

title

ページ タイトルを入力ダイアログで表示し、コピーを補助する。

title
javascript:(
  function(){window.prompt('page title', document.title);
})();
title (ブックマーク登録用)
javascript:(function(){window.prompt('page title', document.title);})();

(補足)

調べ物などをしていてWEBページをメモる時に
URLと一緒にタイトルも一緒に欲しくなることが多い。
firefoxなどではページのプロパティ画面があったのでchromeだと同様のものがなく、また元々手数も多かった。
ページを非破壊でコピーまですることは出来ないので、
入力ダイアログを代用している形。

picture size

画像の解像度を表示。

picture size
javascript:(function(){
  var img = new Image();
  img.onload = function () {
    Promise.resolve().then(function() {
      window.prompt('width x height', '' + img.width + 'x' + img.height + '');
    });
  };
  img.src = location.href;
})();
picture size (ブックマーク登録用)
javascript:(function(){var img = new Image(); img.onload = function () {Promise.resolve().then(function() { window.prompt('width x height', '' + img.width + 'x' + img.height + '');});}; img.src = location.href;})();

(補足)

一応chromeでもファイル名の末尾に解像度は出ているものの、
ファイル名が長くなる + 沢山タブを開いてると、すぐに見えなくなる。
結果的には再ダウンロードがかかってしまうので、トラフィックにはあまり優しくない。

user agent

そのまま。
javascriptで取得出来るUserAgentを表示。

useragent
javascript:(function(){
  window.prompt('user agent', window.navigator.userAgent);
})();
useragent (ブックマーク登録用)
javascript:(function(){window.prompt('user agent', window.navigator.userAgent);})();

amazondp

amazonの商品へのリンク
 ****/dp/****/
を抜き出して遷移。

amazondp
javascript:(function() {
  var p = window.location.pathname.match(/\/dp\/[^\/?#=&]+/g);
  if(p) {
    window.location.href = window.location.origin + p[0];
  };
})();
amazondp (ブックマーク登録用)
javascript:(function(){var p = window.location.pathname.match(/\/dp\/[^\/?#=&]+/g); if(p) {window.location.href = window.location.origin + p[0];};})();

(補足)

AmazonのURLは共有しようとすると○したくなるくらい長い。
~商品名とかURLに埋め込む必要あんの???

openpopup

今開いているページをポップアップで表示。

openpopup
javascript:(function() {
    window.open(window.location.pathname, undefined, 'menubar=no, toolbar=no');
})();
openpopup (ブックマーク登録用)
javascript:(function() { window.open(window.location.pathname, undefined, 'menubar=no, toolbar=no');})();

(補足)

漫画は縦長が多いので、どうしてもアドレスバーやタブバーなどの分の解像度がもったいなくなる。
ポップアップで開くことで高さを稼いだりできる。
ただし、ポップアップ ブロッカーなどで起動の確実性は保証できない。
また、あくまでURLで開き直しているだけなので、POST系などのページでは遷移出来ない。

余談

ブラウザ拡張のツールバー文化は完全廃れたけど。
ブックマークバーとブックマークレットの併用でも、
ブラウザを便利にできることは多いので、もっと活用していけたらなぁと思う、

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

P5.js 日本語リファレンス(textDescent)

このページでは「P5.js 日本語リファレンス」 の textDescent関数を説明します。

textDescent()

説明文

現在のフォントの現在のサイズでディセントを返します。ディセントとはベースラインより下のディセンダーが最も長い文字のピクセル単位の距離を表します。
ascentDescent.png

構文

textDescent()

戻り値

Number:ディセンダー(ピクセル単位)

例1

function setup() {
  createCanvas(200, 200);

  let base = 100;

  // ディセントに乗ずる係数(フォントごとに異なります)
  let scalar = 0.6;

  textSize(32);

  // ディセントを算出します
  let desc = textDescent()  * scalar;

  // ベースラインにラインを描画します
  stroke(255, 0, 0);
  line(0, base, 50, base);

  // 文字のボトムにラインを描画します
  stroke(0, 0, 255);
  line(0, base + desc, 50, base + desc);

  // ベースラインにテキストを描画します
  text('dp', 0, base);

  textSize(64);

  // ディセントを算出します
  desc = textDescent()  * scalar;

  // ベースラインにラインを描画します
  stroke(255, 0, 0);
  line(60, base, 140, base);

  // 文字のボトムにラインを描画します
  stroke(0, 0, 255);
  line(60, base + desc, 140, base + desc);

  // ベースラインにテキストを描画します
  text('dp', 60, base);
}

実行結果

https://editor.p5js.org/bit0101/sketches/oaP4skQVW

著作権

p5.js was created by Lauren McCarthy and is developed by a community of collaborators, with support from the Processing Foundation and NYU ITP. Identity and graphic design by Jerel Johnson.

ライセンス

Creative Commons(CC BY-NC-SA 4.0) に従います。

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

P5.js 日本語リファレンス(textAscent)

このページでは「P5.js 日本語リファレンス」 の textAscent関数を説明します。

textAscent()

説明文

現在のフォントの現在のサイズでアセントを返します。アセントとはベースラインより上の最も高い文字の距離をピクセル単位で表したものです。
ascentDescent.png

構文

textAscent()

戻り値

Number:アセント(ピクセル単位)

例1

function setup() {
  createCanvas(200, 200);

  let base = 100;

  //アセントに乗ずる係数(フォントごとに異なります)
  let scalar = 0.9;

  textSize(32);

  // アセントを算出します
  let asc = textAscent()  * scalar;

  // ベースラインにラインを描画します
  stroke(255, 0, 0);
  line(0, base, 50, base);

  // 文字のトップにラインを描画します
  stroke(0, 0, 255);
  line(0, base - asc, 50, base - asc);

  //ベースラインにテキストを描画します
  text('dp', 0, base);

  textSize(64);

  // アセントを算出します
  asc = textAscent()  * scalar;

  // ベースラインにラインを描画します
  stroke(255, 0, 0);
  line(60, base, 140, base);

  // 文字のトップにラインを描画します
  stroke(0, 0, 255);
  line(60, base - asc, 140, base - asc);

  //ベースラインにテキストを描画します
  text('dp', 60, base);
}

実行結果

https://editor.p5js.org/bit0101/sketches/t7SwDYYj0

著作権

p5.js was created by Lauren McCarthy and is developed by a community of collaborators, with support from the Processing Foundation and NYU ITP. Identity and graphic design by Jerel Johnson.

ライセンス

Creative Commons(CC BY-NC-SA 4.0) に従います。

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

P5.js 日本語リファレンス(textWidth)

このページでは「P5.js 日本語リファレンス」 の textWidth関数を説明します。

textWidth()

説明文

文字または文字列の幅(ピクセル単位)を計算して返します。

構文

textWidth(theText)

パラメタ

  • theText
    String:計算する文字または文字列

戻り値

Number:文字または文字列の幅(ピクセル単位)

例1

function setup(){
  createCanvas(300, 300);

  textSize(14);

  let aChar = 'P';

  // テキストサイズ14の文字幅を取得します
  let cWidth = textWidth(aChar);

  text(aChar, 0, 40);

  // 取得したサイズの位置にラインを描画します
  line(cWidth, 0, cWidth, 50);

  let aString = 'p5.js';

  // テキストサイズ14の文字列幅を取得します
  let sWidth = textWidth(aString);

  text(aString, 0, 85);

  // 取得したサイズの位置にラインを描画します
  line(sWidth, 50, sWidth, 100);

  textSize(28);

  aChar = 'P';

  // テキストサイズ28の文字幅を取得します
  cWidth = textWidth(aChar);

  text(aChar, 100, 40);

  // 取得したサイズの位置にラインを描画します
  line(cWidth + 100, 0, cWidth + 100, 50);

  aString = 'p5.js';

  // テキストサイズ28の文字列幅を取得します
  sWidth = textWidth(aString);

  text(aString, 100, 85);

  // 取得したサイズの位置にラインを描画します
  line(sWidth + 100, 50, sWidth + 100, 100);
}

実行結果

https://editor.p5js.org/bit0101/sketches/WSc3auwdt

著作権

p5.js was created by Lauren McCarthy and is developed by a community of collaborators, with support from the Processing Foundation and NYU ITP. Identity and graphic design by Jerel Johnson.

ライセンス

Creative Commons(CC BY-NC-SA 4.0) に従います。

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

P5.js 日本語リファレンス(textStyle)

このページでは「P5.js 日本語リファレンス」 の textStyle 関数を説明します。

textStyle()

説明文

システムフォントのテキストスタイルを NORMAL、ITALIC、BOLD または BOLDITALIC に設定または取得します。
注:これは CSS スタイルによってオーバーライドされる場合があります。非システムフォント(OpenTypem、TrueType など)の場合は, 代わりにスタイル付きフォントをロードしてください。

構文

textStyle(theStyle)

textStyle()

パラメタ

  • theStyle 定数:設定するテキストスタイル(NORMAL、ITALIC、BOLD、BOLDITALIC)

例1

function setup() {
  createCanvas(200, 200);

  strokeWeight(0);
  textSize(12);

  textStyle(NORMAL);
  text('Font Style Normal', 10, 15);

  textStyle(ITALIC);
  text('Font Style Italic', 10, 40);

  textStyle(BOLD);
  text('Font Style Bold', 10, 65);

  textStyle(BOLDITALIC);
  text('Font Style Bold Italic', 10, 90);
}

実行結果

https://editor.p5js.org/bit0101/sketches/_jPKbmCl7

著作権

p5.js was created by Lauren McCarthy and is developed by a community of collaborators, with support from the Processing Foundation and NYU ITP. Identity and graphic design by Jerel Johnson.

ライセンス

Creative Commons(CC BY-NC-SA 4.0) に従います。

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

P5.js 日本語リファレンス(textSize)

このページでは「P5.js 日本語リファレンス」 の textSize関数を説明します。

textSize()

説明文

現在のフォントサイズを設定/取得します。フォントサイズはピクセル単位です。

構文

textSize(theSize)

textSize()

パラメタ

  • theSize
    Number:設定するフォントサイズ(ピクセル単位)

例1

function setup() {
  createCanvas(200, 200);

  textSize(12);
  text('Font Size 12', 10, 30);
  textSize(14);
  text('Font Size 14', 10, 60);
  textSize(16);
  text('Font Size 16', 10, 90);
}

実行結果

https://editor.p5js.org/bit0101/sketches/68BT2rvNr

著作権

p5.js was created by Lauren McCarthy and is developed by a community of collaborators, with support from the Processing Foundation and NYU ITP. Identity and graphic design by Jerel Johnson.

ライセンス

Creative Commons(CC BY-NC-SA 4.0) に従います。

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

P5.js 日本語リファレンス(textLeading)

このページでは「P5.js 日本語リファレンス」 の textLeading関数を説明します。

textLeading()

説明文

テキストの行間の間隔をピクセル単位で設定/取得します。この設定は textSize() の以降のすべての呼び出しで使用できます。

構文

textLeading(leading)

textLeading()

パラメタ

  • leading
    Number:設定する行間隔のサイズ(ピクセル単位)

例1

function setup(){
  createCanvas(200, 200);
  textSize(12);

  // 表示するテキスト。 '\n' は「改行」文字です
  let lines1 = 'ABC\nDEF\nGHI\nJKL\nMNO\nPQR\nSTU';
  // 行間隔を10に設定します
  textLeading(10);
  text(lines1, 10, 25);

  // 表示するテキスト。 '\n' は「改行」文字です
  let lines2 = 'ABC\nDEF\nGHI\nJKL';
  // 行間隔を20に設定します
  textLeading(20);
  text(lines2, 50, 25);

  // 表示するテキスト。 '\n' は「改行」文字です
  let lines3 = 'ABC\nDEF\nGHI';
  // 行間隔を30に設定します
  textLeading(30);
  text(lines3, 90, 25);
}

実行結果

https://editor.p5js.org/bit0101/sketches/hMPn3Obkn

著作権

p5.js was created by Lauren McCarthy and is developed by a community of collaborators, with support from the Processing Foundation and NYU ITP. Identity and graphic design by Jerel Johnson.

ライセンス

Creative Commons(CC BY-NC-SA 4.0) に従います。

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

P5.js 日本語リファレンス(textAlign)

このページでは「P5.js 日本語リファレンス」 の textAlign関数を説明します。

textAlign()

説明文

テキストを描画するための現在の配置を設定します。 horizAlign(LEFT、CENTER、RIGHT) および vertAlign(TOP、BOTTOM、CENTER、BASELINE)の2つの引数を受け入れます。

horizAlign パラメータは text() 関数のx値を参照し、vertAlign パラメータはy値を参照しています。

したがって、textAlign(LEFT) と記述する場合、テキストの左端を text() で指定したx値に揃えます。 textAlign(RIGHT, TOP)と書くとテキストの右端をx値に、テキストの上端をy値に揃えます。

構文

textAlign(horizAlign, [vertAlign])

textAlign()

パラメタ

  • horizAlign

    定数:水平方向の配置。LEFT, CENTER または RIGHT

  • vertAlign

    定数:垂直方向の配置。TOP, BOTTOM, CENTER または BASELINE(オプション)

例1

function setup(){
  textSize(16);
  strokeWeight(0.5);
  line(width/2, 0, width/2, height);

  // horizAlign = RIGHT のとき
  textAlign(RIGHT);
  text('ABCD', 50, 30);

  // horizAlign = CENTER のとき
  textAlign(CENTER);
  text('EFGH', 50, 50);

  // horizAlign = LEFT のとき
  textAlign(LEFT);
  text('IJKL', 50, 70);
}

実行結果

https://editor.p5js.org/bit0101/sketches/LFTwB-_ak

例2

function setup(){
  createCanvas(120, 120);

  textSize(16);
  strokeWeight(0.5);

  // vertAlign = TOP のとき
  line(0, 12, width, 12);
  textAlign(CENTER, TOP);
  text('TOP', 0, 12, width);

  // vertAlign = CENTER のとき
  line(0, 40, width, 40);
  textAlign(CENTER, CENTER);
  text('CENTER', 0, 40, width);

  // vertAlign = BASELINE のとき
  line(0, 68, width, 68);
  textAlign(CENTER, BASELINE);
  text('BASELINE', 0, 68, width);

  // vertAlign = BOTTOM のとき
  line(0, 110, width, 110);
  textAlign(CENTER, BOTTOM);
  text('BOTTOM', 0, 110, width);
}

実行結果

https://editor.p5js.org/bit0101/sketches/xLU2NwpLc

著作権

p5.js was created by Lauren McCarthy and is developed by a community of collaborators, with support from the Processing Foundation and NYU ITP. Identity and graphic design by Jerel Johnson.

ライセンス

Creative Commons(CC BY-NC-SA 4.0) に従います。

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

JavaScriptの日付フォーマットはこれだけあれば十分な気がする

日付フォーマットは言語やライブラリによって微妙に書き方が違ったりして覚えるのが面倒です。

JavaScriptならライブラリ不要で以下の定義を入れておけば足りそうな気がしたのでメモしておきます。

Date.prototype.format = function(formatter, utc) {
  var month_en = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
  var wday_en = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
  var wday_ja = '日月火水木金土';
  return formatter(utc ? {
    year: this.getUTCFullYear(),
    month: ('0' + (this.getUTCMonth() + 1)).slice(-2),
    month_en: month_en[this.getUTCMonth()],
    day: ('0' + this.getUTCDate()).slice(-2),
    wday: this.getUTCDay(),
    wday_ja: wday_ja.charAt(this.getUTCDay()),
    wday_en: wday_en[this.getUTCDay()],
    hour: ('0' + this.getUTCHours()).slice(-2),
    minute: ('0' + this.getUTCMinutes()).slice(-2),
    second: ('0' + this.getUTCSeconds()).slice(-2),
    ms: ('00' + this.getUTCMilliseconds()).slice(-3)
  } : {
    year: this.getFullYear(),
    month: ('0' + (this.getMonth() + 1)).slice(-2),
    month_en: month_en[this.getMonth()],
    day: ('0' + this.getDate()).slice(-2),
    wday: this.getDay(),
    wday_ja: wday_ja.charAt(this.getDay()),
    wday_en: wday_en[this.getDay()],
    hour: ('0' + this.getHours()).slice(-2),
    minute: ('0' + this.getMinutes()).slice(-2),
    second: ('0' + this.getSeconds()).slice(-2),
    ms: ('00' + this.getMilliseconds()).slice(-3)
  });
};

※IE8とかでも動くような書き方にしています。

//年月日曜日なら
new Date().format(({year, month, day, wday_ja}) => `${year}${month}${day}日(${wday_ja})`)
//=> 2020年06月19日(金)
//前0を消したいならNumber()で
new Date().format(({year, month, day, wday_ja}) => `${year}${Number(month)}${Number(day)}日(${wday_ja})`)
//=> 2020年6月19日(金)
//年月なら
new Date().format(({year, month}) => `${year}/${month}`)
//=> 2020/06
//時間なら
new Date().format(({hour, minute, second, ms}) => `${hour}:${minute}:${second}.${ms}`)
//=> 17:51:33.491
//分割代入やアロー関数、テンプレートリテラルが使えない場合は
new Date().format(function(t) { return t.hour + ':' + t.minute + ':' + t.second + ':' + t.ms })
//=> 17:51:33.491
//HTTPヘッダ用日付形式なら
new Date().format(t => `${t.wday_en.slice(0, 3)}, ${t.day} ${t.month_en.slice(0, 3)} ${t.year} ${t.hour}:${t.minute}:${t.second} GMT`, true)
//=> Fri, 19 Jun 2020 09:20:58 GMT

という感じで、どんな形式でもわりと直感的にかけるのではと思いました。

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

ビデオチャット作るときの逆引きTipsまとめ

リモートワークのため社内用ビデオチャットツールを先日作ったのですが、せっかくなので一部をオープンソースにしてGitHubにあげておきました。
ukkz/v-sky: Single page video chat appplication
※リファクタしてないので見辛いです

SPAなのでGiHub Pages上でそのまま利用もできます。
https://ukkz.github.io/v-sky

で、一旦開発が落ち着いたので、作業時のメモを簡単に整理して公開しておくことにします。

構成

  • Vue.js (@vue/cli v4.3.1)
  • Vuetify @2.3.1
  • Vuex @3.4.0
  • Vue-Router @3.3.4
  • SkyWay

まとめ一覧

ビデオストリームの情報を知りたい

使ったところ:実際の映像アスペクト比とブラウザ画面のアスペクト比から複数映像配置時の自動最適化 

VideoTrack.getSettings()でいろいろ情報がとれました。

// メディアストリームオブジェクト
const my stream = new MediaStream;
// ビデオトラックの配列(ないこともある)
const video_tracks = mystream.getVideoTracks();
// 少なくとも1つのビデオトラックがあるとき、そのトラックの情報を見る(空配列はtrue判定になるので必ずインデックス0を確認する)
if (video_tracks[0]) {
  const track_settings = video_tracks[0].getSettings();
  console.log( track_settings );
  /* 検出例(640x480の映像のとき)
    aspectRatio: 1.3333333333333333,
    deviceId: "1d3fa9…",
    frameRate: 30.000030517578125,
    groupId: “66fa41…",
    height: 480,
    resizeMode: "none",
    width: 640,
  */
}

アス比は循環小数になることがあるためあまり使い勝手がよくなさそうです。
ストリームやトラックに関しては以下がたいへん詳しく書かれておりました。
getUserMedia()の設定項目|npaka|note

ビデオタグに関して幅と高さを知りたい・設定したい

使ったところ:映像にcanvasをオーバーレイさせるときのcanvasサイズの指定 

<video id="my-video" :srcObject.prop="my_stream" width="160px" height="120px"></video>
<style>
#my-video {
  width: 320px;
  height: 240px;
  background-color: red;
}
</style>

上記のDOMの場合のビデオタグの大きさは、インライン指定よりもCSS指定のほうが優先されます。
実際のビデオトラック(映像)はCSSで指定されたサイズ(CSSしていなければインライン指定サイズ)の枠内に収まるように配置されます。
上記で枠内の余白部分はbackground-colorで指定された色になります。
CSSもインラインもどちらも指定されていなければ、ビデオトラックのオリジナルサイズで配置されます。

const video_element = document.getElementById('my-video’);
// 以下、取得できる様々なサイズの例
console.log( video_element.width );        // 160px(インライン指定の幅・未指定ならundefined)
console.log( video_element.height );       // 120px(インライン指定の高さ・未指定ならundefined)
console.log( video_element.offsetWidth );  // 320px(CSS指定の幅・mounted前ならundefined)
console.log( video_element.offsetHeight ); // 240px(CSS指定の高さ・mounted前ならundefined)
console.log( video_element.videoWidth );   // 640px(ビデオトラックの実際の幅・メタデータ受信前ならundefined)
console.log( video_element.videoHeight );  // 480px(ビデオトラックの実際の高さ・メタデータ受信前ならundefined)

メディアストリームをビデオタグに動的に追加したい [Vue.js]

使ったところ:相手からストリームを受信する前にvideoタグをDOMにマウントしたいとき 

<video :srcObject.prop="my_stream"></video>

:srcObject.propdataで定義した変数を指定しておいて、相手からストリーム受信したり自分がgetUserMediaしたりしたタイミングでメディアストリームオブジェクト(空の場合new MediaStream()で生成)を入れればよいです。
Vue歴2ヶ月ですが、リアクティブっていいなあと思いました。

ビデオタグのプロパティについて

使ったところ:ビデオタグぜんぶ(ほぼテンプレ構文) 

<video :srcObject.prop="my_stream" muted autoplay playsinline></video>

muted:音声再生しません。自身のストリームだとハウリング起こすので必須の属性です。
autoplay:mutedとともに有効のとき自動再生します。カメラからのストリームだとこれがないとそもそもデータが流れないので表示されません。インライン(videoタグ内)で指定しない場合は、スクリプト内のどこかで明示的にplay()を実行しないと再生されません。
playsinline:インライン(Webページ上の表示そのまま)で再生します。これがないとiOSでは自動的に映像がフルスクリーンになってしまうのでほぼ必須です。

ボタンの背景色にあわせて文字色の白黒を変えたい [Vuetify]

使ったところ:ボタンのデザイン全般 

Vuetifyのv-btnでは基本的にdarkプロパティをつけておくと良さそうです。
背景色にあわせて文字色も自動で変えてくれる唯一のコンポーネントのようです。

<v-btn rounded dark color="blue darken-4" @click=“any">
  <v-icon>mdi-message-text</v-icon>
  チャット
</v-btn>

v-dialogを使った子コンポーネントを親コンポーネント側から開閉させたいが反応しない [Vuetify, Vue.js:v-model]

使ったところ:ボタンクリックでテキストチャットダイアログを開くとき 

v-dialogv-ifなどではなくv-modelで開閉します。
(開いているときにダイアログ外側をクリックorタップで閉じれるようにするため)
このv-dialogを用いた子コンポーネントを親コンポーネントから制御するときは、computedにおいてゲッターとセッターを利用するとうまくいきます。
Vue.jsのv-modelを正しく使う - Qiita

親コンポーネント
<template>
  <v-container>
    <!-- v-dialogを開くボタン -->
    <v-btn @click.stop="chat_open = true">チャットを開く</v-btn>
    <!-- :showで開く・toggleイベント受信で閉じる -->
    <ChatWindow :show="chat_open" @toggle="chat_open = $event" />
  </v-container>
</template>

<script>
  data() {
    return {
      chat_open: false,
    }
  }
</script>
子コンポーネント
<template>
  <v-dialog v-model="toggle">
    // 略
  </v-dialog>
</template>

<script>
  name: 'ChatWindow',
  props: {
    show: {
      type: Boolean,
      required: true,
      default: false,
    },
  },
  computed: {
    toggle: {
      get() { return this.$props.show },
      set(v) { this.$emit('toggle', v) }
    },
  },
</script>

ゲッターはprops変数を受け取り、セッターは$emitで親コンポーネントに変化を通知します。
親側では変化を受信してpropsに与えた変数を子側から受け取った$event値に変更します。

チェックボックスなど設定値の変化でVuexの値を変えたい [Vue.js:v-model]

使ったところ:設定ダイアログで特定機能をスイッチボタンでON/OFFするとき 

v-dialogの例と同様で、入力系コンポーネントでv-modelを使うときはゲッターとセッターを使います。
ゲッターは$store.stateから取り出し、セッターでは$store.commitでVuexにセットします。

<template>
  <v-container>
    <v-switch v-model="speech_onoff" :label="` 音声認識:${(speech_onoff)?'有効':'無効'} `"></v-switch>
  </v-container>
</template>

<script>
  computed: {
    speech_onoff: {
      get() { return this.$store.state.config.speech_recognition },
      set(onoff) { this.$store.commit('speechConfig', onoff) },
    },
  },
</script>

テキストチャットで長文入力したときに折り返しできない・overflowやword-breakが効かない [Vuetify]

使ったところ:テキストチャット内の吹き出し部分(個別の発言) 

pタグのようなブロック要素で囲みtext-wrapクラスを適用させましょう。
(Vuetifyのpタグはデフォルトでmargin-bottomが入ってしまうのでmb-0クラスと組み合わせるとよい)
text-align: left;にしておくと折り返しても左寄せで表示してくれます。

文字起こししたい

使ったところ:そのまま(喋った内容を文字列にしてテキストチャットに逐次送信) 

SpeechRecognition(Web Speech API)が便利です。すごく簡単。
Webページでブラウザの音声認識機能を使おう - Web Speech API Speech Recognition - Qiita

音声入力を明示的に指定できるわけではない(というか選べない)みたいなので、マイクミュート時は手動でabortすべきですね。
2020年6月現在もChromiumベースのブラウザしか対応していないので注意です。
Samsung, Baidu, QQといった中韓系ブラウザは利用者層が音声入力を好むためか対応しておりなかなか興味深いです。

背景切り抜きなどの加工をしたい

使ったところ:画面共有とカメラ映像の合成(公開してないコンポーネントで利用) 

今回オープン版には実装してませんが内部向けで消したり合成したりの用途にはOpenCV.jsのパターンを使いました。
findContoursで輪郭抽出後、一番外の階層のある程度の大きさの輪郭をアルファで塗りつぶすだけで、クロマキー色を指定したりしなくても消したり背景合成したりすることができました。
上記参考記事では画像に対して利用されていますが、映像に対しても15fps程度の速さで処理できたのでそのうち別途記事かきます。

スクリーン共有でストリームを置き換えたら音声が送られなくなった

使ったところ:画面共有とカメラ映像の切替時 

getUserMediaで音声取得 + getDisplayMediaで映像取得 を組み合わせたメディアストリームを作ります。
MediaStream APIで画面キャプチャとマイクからの音声を同時に収録する - エムスリーテックブログ

const display_stream = await navigator.mediaDevices.getDisplayMedia({
  video: true,
  audio: false
});
const camera_stream = await navigator.mediaDevices.getUserMedia({
  video: true,
  audio: true
});
// ディスプレイ映像と外部音声のトラックから新しくメディアストリームを作成
const merged_stream = new MediaStream([ display_stream.getVideoTracks()[0], camera_stream.getAudioTracks()[0]  ]);
// または、既存のカメラからのストリームに画面キャプチャ用のトラック追加してもいい(受信側で区分する必要あり)
camera_stream.addTrack( display_stream.getVideoTracks()[0] );

スマホとPCでエレメントのサイズ・デザインを簡単に切り替えたい [Vutify]

使ったところ:レスポンシブデザインとして全体 

v-if$vuetify.breakpoint.smAndDownを組み合わせましょう。
ブレイクポイントはほかにもたくさんあります。
Breakpoints — Vuetify.js

v-ifv-elseで同一機能を異なるデザインで作成して並列に配置すると画面幅によって表示切り替えが可能です。
以下はボタンの例で、PCではアイコン+文字だが、smサイズ以下だとアイコンのみのボタンが表示されます。

<v-list-item-action>
  <v-btn v-if="$vuetify.breakpoint.smAndDown" fab depressed small @click="open = true"><v-icon>mdi-open</v-icon></v-btn>
  <v-btn v-else rounded depressed @click="open = true"><v-icon>mdi-open</v-icon>開く</v-btn>
</v-list-item-action>

ビデオを上下中央センタリングしたい

使ったところ(トリミングなし):自分の映像の確認用・チャットルーム内で話者の映像を拡大する用
使ったところ(トリミングあり):チャットルーム内での基本的な映像表示 

単純なCSSだけどメモ
一番外側の枠となるdivはあらかじめサイズが決まっている(100%指定とかでもよい)ものとし、その中にビデオを配置するときにどうしましょ?という話です。

<div class="video-frame">
  <video :srcObject.prop="local_media_stream" muted autoplay playsinline></video>
</div>

上記DOMが基本構造とします。

トリミングなし(上下または左右に余白を作る)

div.video-frame {
  position: relative;
  background-color: black;
  width: 100%;  // 枠幅:固定値でもよい
  height: 100%; // 枠高さ:固定値でもよい
}

div.video-frame video {
  position: absolute;
  top: 50%; left: 50%;
  transform: translateY(-50%) translateX(-50%) scale(-1, 1);
  width: auto;
  height: auto;
  max-width: 100%;
  max-height: 100%;
}

ビデオ側でwidthheightautoにしつつも最大サイズを親要素の100%にしておくとアスペクト比を変えずにフィットさせられます。
このままだと左上に寄ってしまうので、topleftを使って親要素の上左からそれぞれ半分(つまり真ん中)に寄せつつ、translateで自要素の幅と高さ半分ずつ戻すことでセンタリングさせています。

また、センタリングとは関係ないですが、scale(-1, 1)は左右フリップして鏡状態にしてくれるので自分のカメラ映像の時のみ使います。transformは多重に適用できないため、別のクラスなどでtransform: scale(-1, 1);を指定してもうまく反映されません。そのためここでまとめて指定しています。

余白は外枠であるvideo-frameクラスで指定した背景色になります。

トリミングあり(上下をフィットさせ左右をカットする)

div.video-frame {
  position: relative;
  background-color: black;
  width: 100%;  // 枠幅:固定値でもよい
  height: 100%; // 枠高さ:固定値でもよい
  overflow: hidden; // はみ出ても表示させない
}
div.video-frame video {
  position: absolute;  
  left: 50%;
  transform: translateX(-50%);
  width: auto;
  height: 100%;
}

width: auto;height: 100%;で、アスペクト比を維持したまま高さが外枠に合わせられます。
そのあと左右方向に対してleft: 50%;translateX(-50%)でセンタリングさせています。

トリミングあり(左右をフィットさせ上下をカットする)

div.video-frame {
  position: relative;
  background-color: black;
  width: 100%;  // 枠幅:固定値でもよい
  height: 100%; // 枠高さ:固定値でもよい
  overflow: hidden; // はみ出ても表示させない
}
div.video-frame video {
  position: absolute;  
  top: 50%;
  transform: translateY(-50%);
  width: 100%;
  height: auto;
}

上下と左右、widthheightを入れ替えると左右方向のフィッティングおよび上下センタリングにも対応できます。

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

【Google Apps Scriptとスプレッドシートで在庫管理】選択範囲の中で取得した値を使って、別シートで計算する

はじめに

この動画を見ていただけると、大体何をやるのか理解していただけるかと。。

経緯

Google Apps Script 入力された文字を別シートにて検索し、その隣にある文字を書き出す
こちらの続き:sweat_smile:

スプレッドシートでアクセサリーの在庫管理をしていると、とてもめんどくさいのが以下の作業!
在庫として保有している材料の数 ー 今回使った材料の数 = 現在の在庫の数

アクセサリー作りで使う材料の数は多いようで、それを毎回手動でスプレッドシートを使い引き算するのが面倒だということですね。。。

用意するもの

在庫をまとめたシート

スクリーンショット 2020-06-19 17.33.59.jpg

作ったアイテムをまとめたシート

スクリーンショット 2020-06-16 21.37.07.jpg

全体の流れ

①「作ったアイテム」のシートで使った材料を範囲選択(動画参照)
②カスタムメニューでボタンを作っておいて、それを押すことで発動し、①で選択した範囲を2次元配列で取得
③「在庫」シートにて同じ名前の材料を検索し、2つ横のセルの値(各材料の在庫数)を取得
④②と③で取得した在庫数を引き算して、残った在庫数を書き出す

大体の処理の流れはこんな感じ。

全てのコード

コード.gs
// Spreadsheetが開かれた時に自動的に実行されます.
function onOpen() {
  // 現在開いている、スプレッドシートを取得します.
  var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();

  // メニュー項目を定義します.
  var entries = [
    {name : "在庫から使った数を減らす"  , functionName : "menuItem1"},
  ];
  // 「Custom」という名前でメニューに追加します.
  spreadsheet.addMenu("Custom", entries);
}

 // menuItem1をクリックした時の処理
function menuItem1() {
 //現在アクティブなシートの名前を取得
 var nowSheet = SpreadsheetApp.getActiveSheet().getName();
     //シート「作ったアイテム」以外で実行したらアラート
  if (nowSheet === '作ったアイテム') {
     var data = getData();
     matchData(data);
    } else {
    alert();
    }
}

function getData() {
    //現在のスプレットシートを取得する
    var sheet1 = SpreadsheetApp.getActiveSheet();
    var values = sheet1.getActiveRange().getValues();
    var numRows = sheet1.getActiveRange().getNumRows();

    return [values, numRows];
}

function matchData(data) {
    //現在のスプレッドシートを取得
 var ss = SpreadsheetApp.getActiveSpreadsheet();
   //シート「在庫」を取得
 var stockSheet = ss.getSheetByName('在庫');

  //使った材料の配列「values」をdataの中から取り出す
  var items = data[0];

 // ループする回数「numRows」をdataの中から取り出す
  var dataNumRows = data[1];
  var stopSetCount = dataNumRows - 1;

  for(var i = 0; i <= stopSetCount; i++){
    var item = items[i];

    var itemName = item[0];
    var usedCount = item[2];

    //シート「在庫」で材料名を探す
    var textFinder = stockSheet.createTextFinder(itemName);
    var ranges = textFinder.findAll();
    //材料名のセル範囲を取得
    var textPos = ranges[0].getA1Notation();
    //材料名の位置を取る
    var textPosCell = stockSheet.getRange(textPos);
    //材料名の2つ右隣のセルの値を取る
    var targetValue = textPosCell.offset(0, 2).getValue();
    //在庫の数から使った数を引く
    var afterUsedCount = targetValue - usedCount;
    textPosCell.offset(0, 2).setValue(afterUsedCount);
  }
}

function alert() {
  var ui = SpreadsheetApp.getUi();
  var title = '「作ったアイテム」のシートで実行してよ';
  var text = 'おかしくなるから!';
  ui.alert(title, text, ui.ButtonSet.OK_CANCEL);
}

軽く解説

なぜループする回数を-1するのか?

// ループする回数「numRows」をdataの中から取り出す
  var dataNumRows = data[1];
  var stopSetCount = dataNumRows - 1;

 for(var i = 0; i <= stopSetCount; i++){

範囲選択した部分が何行か?を取得して、ループを回す回数を決めているんだが、なぜ−1するのか?
結論を言うと、−1しないとループする回数が1回増えてしまうからだ。

もし範囲選択した部分が4行だったとする。すると4回ループしないといけない。
しかし、for分はvar i = 0;で0から始まってる。だから−1しないと、1回ループする回数が増えてしまいエラーが起こる。

なので、var i = 0;var i = 1;としても良い。

matchDataで受け取るdataは何?

 // menuItem1をクリックした時の処理
function menuItem1() {
 //省略
     var data = getData();
     matchData(data);
  //省略
}


function matchData(data) {
//省略
//使った材料の配列「values」をdataの中から取り出す
  var items = data[0];

 // ループする回数「numRows」をdataの中から取り出す
  var dataNumRows = data[1];

}

ここで受け取ってるのはgetData()で返されている値。
1つ目の配列(つまりvalues)が範囲選択した部分のデータが2次元配列で入っている。
2つ目の配列(つまりnumRows)が「範囲選択した部分は何行あったか?」と言うデータが入っている。

function getData() {
    //現在のスプレットシートを取得する
    var sheet1 = SpreadsheetApp.getActiveSheet();
    //getValueではなくgetValues。複数形のsがあるよ!
    var values = sheet1.getActiveRange().getValues(); 
    var numRows = sheet1.getActiveRange().getNumRows();

    return [values, numRows];
}

カスタムメニューはこの部分で作られてる

// Spreadsheetが開かれた時に自動的に実行されます.
function onOpen() {
  // 現在開いている、スプレッドシートを取得します.
  var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();

  // メニュー項目を定義します.
  var entries = [
    {name : "在庫から使った数を減らす"  , functionName : "menuItem1"},
  ];
  // 「Custom」という名前でメニューに追加します.
  spreadsheet.addMenu("Custom", entries);
}

 // menuItem1をクリックした時の処理
function menuItem1() {
 //現在アクティブなシートの名前を取得
 var nowSheet = SpreadsheetApp.getActiveSheet().getName();
     //シート「作ったアイテム」以外で実行したらアラート
  if (nowSheet === '作ったアイテム') {
     var data = getData();
     matchData(data);
    } else {
    alert();
    }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ESLint7系を使っていると「TypeError: createRequire is not a function」が出る

概要

ESLint 7 系とNode.js 12未満をつかっていると以下のエラーがでる場合がある

ESLint 7 がサポートしている Node.js バージョンは ^10.12.0 || >=12.0.0 となっています

↑だそうです。(コメントありがとうございました)

TypeError: createRequire is not a function
Referenced from: /something/.eslintrc
    at Object.resolve (/something/node_modules/eslint/lib/shared/relative-module-resolver.js:28:20)
    at ConfigArrayFactory._loadExtendedShareableConfig (/something/node_modules/eslint/lib/cli-engine/config-array-factory.js:854:39)
    at ConfigArrayFactory._loadExtends (/something/node_modules/eslint/lib/cli-engine/config-array-factory.js:763:25)
    at ConfigArrayFactory._normalizeObjectConfigDataBody (/something/node_modules/eslint/lib/cli-engine/config-array-factory.js:702:25)
    at _normalizeObjectConfigDataBody.next (<anonymous>)
    at ConfigArrayFactory._normalizeObjectConfigData (/something/node_modules/eslint/lib/cli-engine/config-array-factory.js:647:20)
    at _normalizeObjectConfigData.next (<anonymous>)
    at ConfigArrayFactory.loadInDirectory (/something/node_modules/eslint/lib/cli-engine/config-array-factory.js:495:28)
    at CascadingConfigArrayFactory._loadConfigInAncestors (/something/node_modules/eslint/lib/cli-engine/cascading-config-array-factory.js:355:46)
    at CascadingConfigArrayFactory._loadConfigInAncestors (/something/node_modules/eslint/lib/cli-engine/cascading-config-array-factory.js:374:20)

対策

その1

Node.jsを最新にする

その2

ESLintをダウングレードする

package.json (ESLintを6系にダウングレード)
"eslint": "^6.8.0",
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

javascript学習(2020/06/19)

setTimeoutメソッド

sample.js
setTimeout(ファンクション, 待ち時間)

待ち時間後にファンクションを一度だけ実行するメソッド。待ち時間はミリ秒で設定。ファンクション名の後ろに()はつけない。

日時を設定した状態でDateオブジェクトを初期化する

sample.js
new Date(,,,,,,ミリ秒);

padStartメソッド

sample.js
ある文字列.padStart(揃える文字列,埋め合わせ用の文字);

ある文字列をそろえる文字数に揃えるメソッド。もし、ある文字列の文字数が揃える文字数織少ない場合は先頭に埋め合わせ用の文字をくっつけて、文字数を揃える。

Stringオブジェクト

sample.js
String(数値データ).padStart(2,'0');

()内の数値データを文字列データに変換する。

URLを書き換える(新しいURLを指定する)

sample.js
location.href = 新しいURL;

cssセレクタで要素を取得する

sample.js
document.querySelector('CSSセレクタ');

document.querySelectorメソッドは()内に書かれたセレクタにマッチする要素を取得する。
複数の要素にマッチした場合は最初にマッチした要素の一つだけを取得。

selected属性の追加

sample.js
document.querySelector('option[value="index.html"]').selected = true;

ブール属性であるselected属性をtrueにする(代入する)と、selected属性がオンになる。この場合,
<option>タグにselected属性が追加される。

クッキー(cookie)

クッキーとは、ブラウザに保存される小さなデータのこと。クッキーのデータはブラウザとWebサーバーの間で送受信され、主にECサイトやSNSサイトなそでユーザーのログイン情報を管理するのに使われる。
クッキーは基本的にはブラウザとWebサーバーの間でデータをやり取りするものだが、javascriptからも読み取りと書き込みができる。javascriptでクッキーのデータを扱う場合は
・簡易的なアンケートや確認ボタンなどで過去に回答したことがあるかボタンをクリックしたことがあるかどうか
・そのwebサイトに何度訪問したか
・文字の大きさや背景色、使用言語などを変更できるサイトの場合は、その設定情報
などを保存するのに使われる。

指定した条件でクッキーを保存するjs-cookieライブラリのメソッド

sample.js
Cookie.set('クッキー名','',{expires: 有効期限});

js-cookieはオープンソースのライブラリ

HTML要素の削除

sample.js
取得した要素.removeChild(削除する要素)

取得した要素に含まれる子要素または孫要素のうち、()内のパラメータで指定した要素を削除する。

マッチする要素すべての取得

sample.js
document.querySelectorAll('CSSセレクタ');

()内で指定されたCSSのセレクタにマッチする要素すべてを取得する。
取得した要素は配列になる。

配列の繰り返し

sample.js
配列.forEach(function(item, index){
  処理内容
});

パラメータがitemで、これには配列の項目が一つ代入される。つまり、最初の繰り返しでは0番目の項目が代入される。2番目のパラメータはindexで配列の項目のインデックス番号が代入される。

javascriptでdata-ナンデモ属性も値を読み取る

sample.js
<タグ名 data-image="img.jpg">

imageの部分は自由に決めることができる

data-ナンデモ属性はナンデモの部分を自分で自由に決めてよい属性。
data-ナンデモ属性はjavascriptでその値を読み取るのに利用する。読み取るときは次のようにする。

sample.js
取得した要素.dataset.ナンデモのところにつけた名前

属性の値を読み取る

sample.js
取得した要素.属性

属性の値を書き換える

sample.js
取得した要素.属性 = ;

ブール属性の値を書き換えるには、実際の値ではなくtrueまたはfalseを代入する。

配列に保存されているデータの項目数を調べる

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

Vue CLI+ Laravel 5.8 で、チャット作成 firebase Cloud Messaging対応

概要

前の 、クロスドメイン構成の
Vue-CLI + Laravel 関連となり。チャット機能の作成です

・会員制となり、Google認証で
 ログインに対応しています。
・PWAも対応しています
・ 前のLaravel版チャットを、Vue CLIに移植した形です

構成

PWA
firebase Cloud Messaging / FCM
Notification API
Vue CLI
vue-router
vue/cli-service : 4.4.0
・API サービス:
Laravel 5.8
nginx
mysql
・フロント設置ドメイン ,Vue-CLI:
netlify / ホスティングサービス


画面

・web push 受信時の、通知API で。タスクバー表示

ss-chat-0619-noti.png

・チャット画面、詳細

ss-chat-0619-show.png

・一覧
左側に、参加チャットの表示
ss-chat-0619a.png


Vue components

・index
https://github.com/kuc-arc-f/vue_spa3a_4chat/blob/master/src/components/Chats/Index.vue

・create
https://github.com/kuc-arc-f/vue_spa3a_4chat/blob/master/src/components/Chats/new.vue

・show
https://github.com/kuc-arc-f/vue_spa3a_4chat/blob/master/src/components/Chats/show.vue

package.json

https://github.com/kuc-arc-f/vue_spa3a_4chat/blob/master/package.json


参考のページ

https://knaka0209.hatenablog.com/entry/lara58_31cross_chat

・Vue CLI+ Laravel 5.8で、クロスドメイン SPA構成/PWA対応のCRUD作成する。
https://qiita.com/knakaqi/items/1bae7a540aa13ce8233b


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

bootstrap5を使ってみたよ

bootstrap5をちょいつまみぐい(GUI)

現在、自社システムを絶賛開発中で、UI周りはbootstrapにしようかと思っており、
バージョン4で作っていたのですが、先日アルファー版公開されたってことで早速突っ込んでみた。
これは実験的なもので、システムもデモ版なので安定性より動作優先なので、
今後のメンテナンスなどによるバージョンアップを考慮して、導入検討したものとなります。
つまみぐいなので、紹介コードは「アラート」と「トースト」のみです。

時期みて、バージョン対応していこうかと思っていますので
今回は、バージョン別で書くのではなく、実行環境バージョンを調べ、
自動処理による未来処理コードで対応してます。

では早速、jsのコードを書いていきます。
ちなみに5系では、実際にjQueryの読み込みなしで動きました。
なお、注意して欲しいことは5系の場合、
この状態でjQueryが読み込まれているとバッディングしてうまく動かないかもしれません。

ただ、4系までは、jQueryの読み込みは必須ですので、
実際に試す際はしっかりと各バージョンにあった公式ドキュメントを読んで実装方法に従ってお試しください。

main.js
/*---------------------------------------------------- format ---------------------------------------------------------------*/
/** @:description 文字列フォーマットです。
 * String.prototype.format
 * @param  ex:) '{year}/{month}/{day}'.format({'year':year,'month':month,'day':day}) or '{year}/{month}/{day}'.format({'2017','11','01'})
 * @return {Object} or {String}
 */
// 存在チェック

if (String.prototype.format == undefined) {  
  /**
   * フォーマット関数
   */
  String.prototype.format = function(arg)
  {
    // 置換ファンク
    var rep_fn = undefined;
    // オブジェクトの場合
    if (typeof arg == "object") {
      rep_fn = function(m, k) { return arg[k]; }
    }
    // 複数引数だった場合
    else {
      var args = arguments;
      rep_fn = function(m, k) { return args[ parseInt(k) ]; }
    }
    return this.replace( /\{(\w+)\}/g, rep_fn );
  }
}
/*---------------------------------------------------- $ ---------------------------------------------------------------*/
/** @:description ID と Classを変換する関数。これはjQueryもどきで大幅な変更しないための策。
 * $('#id') or $('.className') or $('+name')
 * @param  ex:) {String} - let id = $('#id') or let classname = $('.className') or let name = $('+name')
 * @return {Object} - id OR class OR name Object.
 */
if (String.prototype.$ == undefined){
  $ = (x) => {
    const nem = /^[+]/g
    if (typeof x == "string") {
      if(x.match(nem)) return document.querySelector("input[name='{0}']".format(x.replace(nem, "")))
      return document.querySelector(x)
    }
    return x
  }
}


/*---------------------------------------------------- alertMessage ---------------------------------------------------------------*/
/** アラートメッセージを表示させる関数
 * alertMessage("Title", "Message", flag, delay)
 * @param  ({String},{Number},{Number}) - ex:) alertMessage("Message", flag, delay)
 * @return "undefined"
 */

let alertMessage = (msg, f, delay) => {
  let alertFlag = ["alert-primary","alert-secondary","alert-success","alert-danger","alert-warning","alert-info","alert-light","alert-dark"]
  let alertTag  = $("#alert_tag")
  let alertLen  = "alert_{0}".format(((parseInt(bootstrap.Tooltip.VERSION)>4)? alertTag.querySelectorAll("alert").length:$(".alert").length))
  let tagMsg    = `<div class="alert {1} alert-dismissible fade show {2}" role="alert" data-delay="{3}">
                    {0}
                    <button type="button" class="close" data-dismiss="alert" aria-label="Close">
                      <span aria-hidden="true">&times;</span>
                    </button>
                  </div>
                  `.format(msg.toString(), alertFlag[f], alertLen, delay)
  if((parseInt(bootstrap.Tooltip.VERSION)>4)){
    alertTag.insertAdjacentHTML('beforeend',tagMsg)
    var alertNode = document.querySelector('.{0}'.format(alertLen))
    var alert     = new bootstrap.Alert(alertNode)
    setTimeout(() => {alert.close()}, delay);
  }else{
    $(tagMsg).prependTo(alertTag)
    $("."+alertLen).toast('show')
  }
}

/*---------------------------------------------------- toastMessage ---------------------------------------------------------------*/
/** トーストを表示させる関数
 * toastMessage("Title", "Message", flag, delay)
 * @param  ({String},{String},{Number},{Number/min:500[ms]}) - :) toastMessage("Title", "Message", 1, 3000)
 * @return "undefined"
 */
let toastMessage = (title, msg, f, delay)  => {
  let toastTag = $("#toast_tag")
  let toastLen = "toast_{0}".format(((parseInt(bootstrap.Tooltip.VERSION)>4)? toastTag.querySelectorAll("toast").length:$(".toast").length))
  let tagMsg   = `<div class="toast {2}" role="alert" aria-live="assertive" aria-atomic="true" data-animation="true" data-delay="{3}">
                  <div class="toast-header">
                    <strong class="mr-auto">{0}</strong>
                    <button type="button" class="ml-2 mb-1 close" data-dismiss="toast" aria-label="Close">
                      <span aria-hidden="true">&times;</span>
                    </button>
                  </div>
                  <div class="toast-body">{1}</div>
                </div>
                `.format(title.toString(), msg.toString(), toastLen, delay)
  if((parseInt(bootstrap.Tooltip.VERSION)>4)){
    toastTag.insertAdjacentHTML('beforeend',tagMsg)
    var toastNode = document.querySelector('.{0}'.format(toastLen))
    var toast = new bootstrap.Toast(toastNode)
    toast.show()
  }else{
    $(tagMsg).prependTo(toastTag)
    $("."+toastLen).toast('show')
  }
}

未来のじぶんへの解説

jQueryのprependToに変わるコードを実装しました。
insertAdjacentHTMLbeforeendすることで指定タグに追加される。
イメージは、最初に追加されたのが下の方へ流れる感じです。

なおアラートは、5系の場合表示され続けるため、close処理をいれる必要がある。
そのほかにも書き方があるので追加表示ではなく、
静的なページの場合はそちらを選ぶといいと思う。

以上、また何かあれば更新しようと思う。

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

JAWS-UG CLI専門支部に捧ぐ - 手順実行の際にコピペを楽にするブックマークレット

JAWS-UG CLI専門支部 楽しいですよね。ご存じない方は 総合案内 あたり参照してみてください。

手順を実行する際に Web から CLI コマンドをコピペするのですが、ここで選択範囲をミスってコマンドエラーになると少し悲しいです。私は手先が器用ではないので、たまにありますw

というわけで、自分用のクイックハックとして「CLI コマンドをワンクリックで全選択する JavaScript コード」を書いてみました。ブックマークレット書くなんて何年振りだろう…

JavaScript コード

対象のページ をみると、CLI コードが記載されている HTML Element は以下の3種の CSS セレクターで選択できそうです。

  • pre.setenv
  • pre.checkenv
  • .highlight-sh .highlight

であればコードは簡単で、HTML 要素を選択して、onClick イベントを仕込めばok。たとえば以下のようになります。

document.querySelectorAll("pre.setenv, pre.checkenv, .highlight-sh .highlight").forEach(e => {
  e.addEventListener('click', function(){
    var r = document.createRange();
    r.selectNodeContents(this);
    var sel = window.getSelection();
    sel.removeAllRanges();
    sel.addRange(r);
  });
});

JAWS-UG CLI の手順ページでコンソールを開き、上記のコードをコピペして実行すれば、CLI コードをワンクリックで選択できるようになります。
image.png
あとは Ctrl-C Ctrl-V などで、コンソールにコピペすればok。

一応、Chrome と Firefox ブラウザで動作を確認してあります。

ブックマークレット化してみる

毎回 Web ブラウザのコンソールで実行するのも面倒なので、ブックマークレットにしておきましょう。ワンクリックで機能が使えるようになります。

Chrome の場合、まずは適当にブックマークしてタイトル(名前)を入力します。
image.png
「その他」をクリックして詳細設定の画面に移動して
image.png
以下のコードを URL 欄に設定すればokです。

javascript:document.querySelectorAll("pre.setenv, pre.checkenv, .highlight-sh .highlight").forEach(e => {e.addEventListener('click', function(){var r = document.createRange();r.selectNodeContents(this);var sel = window.getSelection();sel.removeAllRanges();sel.addRange(r);});});

後は手順ページで、この作成したブックマークレットをクリックすれば、ワンクリックでの CLI コード選択が使えるようになります。

これで手順をスピードアップし、もっと雑談チャットに参加しましょうw

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

8 Features of AngularJS that make it the Perfect Choice for Dynamic Web Apps

Angular has been around for about a decade. It is popular as a web application framework that allows users to build quality and easy maintenance web apps. Especially for applications that need to be designed to a large scale and require top-level performance. This overview will discuss some of the main features that make AngularJS a good choice for web development services.

Templates:

Angular uses standard HTML for a template. No changes are made by Angular or manipulation through strings. The input in this case is browser DOM. Not HTML string. The way this works is as follows: Templates are parsed into the DOM by the browser. This then goes on to be the input for the compiler. The DOM template provides any rendering instructions (directives). After that, all the directives go into the setup for data-binding, which gives you the application view.

For Angular, the data-bindings have nothing to do with innerHTML changes but are actually DOM transformations. The key difference to notice here is that instead of strings we are using DOM as the input. This means you can take the directive vocabulary and further expand on it to design your own rendering instructions. If you are a developer or designer, this offers a much closer workflow between you and your team.

MVC Use:

Model-View-Controller. The implementation of this model covers a few key elements. First, the data in the application. The model is objects or data in the script. This eliminates the need for a few things including special methods to access the objects, use any proxies, or go into framework classes. The system is straightforward and easy to use. No extra complications.

Second, the view is what you have after everything is compiled. This is the HTML parsed with any bindings or rendering.

Finally, the controller. The controller controls behavior. The main job is to set up the initial state and augment the scope. Here, the state is not stored by the controller and doesn’t do anything with remote services. By doing this, AngularJS gives you a better starting point to build your app. All together you can get to the data you need, define all behavior, and allows the controller to respond during interaction properly.

AngularJS requires Less Code:

Everyone wants to minimize the workload they have to do. For Angular, you won’t be writing more code for the MVC. You can attribute this to the simplicity of HTML when building the interface for your app. Everything is kept short. Removing the complexity from the elements and the attributes or tags that go into it. Manual moving of the data won’t matter as much because of the data binding helps handle that work. Plus, when it comes to the rendering instructions and application code, they are separate. This is really important because it means you can work on both at the same time. Dividing up the labor as effectively as possible.

When you start to get into application code you won’t need to be rewriting multiple parts of code for each device. You can easily rely on one code that goes with just one application. Then, you can move to other platforms with the one application you have ready.

Better Data-Binding:

Another feature that makes sure you don’t have to spend as much time putting together code. Many web apps will use most of their code on just interacting with the DOM. For Angular, you get to forget about that big chunk of code. Which is great because the majority of your effort should go into the actual app design.

With the data-binding features, you can take your model, which asks as a hub for all application updates or processes, and goes straight to the app view. No effort, and super easy. The old way was to go in and change the DOM yourself to meet whatever attributes required it. In this case, everything affects each other. The DOM makes the changes to the model, and the model moves changes to the DOM. Of course, the person behind the program has to handle all this and understand what is going on in order to update as necessary. The more size and complexity, the worse it will become. AngularJS removes any work in this department.

Better Communication

In a non-context sensitive system, you can run into trouble when messages need to be read by specific controllers with each node. This can get tricky when you are dealing with messages that could be read by parent or child controllers, but you need to go through one but not the other. Angular changes this up by having broadcast default all messages to child controllers. While emit will do the opposite.

You can also do get context-aware communication through the data-binding. As the controller moves a message to look at an update, it will also give the view of the update with changes to the properties. These properties will be on only one side of the controllers, and the other controllers do not always have to get the same properties. They could adjust them before they inherit the new properties. This whole process updates automatically by the other controllers as well.

Dependency Injection:

A lot of the features on this list are focused on things that make your life easier. Well, the included dependency injection is no different. It takes everything about the application and makes it more developer-friendly. The process eliminates you from having to make dependencies yourself or go out and discover them. You simply can ask for what you want, making understanding the process, and testing a whole lot easier. The Dependency Injection will take on the task of building and providing what you need.

All need to do is take service and go add it as a parameter. The instance will become available once the system realizes you requested that service. Or if you’d like, you can put together custom service requests for the dependency injection process. The more automation, the easier your life becomes!

Directives:

We mentioned these before, but the directives are a great feature that on their own that allows for much more control during design. It can be difficult to implement, but can easily become a rewarding process.

If you want to design your own HTML tags to build custom functions, you can use directives. For the creative developer, you can get quite unique with elements and DOM attributes. Manipulating the behavior to new results. If you follow the model of building your app with individual parts, you can make changing functionality much easier. Adding and deleting parts as needed, or making updates as you go.

Simple Testing:

Another streamlined and simplified process. You may be used to build a test page each time you needed to do testing. Then having the page test the results of the component. Well, not anymore. AngularJS has DI to manage the controllers for you and allow data to pass. The DI will generate the needed data to figure out the behavior of each controller.

You will also find a full and unit test runner built-in. The documentation allows the user to quickly see how everything should work. Figuring out issues or how your app is performing is now a breeze. It makes the whole process much easier and encourages better practices in writing testable applications.

Summary:

There is a lot to discover with AngularJS. These features are just the tip of the iceberg in what kind of functionality you can expect. A program is definitely worth checking out for anyone with web development services in mind.

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

Nodejs用のモデルライブラリを作ってみた【Express】

リポジトリ

https://github.com/kbc18a11/oreoreExpress
ライブラリ本体はoreoreExpress/database/AbstractModel.jsです。

解説用の構成や注意点

  • FW:Express
  • ディレクトリ構成:WebStormの新規Expressプロジェクトの状態にプロジェクト名/databaseプロジェクト名/modelというディレクトリを生成
  • 本記事はチーム開発のメンバーのマニュアルとしても書いていますので、ライブラリには直接関係ないことも書いています。

今回利用しているtestsテーブルは以下の構成になっています

id text created_at updated_at

使い方

リポジトリのものを利用するのであれば、以下のコマンドを実行してください。

npm install mysql2

ライブラリだけを使用するのであれば、mysql2date-utlisをインストールします

npm install mysql2
npm install date-utlis

そして以下のとおり、mysql2の設定ファイルを作ります。

プロジェクト名/database/mysqlConnection
const mysql = require('mysql2');
//MySQLの接続設定
const connection = mysql.createConnection({
    host: 'localhost',
    user: '',
    password: '',
    database: ''
}).promise();

module.exports = connection;

下記の通り、作りたいモデルクラスに設定します。
また以下のメソッドをオーバーライド、オーバーロードします。
abstractTABLE_NAME
abstractVALIDATIONRULES
update

メソッドabstractTABLE_NAMEは、連携しているテーブル名のです。
メソッドabstractVALIDATIONRULESは、バリエーションルールを記述する所です。
バリエーションのルールはvalidatorjsのものになります。
https://www.npmjs.com/package/validatorjs

プロジェクト名/model/Tests
const AbstractModel = require('./AbstractModel');
const connection = require('../database/mysqlConnection');
require('date-utils');

class Tests extends AbstractModel {
    constructor() {
        super();
    }

    /**
     * テーブル名
     * @override
     * @returns {string}
     */
    static get abstractTABLE_NAME() {
        return 'tests';
    }

    /**
     * バリデーションルール
     * @override
     * @return {Object}
     */
    static get abstractVALIDATIONRULES() {
        return {
            get:{
                rule: {
                    id: 'required|integer'
                },
                errorMessage: {
                    required: '必須項目です。',
                    integer: '数値で入力してください'
                }
            },
            //POSTリクエスト用
            post: {
                rule: {
                    text: 'required'
                },
                errorMessage: {
                    required: '必須項目です。',
                }
            },
            //PUTリクエスト用
            put: {
                rule: {
                    id: 'required|integer',
                    text: 'required'
                },
                errorMessage: {
                    required: '必須項目です。',
                    integer: '数値で入力してください'
                }
            },
            //DELETEリクエスト用
            delete: {
                rule: {
                    id: 'required|integer'
                },
                errorMessage: {
                    required: '必須項目です。',
                    integer: '数値で入力してください'
                }
            }
        };
    }

    /**
     * UPDATE文の準備を行って、親クラスのupdate()に実行をさせる
     * @param {Object} insertParam
     */
    static async update(insertParam) {
        //UPDATE文
        const sql = `UPDATE ${this.abstractTABLE_NAME} SET text = ?,updated_at = ? WHERE id = ?`;

        //create_at用の日付時間取得
        insertParam.updated_at = new Date().toFormat('YYYY-MM-DD HH:MI:SS');

        //SQLの実行
        await super.update(insertParam, sql);
    }
}

module.exports = Tests;

機能

基本的にライブラリの機能を使う場合は、Expressのルーティングファイルの無名関数にasyncを付与します。

プロジェクト名/routes/ルーティングファイル
router.get('/tests', async (req, res, next) => {

そして、ルーティングファイルで以下のライブラリを取り組みます。

プロジェクト名/routes/ルーティングファイル
const express = require('express');
const router = express.Router();
const validator = require('validatorjs');
const Tests = require('../model/Tests');

ライブラリでデータベースを操作するメソッドは、Promiseオブジェクトがかかわっているため、呼び出しの際には、awaitを付与します。

プロジェクト名/routes/ルーティングファイル
//レコードをすべて取得
await Test.all()
//引数idのカラム取得
await Test.find(id)
//引数idの存在確認を行う
await Test.existId(id)
//引数paramの値で新規登録を行う
await Test.insert(param)
//引数paramの値で更新を行う
await Test.update(param)
//引数idのレコードを削除する
await Test.delete(id)

all() レコードをすべて取得

プロジェクト名/routes/test.js
/**
 * @GET
 * testsのレコードをすべて取得
 */
router.get('/tests', async (req, res, next) => {

    try {
        //レコードをすべて取得
        const allRows = await Tests.all();

        //レコードを返す
        return res.send(allRows);
    } catch (error) {
        //レコードの取得失敗時
        console.log(error);
        res.status(500);
        return res.send({'error': 'サーバー側でエラーが発生しました'});
    }

});

http://localhost:3000/testsGETでアクセスします。

http
[
    {
        "id": 1,
        "text": "これはテストです",
        "created_at": "2020-06-18T16:50:45.000Z",
        "updated_at": null
    },
    {
        "id": 2,
        "text": "これはテストです",
        "created_at": "2020-06-18T16:50:50.000Z",
        "updated_at": null
    },
    {
        "id": 3,
        "text": "これはテストです",
        "created_at": "2020-06-18T16:50:51.000Z",
        "updated_at": null
    }
]

find(id) 引数idのカラム取得

プロジェクト名/routes/test.js
/**
 * @GET
 * 指定されたidのカラムを取得
 */
router.get('/tests/:id', async (req, res, next) => {
    //バリデーションの検証を受ける値
    const verificationValue = {
        id: req.params.id
    }
    //バリデーションの結果にエラーがあるかのチェック
    const validation = new validator(
        verificationValue,
        Tests.abstractVALIDATIONRULES.get.rule,
        Tests.abstractVALIDATIONRULES.get.errorMessage
    );
    if (validation.fails()) {
        //エラーを422で返す
        return res.status(422).send({errors: validation.errors.all()});
    }

    try {
        //レコードを取得
        const row = await Tests.find(verificationValue.id);
        //レコードを返す
        return res.send(row);
    } catch (error) {
        //レコードの取得失敗時
        console.log(error);
        res.status(500);
        return res.send({'error': 'サーバー側でエラーが発生しました'});
    }
})

http://localhost:3000/(testのidを指定)GETでアクセスします。

http
[
    {
        "id": 1,
        "text": "これはテストです",
        "created_at": "2020-06-18T16:50:45.000Z",
        "updated_at": null
    }
]

また、URIのidを指定する所が以下のとおり数値以外の場合は、エラーメッセージを返します。
http://localhost:3000/aaaa77

http
{
    "errors": {
        "id": [
            "数値で入力してください"
        ]
    }
}

insert() 引数paramの値で新規登録を行う

プロジェクト名/routes/test.js
/**
 * @POST
 * testsに新しいレコードを挿入
 */
router.post('/test', async (req, res, next) => {
    //バリデーションの検証を受ける値
    const verificationValue = {
        text: req.query.text
    }
    //バリデーションの結果にエラーがあるかのチェック
    const validation = new validator(
        verificationValue,
        Tests.abstractVALIDATIONRULES.post.rule,
        Tests.abstractVALIDATIONRULES.post.errorMessage
    );
    if (validation.fails()) {
        //エラーを422で返す
        return res.status(422).send({errors: validation.errors.all()});
    }

    try {
        //レコードの挿入開始
        await Tests.insert({text: req.query.text});
        return res.send({'insertResult': true});
    } catch (error) {
        //レコードの挿入失敗時
        console.log(error);
        return res.status(500).send({'insertResult': false});
    }
});

http://localhost:3000/test?text=テストやりたいPOSTでアクセスします。

http
[
    {
        "id": 1,
        "text": "これはテストです",
        "created_at": "2020-06-18T16:50:45.000Z",
        "updated_at": null
    }
]

リクエストのボディにtextがない場合は、エラーメッセージを返します。

http
{
    "errors": {
        "text": [
            "必須項目です。"
        ]
    }
}

update(param) 引数paramの値で更新を行う existId(id) 引数idの存在確認を行う

プロジェクト名/routes/test.js
/**
 * @PUT
 * レコードの更新
 */
router.put('/test/:id', async (req, res, next) => {
    //バリデーションの検証を受ける値
    const verificationValue = {
        id: req.params.id,
        text: req.query.text
    }
    //バリデーションの結果にエラーがあるかのチェック
    const validation = new validator(
        verificationValue,
        Tests.abstractVALIDATIONRULES.put.rule,
        Tests.abstractVALIDATIONRULES.put.errorMessage
    );
    if (validation.fails()) {
        //エラーを422で返す
        return res.status(422).send({errors: validation.errors.all()});
    }

    //idは存在しないか?
    if (!await Tests.existId(verificationValue.id)) {
        //エラーを422で返す
        return res.status(422).send({
            errors: {
                id: ['idが存在しません']
            }
        });
    }

    try {
        //レコードの更新開始
        await Tests.update(verificationValue);
        return res.send({'updateResult': true});
    } catch (error) {
        //レコードの更新失敗時
        console.log(error);
        return res.status(500).send({'updateResult': false});
    }

});

http://localhost:3000/test?text=テストやりたいPUTでアクセスします。

http
{
    "updateResult": true
}

URIのidを指定する所が以下のとおり数値以外やリクエストのボディにtextがない場合は、エラーメッセージを返します。
http://localhost:3000/test/aaa

http
{
    "errors": {
        "id": [
            "数値で入力してください"
        ],
        "text": [
            "必須項目です。"
        ]
    }
}

そして、find(id)の引数のidがレコードに存在しない場合、このようなエラーを返します

http
{
    "errors": {
        "id": [
            "idが存在しません"
        ]
    }
}

delete(id) 引数idのレコードを削除する

プロジェクト名/routes/test.js
/**
 * @DELETE
 * レコードの削除
 */
router.delete('/test/:id', async (req, res, next) => {
    //バリデーションの検証を受ける値
    const verificationValue = {
        id: req.params.id,
    }
    //バリデーションの結果にエラーがあるかのチェック
    const validation = new validator(
        verificationValue,
        Tests.abstractVALIDATIONRULES.delete.rule,
        Tests.abstractVALIDATIONRULES.delete.errorMessage
    );
    if (validation.fails()) {
        //エラーを422で返す
        return res.status(422).send({errors: validation.errors.all()});
    }

    //idは存在しないか?
    if (!await Tests.existId(verificationValue.id)) {
        //エラーを422で返す
        return res.status(422).send({
            errors: {
                id: ['idが存在しません']
            }
        });
    }

    try {
        //レコードの削除開始
        await Tests.delete(verificationValue.id);
        return res.send({'deleteResult': true});
    } catch (error) {
        //レコードの削除失敗時
        console.log(error);
        return res.status(500).send({'deleteResult': false});
    }


})

http://localhost:3000/(testのidを指定)DELETEでアクセスします。

http
[
    {
        "id": 1,
        "text": "これはテストです",
        "created_at": "2020-06-18T16:50:45.000Z",
        "updated_at": null
    }
]

URIのidを指定する所が以下のとおり数値以外の場合は、エラーメッセージを返します。
http://localhost:3000/aaaa77

http
{
    "errors": {
        "id": [
            "数値で入力してください"
        ]
    }
}

そして、find(id)の引数のidがレコードに存在しない場合、このようなエラーを返します

http
{
    "errors": {
        "id": [
            "idが存在しません"
        ]
    }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

GraphQLクライアント周り調べて設定した

雑多な感じです

TypeScript編

サンプルリポジトリ: https://github.com/sisisin-sandbox/try-gql/pull/1
(業務で使ってる構成の再現構成となっています)

クライアントライブラリ選び

多分選択肢はこんな

観測範囲でapollo使ってるのを多く見ていた/relayはReact特化っぽい/apolloのclient cacheを使うケースがありそうなどでapollo clientを選択
(といいつつrelayはろくに調べてないです)

apollo client設定

apollo clientについて

doc: https://www.apollographql.com/docs/

apolloはreact向けのクライアント以外にもサーバー実装・iOSクライアント・graphqlの運用サポートSaaSという具合に色々提供してくれてるすごいやつ(小並感)
この記事ではreact向けクライアントについて書きます

apollo clientはサーバーへのqueryはもちろんgraphql schemaを用いたローカルの状態管理の仕組み、middleware的なレイヤー(apollo-link)など様々な機能があります
この辺多少調べて設定したのでそれについて書いていきます

やったこと

  • ApolloClientをapollo-boostを使わずに設定(apollo-boostは設定を細かく出来ないので
  • apollo client:codegenコマンドによる型定義コード生成
  • vscodeのapollo clientプラグイン設定

ApolloClientをapollo-boostを使わずに設定

ドキュメントにapollo-boost/apollo-clientの例が入り混じってて大変紛らわしいやつ
apollo-boostはエントリー用パッケージのようなので実戦投入するなら自前でapollo-clientを設定するのが良い
(apollo-client v3ではapollo-boostぐらいのお手軽設定から始められるようになるっぽい)

初期設定自体はapollo-boostからのマイグレーションというドキュメントがあるのでこれを見ればいい
参考リンク: https://www.apollographql.com/docs/react/migrating/boost-migration/

apollo client:codegenコマンドによる型定義コード生成

typescript使うなら型定義の生成は人権
選択肢としては他にもいくつかあったが、apollo-clientを利用することにした

他の選択肢

選んだ理由としては、

  • 一通りの機能をサポートしている
    • 例えばts-graphql-pluginは別名に対応してないなどがある
  • 同じエコシステムにしとくほうが無難な気がした(完全に個人の感想)
    • apollo client v3でcustom scalarのdeserializeが書けるようになりそうで、もしそこと生成する型定義が上手くつなぎ込めるなら・・・?という希望もあった
  • graphql-code-generator乗り換えは検討しても良いかも?とは思っている
    • 機能はこちらのほうが豊富(後述)

その他細々と以下のような設定をしたりしました

  • globalTypes.ts/__generated__ ではなく /src 配下へ配置
    • create-react-appなどを利用する場合に取り回しやすいように
  • コード生成時に --customScalarsPrefix を付け、 global.d.ts のようなファイルを配置してそのファイル内で declare type GitHubDate=string のように定義
    • まあサンプル通りですが
  • introspectionを使わずschema.graphqlをダウンロードしてきてコード生成に利用
    • 諸般の事情によりサーバーがVPN内にいてCIからエンドポイントを叩けないのでこのような構成に

vscodeのapollo clientプラグイン設定

インストールするだけっちゃするだけ
fragmentとかへ定義ジャンプ出来たりフィールド名のサジェストが出てきたりして便利
これがないとschemaを手書きすることになるので大変です

やってない(やれてない)こと

  • apollo-link-scalarsを用いたデータ取得時のcustom scalarのdeserialize
  • vscodeのgraphql プラグインによるエディタからのquery実行
  • graphql code generatorによるコード生成
  • ApolloClientのラッパー実装

apollo-link-scalarsを用いたデータ取得時のcustom scalarのdeserialize

custom scalarとして定義された Date 型(ISO 8601形式文字列であるとする)をfetchしてきて受け取った時点で new Date(val)しておきたい、というのが人情だと思うんですよ(?)
サーバーサイドでは実際そういう仕組はあるけれど、apollo clientではcustom scalarに対してそういった統一的なfetch時のdeserialize処理を挟むことが出来ません

そこでapollo-linkの仕組みを利用して、fetch時にcustom scalarをdeserializeする処理を挟み込んでやってしまおう、というのが apollo-link-scalars というライブラリのアイデアです
が、問題点があり、

  • apollo-cache-persist などのhttpを経由しない値の取得で意図通り動かない
  • apollo-link-scalars の実装ではサーバーサイド向けの実装に依存している( Example of loading a schema参照のこと)
    • 結果、bundleサイズがやべえことになる
      • 仮にそこを無視できるとしても、 サーバーサイド実装はユニバーサルなjsとしてメンテされる保証がないのでリスキー
      • 現時点で graphql-tools はfsに依存してたりしてcreate-react-appなどではそのままでは動かない
    • ついでにschema.graphql相当の文字列を突っ込むだけでも結構サイズが膨れる

custom scalar対応のfeature requestはあるのでapollo client v3に期待・・・?

vscodeのgraphql プラグインによるエディタからのquery実行

ちょっと触って詰まったのでちゃんと調べてない・・・(後で試したい)

graphql code generatorによるコード生成

先述したようにapollo client:codegenを利用してるのでこちらはやってません
機能的にはこちらのほうが出来ることは多いので再検討しても良いかもとは考えてます

  • cameCaseの別名で一括解決してくれる
  • custom scalar向けの型を独自定義できる
  • 生成した型を利用するreact hookも一緒にコード生成してくれる

ただコード生成って機能が豊富になっても初手の手数が減るだけなのでよっぽど魅力的じゃないとわざわざ乗り換えるほどでもない、とはなりそう・・・

ApolloClientのラッパー実装

例えばreduxのmiddlewareレイヤーでクライアントを使いたい場合に欲しいはずなのでそのうちやるつもりです
具体的には、graphql errorのときにrejectedなPromiseが返ってくるためにanyで推論されてしまって、せっかく GraphQLError 型があるのに使いにくい、というような点が挙げられます
他には queryメソッドの型引数が省略可能(デフォルトany)になるなど、微妙な点があるのでその辺をラップした自前のクライアントクラスを作ろうかなとは考えてます
この辺は追々。

Scala編

sbt-graphqlの話を書こうと思ったけどタイムアップ

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

Shift + tab キーでフォーカス逆移動が利かない

Shift + tab キーでフォーカス逆移動が利かない

「Shift」+「Tab」でフォーカス逆移動は、デフォルト設定なのに
押下してもそのまま次の要素へ移動する事象が発生。

原因

JSファイルの中でエンターキーとTabキー押下制御の処理を書いており、
それが優先されていたため、Shift + tab キーの処理が反映されていなかった!

js
/*エンターキー、タブキーの場合*/
if (e.keyCode == 13 || e.keyCode == 9 && event.shiftKey == false) {
     if(e.keyCode == 9){
           /*やりたい処理*/
     }else if((e.keyCode == 13 && !foucsInput.is(':focus'))){
      /*やりたい処理*/
   }
}

「 if (e.keyCode == 13 || e.keyCode == 9 && event.shiftKey == false) { 」

&&event.shiftKey == false を書き加えると動きました。

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

UnityのWebGLで作ったサイトにドラッグ&ドロップでVRMを出現させる

VRMの切り替えをドラッグ&ドロップでやりたい

VRMをPartyParrot風に表示できるPartyParrotVRMを作ってみたという記事にまとめている「PartyParrotVRM」を作っている際に、VRMの切り替えをドラッグ&ドロップでやりたいと思いました。

実装したものがこちらです
change

途中で固まってしまうのがネックなのですが別スレッドで実行するなどの処理がうまくいかず現時点ではこのようになっています…一応切り替えれているのでいいかなと…

drag&dropを検知してUnityにデータを渡す

VRMのSDKを入れたプロジェクトを作成しておきます。
そして、以下のようなC#を定義してGameObjectに追加します。

using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using UnityEngine;
using VRM;

namespace Provider
{
    public class VrmProvider : MonoBehaviour
    {

        private int length;
        private int index;
        private List<byte> byteContent = new List<byte>();

        public void ByteLength(int length)
        {
            this.length = length;
            index = 0;
            byteContent.Clear();
        }

        public void AddRange(string byteString)
        {
            var decode = Convert.FromBase64String(byteString);
            byteContent.AddRange(decode);
            index += 1;
            if (length != index) return;
            Spawn();
        }

        public void Spawn()
        {
            var vrmBytes = byteContent.ToArray();
            var context = new VRMImporterContext();
            context.ParseGlb(vrmBytes);
            context.Load();
            context.ShowMeshes();
            context.EnableUpdateWhenOffscreen();
        }
    }
}

上記のcsのメソッドに対してjsからデータを送り込んでいきます。

UnityのWebGLTempleteを編集して以下のjsを追加して、index.htmlにもscriptタグで追加しました。

dragAndDrop.js
document.addEventListener('DOMContentLoaded', function () {
    var dropArea = document.getElementById('screen')

    function confirmVrmFile(files) {
        let length = files.length, file

        for (let i = 0; i < length; i++) {
            file = files[i]

            let isVrmFile = file.name.endsWith('.vrm')

            if (isVrmFile) {
                // .vrmのbyteをstringに変換する
                let reader = new FileReader()
                reader.onload = function () {
                    let source = this.result
                    let bytes = new Uint8Array(source)
                    let len = source.byteLength
                    let byteString = ""
                    for (var i = 0; i < len; i++) {
                        byteString += String.fromCharCode(bytes[i])
                    }
                    var base64String = window.btoa(byteString)
                    sendUnity(base64String)
                };
                reader.readAsArrayBuffer(file)
                break
            }
        }
    }

    function sendUnity(base64String) {
        // stringの容量が大きいままSendMessageに渡そうとするとエラーが起きるので文字列を分割する
        let splitLength = 1000
        let len = parseInt(base64String.length / splitLength)
        unityInstance.SendMessage("VrmProvider", "ByteLength", len + 1)
        for (let i = 0; i < len; i++) {
            let next = base64String.substr(i * splitLength, splitLength)
            unityInstance.SendMessage("VrmProvider", "AddRange", next)
        }
        let last = base64String.substr(
            splitLength * len,
            base64String.length % splitLength
        )
        unityInstance.SendMessage("VrmProvider", "AddRange", last)
    }

    dropArea.addEventListener('dragover', function (event) {
        event.preventDefault()
        event.dataTransfer.dropEffect = 'copy'
        dropArea.classList.add('dragover')
    })

    dropArea.addEventListener('dragleave', function () {
        dropArea.classList.remove('dragover')
    })

    dropArea.addEventListener('drop', function (event) {
        event.preventDefault()
        dropArea.classList.remove('dragover')
        confirmVrmFile(event.dataTransfer.files)
    })
})

実際にPartyParrotVRMというので使っているものから少し変えていますがこれでドラッグ&ドロップで動くと思います。

まとめ

画面が固まらないようにするのができれば切り替えたいとは思っています。最低限の切り替え処理にはなりますがそんなにコードを書かなくてもできたので便利だなと思っています。VRMも簡単に扱えてとても便利な印象を持ちました。

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

JavaScriptクイズアプリ

jsコード

const quiz = [ //配列にオブジェクトを格納
  {
    question: "ゲーム市場、最も売れたゲーム機は次のうちどれ?",
    answers: [
      "スーパーファミコン",
      "プレイステーション2",
      "ニンテンドースイッチ",
      "ニンテンドーDS",
    ],
    correct: "ニンテンドーDS" //選択されたHTMLのテキストと比較する変数を指定
  },{
    question: "日本の初代内閣総理大臣は誰?",
    answers: [
      "伊藤博文",
      "大隈重信",
      "安倍晋三",
      "高橋是清",
    ],
    correct: "伊藤博文"
  },{
    question: "横浜ベイスターズが初めて日本一に輝いた時の監督は?",
    answers: [
      "三浦大輔",
      "ラミレス",
      "中畑清",
      "権藤博",
    ],
    correct: "権藤博"
  }
];

const quizLength = quiz.length;
let quizIndex = 0;
let score = 0;

const $button = document.getElementsByTagName("button");
const buttonLength = $button.length; //buttonをノードとして取得し、変数化

const setupQuiz = function(){  //quizを解いたら次のquizをセットする関数
  document.getElementById("js-question").textContent = quiz[quizIndex].question;
  let buttonIndex = 0;
  let buttonLength = $button.length;
  while(buttonIndex < buttonLength){
    $button[buttonIndex].textContent = quiz[quizIndex].answers[buttonIndex];
    buttonIndex++
  }
}
setupQuiz();

const clickHandler = function(e){
  if (quiz[quizIndex].correct === e.target.textContent){  //イベントをオブジェクトとして取得 + テキストの内容と解答を比較
    window.alert("正解");
    score++;  //正解ならスコアを+1
    } else {
      window.alert("不正解");
    }

    quizIndex++;  //次のクイズをセットするためにインデックスを+

    if(quizIndex < quizLength){
      setupQuiz();
    }else{
      window.alert("終了!あなたの正解数は"+score+"/"+quizLength+"です。");
    }
}

let handlerIndex = 0;

while(handlerIndex < buttonLength){
  $button[handlerIndex].addEventListener("click",function(e){
    clickHandler(e);
  });
  handlerIndex++
}

HTMLコード

・・・前略
<body>
  <div class="container">

    <div class="jumbotron mt-5">
      <div class="d-flex justify-content-center">
        <div id="js-question" class="alert alert-primary" role="alert">
          A simple primary alert—check it out!
        </div>
      </div>

      <div id="js-items" class="d-flex justify-content-center">
        <div class="m-2">
          <button type="button" id="js-btn-1" class="btn btn-primary">Primary</button>
        </div>
        <div class="m-2">
          <button type="button" id="js-btn-2" class="btn btn-primary">Primary</button>
        </div>
        <div class="m-2">
          <button type="button" id="js-btn-3" class="btn btn-primary">Primary</button>
        </div>
        <div class="m-2">
          <button type="button" id="js-btn-4" class="btn btn-primary">Primary</button>
        </div>
      </div>
    </div>
  </div>

  <script src="main.js"></script>

</body>

解説

①クイズの内容とボタン等にテキストを.textContent = でjsの内容に変更する
②設定したクイズの内容が一問解いたら切り替わるようにquizIndexを関数の内部で++する。
③正解の場合スコアを記録し、alertで通知

Image from Gyazo

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