20200803のJavaScriptに関する記事は25件です。

【javascript】テトリスの作成に向けて

javascriptでのテトリス作成において必要になった知識をまとめていきます。

ランダムな整数値の生成

これでmin以上max以下の整数を得られる

function getRandomIntInclusive(min, max) {
  min = Math.ceil(min);
  max = Math.floor(max);
  return Math.floor(Math.random() * (max - min + 1)) + min; //The maximum is inclusive and the minimum is inclusive 
}

出典:Math.random()

多重ループを抜け出す

ラベルをつけることでループを抜け出せる

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

function validate(value) {
  return value !== 5;
}

grid_loop:
for (var row = 0; row < grid.length; row++) {
  for (var col = 0; col < grid[row].length; col++) {
    var value = grid[row][col];
    console.log(value);
    if (!validate(value)) {
      break grid_loop;
    }
  }
}

出典:JavaScriptで2重ループをbreakする

配列の中で最大値を見つける

var arr = [1,2,3];
var max = arr.reduce(function(a, b) {
    return Math.max(a, b);
});

出典:Math.max()

スプレッド構文

var arr = [1, 2, 3];
var max = Math.max(...arr);

出典:Math.max()

スプレッド構文により、イテラブルである配列を展開している

配列内を検索する

Array.prototype.includes()

配列内を検索して真偽値を返す

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

addEventListenerを使用する際に注意するべきこと

jsの実装中にエラーが起きたのでまとめます。

addEventListnerとは

addEventListenerとは、イベント発火の際に実行する関数を定義するためのメソッドです。

以下のようにして実行できます。

要素.addEventListener('イベント名', 関数);

エラーが起きたときのコード

function calc() {
  const value = document.getElementsByClassName("price-input");
  value.addEventListener('input', (e) => {
    console.log("")
  });
}

window.addEventListener("load", calc);

エラー内容

Uncaught TypeError: value.addEventListener is not a function
    at calc (calculation.js:5)

なぜかvalueが定義されてないことになっています。

valueがちゃんと定義されているか確認します。

function calc() {
  const value = document.getElementsByClassName("price-input");
  console.log("value")
  value.addEventListener('input', (e) => {
    console.log("")
  });
}

window.addEventListener("load", calc);

コンソールを確認します。

HTMLCollection [input#item_price.price-input, item_price: input#item_price.price-input, item[price]: input#item_price.price-input]

なぜかいっぱいある。。。

そうですgetElementsbyClassNameで取得できるのは
HTMLCollectionというオブジェクト
になります。

HTMLCollectionは複数ノードなので
単一ノードのみ指定できるaddEventListnerでは取得することができなかったのですね。

idで取得するように変更してみます。

function calc() {
  const value = document.getElementById("item_price");

  value.addEventListener('input', (e) =>{
    console.log("")
  });
}

window.addEventListener("load", calc);

これでエラーが解決できました。

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

簡易SPARQLエンドポイント検索アプリをjQueryからVue.jsに書き直しました

RDFデータベースをSPARQLと呼ばれるクエリ言語で検索できるWeb API「SPARQLエンドポイント」を公開するオープンデータサイトが行政を中心に増えています。例えば、

これらのサイトの多くは、Webブラウザ上でSPARQLクエリを実行して、表形式で検索結果を見られるようになっています。
しかし、Web APIなので本来は仕様に基づいた形式でエスケープ処理をしたSPARQLクエリをGETもしくはPOSTしなけれならないので、上記のような気の利いたページを公開しないSPARQLエンドポイントもあります。

そこでだいぶ前にWebブラウザ上でSPARQLエンドポイントへ簡単にSPARQLクエリを実行できて、その結果を見やすい表形式で表示するWebアプリをjQueryで作りました。

JavaScriptによるSPARQL利用サンプル(クエリ検索アプリ)
https://github.com/uedayou/simple-sparqlsearch-js

JavaScriptによるSPARQL利用サンプル(クエリ検索アプリ)

かなり古いアプリですが、今でも問題なく動きます。ただ、今見るとコードの可読性が悪いなとおもいます。とても短いコードでもあるので、jQueryを使わずに書き直すことにしました。

Vue.js で書き直し

今回は Vue.js を使うことにしました。React, AngularでもWebアプリを書いたことがありますが、今回はできるだけコード量を減らして、Webpack等でのビルドを行わない形にしたかったので、Vue.jsを使いました。

Vue.jsによるSPARQLエンドポイント検索アプリ
https://github.com/uedayou/simple-sparqlsearch-vue

Vue.jsによるSPARQLエンドポイント検索アプリ

機能としては全く同じですが、検索した結果をJSONファイルでダウンロードできるようにしました。

ダウンロード

検索結果が表示されているときに、ダウンロードボタンを押すとJSONファイルがダウンロードできます。

Vue.js vs jQuery

Vue.js で書き直してみて、jQueryのコードよりも個人的には格段に可読性が上がったと思います。
jQueryは、DOM操作をコード内で行わないといけなかったのが、Vue.jsだとDOMにデータをバインディングできるので書き直すのも楽にできました。

Vue.jsのコード
Vue.use(VueLoading);
Vue.component('loading', VueLoading);

var app = new Vue({
  el: '#app',
  data: {
    query: 'select * where {?s ?p ?o} LIMIT 10',
    results: {
      data: null,
      head: [],
      body: [],
    },
  },
  methods: {
    doSearch: function() {
      var loader = this.$loading.show();
      var that = this;
      axios.get(
        endpoint+"?query="+encodeURIComponent(this.query),
        { headers: {'Accept': 'application/sparql-results+json'} })
      .then(function(res) {
        that.results.data = res.data;
        that.results.head = res.data.head.vars;
        that.results.body = res.data.results.bindings;
      })
      .catch(function(error) {
        console.log(error);
        alert("Error!");
      })
      .then(function() {
        loader.hide();
      });
    },
    downloadData: function() {
      var filename = "results.json";
      var a = document.createElement('a');
      var uriContent = 'data:application/octet-stream,'+encodeURIComponent(JSON.stringify(this.results.data));
      a.setAttribute('href', uriContent);
      a.setAttribute('download', filename);
      document.body.appendChild(a);
      a.click();
      document.body.removeChild(a);
    }
  }
})

jQueryのコード
(function(){
  $.fn.modal.defaults.spinner = $.fn.modalmanager.defaults.spinner = '<div class="loading-spinner" style="width: 200px; margin-left: -100px;"><div class="progress progress-striped active"><div class="progress-bar" style="width: 100%;"></div></div></div>';
  $('#find_query').click(function(){
    $('body').modalmanager('loading').find('.modal-scrollable').off('click.modalmanager');
    qr = sendQuery(endpoint,encodeURIComponent($('#query_area').val().replace(/[\n\r]/g,"")));
    qr.fail(
      function (xhr, textStatus, thrownError) {
        $('body').modalmanager('removeLoading');
        alert("Error: A '" + textStatus+ "' occurred.");
      }
    );
    qr.done(
      function (d) {
        $('body').modalmanager('removeLoading');
        $('body').removeClass('modal-open');
        result_table(d.results.bindings);
      }
    );
  });
  $('#result_div').hide();
}());

var result_table = function(data){
  var result_div = $('#result_div');
  var table = $('#result_list')[0];
  if (table == undefined) {
    result_div.append($('<table></table>').attr({
      'id' : 'result_list',
      'class' : 'table'
    }));
    table = $('#result_list')[0];
  }
  while (table.rows.length > 0) { table.deleteRow(0); }
  if (data instanceof Array) {
    result_div.show();
    var header = table.createTHead();
    var headerRow = header.insertRow(0);
    id = 1;
    for (var d = 0; d < data.length; d++) {
      var row1 = table.insertRow(d + 1);
      if (d == 0) {
        for ( var key in data[0]) {
          var th = document.createElement('th');
          var label = key;
          th.innerHTML = key;
          headerRow.appendChild(th);
        }
      }
      var i = 0;
      for ( var key in data[d]) {
        var cell = row1.insertCell(i++);
        var value = data[d][key];
        if (value.value != undefined){value = value.value;}
        if (value == null) {value = '';}
        var link = true;
        if (link) {
          if (value != null && value.indexOf("http://") == 0) {
            value = '<a href="'+value+'" target="_blank">'+value+'</a>';
          }
        }
        cell.innerHTML = value;
      }
    }
  }
};
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

border-radiusでムニュムニュするだけのページ作成

動機

border-radiusってせいぜい使っても50%で球体が書けるねって話で終わってしまっていて、もっといろいろ書きたくなった。

実装

まあ、何も言わず見てくれい
https://emptyset.sakura.ne.jp/munu/
image.png
こんな感じのヤツがひたすらムニュムニュする。

動作原理

タイマでランダムにborder-radiusを生成してCSSを修正し、transitionさせる。

結論

transitionって面白いな。
何も使い道が思いつかないけど。

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

オブジェクトのKeyの値を変更する

個人メモ

array: [
  { id: 1, text: 'test' },
  { id: 2, text: 'test' },
  { id: 3, text: 'test' },
  { id: 4, text: 'test' },
  { id: 5, text: 'test' }
]

この配列のtesttestTextというKey名に変える。

スクリーンショット 2020-08-03 午後21.22.44 午後.png

changeKey () {
  const array = this.array.map(value => {
    return {id: value.id, testText: value.text}
  })
  return array
}

スクリーンショット 2020-08-03 午後21.24.42 午後.png

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

【JS】constは定数を定義できないよ。

constとは

再代入できない変数を定義する。なので、オブジェクトの内容などは変えることができる。

const name = {
  key: "変更前",
};
console.log(name.key);
// 値を変えてみる
name.key = "変更後";
// 正常に動作する
console.log(name.key);

このように、中身は変えることができる。とはいえ再代入はできない。

const name = "変更前";
console.log(name);

const name = "変更後"; // => Uncaught SyntaxError: Identifier 'name' has already been declared

つまり定数ではない。

 オブジェクトの中身も固定化したい

この場合はfreezeメソッドを使えばOK。

const name = Object.freeze({
  key: "value",
});
name.key = "ここで変更できない";
console.log(name.key); // => value

ちなみに配列でも同じことができます。

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

【JS】変数名に使える名前のルールを学ぶ

 ルールの概要

  • _(アンダーバー),$(ダラー),数字,半角アルファベットをの組み合わせ
  • 数字からは開始できない。
  • 予約語は使えない。

 例

const _ = "test1";
const $ = "test2";
const a = "test3";
const 1aaa = "test4";
const let = "test5";

console.log(_); // => test1
console.log($); // => test2
console.log(a); // => test3
console.log(1); // => Uncaught SyntaxError: Invalid or unexpected token
console.log(let); // => let is disallowed as a lexically bound name
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

TypeScriptのnever型について

never型って?

typeScriptにおけるbottom型です。

bottom型って何よ

型理論、数理論理学において値を持たない型のことである

↑wikiからの抜粋です。
値を持たない型, これだけだと???となりそうですが、

戻り値の型がボトム型である関数は、いかなる値も返さない。

こちらの説明だと少しわかりやすいかと思います。

つまり、

const foo = () => {
 while(true){}
}

上記のようにwhile(true)で戻り値がない場合(= returnが絶対に走らない場合)などは、関数fooの戻り値の型はneverと言えます。

スクリーンショット 2020-08-02 11.27.54.png

特徴

neverは値のない型なので、値を入れるとエラーになります。

let foo:never

foo = 'test' // コンパイルerror

実際に戻り値がneverとなる例

上記でwhileの例を上げましたが、他の例とも合わせて再掲します。

// 無限ループする場合
const foo = () => {
 while(true){}
}

// errorをthrowする場合
const bar = () => {
    throw new Error("これはエラーです")
}

voidneverの違いは?

これは実際にコードを見てもらった方がわかりやすいです。

const foo = () => {
}

const bar = () => {
    return 
}

const baz = () => {
    while(true){}
}


let fooVar:never = foo() // error
let barVar:never = bar() // error
let bazVar:never = baz() //ok!

上記では bazのみ戻り値がnever型で、 foobarの戻り値はvoid型です。

JSに置いてreturn文を省略された場合はundefinedが返る仕様なので、
foobarは実質的に同じです。

voidはreturnでの戻り値なし!の型なのに対して
neverはそもそもreturnしない。 
似ているようで全然違っています。

実際での使用例

値を入れることができない という部分が生きてきます。

下記のような型がそれぞれあったとします。

interface Neko {
    type: "neko"
}

interface Inu {
    type: "inu"
    name: number
}
interface Other {
    type: "other"
}

type Animal = Neko | Inu | Other

上記Animal型を使う上で、typeによってそれぞれ別の処理をさせたい場合、
下記のようなコードになるかと思います。

function intro(s: Animal) {
    if (s.type === "neko") {
        return `吾輩は猫である。名前はまだない`
    }
    else if (s.type === "inu") {
        return `${name}は犬のお廻りです。`
    }
}

上記ではNeko Inu の型しか想定されていません。
Other型, またそれ以外でも想定しない型が渡ってきた場合にerrorを返す仕組みがあれば便利です。
そこで、予期しない型が渡ってきた場合はneverに代入することでコンパイルエラーを出し、
網羅漏れに気づくことができます。
(全て網羅できていれば、 _exhaustiveCheckはnever型となります。)

function intro(s: Animal) {
    if (s.type === "neko") {
        return `吾輩は猫である。名前はまだない`
    }
    else if (s.type === "inu") {
        return `${name}は犬のお廻りです。`
    }
    else {
        // Otherが渡ってきた場合、neverの特徴によってerrorが出て気づくことができる。
        const _exhaustiveCheck: never = s;
    }
}

上記の手法はswitchのdefault文に置いても有効ですね。

参考文献

TypeScript Deep Dive 日本語版
Use the never type to avoid code with dead ends using TypeScript
TypeScriptの型入門

wiki ボトム型

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

【プラグイン導入】SyntaxError Cannot use import statement outside a module【Nuxt.js】

参考対象者

  • 外部ライブラリをNuxt.jsに導入したい方
  • npmでプラグイン等を管理している方

環境

package.json
{
  "name": "nuxt-proj",
  "version": "1.0.0",
  "private": true,
  "scripts": {
    "dev": "nuxt",
    "build": "nuxt build",
    "start": "nuxt start",
    "generate": "nuxt generate"
  },
  "dependencies": {
    "nuxt": "^2.14.0"
  },
  "devDependencies": {}
}

状況

外部ライブラリのvue-flag-iconを導入したいが、SyntaxErrorになってしまう。

SyntaxError
Cannot use import statement outside a module

エラーにならない、プラグイン導入方法(node_modulesで管理)

  1. プラグインをnpmで、インストール
  2. pluginsディレクトリに、プラグインをimportするファイルを作成
  3. nuxt.config.jsファイルに、importファイルを読み込む設定をする
  4. サーバーの再起動

プラグインをnpmで、インストール

まずは、今回のプラグインであるvue-flag-iconをインストールしていきます。

$ npm i vue-flag-icon@latest

すると自動でpackage.jsonにも記述されますね。

package.json
"dependencies": {
    "nuxt": "^2.14.0",
    "vue-flag-icon": "^1.0.6"
  },

pluginsディレクトリに、プラグインをimportするファイルを作成

プラグイン公式(https://github.com/vikkio88/vue-flag-icon)
にもあるように、必要な記述を設定していきます。

そしてその設定は、どのファイルからでもライブラリを読み込めるように、pluginディレクトリというVue.jsをインスタンス化する前に読み込んでくれるJSファイルの置き場所に、新規ファイルでしていく。

plugins/flag-icon.js
import Vue from 'vue';                  //Vueを定義するために、Vueをimport
import FlagIcon from 'vue-flag-icon';
Vue.use(FlagIcon);

nuxt.config.jsファイルに、importファイルを読み込む設定をする

あとは、先ほど設定したimportファイルを、Nuxt.js自体の設定ファイルに読み込む記述をする。

nuxt.config.js
export default {
  //省略
  plugins: [
    '@/plugins/flag-icon',
  ],
  //省略
}

ただ、これだけだとエラーになってしまう。
原因は、今回のライブラリの管理方法がnpmなので、さらに専用の設定が必要なため。

とは言っても、あと1行だけ追記すればいいのだが、

nuxt.config.js
export default {
  //省略
  build: {
    transpile: ['vue-flag-icon']
  }
}

これにて、ライブラリが使えるようになる。

サーバーの再起動

と、よく忘れますが、nuxt.config.jsファイルに変更を加えた場合、サーバーの再起動をする必要があるので、

$ npm run dev

して実際に使ってみましょう!!

実際に、使ってみる

どのVueファイルでも良いので、<flag iso="jp" />を追加してみると、

pages/index.vue
<template>
  <div>
    //省略
    <flag iso="jp" />    
  </div>
</template>

我らが日本の国旗が、表示されるであろう!

参考

Nuxt.js公式様
https://ja.nuxtjs.org/guide/plugins/

@fj_yohei様
https://qiita.com/fj_yohei/items/cddf267a94fa30ecb0b8

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

storybookでPHPを利用してAPIデータを取得する

別ドメインのAPIデータを取得するような仕組みの場合、phpを使用して同ドメインのデータとして取得したい場合があると思います。
storybookでこれを実現する方法を紹介します。

フォルダ構成

npxでインストールしたものとAPI用のphpフォルダを置いた状態で前提として進めます。

/.storybook
/stories
/php/
  /api/
    items.php
/package.json

ローカル環境でPHPを使う場合

APIを取得するだけであればphpのビルドインサーバー使うのが楽だと思います。

php -S 0.0.0.0:8000 -t ./php

0.0.0.0(localhost)

storybookでproxyを使う方法

1. 【http-proxy-middleware】をインストールする

npm i http-proxy-middleware -D

2. storybookのconfigに【middleware.js】を置く

configのフォルダ(デフォルトだと.storybook)下に【middleware.js】を置いて以下の様な記述をします。

【例】拡張子がphpになってるものをproxyするパターン

const { createProxyMiddleware } = require('http-proxy-middleware');

module.exports = router =>
  router.use(
    "**/*.php",
    createProxyMiddleware({
      target: "http://localhost:8000/",
    })
  );

【例】/api以下をproxyして、本番URLに合わせてリライトするパターン

const { createProxyMiddleware } = require('http-proxy-middleware');

module.exports = router =>
  router.use(
    "/api",
    createProxyMiddleware({
      target: "http://localhost:8000/",
      pathRewrite: {
        '^/api/items': '/api/items.php',
      },
    })
  );

ビルドインサーバーを実行した状態で、storybookの【 http://localhost:6006/api/items 】を叩くとproxyが動作していることを確認できます。

一つのコマンドで実行できるようにする

ビルドインサーバーとstorybook両方のコマンドを実行するのは面倒だと思うので、一つで実行できるようにします。

1. 【npm-run-all】をインストール

npm i npm-run-all -D

2. package.jsonにコマンドを追加する

  "scripts": {
    "storybook": "start-storybook -p 6006",
    "build-storybook": "build-storybook",
    "proxy": "php -S 0.0.0.0:8000 -t php/",
    "start": "npm-run-all -p proxy storybook"
  }

storybook実行中にproxyのログが必要ない場合は、proxyのオプションに-qを付けると消せます。

ローカルだけproxyを使いたい場合やパスを変えたい場合

storybookは、.storybookの中身を別のフォルダに用意することで、configを分けることが出来ます。
コマンドのオプション(-c)でフォルダを指定します

start-storybook -c .storybook/dev
build-storybook -c .storybook/build

このような形でconfigを分けると、dev専用の【middleware.js】を仕込むことができます。

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

ワイニート 「ヤフージャパンのロゴ変えたろ」

ヤフージャパンのトップを見つめるワイ

1.png

ワイ 「ヤフージャパンのロゴ変えたいな」
ワイ 「久々Chrome拡張機能でも書くか」
娘 「ユーザスクリプト書くのね」
娘 「manifest.jsonJavaScriptのファイルが必要だね」

manifest.json
{
  "name": "ヤフージャパンのロゴを変えてみる",
  "version": "1.0.0",
  "manifest_version": 2,
  "description": "ヤフージャパンのロゴを変えてみる",
  "content_scripts": [
    {
      "matches": ["https://www.yahoo.co.jp/"],
      "js": ["index.js"]
    }
  ]
}
index.js
document.getElementsByTagName('h1')[0].style.backgroundImage =
  'url(data:image/png;base64,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)';

ワイ 「ヤフージャパンのロゴの所をスクリプトで書き換えたった」

無題.png

〜おしまい〜

動画(YouTube)

IMAGE ALT TEXT HERE

ワイのGitHubとか

GitHub: https://github.com/yuzuru2
YouTube: https://www.youtube.com/channel/UCuRrjmWcjASMgl5TqHS02AQ
Qiita: https://qiita.com/yuzuru2
LINE: https://line.me/ti/p/-GXpQkyXAm
Twitter: https://twitter.com/yuzuru_program
成果物まとめ: https://qiita.com/yuzuru2/items/b5a34ad07d38ab1e7378

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

JSのpush()について

JSのpush()について

初心者がJSのpush()についてメモしています。

push() メソッド

配列の末尾に 1 つ以上の要素を追加する
参考

const animals = ['pigs', 'goats', 'sheep'];

const count = animals.push('cows');
console.log(count);
// expected output: 4
console.log(animals);
// expected output: Array ["pigs", "goats", "sheep", "cows"]

animals.push('chickens', 'cats', 'dogs');
console.log(animals);
// expected output: Array ["pigs", "goats", "sheep", "cows", "chickens", "cats", "dogs"]

参考「push」の返り値について

var items = [1,2,3,4];
var result = items.push();
console.log(result);
  • 連想配列では「push」を使えない
  • 「push」はあくまで配列の組み込みメソッド
  • 「push」を使って、配列の中に別の配列を追加するのは特に問題ない
var obj = { name:'太郎', age:30 };
obj.push( ['花子', 28] );
console.log( obj );

実行結果

Uncaught TypeError: obj.push is not a function
  • 連想配列に要素を追加するには、まだ連想配列にないキー文字列を指定して値を代入します。疑似コードは以下
arr[キー配列] = //keyとvalueをセットする
  • しかし、配列の中にオブジェクトを追加することは可能 連想配列に配列を pushはだめ、 配列にオブジェクトを pushは OK
var items = [1,2,3,4];
items.push({one:1,two:2,three:3});
console.log(items);

「pop」は配列の末尾のデータを削除する

pop.js
var array = [1,2,3,4,5];
array.pop();
console.log(array);
[1,2,3,4]

concatメソッドで結合

var array1 = ['aaa','bbb'];
var array2 = ['ccc','ddd'];
var result = array1.concat(array2);

console.log(result);
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JSの Promise

JSの Promise
Promise.prototype.then()
JS初心者が苦し紛れにメモを取っています。

参考:Promise.prototype.then()

  • then() メソッドは Promise を返す。最大2つの引数、 Promise が成功した場合と失敗した場合のコールバック関数を取る。
const promise1 = new Promise((resolve, reject) => {
  resolve('Success!');
});

promise1.then((value) => {
  console.log(value);
  // expected output: "Success!"
});

?よくわからない、、

参考2:JavaScript の Promise: 概要
- JavaScript はシングル スレッド
- JavaScript はペイント、スタイルの更新、ユーザー操作の処理(テキストのハイライト表示やフォーム コントロールの操作など)と同じキューに入っています。 これらいずれかの処理のアクティビティが、他のアクティビティを遅延させる

参考3 JavaScriptのPromiseとjQueryのPromise

今のところの理解

  • 順番通りに実行させたいときに使うもの
  • 昔は順番通りに実行させたいときにコールバックなどでなんとか解決されていたが、ES6から公式になったもの
  • 少し疲れたので、また気力があるときに理解を進める
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ウェブサイト作成用備忘録・2号:background-image の疑似アニメーション制御その2

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

今回は background-image プロパティの疑似アニメーション制御について

background-image プロパティは transition プロパティや animation プロパティ等のアニメーション制御には対応していません。

そこで、自分なりに試行錯誤することで、疑似的なアニメーション制御に成功したので、その方法などを記録していきます。

方法2:JavaScript の動的制御で2種類の background-image の opacity を同時に操作することで、疑似的なクロスフェードを実装する。

記述例

HTML

<html>
  <body>
    <div id="background1" class=""></div>
    <div id="background2" class="none"></div>
    <button id="change_screen" class="" type="button">背景変更</button>
  </body>
</html>

CSS

body {
  margin: 0;
}
#background1,
#background2 {
  min-height: 100%;
  min-width: 100%;
  opacity: 1;
  position: fixed;
  transition: all 1s;
  -webkit-transition: all 1s;
}
#background1 {
  background-image: url(背景画像1)
}
#background2 {
  background-image: url(背景画像2)
}
#background1.none,
#background2.none {
  opacity: 0;
}
#change_screen {
  position: relative;
}

javascript

jQuery(document).ready(function(){

  $("#change_screen").click(function(){
    $("#background1").toggleClass("none");
    $("#background2").toggleClass("none");
  });

});

解説

1・背景画像の表示専用の空の div タグを二種類を用意する。

2・予め片方の div タグにはクラス none を設定し、透過処理の初期設定を行う。

3・背景変更ボタンをクリックすると、toggleClass メソッドでそれぞれの div タグの none クラスが切り替わる。

4・それぞれの div タグの透明度が逆転し、transition プロパティによって、1秒間の間にそれぞれの背景がアニメーションすることで、結果的にクロスフェードする。

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

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

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

JSのprototype_初心者覚書

初心者が記事などを見てメモをしています。
(記事を理解しようとしてメモをしているだけなので、下記の記事を見た方がわかりやすいです。すみません、、)

参考:
【JavaScript入門】プロトタイプ

「prototype」とは?

「プロトタイプ」と呼ばれる最小テンプレートがあり、それをコピーして新しいオブジェクトを作るようなイメージ
オブジェクト同士の繋がりを保持する機能もあるため「継承」も簡単に行えるわけです。JavaScriptはプロトタイプベースのオブジェクト指向言語

「prototype」の使い方

基本的なコンストラクタの作成方法

var User = function(name,age){
  this.name = name;
  this.age = age;
}
var taro = new User('太郎','12');
console.log(taro);
console.js
User {name: "太郎", age: "12"}
age: "12"
name: "太郎"
__proto__: Object

prototypeを使ってメソッド定義

  • プロパティだけでなく、メソッドも一緒に定義する場合、プロトタイプでメソッドを効率的に定義することができます。
オブジェクト名.prototype.メソッド名=function(){}
  • メソッドはどのインスタンス先でも内容は同じなので、一緒にコピーするのは無駄
  • 「参照」であれば、大量のメソッドがあってもインスタンス化する度にメモリが圧迫されない
var User = function(name,age){
  this.name = name;
  this.age = age;
}
User.prototype.getName = function(){
  return this.name;
}
var User = function(name,age){
  this.name = name;
  this.age = age;

  this.getName = function(){
    return this.name;
  }
}
  • 参照されないので、毎回メソッドがコピーされてメモリを無駄遣い
複雑な書き方.js
var User = function(name, age) {
    this.name = name;
    this.age = age;
}


User.prototype.getName = function() {
    return this.name;
}

User.prototype.getAge = function() {
    return this.age;
}
  • オブジェクト形式でメソッドを定義すると簡潔に書ける
完結な書き方.js
var User = function(name, age) {
    this.name = name;
    this.age = age;
}

User.protptype = {
  getName: function(){
    return this.name;
  },
  getAge: function(){
    return this.age;
  }
}

プロトタイプチェーンの仕組み

var User = function() {};
var MemberA = function() {};
var MemberB = function() {};
MembarA.prototype = new User():
  • 「User」と「MemberA」のプロトタイプはそれぞれ参照できる状態に
MemberB.prototype = new MemberA();
  • 「MemberB」と「User」も同じように参照関係
var User = function() {};
var Member = function() {};

User.prototype.hello = function() {
    return 'こんにちは!';
}

Member.prototype = new User();
Member.prototype.hello();
"こんにちは!"

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

JavaScript: Promise.raceのどれかがエラーになったらどうなるか?

JavaScriptのPromise.raceは2つ以上のPromiseに対して、早く完了したものを戻り値として得られる関数です。

もし、Promise.raceで競争するPromiseのうち、どれかがエラー(reject)になったら、どうなるのでしょうか? これについて検証してみます。

早いほうがエラーになった場合

下のサンプルコードでは、2つのPromiseが競争します。

ひとつは、100ミリ秒後にエラーになるPromise。もうひとつは、200ミリ秒後に成功裏に完了するPromiseです。

この2つのPromiseをPromise.raceで処理すると、前者のエラーのほうが勝ち、new Error('100ミリ後にエラーになった処理')のほうが戻り値になります。

async function test1() {
  // 100ミリ後にエラーになる処理
  const willError = new Promise((_, reject) =>
    setTimeout(() => reject(new Error('100ミリ後にエラーになった処理')), 100),
  )

  // 200ミリ秒後に完了する処理
  const willSuccess = new Promise(resolve =>
    setTimeout(() => resolve('200ミリ後に完了した処理'), 200),
  )

  try {
    await Promise.race([willError, willSuccess])
  } catch (error) {
    console.log(error.message)
    //=> "100ミリ後にエラーになった処理"
  }
}

test1()

遅いほうがエラーになる場合

今度は、エラーのほうが遅い場合です。

ひとつめのPromiseは、100ミリ秒後に成功裏に完了します。もうひとつは、200ミリ秒後にエラーになるPromiseです。

この2つのPromiseをPromise.raceで処理すると、前者のほうが勝ち、戻り値としては文字列の'100ミリ後に完了した処理'になります:

async function test2() {
  // 100ミリ秒後に完了する処理
  const willSuccess = new Promise(resolve =>
    setTimeout(() => resolve('100ミリ後に完了した処理'), 100),
  )

  // 200ミリ後にエラーになる処理
  const willError = new Promise((_, reject) =>
    setTimeout(() => reject(new Error('200ミリ後にエラーになった処理')), 200),
  )

  const result = await Promise.race([willSuccess, willError])
  console.log(result)
  //=> "100ミリ後に完了した処理"
}

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

JavaScript: Promise.raceの基本的な使い方

Promise.raceは2つ以上のPromiseのどちらか早い方を戻り値として取得する関数です。

下の例では、100ミリ秒後に完了するPromiseと、200ミリ秒後に完了するPromiseのどちらか早く完了するほうを取得するものです。この例では、前者のほうが早いので、Promise.raceで得られる値はPromise<"100ミリ後に完了した処理">のほうになります。

async function test1() {
  // 100ミリ後に完了する処理
  const one = new Promise(resolve =>
    setTimeout(() => resolve('100ミリ後に完了した処理'), 100),
  )

  // 200ミリ秒後に完了する処理
  const two = new Promise(resolve =>
    setTimeout(() => resolve('200ミリ後に完了した処理'), 200),
  )

  const result = await Promise.race([one, two])
  console.log(result)
  //=> "100ミリ後に完了した処理"
}

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

JS 配列の後方最初に一致したindex番号を返す(開始位置も指定できる)

lodashのlastIndexOfを作成してみた

const lastIndexOf = (array, selectNum, startIndex = array.length - 1) => {
  for (let i = startIndex; 0 <= i; i--) {
    if (array[i] === selectNum) {
      return i
    }
  }
  return -1
}

console.log(lastIndexOf([1, 2, 1, 2], 2))

// =>  3

console.log(lastIndexOf([1, 2, 1, 2], 5))
// => -1

console.log(lastIndexOf([1, 2, 1, 2], 2, 2))
//2番目から『2』の値が最初に一致したindexを返す
// => 1
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【JS/TS】Lookupテーブルを活用する

概要

戻り値の値が与えられた値に依存している際に、ifswitchを使用して、下記のように対応する場面があるかと思います。

Ifを使用した場合場合

const colorMapper = (color) => {
  if (color == "yellow") {
    return "";
  } else if (color == "blue") {
    return "";
  } else if (color == "red") {
    return "";
  } else {
    return "該当なし";
  }
};

colorMapper("yellow"); //=> 黄
colorMapper("pink"); //=> 該当なし

Switchを使用した場合

const colorMapper = (color) => {
  switch (color) {
    case "yellow":
      return "";
    case "blue":
      return "";
    case "red":
      return "";
    default:
      return "該当なし";
  }
};

colorMapper("yellow"); //=> 黄
colorMapper("pink"); //=> 該当なし

Lookupテーブルの活用

Lookupテーブルを使用することで、下記のように書くことが可能です。
これによって、コードの見易さの向上、また実行時間の短縮に繋がります。

const colorsTable = {
  yellow: "",
  blue: "",
  red: "",
};

const colorMapper = (color) => colorsTable[color] || "該当なし";

colorMapper("yellow"); //=> 黄
colorMapper("pink"); //=> 該当なし

ちなみに、TypeScriptを使用すると下記のように書けます。

type ColorEn = "yellow" | "blue" | "red";
type ColorJa = "" | "" | "";

const colorsTable: Record<ColorEn, ColorJa> = {
  yellow: "",
  blue: "",
  red: "",
};

const colorMapper = (color: ColorEn): ColorJa | "該当なし" => colorsTable[color] || "該当なし";

colorMapper("yellow"); //=> 黄
colorMapper("pink"); //=> 該当なし

注意点

下記の記事でありますが、必ずしもLookupテーブルを使用することで実行時間の短縮が出来るわけでは無いようです。

参考

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

AWS Amplify での Cognito アクセスは React Context.Provider を使って認証処理を Hooks 化しよう

Amplify × React Hooks.png

AWS Cognito は認証・認可を提供している AWS のサービスです。Amplify と統合することで、超高速に構築できます。Cognito を使用することで、API Gateway や S3 など他の AWS サービスとの統合がより簡単にできるようになります。

本記事では、Cognito を使用した React アプリケーションの実装例を紹介します。Cognito へのアクセスには amplify-js というライブラリを使用します。さらに React の Context.Provider という機能を使うことで認証に関連する処理をカスタムフックに集約する方法を考察します。

本記事で実装されたアプリケーションは以下のような動作をします。ログイン、ログアウト、サインアップ、確認メールなど。

完成するアプリケーション

本アプリケーションは Vercel にデプロイされています。
https://task-app.geeawa.vercel.app/login

また、以下の GitHub リポジトリにホストしています。
https://github.com/daisuke-awaji/task-app

amplify-js でも React Hooks を使いたい

先週は React アプリに Auth0 でシュッと認証を組み込んで Vercel に爆速デプロイする という記事を書きました。Auth0 のクライアントライブラリは非常に使い勝手がよく、<Auth0Provider> という Provider で包むだけで useAuth0 フックを使用できるようになります。

index.tsx
import React from "react";
import ReactDOM from "react-dom";
import { Auth0Provider } from "@auth0/auth0-react";
import "bootstrap/dist/css/bootstrap.min.css";
import { App } from "./App";

ReactDOM.render(
  <Auth0Provider
    domain={process.env.REACT_APP_AUTH0_DOMAIN!}
    clientId={process.env.REACT_APP_AUTH0_CLIENT_ID!}
    redirectUri={window.location.origin}
  >
    <App />
  </Auth0Provider>,
  document.querySelector("#root")
);

一方で amplify-js にはこのような機能はありません。認証系処理のメソッドは Auth モジュールから取り出して使う必要があります。以下はサインアップするメソッドです。参考: 公式 Sign up, Sign in & Sign out

import { Auth } from "aws-amplify";

async function signUp() {
  try {
    const user = await Auth.signUp({
      username,
      password,
      attributes: {
        email,
        phone_number,
      },
    });
    console.log({ user });
  } catch (error) {
    console.log("error signing up:", error);
  }
}

メソッドしか用意されておらず、ログインユーザの情報などを React アプリでグローバルに保持する仕組みは自分で用意する必要があります。amplify-js でも Auth0 のような使いやすい DX(開発者体験)にしたい! ということが本記事のモチベーションです。つまり、以下のように使用したいわけです。

index.tsx
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
import * as serviceWorker from "./serviceWorker";
import "./index.css";
import CognitoAuthProvider from "./cognito/CognitoAuthProvider";

ReactDOM.render(
  <CognitoAuthProvider>
    <App />
  </CognitoAuthProvider>,
  document.getElementById("root")
);

<App/> コンポーネントを <CognitoAuthProvider> でラップするだけで、認証系の処理やログインユーザのステートを取り出す useAuth フックが使えるようにしていきます。

LogoutButton.tsx
import React from "react";
import { useAuth } from "../../cognito/CognitoAuthProvider";

export default function LogoutButton(props: any) {
  const { isAuthenticated, signOut } = useAuth();

  if (!isAuthenticated) return null;

  return <Button onClick={() => signOut()} {...props} />;
}

React.Context とは

React の Context は配下の子コンポーネントにデータを渡すための便利な方法です。従来は props を使用することで、子コンポーネントにデータを渡していましたが、コンポーネントのネストが深くなると非常に面倒で複雑になります。 Context を使用することで 認証UI テーマ など多くのコンポーネントが使用する情報を共有して保持・取得できます。

context.provider.png

React.createContext

Context オブジェクトを作成します。React がこの Context オブジェクトが登録されているコンポーネントをレンダーする場合、ツリー内の最も近い上位の一致する Provider から現在の Context の値を読み取ります。

const MyContext = React.createContext(defaultValue);

Context.Provider

全ての Context オジェクトには Context.Provider コンポーネントが付属しています。これにより Context.Consumer コンポーネントは Context の変更を購読できます。実際のユースケースでは Consumer ではなく、useContext フックを使用することが多いでしょう。

<MyContext.Provider value={/* 何らかの値 */}>

useContext

Context オブジェクトを受け取り、その Context の value を返します。<MyContext.Provider/> が更新されると、このフックは MyContext.Provider に渡された value を使用してコンポーネントを再レンダーします。

const value = useContext(MyContext);

認証情報を Context に集約する

さて、認証情報として以下のようなメソッドとステートを保持する Context を作っていきます。これらの値があればログイン、ログアウト、サインアップ、確認コード入力の一連の流れが実装できます。

項目 概要
isAuthenticated ログインしているか
isLoading ローディング中か(画面制御で使用)
user ログインしているユーザの情報
error ログイン処理、サインアップ処理などでエラーがあれば詰める
signIn サインインする。
signUp サインアップする。
confirmSignUp サインアップ確認コードを入力する
signOut サインアウトする。

State

Context が保持するステートの定義(インタフェース)を作成します。

import { CognitoUser } from "amazon-cognito-identity-js";
export interface AuthState {
  isAuthenticated: boolean;
  isLoading: boolean;
  user?: CognitoUser;
  error?: any;
}
const initialState: AuthState = {
  isAuthenticated: false,
  isLoading: false,
};
const stub = (): never => {
  throw new Error(
    "You forgot to wrap your component in <CognitoAuthProvider>."
  );
};
export const initialContext = {
  ...initialState,
  signIn: stub,
  signUp: stub,
  confirmSignUp: stub,
  signOut: stub,
};

Context

Context オブジェクトを作成します。各コンポーネントから取り出すためのカスタムフック useAuth() を合わせて作成しておきます。

import React, { useContext } from "react";
import { SignUpParams } from "@aws-amplify/auth/lib-esm/types";
import { CognitoUser } from "amazon-cognito-identity-js";
import { AuthState, initialContext } from "./AuthState";
import { LoginOption } from "./CognitoAuthProvider";
interface IAuthContext extends AuthState {
  signIn: (signInOption: LoginOption) => Promise<void>;
  signUp: (params: SignUpParams) => Promise<CognitoUser | undefined>;
  confirmSignUp: (params: any) => Promise<void>;
  signOut: () => void;
}
export const AuthContext = React.createContext<IAuthContext>(initialContext);
export const useAuth = () => useContext(AuthContext);

Provider

最後に Provider には Cognito とやりとりする処理と、認証情報を保持する処理を実装します。

import React from "react";

import { useState, useEffect } from "react";
import { SignUpParams } from "@aws-amplify/auth/lib-esm/types";
import { CognitoUser } from "amazon-cognito-identity-js";

import { Auth } from "aws-amplify";
import Amplify from "aws-amplify";
import { AuthContext } from "./AuthContext";

export type LoginOption = {
  username: string;
  password: string;
};
interface ICognitoAuthProviderParams {
  amplifyConfig: {
    aws_project_region: string;
    aws_cognito_identity_pool_id: string;
    aws_cognito_region: string;
    aws_user_pools_id: string;
    aws_user_pools_web_client_id: string;
    oauth: {
      domain: string;
      scope: string[];
      redirectSignIn: string;
      redirectSignOut: string;
      responseType: string;
    };
    federationTarget: string;
  };
  children: any;
}

export default function CognitoAuthProvider(props: ICognitoAuthProviderParams) {
  Amplify.configure(props.amplifyConfig);
  const [isAuthenticated, setIsAuthenticated] = useState(false);
  const [isLoading, setIsLoading] = useState(true);
  const [error, setError] = useState(null);
  const [user, setUser] = useState<CognitoUser>();

  useEffect(() => {
    checkAuthenticated();
    currentAuthenticatedUser();
  }, []);

  const checkAuthenticated = () => {
    setIsLoading(true);
    Auth.currentSession()
      .then((data) => {
        if (data) setIsAuthenticated(true);
      })
      .catch((err) => console.log("current session error", err))
      .finally(() => {
        setIsLoading(false);
      });
  };

  const currentAuthenticatedUser = async (): Promise<void> => {
    const user: CognitoUser = await Auth.currentAuthenticatedUser();

    setUser(user);
  };

  const signIn = async ({ username, password }: LoginOption): Promise<void> => {
    setIsLoading(true);
    try {
      await Auth.signIn(username, password);
      setIsAuthenticated(true);
    } catch (error) {
      console.log("error signing in", error);
      setError(error);
      setIsAuthenticated(false);
    }
    setIsLoading(false);
  };

  const signUp = async (
    param: SignUpParams
  ): Promise<CognitoUser | undefined> => {
    setIsLoading(true);
    let result;
    try {
      result = await Auth.signUp(param);
      setUser(result.user);
    } catch (error) {
      console.log("error signing up", error);
      setError(error);
    }
    setIsLoading(false);
    return result?.user;
  };

  const confirmSignUp = async ({ username, code }: any): Promise<void> => {
    setIsLoading(true);
    try {
      await Auth.confirmSignUp(username, code);
      setIsAuthenticated(true);
    } catch (error) {
      console.log("error confirming sign up", error);
      setError(error);
    }
    setIsLoading(false);
  };

  const signOut = () => {
    setIsLoading(true);
    Auth.signOut()
      .then(() => {
        setIsAuthenticated(false);
      })
      .catch((err) => console.log("error signing out: ", err))
      .finally(() => {
        setIsLoading(false);
      });
  };

  return (
    <AuthContext.Provider
      value={{
        isAuthenticated,
        isLoading,
        signIn,
        signUp,
        confirmSignUp,
        signOut,
        user,
        error,
      }}
    >
      {props.children}
    </AuthContext.Provider>
  );
}

使用方法

ここまで準備ができれば使用する側はこの CognitoAuthProvider でコンポーネントをラップすることで useAuth() フック経由で各種ステートの値またはメソッドを使用できます。

amplifyConfig として設定値は外部ファイルで保持しています。

import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
import "./index.css";
import CognitoAuthProvider from "./cognito/CognitoAuthProvider";
import awsconfig from "./aws-exports";

ReactDOM.render(
  <CognitoAuthProvider amplifyConfig={awsconfig}>
    <App />
  </CognitoAuthProvider>,
  document.getElementById("root")
);

amplifyConfig は以下のようなファイルになります。

const amplifyConfig = {
  aws_project_region: "ap-northeast-1",
  aws_cognito_identity_pool_id: "ap-northeast-1:12345678909876543234567890",
  aws_cognito_region: "ap-northeast-1",
  aws_user_pools_id: "ap-northeast-1_xxxxxxxx",
  aws_user_pools_web_client_id: "xxxxxxxxxxxxxxx",
  oauth: {
    domain: "mydomain.auth.ap-northeast-1.amazoncognito.com",
    scope: [
      "phone",
      "email",
      "openid",
      "profile",
      "aws.cognito.signin.user.admin",
    ],
    redirectSignIn: "http://localhost:3000/",
    redirectSignOut: "http://localhost:3000/logout/",
    responseType: "code",
  },
  federationTarget: "COGNITO_USER_POOLS",
};

export default amplifyConfig;

ログアウトボタンのコンポーネントです。コードベースをシンプルにできました。

LogoutButton.tsx
import React from "react";
import { useAuth } from "../../cognito/CognitoAuthProvider";

export default function LogoutButton(props: any) {
  const { isAuthenticated, signOut } = useAuth();

  if (!isAuthenticated) return null;

  return <Button onClick={() => signOut()} {...props} />;
}

さいごに

React の Context を使用することで、認証情報などのグローバルな値を一元的に管理できるようになります。
ただ、 Context は多くのコンポーネントからアクセスされる場合に使用することとしましょう。
Context はコンポーネントの再利用をより難しくする為、慎重に利用してください。

本記事で紹介した React.Context を使用したカスタムフックを使用するという発想はそのうち amplify-js に PullRequest しようと思います。Cognito ユーザ(または Amplify ユーザ)が個別にこのような実装をしなくとも、ライブラリとして提供し、すぐに簡単なインタフェースで認証処理を実現できるようにしていきたいですね。

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

Magentaチームによる機械学習&音楽のリモートワークショップ&ハッカソンイベント”Bit Rate”が開催

Google の機械学習音楽ライブラリーMagenta開発チームと、サンフランシスコのメディアアート機関”Gray Area”による機械学習&音楽のリモートワークショップ&ハッカソンイベント”Bit Rate”が8月7日〜9月7日に開催されます。

開催場所はもちろんアメリカサンフランシスコと言う事ですが、今回はコロナ禍の状況下でリモート開催と言う事で日本からも参加できるのではないかと思います。

image.png

https://grayarea.org/event/bitrate-ml-music-series/

主にJavaScriptの音楽ライブラリーやMagentaを組み合わせて、機械学習&音楽の新しいアプリを開発するワークショップとハッカソンの組み合わせでスケジュールは下記の様になっています。

INTRODUCTION & OPENING – AUGUST 7
5:30 – 6pm PST: Introductions by Gray Area and Magenta team at Google Research.

WORKSHOPS & LECTURES – AUGUST 7 – AUGUST 9
August 7, 6-9pm PST: p5.js Workshop – Instructor: Rachel Rose Waterhouse
August 8, 11am – 2pm PST: Magenta.js Workshop – Instructor: Tero Parviainen
August 9, 12pm – 3pm PST: ml5.js Workshop – Instructor: Stephanie Andrews

SERIES BEGINS – AUGUST 7
Teams gather and begin prototyping process.

PROJECT SUBMISSIONS – AUGUST 31
Projects created will be submitted on devpost.com.

CONCLUSION & AWARDS – SEPTEMBER 7
Finalists selected and announced.

Bit Rate概要

8月7日にスタート。
7、8、9日はそれぞれワークショップが開催され

8月7日
Audio Visualization with P5.js + Tone.js Workshop
P5.jsとTone.jsを使用したオーディオビジュアライゼーションワークショップ
https://grayarea.org/workshop/bitrate-series-audio-visualization/
image.png

8月8日
Making Interactive Music Apps with Magenta.js Workshop
Magneta.js(JavaScript版Magenta)を使用した音楽アプリのワークショップ
https://grayarea.org/workshop/bitrate-series-interactive-music-apps/
image.png

8月9日
Building An Interactive Machine Learning Orchestra Using ml5.js Workshop
JavaScriptでインタラクティブな機械学習オーケストラの作成ワークショップ
https://grayarea.org/workshop/bitrate-series-machine-learning-orchestra/
image.png

その後31日までハッカソンが開催されます。
通常リアル開催だと週末の3日間だけなどが多く、正直何かの開発をするのは時間的に厳しいのですが、開催場所を確保しなくても良いオンラインの場合、この様な長期のハッカソンも可能という事でしょう。
1ヶ月近くあれば実践的なアプリの開発も十分可能ではないかと思います。

8月31日に締め切りで提出
9月7日に優勝チームが発表されます。

優勝チームには500ドルの他、Magetna開発チームへのプレゼンテーション、その他Google Nest Hubなどが贈呈されるとの事です。

日本の方も(英語がある程度できれば)今回は参加できそうなので是非チャレンジしてみてはいかがでしょうか?

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

Spring+Vue.js でAPIの連携をする

はじめに

今回SpringフレームワークでRestAPIを作成し、
Vue.jsでデータを表示させることをゴールに記載していきます。
(Javaのインストール等の初期設定となる環境構築は省きます。)

プロジェクト作成

◇Vue.js

VueCliを用いて作成していきます。

①VueCliをnpmよりインストールする

コンソール
npm install -g @vue-cli

②プロジェクト作成

コンソール
vue create practice

実行すると、アプリケーション作成に必要な設定ファイルやサンプルソースが自動で作成されます。
image.png
③サンプル画面確認
作成したプロジェクト配下にて以下実行

コンソール
npm run serve

image.png

◇Spring

「Spring Tools for Eclipse」を使用して、進めていきます。

①プロジェクト作成
「Spring Starter Project」を選択肢プロジェクトを作成する

image.png

プロジェクトを作成すると以下のようなフォルダ、ファイルが作成されます。
(今回は「gradle」を使用しております。)
image.png

RestAPIの作成

H2データベースからデータを取得するAPIを作成します。
①H2データベース準備
H2データベースとは・・・・・
 JAVAプラットフォーム上でオープンソースのRDB
「インメモリデータベース」として使用が可能でSpring bootではデフォルトで付属されているため複雑な設定不要
以下のJDBCドライバがすでに登録されている
image.png

今回は「インメモリデータベース」として使用するため、初期化するテーブル、データを作成していきます。
「src/main/resources」配下に「data.sql」、「schema.sql」を配置する。
アプリ起動時、インメモリデータベースのため毎回初期化されます。
初期化時には2つのSQLが自動で実行されます。
image.png

data.sql
data.sql
INSERT INTO person(code, name, belong_nm)
VALUES('001', 'テスト1', '総務部');
INSERT INTO person(code, name, belong_nm)
VALUES('002', 'テスト2', '人事部');


schema.sql
schema.sql
CREATE TABLE person
(
   id INT NOT NULL AUTO_INCREMENT,
   code VARCHAR(100) NOT NULL,
   name VARCHAR(100) NOT NULL,
   belong_nm VARCHAR(500) NOT NULL,
   PRIMARY KEY(id)
);

プロジェクト上で右クリックし、「Spring Boot App」にて起動する
image.png
起動後「http://localhost:8080/h2-console」
にアクセスし、テーブルが作成されていることを確認

Connectを選択
image.png
テーブルもデータも作成されている。
image.png

②Controller、Serviceを作成する
ControllerからServiceを呼び出します。
一旦DBの参照は行わず、固定の値を戻すようにする。

固定値をListに詰めて返却します。

PracticeServiceImp.java
PracticeServiceImp.java
package com.example.demo.service;

import java.util.ArrayList;
import java.util.List;

import org.springframework.stereotype.Service;

@Service
public class PracticeServiceImp implements PracticeService {

    @Override
    public List<String> getAll() {
        List<String> list = new ArrayList<>();

        list.add("1");
        list.add("2");
        list.add("3");

        return list;
    }
}

Service呼び、取得した値をListに詰めて、返却します。

PracticeController.java
PracticeController.java
package com.example.demo.controller;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.example.demo.service.PracticeService;
import com.example.demo.service.PracticeServiceImp;

@RestController
@RequestMapping("api/practice")
public class PracticeController {

    private final PracticeService practiceService;

    @Autowired
    public PracticeController(PracticeServiceImp practiceService){
        this.practiceService = practiceService;
    }

    @GetMapping
    public List<String> getAll() {
        List<String> list = practiceService.getAll();

        return list;
    }
}


実際に実行し確認していきます。
image.png

http://localhost:8080/api/practice」
にアクセスし、固定値が表示されました。

③DBから取得した値をFormクラスに格納し、returnするよう修正
DBとの接続、SQLの発行はDAOクラスに任せます。
「Controller(リクエスト、レスポンスのハンドリング)」「Service(ロジック)」「Dao(DB操作)」という役割です。

PracticeServiceImp.java
PracticeServiceImp.java
package com.example.demo.service;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.example.demo.dao.PracticeDao;
import com.example.demo.form.PracticeForm;

@Service
public class PracticeServiceImp implements PracticeService {

    private final PracticeDao dao;

    @Autowired
    public PracticeServiceImp(PracticeDao dao) {
        this.dao = dao;
    }
    @Override
    public List<PracticeForm> getAll() {
//      List<PracticeForm> list = new ArrayList<>();
//       
//        list.add("1");
//        list.add("2");
//        list.add("3");
        return dao.getAll();
    }
}

PracticeController.java
PracticeController.java
package com.example.demo.controller;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.example.demo.form.PracticeForm;
import com.example.demo.service.PracticeService;
import com.example.demo.service.PracticeServiceImp;

@RestController
@RequestMapping("api/practice")
@CrossOrigin(origins = {"http://localhost:8081"})
public class PracticeController {

    private final PracticeService practiceService;

    @Autowired
    public PracticeController(PracticeServiceImp practiceService){
        this.practiceService = practiceService;
    }

    @GetMapping
    public List<PracticeForm> getAll() {
        List<PracticeForm> list = practiceService.getAll();

        return list;
    }
}

データを格納するための新規ファイル

PracticeForm.java
PracticeForm.java
package com.example.demo.form;

import javax.validation.constraints.NotNull;

public class PracticeForm {
    public PracticeForm() {};

    public PracticeForm(int id, String code, String name, String belong_nm) {
        super();
        this.id = id;
        this.code = code;
        this.name = name;
        this.belong_nm = belong_nm;
    }

    @NotNull
    private int id;

    @NotNull
    private String code;

    @NotNull
    private String name;

    @NotNull
    private String belong_nm;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getCode() {
        return code;
    }

    public void setCode(String code) {
        this.code = code;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getBelong_nm() {
        return belong_nm;
    }

    public void setBelong_nm(String belong_nm) {
        this.belong_nm = belong_nm;
    }

}

DB操作するための新規ファイル

PracticeDaoImp.java
PracticeDaoImp.java
package com.example.demo.dao;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

import com.example.demo.form.PracticeForm;

@Repository
public class PracticeDaoImp implements PracticeDao {

    private final JdbcTemplate jdbcTemplate;

    @Autowired
    public PracticeDaoImp(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    @Override
    public List<PracticeForm> getAll() {
        // TODO Auto-generated method stub
        String sql = "select id, code, name, belong_nm from person";
        List<Map<String, Object>> resultList =  jdbcTemplate.queryForList(sql);
        List<PracticeForm> list = new ArrayList<PracticeForm>();
        for(Map<String, Object> result : resultList) {
            PracticeForm practiceForm = new PracticeForm();
            practiceForm.setId((int)result.get("id"));
            practiceForm.setCode((String)result.get("code"));
            practiceForm.setName((String)result.get("name"));
            practiceForm.setBelong_nm((String)result.get("belong_nm"));
            list.add(practiceForm);
        }
        return list;
    }

}

上記修正後、サイドアクセスしてみます。
image.png

DBの値が取得できております。

Vue.jsからAPIを実行

フロントエンド側からAPIを呼び出しデータを表示させます。
①axiosをインストール
APIを実行するために「axios」を使用します。
axios:HTTP通信が可能なJavaScriptのライブラリ

コンソール
npm install --save axios

②axiosでAPIを呼び出す。
※VueもSpringもサーバーを起動させておく。
axiosにて、APIのURLを指定しデータを取得する。

Home.vue
Home.vue
// Home.vue
<template>
  <div>
    {{ people }}
  </div>
</template>

<script>
import axios from 'axios'
export default {
  data () {
    return {
      people: []
    }
  },
  methods: {
    getPerson () {
      const path = 'http://localhost:8080/api/practice'
      axios.get(path)
        .then(response => {
          this.people = response.data
        })
        .catch(error => {
          console.log(error)
        })
    }
  },
  created () {
    this.getPerson()
  }
}
</script>

画面を確認する。
image.png

データが取得されました。

③テーブルに表示させる
UIフレームワーク「vuetify」のデータテーブルを用いて、画面をそれっぽく加工してみます

Person.vue
Person.vue
// Person.vue
<template>
  <div>
    <h1>社員一覧</h1>
    <v-data-table
          :headers="headers"
          :items="people">
    </v-data-table>
  </div>
</template>

<script>
import axios from 'axios'
export default {
  components:{

  },
  data () {
    return {
      people: [],
      singleSelect: false,
        selected: [],
        headers: [
          {
            align: 'start',
            sortable: false,
          },
          { text: 'ID', value: 'id' },
          { text: '氏名', value: 'name' },
          { text: '社員コード', value: 'code' },
          { text: '所属名', value: 'belong_nm' },
        ],
    }
  },
  methods: {
    getNews () {
      const path = 'http://localhost:8080/api/practice'
      axios.get(path)
        .then(response => {
          this.people = response.data
        })
        .catch(error => {
          console.log(error)
        })
    }
  },
  created () {
    this.getNews()
  }
}
</script>

データテーブルを使って表示させることで、簡単にそれっぽいものが表示されました。
image.png

まとめ

簡単なAPIを作成しそれをフロントエンドと連携させることができました。
いまどきのフレームワークのおかげでどこにどういうロジックを書くかがだいたい決まっております。
そのため、役割もはっきりし便利ですね。

参考文献

参考にさせていただきました。
https://b1tblog.com/2020/03/17/spring-rest-2/

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

[JavaScript] セミコロンをつければ絶対安心できるのか?;

はじめに

こちらの記事 を読んで、改めてJavaScriotの ; について気になったので調べてみました。

結論

私が思ったより安心じゃなかった。

セミコロンありのスタイルでも、下記のように予期せぬセミコロンの挿入が起こってしまうパターンがあります。

function f() {
    return
        {
          foo: 'foo'
        };
}

f(); // undefined

セミコロンに関することは参考にさせていただいたセミコロンに関する ESLint ルールという記事に全部書いてありました :pray::pray::pray:

以下の内容はリンク先の記事に(個人的な感想以外)書いてあることなので、ぜひそちらを参考にしてください!

ASI(Auto Semicolon Insertion)

JavaScript の改行箇所で構文エラーがあった際にセミコロンにを自動的に補い再解釈する言語機能のことですが、結局のところこいつが曲者で、初心者や他言語からJavaScriptを触るようになった方が理解し難く感じる要因でしょう。

ECMAScript の言語仕様に ASI 利用に関する警告が ES2019 から追加 されています。

ASIの振る舞いで気をつけるべきこと

予期せぬセミコロン挿入の欠損

console.log(4) // > 4
['foo', 'bar'].forEach(el => console.log(el)) // > Uncaught TypeError: Cannot read property 'bar' of undefined

console.log(4)['foo', 'bar'].forEach が構文上正しいと解釈され、セミコロンの自動挿入が起こりません。, がカンマ演算子として解釈されるため、 bar が未定義というエラーが出ます。

このパターンは実行してみてエラーで気づくことができそうです。また、TypeScriptなら型チェックで気づけます。

予期せぬセミコロン挿入

function f1()
{
    return
        2020;
}

function f2()
{
    return
        {
          foo: 'foo'
        };
}

function f3()
{
    return
        ({
          foo: 'foo',
          bar: 'bar'
        });
}

f1(); // undefined
f2(); // undefined
f3(); // undefined

こちらが先に上げたパターンと同じですが、 return のあとにセミコロンが自動挿入され、改行後の値が返されません。

また、f2の関数には Object そのものが存在していません。
{ foo: 'foo' } の部分は Object ではなく、ブロックとして解釈されてしまっています。 foo:fooラベル構文です。意味のないブロックですが、文法そのものに影響しないので無視されています。

C#とか Allman brace style が推奨の言語から来た方は引っかかる?(多くの場合はそんなことないでしょうが)
Lint などがない場合は間違って改行していた場合気づかないかもしれませんね。

ちなみに、ラベル構文の後にカンマをつけることはできないため以下は構文エラーになります。

function f() {
    return
        {
          foo: 'foo',
          bar: 'bar
        };
}

// > Uncaught SyntaxError: Unexpected token :

まとめ

今回調べてみて結局のところ、セミコロンありなのかなしなのかは好みの問題という域を出ないように感じました。
それよりも、ASIの独特の動きを理解して(といってもそんなに多くの動作は無いように思います。)、Lint や Formatter などのツールをしっかり活用することで、こういった問題を起こさない様にすることの方が重要だと思います。

特に、ESLintやPrettierを使う際にはJavaScript Standard Styleのようなプリセットを利用し、極力自力で設定しないことが、このような罠にハマらないために大切です。(JavaScript Standard Styleにはセミコロンあり版が存在します)

また、可読性についても一長一短で、個人によるところが大きいと思います。(私はセミコロンレスの方が見やすく感じます)

参考

セミコロンをつけ忘れただけなのに...【JavaScript】

セミコロンに関する ESLint ルール

To Semicolon, Or Not To Semicolon;

Using Semicolons? Never Use Them!

JavaScript Standard Style

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

通常関数とアロー関数でのthisの違い【JavaScript】

最近JavaScriptを勉強し始めた中でthisの使い方がイマイチぴんと来ていないので、今回は、

  • function(){・・・}(通常関数)
  • ()=>{・・・}(アロー関数)

thisの使い方の違いについてまとめます。

何が違うのか?

私自身、調べる前は「thisの範囲が違うらしい」ということくらいしか分かっていませんでした。

違いを一言で言うと、

  • 通常関数のthisは、function を呼んだ時の . の前についているオブジェクト
  • アロー関数のthisは、関数の外のthis

を指してます。

通常関数を先に説明をします。

通常関数のthis

function test() {
    console.log(this)
}
var obj = {}
obj.test = test
obj.test() // => {test: ƒ}

thisfunctionを呼んだ時の.の前についているobjオブジェクトとなります。

function test() {
    console.log(this)
}
test() // => Window {frames: Window, postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, …}

オブジェクトを指定しない場合は、グローバルオブジェクトになります (non-strict モード時)。 strict モードでは undefined になります。

とまあ、通常関数の場合は関数の呼び出し方に応じてthisの中身が変わります。下の記事に呼び出し方による違いが書いてあります。

JavaScript の this を理解する多分一番分かりやすい説明 - Qiita

記事は非常に分かりやすく書いてあります。が、実行の仕方によってthisが変わってしまい、分かりにくくないですか? 私は読んでいて嫌になってきました。

その複雑さを改善したのがアロー関数のようです。

アロー関数のthis

アロー関数のthisは、関数の外側のthisに固定されます。

const test = () => {
    return this
}
// 関数の外側のthis
const lexicalThis = this

// 関数定義したタイミングで、関数の外側のthisを参照する
console.log(test() === lexicalThis) //=> true

// メソッドとして実行しても、thisはメソッドが属するオブジェクトを指さない
const obj = { method: test }
console.log(obj.method() === obj) //=> false
console.log(obj.method() === lexicalThis) //=> true

先ほどの通常関数のobj.test()this=objとして扱っていたのに対して、アロー関数のobj.test()this=lexicalThis(関数の外側のthis)として扱っていることが分かります。
アロー関数のthisは、.前のオブジェクトは指さず、必ず関数の外側のthisを指します。

また、強制的にあるオブジェクトと結びつけるbind等も無視するようです。通常関数では有効です。

通常関数のbindの例
function test() {
    console.log(this)
}
var obj = { name: "obj" }
var check = test.bind(obj)
check() // => {name: "obj"}

まとめ

通常関数は、さまざまな参照の仕方があるため汎用性があるが、thisの対象が実行の仕方によって変動してしまう。
一方、アロー関数は、参照するオブジェクトに制約があるものの、オブジェクトが決まっているため分かりやすい。

使用上の注意としては、

  • アロー関数を使うときは、間違ってオブジェクトのメソッドとして使わないようにする。
  • 通常関数を使うときは、thisが使い方によって変わることに気を付ける。

以上の2点でしょうか。

アロー関数で書くと格好よく書けるなー、としか思っていませんでした。
思った以上に様々な制約があり、他の違いもしっかり知っておく必要があると感じました。

最後に

通常関数とアロー関数の違いはthisの使い方だけではありません。
その他の違いについてはこちらをご覧ください。
JavaScript: 通常の関数とアロー関数の違いは「書き方だけ」ではない。異なる性質が10個ほどある。 - Qiita

初投稿で拙い文章となってしまいましたが、最後までお読みいただきありがとうございました。
何か誤り等ありましたらご指摘ください。

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

【超初心者向け】30秒ではじめるプログラミング

はじめまして
Qiitaはじめてみましたゲバラです。

今回はプログラミングを全く触ったことのない方にとりあえずプログラミングを10秒で触れていただく方法をご紹介します。

前置き

プログラミングって最初に何をしたらいいかわかりませんよね。やるにしても色々と準備しなきゃいけないし、時間もかかるわけです。
なのでプログラミングに超簡単に触れて、チャレンジするきっかけになれば幸いです。

30秒でプログラミングしてみよう

1. PCを用意してください。

WindowsかMacでOKです。

2. GoogoleChromeを開いてください
3. 画面で右クリックして「検証」というメニューを押してください。
4. 右側でてくるウィンドウ内の上部にある「Console」というタブを押してください。
5. 以下のコードをコピーしてください。
let hako = "これからプログラミングをはじめてみよう。";
console.log(hako);
6. 以下の場所にコピーしたコードを貼り付けて、Enterボタンを押してください。

image.png

7. 「これからプログラミングをはじめてみよう。」という文字が表示されればプログラムが実行できました。おめでとうございます!

image.png

実行したコードの解説

はじめてプログラムを実行できましたね。おめでとうございます!
これでプログラミングをはじめることができます。

やっていることは二つです
1. 表示したい文字を変数という箱の中に定義する。
2. 定義した箱の中身を画面上に表示する

// 「hako」という変数に「これからプログラミングをはじめてみよう。」という文字を入れています。
let hako = "これからプログラミングをはじめてみよう。";
// 「hako」の中身を画面に表示しています。
console.log(hako);

でそうな質問

Q.PCがないです。

A.買いましょう。

Q.GoogleChromeって何?

A.Webブラウザのことです。普段インターネット見るのに使っているものです。

Q.「Console」ってどこ?

A.ここです。
image.png

Q.左側にウインドウって何?

A.開発者向けのツールです。Webエンジニアになるならめちゃくちゃお世話になるものです。

次回予告、「自分への未来のメッセージをプログラムで書いてみる」

未来考えるの大変だから臨機応変にメッセージを組むプログラムを書いてみます。

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