- 投稿日:2019-12-04T23:58:56+09:00
ラズパイとMESHで始めるお手軽IoT
Ubiregi Advent Calendar 2019 4日目はラズパイとMESHを使ったお手軽IoTを紹介します。
作るもの
MESHブロックの「ボタン」を押すと、Slackに決まったメッセージが投稿される、という非常に簡単なものを作ります。
応用としてはラズパイにモニタを付けてドアホンとして使う、見たいのはアリかなとか思っています。一人暮らしだと留守の間、人が訪問してきてるか分からない問題があり、代わりにMESHボタンを配置しておけば人が来たことをスマホに通知する、みたいなことができてよさそうです。用意するもの(今回使ったもの)
- MESHのボタンブロック
- Raspberry Pi Zero WH(以下ラズパイ)
- スマホ
前提条件
- ラズパイでRaspbianが動くようになっていること
- MESHアプリがスマホにインストールされていること
- Slackのアカウントを持っていること
手順
ラズパイを「MESHハブ」にする
これに関しては公式が非常に手厚いので、そちらを参照した方がよいです。
Raspberry Piに「MESH ハブ」アプリをインストールする方法を教えてほしい – MESHサポート | 遊び心を形にできる、アプリとつなげるブロック形状の電子タグ
補足
今回使ったラズパイはZeroなので、ブラウザを立ち上げると大変重たいです。
ということで、上記手順のブラウザでアクセスする部分は通常のPCで行ない、
②Raspberry Piにログインしてダウンロード用のコマンドを実行する方法
のコマンドをコピーしてSSH経由でダウンロードするのが楽だと思います。Incoming WebhookのURLを取得
事前に通知先のチャンネルを決める/作るなどしておきます。
次に、
SlackのIncoming Webhooksを使い倒す - Qiita
を参考にIncoming Webhookを追加してURLをコピーしましょう。
メモ帳でもなんでもいいのでURLを退避させておくことをおすすめします(次で使います)。PythonでServerを書く
Pythonを使っているのは単純に僕がPythonをよく使うので使っている感じなので、同じことができれば言語は何でもいいです。
ちなみにRasbianはデフォルトでPythonがインストールされているため特にPythonそのものをインストールしたりする必要はありません。
なお、今回使用したPythonのバージョンはPython 2.7.13
となります。MESHからラズパイのプログラムを叩くための簡単なHTTP Serverを作ります。
今回はbottle
という軽量マイクロWebフレームワークを使いました。事前準備
ソースコード内で使用するライブラリを
pip
でインストールしておきます。
(requests
はSlackをたたきに行くときに使います)$ pip install bottle requestsソースコード
場所はどこでもいいので適当なところにファイルを作り、下記ソースコードを書きます。
名前は何でもいいですが今回はserver.py
としました。
(「ラズパイ版MESHを使って、Apple Watchで操作するおしゃべりロボットを作る - Qiita」を参考にしました)server.py# -*- coding: utf-8 -*- from bottle import route, run, template import json import requests slack_settings = { # channel_nameは任意で変えましょう # こうしておくと他のチャンネルにも通知したくなったとき設定を増やしやすいのでこうしてあります 'channel_name': { 'url' : '{「Incoming WebhookのURLを取得」の工程で取得したURL}', 'botname': 'test', 'icon' : ':muscle:', 'message': '<!here>\nMESHからの通知だよ' } } def push(setting): datas = { 'username' : setting['botname'], 'icon_emoji' : setting['icon'], 'text' : setting['message'], 'contentType': 'application/json' } payload = json.dumps(datas) result = requests.post(setting['url'], payload) print(result) @route('/post/slack/<command>') def mesh_button(command): if command == 'channel_name': push(slack_settings['channel_name']) else: raise Errorサーバの起動
以下コマンドでサーバを起動します。特に難しいことは無いと思います。
$ python server.py
上手く起動すればこんな表示が出ます。
Bottle v0.12.17 server starting up (using WSGIRefServer())... Listening on http://localhost:8080/ Hit Ctrl-C to quit.試しに
curl
で叩いて試してみましょう。python server.py
をしているコンソールを閉じてしまうとサーバが止まってしまうので、別コンソールを開きましょう(SSH接続してる場合も)。$ curl http://localhost:8080/post/slack/channel_name
サーバを起動している側に
<Response [200]> 127.0.0.1 - - [04/Dec/2019 13:46:15] "GET /post/slack/channel_name HTTP/1.1" 200 0というログが出ると思うので、出ていれば成功です。
MESH SDKからカスタムブロックを作成する
MESHではブロックと呼ばれる定型処理を線でつなぐことで処理を作っていきます。
(のちに設定します)
ということで、先ほど作成したserver.py
のURLを叩くブロックを作成しておく必要があるんですね。なので、まずはそれを先に作ります。カスタムブロック作成ページへアクセス
SDK_TOP_JPにアクセスします。
「MESH SDKを使う」からカスタムブロックの管理画面に行けます。
カスタムブロックを作成する
Create New Block
をクリックすると、カスタムブロックの作成画面になるので設定していきます。
設定は下記の通りです。一通り設定できたら左上の「Save」をクリックして保存しましょう。Codeに設定したソースコードvar localhost = 'http://localhost:8080' + properties.path; ajax({ url : localhost, type : 'get', timeout: 5000, success: function (data) { callbackSuccess({ resultType : 'continue' }); }, error: function(request, errorMessage) { log('ERROR: ' + errorMessage); callbackSuccess({ resultType: 'continue' }); } }); return { resultType: 'pause' }設定の簡単な説明
この辺の設定は「ラズパイ版MESHを使って、Apple Watchで操作するおしゃべりロボットを作る - Qiita」を参考にしました。
Connector
これはInput Connectorのみを設定しました。
あとでMESHアプリを触れば分かるんですが、MESHのブロックにはInputとOutputがあります。
今回はボタンを押したあとこのカスタムブロックの処理を起動して終わりという感じになるので、Output Connectorには設定なしという状態です。
もしもこのカスタムブロックが動いた後に何か他のことをしたいとなったら、Output Connectorの方にも設定が必要です。Property
これはコード内で参照できるプロパティを作れるものになります。カスタムブロック配置時に、プロパティの値をいじれます。なので、例えば別のチャンネルに通知したくなったとかでも、ブロックを新しく作る必要はなく、配置するときにプロパティの値だけいじればOKみたいな風にできるということです。
Code
これはカスタムブロックがキックされたときに実行されるJavaScriptのコードになります。コードがキックされたら、Ajaxで先ほどPythonで書いたサーバを叩きに行ってます。
MESHアプリでレシピを作成する
MESHアプリの簡単な説明
MESHを使って処理を作るには必ずMESHアプリで「レシピ」を作る必要があります。このレシピはMESHアプリからでないと作れません。ラズパイで動作させるレシピもMESHアプリから作ることになります。
MESHハブの登録
事前準備として、スマホとラズパイのBluetoothはオンにしておきましょう。
スマホからMESHアプリを起動します。右上にスマホマークがありますが、これをタップすると以下の「他の端末に変更する」っていうのが出てくると思うのでこれをタップします。すると「端末を選ぶ」画面になるので、サインインして「ハブの追加」をタップします。
「ハブの追加」をタップすると次の画面になります。「セットアップを始める」をタップして次へ行きましょう。
スマホとラズパイの接続に成功すると以下の画面になります。もし何分待ってもつながらないということであれば、Bluetoothを確認するか、ラズパイがセットアップ可能な状態か確認しましょう(画面に表示されているリンクをタップすればやり方が書いてあると思います)。
この画面ですが、ラズパイのWi-Fi設定をするかしないかの画面です。有線で接続してしまっている場合は別ですが、基本的にはWi-Fiの接続も終わっているはずなのでスキップします。
上手くいくと最終的にこの画面になるので、先ほど追加したラズパイのMESHハブを選択して、画面下の「選択」をタップしましょう。
ブロック/カスタムブロックの追加・レシピの作成
※この工程を始める前にスマホのBluetoothを切っておくことをおすすめします。MESHブロックの電源を入れると、スマホの方とペアリングしようとしてきますが、ペアリングの相手はラズパイです。スマホのBluetoothを切っておかないと、MESHは永遠にスマホにペアリングしようとしてくるのでラズパイとペアリングしてくれません。
アプリトップの画面で「新しいレシピ」をタップするとこの画面になります。まずはMESHブロックを追加しましょう。MESHブロックの電源を入れ、左下の「ブロック 追加」のプラスマークをタップしましょう。
(電源はMESHブロックのシリコン素材部分を長押しで入ります、もし入らない場合は充電してください)
上手く追加できると左下のブロックのところに「ボタンブロック」が表示されるので、タップしてドラッグして配置しましょう。下記画面のようになるはずです。
次にカスタムブロックを追加します。先ほどWeb画面でJavaScriptのコードを登録したりしたものです。上画面に映っていますが「カスタム 追加」のプラスマークをタップすると追加画面へ行けます。そうすると以下のように先ほど登録したカスタムブロックが表示されるので、タップして、「追加」ボタンをタップします。
無事追加できたと思うので、先ほど「ボタンブロック」を配置したのと同じ要領でカスタムブロックを配置し、ボタンブロックの端とカスタムブロックの端を下記のようにつなげます。配置済みのボタンブロックからカスタムブロックまでをドラッグすれば線でつなぐことができます。
補足
配置したカスタムブロックをタップするとこのような画面になります。ここでプロパティの値をいじれるんですね。Pythonのサーバ側プログラムを変更すれば、同じカスタムブロックでここのPathを変えれば動作を変えられるというわけです。
動作確認
以上で設定は終わったので、MESHのボタンを押してみましょう。Slackに投稿がされれば成功です。
まとめ
以上でMESHのボタンを押したらSlackで通知する(ラズパイ経由で)が完成しました。
手順は結構多いですが、お気づきの通り書いたソースコードは僅かです。そもそもラズパイのインストールができないといけないみたいなハードルはありますが、それさえ超えてしまっていれば割とサクッとできてしまいます。
MESHはボタン以外にもLED、人感センサー、動きセンサー、温度湿度センサー、明るさセンサー、GPIOと様々なブロックがあります。強みとして、(GPIOを除いて)電子工作が全くできなくてもIoTの自作ができてしまうというところにあります。電子工作ってなるとどうしても多少電気電子の知識が必要になってくるので、普通にソフトウェアエンジニアをしてきた人からすると若干ハードルが高かったりするんですよね。しかしMESHタグなら電子工作の知識がほぼ不要で、ソフトウェアでほとんどのことがなぎ倒せるのでオススメです。
ちなみに物理的に何かしたいけど電子工作の知識がない…とかであれば、Ejectコマンドっていう大変便利なものがあるのでそれを使ってみるといいかもしれません(参考:EjectコマンドをRaspberry Piで遊ぼう ~CD-ROMドライブでかんたん工作~)。
- 投稿日:2019-12-04T23:53:59+09:00
2019年 よく使ったJavaScriptたちをまとめる
はじめに
JavaScript Advent Calendar 2019の4日目です。
本日は@watsuyo_2が「2019年 よく使ったJavaScriptたちをまとめる」を記事にします!フロントエンドエンジニア1年目を振り返りながら、これからJavaScriptを学ぶ人やよく使うメソッドを知りたい人がわかりやすいようにまとめます。
普段、 Nuxt.js(Vue.js) と Firestore を使用した開発をしているので例としてでてきますがご了承ください。↓は1年前に転職するまでの流れを書いた記事にあります。
新年からWebエンジニアになる僕に2018年、起こったことArray.prototype.map()
配列の要素をひとつひとつ取り出し、第一引数の値(例で言うx)を元に処理を行い、その結果から新たな配列を作れる
実務的な例
よく使うパターンとして、オブジェクト配列をそれぞれ取り出して、Firestoreにデータ更新、取得するような時に使います。
const todos = [ { id: 'H6BHBW7WS7H8DAT8HAK', content: '早起きする' }, { id: 'HGX3DMZ9ZT8GN7KHYUY', content: '早く寝る' }, { id: '4MT32NDNS6XFJUNGRPH', content: '牛乳を買う' }, { id: 'PHCXSNTRVZHNAJYWJT6', content: 'Youtubeは1日2時間まで' } ] todos.map(todo => ( firebase.firestore().collection(`todos/${todo.id}`).update({ todo.content }) ))Array.prototype.filter()
配列の要素をひとつひとつ取り出し、第一引数の値(例で言うx)を元に比較を行い、trueだった結果から新たな配列を作れる
実務的な例
オブジェクト配列をそれぞれ取り出して、特定の要素を削除する場合などで使いました。
const todos = [ { id: 'H6BHBW7WS7H8DAT8HAK', content: '早起きする' }, { id: 'HGX3DMZ9ZT8GN7KHYUY', content: '早く寝る' }, { id: '4MT32NDNS6XFJUNGRPH', content: '牛乳を買う' }, { id: 'PHCXSNTRVZHNAJYWJT6', content: 'Youtubeは1日2時間まで' } ] // 特定のtodo.idが'PHCXSNTRVZHNAJYWJT6'以外の配列を作成 const deleteOne = () => todos.filter(todo => todo.id != 'PHCXSNTRVZHNAJYWJT6' ) console.log(deleteOne()) // [ // { id: 'H6BHBW7WS7H8DAT8HAK', content: '早起きする' }, // { id: 'HGX3DMZ9ZT8GN7KHYUY', content: '早く寝る' }, // { id: '4MT32NDNS6XFJUNGRPH', content: '牛乳を買う' } // ]Array.prototype.find()
配列の要素をひとつひとつ取り出し、第一引数の値(例で言うx)を元に比較を行い、trueだった結果を取得する
実務的な例
オブジェクト配列をそれぞれ取り出して、配列中の特定の要素を抽出する場合などで使いました。
const todos = [ { id: 'H6BHBW7WS7H8DAT8HAK', content: '早起きする' }, { id: 'HGX3DMZ9ZT8GN7KHYUY', content: '早く寝る' }, { id: '4MT32NDNS6XFJUNGRPH', content: '牛乳を買う' }, { id: 'PHCXSNTRVZHNAJYWJT6', content: 'Youtubeは1日2時間まで' } ] const pickUpTodo = () => todos.find(todo => todo.id === 'PHCXSNTRVZHNAJYWJT6' ) console.log(pickUpTodo()) //{ id: 'PHCXSNTRVZHNAJYWJT6', content: 'Youtubeは1日2時間まで' }Array.prototype.push()
配列に新しい要素を追加する
実務的な例
配列になにか新しい要素を追加する場面はよく遭遇しました。
const todos = [ { id: 'H6BHBW7WS7H8DAT8HAK', content: '早起きする' }, { id: 'HGX3DMZ9ZT8GN7KHYUY', content: '早く寝る' }, { id: '4MT32NDNS6XFJUNGRPH', content: '牛乳を買う' }, { id: 'PHCXSNTRVZHNAJYWJT6', content: 'Youtubeは1日2時間まで' } ] todos.push({ id: 'WRABJ9IY4RJML7L4OB2', content: 'リーダブルコードを読む' }) console.log(todos) // [ // { id: 'H6BHBW7WS7H8DAT8HAK', content: '早起きする' }, // { id: 'HGX3DMZ9ZT8GN7KHYUY', content: '早く寝る' }, // { id: '4MT32NDNS6XFJUNGRPH', content: '牛乳を買う' }, // { id: 'PHCXSNTRVZHNAJYWJT6', content: 'Youtubeは1日2時間まで' }, // { id: 'WRABJ9IY4RJML7L4OB2', content: 'リーダブルコードを読む' } // ]String.prototype.match()
正規表現に対して文字列のマッチングし、マッチすればtrue、しなければfalseを返します。
実務的な例
入力されたデータに対し、バリデーションをかける時などでよく使いました。
const telA = '070-7374-0044' const telB = '09024570411' console.log(telA.match(/^(0{1}\d{9,10})$/g)) // null console.log(telB.match(/^(0{1}\d{9,10})$/g)) // true三項演算子
if文の省略的な立ち位置になります。
以下がif文と三項演算子との対照表です。パターンA: if文
const hoge = 0 if(hoge === 0) { return 'fuga' } else { return 'hoge' }パターンB: 三項演算子
const hoge = 0 hoge === 0 ? 'fuga' : 'puge'実務的な例
実務では上記の例のように全く対象のものとしては捉えておらず、可読性を重視します。
特にreturn
で結果だけが欲しい場合では三項演算子
を使うことが多いです。例えばVue.js×TypeScriptに限る話になりますが、computed内で値が
this
を使って参照する値が存在するかをチェックし、存在すればその値を返し、無ければnullを返す場合に使います。computed: { todo (): Terms | null { return this.todo.titles ? todo.titles : null } },おわりに
今回、例としてあげた部分はほんの一部で、その他にもたくさんありますので奥は深いです。
振り返ってみても、JavaScriptフレームワークが様々な現場で普及している2019年ですが、基本的なメソッドや書き方は初心忘れるべからずということで押さえておきましょう。メンションつきツイートをしていただけるとたいへん喜びます!
thanks-mentionsというQiitaの記事を作者に対してメンションを飛ばしながらツイートが出来るPWAを作りました?
ぜひ、メンションつきツイートをしていただけるとたいへんとてもとても喜びます!
JavaScript Advent Calendar 2019
JavaScript Advent Calendar 2019明日の担当は、
@okumurakengoさんです!
- 投稿日:2019-12-04T23:08:36+09:00
Vue.jsでちょっとおしゃんてぃなimport
はじめに
いなたつアドカレの四日目の記事です。
今回はVue.jsで使えるちょっとかっこつけたおしゃんてぃなimport方法について書いてくぜ
じっそー
router.jsconst loadView = view => () => import(`./views/${view}.vue`) export default new Router({ mode: 'history', base: process.env.BASE_URL, routes: [ { path: '/', name: 'home', component: loadView('Home') }, { path: '/about', name: 'about', component: loadView('About') } ] })こんなかんじですね、前提として、viewに関するファイルがviewsディレクトリにまとまっているとおもってください。
loadViewで引数viewを受けて無名関数を高階的にimportする関数を返却しています。
これで、import文を別に何度も書く必要がなく、importすることができ、すこしおしゃんてぃな感じが醸し出せます。
- 投稿日:2019-12-04T22:21:33+09:00
jQuery 基本その4 検索フォーム
今日は基礎的な検索フォームについて
書こうと思います。基本形
まず基本形として
HMTL要素を書き換えるコードを例として書きます。①submitのidからクリックアクションが会ったらイベントが発火
②keywordのidから入力要素取得
③resultのidへtextを入力内容に変更(仮)という手順です。
使用するHTMLは
html<body> <div class="form-group"> <input type='text' id="keyword" class="form-control" placeholder="好きなフルーツを入力してください"> <button type="button" id="submit" class="btn">検索</button> </div> <p id="result"></p> </body>このような形を使用します。
まずコードの雛形について説明していきます。
基本形$(function() { $("#submit").on("click", function() { //①submitのidからクリックアクションが会ったらイベントが発火 let input = $("#keyword").val(); //②keywordのidから入力要素取得 $("#result").text(input); //③resultのidへtextを入力内容に変更(仮) }); });入力内容と同一のものを検索
まずeach構文で配列の全ての要素を引っ張ってきます。
jQuery$.each(fruits, function(i, fruit) { // 配列の要素fruitと入力した値が一致するかどうかの処理を書く // 一致したらループ抜ける });jQUeryvar fruits = ['リンゴ', 'バナナ', 〜〜]; $(function() { $("#submit").on("click", function() { let input = $("#keyword").val(); $.each(fruits, function(i, fruit) { //each構文を設定 if (input === fruit) { //if構文で入力内容と同一のものを条件に $("#result").text(input); //text内容入力内容に変更 return false; //一致するものを確認したらRubyで言うbreak文で終わります。 } }); }); });一致するものがなかった時
例外文の場合の記述はこうです。
jQUery//(省略) return false; } else { $("#result").text("一致する果物はありませんでした。"); } //(省略)一部の言葉で検索
「パイ」と検索したら「パインとパイナップル」が一致するので
この2つのフルーツが表示されるようにしましょう。
このように単語の前方と一致する単語を検索することを前方一致といいます。
前方一致で検索をするには、正規表現を使う必要があります。また複数の検索結果の出力になるので
下記のようなhmtlの設定もした方がいいです。hmtl<ul id="list"></ul>基本的な構文はこちら
jQuery// 正規表現オブジェクトに変換 let reg = new RegExp("前方一致"); // matchメソッドで一致したらtrueかfalseを戻す match(reg);例文を作ってみるとこうです。
三つのコードに分けて記述されています。
①変数listにidがlistを設定
②appendList関数を設定
③submitイベントを設定
という内容です。jQuery$(function() { let list = $("#list"); //①変数listにidがlistを設定 function appendList(word) { //②appendList関数を設定 let item = $('<li class="list">').append(word); //引数wordにlistクラスを追加 list.append(item); //変数listにitemを追加 } $("#submit").on("click", function() { //③submitイベントを設定 let input = $("#keyword").val(); //input内容を取得 let reg = new RegExp("^" + input); //正規表現での前方一致検索 $(".list").remove(); //listクラスの削除 $.each(fruits, function(i, fruit) { //each構文の設定 if (fruit.match(reg)) { //fruitより一致内容検索 appendList(fruit); //一致した内容を②に飛ばします。 } }); if ($(".list").length === 0) { //一致する内容がなかった場合 appendList("一致する果物はありませんでした"); } }); });複数の言葉で検索
複数の言葉でも検索できるようにしましょう。
「パ リ」と検索された場合、「パイン, パイナップル, リンゴ, パリ」が一致するので
これら4つが表示されるようにします。加えた記述主に
④各要素を正規表現で検索の仕様変更。
⑤複数の検索ワードを検索。です。
完成形はこちら
jQueryvar fruits = ['リンゴ', 'バナナ', 〜〜]; $(function() { let list = $("#list"); function appendList(word) { let item = $('<li class="list">').append(word); list.append(item); } function editElement(element) { let result = "^" + element; return result; } $("#submit").on("click", function() { let input = $("#keyword").val(); let inputs = input.split(" "); let newInputs = inputs.map(editElement); let reg = RegExp(newInputs.join("|")); $(".list").remove(); $.each(fruits, function(i, fruit) { if (fruit.match(reg)) { appendList(fruit); } }); if ($(".list").length === 0) { appendList("一致する果物はありませんでした"); } }); });加えた内容だけ表示すると
jQuery//(省略) function appendList(word) { let item = $('<li class="list">').append(word); list.append(item); } function editElement(element) { //④各要素を正規表現で検索の仕様にします。 let result = "^" + element; return result; } $("#submit").on("click", function() { //⑤複数の検索ワードを検索します。 let input = $("#keyword").val(); let inputs = input.split(" "); //入力内容から配列を作成,引数に設定した空欄で分けます。 let newInputs = inputs.map(editElement); //mapを使って要素を加工した新しい配列を作成します。 let reg = RegExp(newInputs.join("|")); //joinを使って|で配列の要素をつないだ正規表現を作成します。 $(".list").remove(); //(省略)splitを使って文字列から配列を作成する
split()
split()は、文字列を複数の部分文字列に区切り、文字列の配列に分割します。引数には文字列を区切るための文字を指定します。jQuerylet inputs = input.split(" ");mapを使って要素を加工した新しい配列を作成する
map()
map()は、配列から要素を1つずつ取り出し、引数に与えられた関数の処理を行った結果から新しい配列を作ります。jQuerylet newInputs = inputs.map(editElement);joinを使って|で配列の要素をつないだ正規表現を作成する
join()
join()は、配列のすべての要素をつないだ文字列を作成します。
引数に文字列を渡すと、その文字列で文字と文字をつなげます。jQuerylet reg = RegExp(newInputs.join("|"));複数の言葉で正規表現を使用するには|を使って言葉を繋げます。
複数の言葉が要素の配列newInputsの要素を|でつなぐことで複数検索ができるようにします。join()の引数に|を渡すことでnewInputsの要素を|でつないでいます。
その結果をRegExpの引数に渡すことで正規表現を作成しています。
- 投稿日:2019-12-04T21:56:14+09:00
PHPからJavaScriptへの値渡し
はじめに
PHPからJavaScriptへの値の渡し方を、phtmlで書いてまとめます。
なるべく同じような型として解釈されるように変換しています。PHP(string) → JS(string)
test.phtml<?php $foo = 'abcde'; ?> <script> let bar = '<?php echo $foo; ?>'; console.log('typeof: ' + typeof bar); console.log('value: ' + bar); </script>console.logtypeof: string value: abcdePHP(integer) → JS(number)
test.phtml<?php $foo = 12345; ?> <script> let bar = <?php echo $foo; ?>; console.log('typeof: ' + typeof bar); console.log('value: ' + bar); </script>console.logtypeof: number value: 12345PHP(boolean) → JS(boolean)
test.phtml<?php $foo = true; ?> <script> let bar = Boolean(<?php echo $foo; ?>); console.log('typeof: ' + typeof bar); console.log('value: ' + bar); </script>console.logtypeof: boolean value: truePHP(array) → JS(object)
test.phtml<?php $foo = [1, 2, 3, 4, 5]; $foo = json_encode($foo); ?> <script> let bar = JSON.parse('<?php echo $foo; ?>'); console.log('typeof: ' + typeof bar); for (let i = 0; i < bar.length; i++) { console.log('value: ' + bar[i]); } </script>console.logtypeof: object value: 1 value: 2 value: 3 value: 4 value: 5PHP(array) → JS(object) [連想配列]
test.phtml<?php $foo = ['key' => 'value', 'aaa' => 'bbb']; $foo = json_encode($foo); ?> <script> let bar = JSON.parse('<?php echo $foo; ?>'); console.log('typeof: ' + typeof bar); for (let key in bar) { console.log(key + ': ' + bar[key]); } </script>console.logtypeof: object key: value aaa: bbbまとめ
Bool or Array を渡すときは、ひと手間必要です。
- 投稿日:2019-12-04T21:38:06+09:00
だから僕は だから僕は GASでTwitterに奇声をあげた
2019/10/01。僕はTwitterを楽しんでいた。
#IT #正社員
— アナコンダ毒蝮 (@yameteyou) January 30, 2019
twitterをちゃんと始めて1週間。
そろそろ自分のことを話そうと思う。
私は正社員でSEだ。
SESの現場を10は経験しただろうか。
一方で自社製品の開発にもPMとして携わった。
予算、要員、品質、ステークホルダ管理。
それなりの経験を持っていると自負している。
(続く)僕「ふう…Twitterは本当に勉強になるなあ」
僕「技術的な課題を呟いたら反応してくれるし」
僕「僕もみんなを喜ばせてあげたい」
僕「将来はTwitterの中の人になろう…」
僕「…ん?」
僕「………」僕「どうしよう…変な人だ」
僕「もしくはブロック…でも…」僕「こんな人もTwitterにいるんだ…」
僕「さすがTwitter、懐が深いや」ピカイア「ピカイア!」
僕「うわびっくりした!野生のピカイアか」※1
僕「………」
僕「なんだか楽しそう!」
僕「僕もやってみたい!」
僕「そうだ! 奇声のエヴァンジェリストになろう!」
作った
僕「自信のあるGASで作ろう」
奇声.gsfunction tweetAnagram(){ var word_list = 'ルイズ!ルイズ!ルイズ!ルイズぅぅうううわぁああああああああああああああああああああああん!あぁああああ…ああ…あっあっー!あぁああああああ!!!ルイズルイズルイズぅううぁわぁああああ!!!あぁクンカクンカ!クンカクンカ!スーハースーハー!'.split(''); var ran_result = ''; ran_result = word_list.sort( function(){ return 0.5 - Math.random() } ).join(''); // if (Math.floor(Math.random() * 10) < 3) { // ran_result = 'チンコ・フルパァン' // } var msg = ran_result; tweet(msg); }僕「これを見たアナタ、色々ツッコみたいところがあるかもしれない」※2
僕「とりあえず無視するね!」
僕「ポイントはsort()のコールバックファンクション!」
奇声.gsran_result = word_list.sort( function(){ return 0.5 - Math.random() } ).join('');僕「javascriptのsort()は引数となったファンクションでソート処理を行うんだ」
僕「上のコードを見ると、return 0.5 - Math.random()という書き方をしている」
僕「つまり、戻り値がTrueになるかFalseになるか分からないんだ」
僕「sort()は対象の文字列の全ての文字にこの処理を行うから、結果としてランダム文字列が出来上がるってワケ」
僕「でも、毎回手動で動かすのはダルいな」
僕「スケジューリングしよう」
僕「準備は出来た」
僕「僕も奇声をあげよう!!」
あ!ああルあんわぁあ!ああクー!あー…ぁああ!!クカうああ!あっ!ぁあズ!インぁあクスああぅあル!ああンズうズああカあスーっあ!ーあああぁあ…ぅぁあああカうああンイあ!ルズイ!ーあルぅあうあイカズイルズイ!!ズハンハあクあルあああイルわあう
— アナコンダ毒蝮 (@yameteyou) November 30, 2019
僕「………できた!!!」僕「みんな喜んでくれるかな」
僕「国民総幸福量が1.5倍になったりして」
僕「国民栄誉賞ものだぞ」
僕「(スピーチの練習しなくちゃ)」
おしまい。
- 投稿日:2019-12-04T21:12:26+09:00
jQuery 基本その3 フォームの内容入力について
検索フォームについて書こうと思いましたが
思いの他入力内容について量が多くなったので
別途書き記します。.val()
「val()」は、HTMLタグ内に記述されている
value属性を取得したり変更することができるメソッドになります。html<form> <input type="text" value="入力"> <input type="checkbox" value="sono1">:その1 <input type="checkbox" value="sono2" checked>:その2 <input type="checkbox" value="sono3">:その3 </form>上記の入力ボックスやチェックボックスのように
value属性が記述されていれば「val()」メソッドで取得・変更することが可能です。より基本的な流れを説明すると
html<button id="btn" value="a">ボタン</button> <!--これでhtmlでidがbtnとvalueを設定-->jQueryvar btn = $('#btn').val(); //変数にidがbtnのvalueを代入 console.log( btn ); //変数btnを出力コンソールa # valueの値が出力されました。
上書き・設定
またはjQueryを下記のようにすると
jQueryvar btn = $('#btn').val('b'); //変数にidがbtnのvalueを代入 console.log( btn ); //変数btnを出力bが出力されます。
これはhtmlにvalueが設定されてなくても一緒です。
コードの順番から行ってjQueryのval()の
引数の値にvalueが設定または上書きされると考えてください。select要素
select要素というのがありますが
ほぼ仕様は一緒でselectと設定すれば値が入力されます。select<select multiple> <option value="1">a</option> <option value="2">b</option> <option value="3">c</option> </select> <button>ボタン</button>jQuery$('button').click(function() { var value = $('select').val(); //ここで設定します。 console.log(value); })コンソール1,2 # aとbを選択したら出力されます。
checkbox要素
checkbox要素の場合はthisで設定されます。
それを含めてポイントは三つ
①input:checked要素で設定
②each構文でtype="checkbox"でチェックされたのを設定
③thisで現在のチェック内容を拾ってきます。html<input type="checkbox" value="1">サンプル1 <input type="checkbox" value="2">サンプル2 <input type="checkbox" value="3">サンプル3 <button>ボタン</button>jQuery$('button').click(function() { $('input:checked').each(function() { //①input:checked要素で設定 //②each構文でtype="checkbox"でチェックされたのを設定 var r = $(this).val(); //③thisで現在のチェック内容を拾ってきます。 console.log(r); }) })radio要素
radio要素は「select / checkbox」と違い、ユーザーが選択できるのは1つだけでなのでシンプルです。
②each構文でtype="checkbox"でチェックされたのを設定がないので短くなります。html<input type="radio" name="sample" value="1">サンプル1 <input type="radio" name="sample" value="2">サンプル2 <input type="radio" name="sample" value="3">サンプル3 <button>ボタン</button>jQuery$('button').click(function() { // ②each構文でtype="checkbox"でチェックされたのを設定がありません。 var result = $('input:checked').val(); console.log(result); })textbox要素
textbox要素は、ユーザーが何らかの文字列を入力することができるボックスになります。
value値を設定することで、あらかじめボックス内に任意の文字列を入力した状態にできます。html<input type="text" value="サンプルテキスト">jQueryvar value = $('input').val(); console.log(value);コンソールサンプルテキスト
- 投稿日:2019-12-04T21:06:06+09:00
3種類のデバイスタイプを判別する2つの方法
Webサイトにアクセスしているユーザーのデバイスデータ(モバイル、タブレット、デスクトップ)を取得するコードのメモ
ウィンドウの大きさで判別する方法
function ...() { var width = window.innerWidth, screenType; if (width <= 520) { screenType = "mobile"; } else if (width <= 820) { screenType = "tablet"; } else { screenType = "desktop"; } return screenType; }参照したサイト:
https://www.upbuild.io/blog/device-specific-gtm-tags/ユーザーエージェントで判別する方法
//mobile-detect.js(https://github.com/hgoebl/mobile-detect.js/)を読み込み後 function ...() { var md = new MobileDetect(window.navigator.userAgent); var mobile = md.phone() ? "mobile" : undefined; var tablet = md.tablet() ? "tablet" : undefined; return mobile || tablet || "desktop"; }参照したサイト:
https://github.com/hgoebl/mobile-detect.js/
https://support.google.com/tagmanager/forum/AAAAnP_FwdICvO-Fx7PeW8背景
- 広告サービスCriteoのサーバーに送信するデータとしてデバイスタイプを取得しておく。
- このJavascriptコードはタグマネジメントツール(GoogleタグマネージャーのカスタムJavascript)に設置する。
- 投稿日:2019-12-04T20:14:13+09:00
Java Scriptで電卓作ったった
電卓を作ってみよう。
・目的
プログラミングを触ってみたい人、始めたばかりの人
・ゴール
足し算、引き算、掛け算、割り算、クリア、バックができる電卓HTML<html> <head> </head> <body> <div class="main"> <form name="form"> <input class="textview" name="textview"> </form> <div> <div> <button class="button" type="button" value="C" onclick="clea()">C</button> <button class="button" type="button" value="<" onclick="back()"><</button> <button class="button" type="button" value="/" onclick="insert('/')">/</button> <button class="button" type="button" value="X" onclick="insert('*')">*</button> </div> <div> <button class="button" type="button" value="7" onclick="insert(7)">7</button> <button class="button" type="button" value="8" onclick="insert(8)">8</button> <button class="button" type="button" value="9" onclick="insert(9)">9</button> <button class="button" type="button" value="-" onclick="insert('-')">-</button> </div> <div> <button class="button" type="button" value="4" onclick="insert(4)">4</button> <button class="button" type="button" value="5" onclick="insert(5)">5</button> <button class="button" type="button" value="6" onclick="insert(6)">6</button> <button class="button" type="button" value="+" onclick="insert('+')">+</button> </div> <div> <button class="button" type="button" value="1" onclick="insert(1)">1</button> <button class="button" type="button" value="2" onclick="insert(2)">2</button> <button class="button" type="button" value="3" onclick="insert(3)">3</button> <button class="button equal" type="button" value="=" onclick="equal()">=</button> </div> <div> <button class="button" style="width: 108;" type="button" value="0" onclick="insert(0)">0</button> <button class="button" type="button" value="." onclick="insert('.')">.</button> </div> </div> </div> </body> </html>次はCSSを当ててみる
<head> <style> button { width: 50px; height: 50px; } * { margin: 0; padding: 0; } .button { width: 50px; height: 50px; font-size: 25px; margin: 2px; cursor: pointer; background: #303AF2; border: 1px solid #303AF2; border-radius: 5px; color: #fffff0; } .textview { width: 224; margin: 2; font-size: 25; padding: 5; background: #303AF2; border: 1px solid #303AF2; border-radius: 5px; color: #fffff0; } .equal { height: 105px; position: absolute; margin-left: 6px; } </style> </head>電卓の形は完成しました。次はスクリプトの処理を書いていきましょう。
<head> <script> function insert(num) { document.form.textview.value = document.form.textview.value + num; } </script> <style> 中略 </style> </head>ボタンから文字を読み取る処理
<head> <script> function insert(num) { document.form.textview.value = document.form.textview.value + num; } function equal() { var exp = document.form.textview.value; if (exp) { document.form.textview.value = eval(exp); } } </script> <style> 中略 </style> </head>equalが押された時に足算してくれる処理。くわしくはeval()について
<head> <script> function insert(num) { document.form.textview.value = document.form.textview.value + num; } function equal() { var exp = document.form.textview.value; if (exp) { document.form.textview.value = eval(exp); } } function clea() { document.form.textview.value = ""; } function back() { var exp = document.form.textview.value; document.form.textview.value = exp.substring(0, exp.length - 1); } </script> <style> 中略 </style> </head>入力したデータを消す処理と一文字戻す処理。
これで完成です!
- 投稿日:2019-12-04T19:50:17+09:00
Vue Composition APIで型がぶっ壊れて楽しかったです
これは bosyu Advent Calendar 2019 の 4日目の記事です。
よろしくね。bosyuのフロントはVueを使っているのですが、最近 Composition API が上がってきているのでそれを使うようにしていこうぜ〜〜〜
という感じでやっております。https://github.com/vuejs/composition-api
これね。でこれがたまにアレなところがあるのですが、先日おもしろいところを見つけたので、それについて書いていくぞというアレです。
多分近々直ります。なにがおきた
タイトル通りなんですが、型が不思議になりました。
具体的なコードを書くと以下のようなときに型が?????となります。nazo.vueexport default createComponent({ setup () { const state = reactive({ status: 'hoge', value: 'fuga' }); const v = state.value; } });ここでは
state
は以下のようになるように期待していますが、type State = { status: string; value: string; }VSCode等でみると
state
はstring
となってしまいます![]()
なんでや
ということで、どんなかんじに型定義されているかみてみましょ。
https://github.com/vuejs/composition-api/blob/v0.3.4/src/reactivity/reactive.ts#L129
export function reactive<T = any>(obj: T): UnwrapRef<T> { if (process.env.NODE_ENV !== 'production' && !obj) { warn('"reactive()" is called without provide an "object".'); // @ts-ignore return; } if (!isPlainObject(obj) || isReactive(obj) || isNonReactive(obj) || !Object.isExtensible(obj)) { return obj as any; } const observed = observe(obj); def(observed, ReactiveIdentifierKey, ReactiveIdentifier); setupAccessControl(observed); return observed as UnwrapRef<T>; }こんなかんじでした。
return
のところの型がas
になってて強さを感じる。まあそれはさておきこの
UnwrapRef<T>
てのがきになりますね。
それを見てみましょう。https://github.com/vuejs/composition-api/blob/v0.3.4/src/reactivity/ref.ts#L17
export type UnwrapRef<T> = T extends Ref<infer V> ? UnwrapRef2<V> : T extends BailTypes ? T // bail out on types that shouldn’t be unwrapped : T extends object ? { [K in keyof T]: UnwrapRef2<T[K]> } : T // prettier-ignore type UnwrapRef2<T> = T extends Ref<infer V> ? UnwrapRef3<V> : T extends BailTypes ? T : T extends object ? { [K in keyof T]: UnwrapRef3<T[K]> } : T // ... // prettier-ignore type UnwrapRef10<T> = T extends Ref<infer V> ? V // stop recursion : Tこんな感じになってます。
ConditionalTypes
をつかって型を判定していますね。
http://www.typescriptlang.org/docs/handbook/advanced-types.html#conditional-typesでこの
Ref
の定義を見てみましょう。https://github.com/vuejs/composition-api/blob/v0.3.4/src/reactivity/ref.ts#L9
export interface Ref<T> { value: T; }です。
つまり
value
をメンバーとして持っている場合はRef
を extends しているとみなされてしまい、
UnwrapRef2
UnwrapRef3
... と流れていってしまいます。
最終的にUnwrapRef10
にたどり着きV
となります。結果的に最初に書いたように
state
がstring
のようになってしまうわけですね〜。かいけつさく
https://github.com/vuejs/composition-api/pull/167
PRはでてますが、Object
の中にObject
があったりすると、同様の問題が起きてしまうので困ったもんだなぁ〜という感じですね。かんたんにはどうこうできるような感じではないので、できれば
value
というのはできる限りつかわないようにするのがとりあえずの対策かなぁ〜。またVue-nextでは同様の問題が起きないような対処がされているようです。
https://github.com/vuejs/vue-next/blob/master/packages/reactivity/src/ref.tsなのでこれと同様な実装を
composition-api
にPRでなげるかとかですかね。まあそんな感じ。
あとは邪悪だけど
createComponent({ setup () { const state = reactive({ status: 'hoge', value: 'fuga' }) as unknown as { status: string, value: string }; const v = state.value; } });
unknown
とかany
とか一回型をぶっ壊して、むりやりやっちゃうとかね。
最高にダメな感じだけど。まとめ
- かたはむずかしい。
- かたはたのしい。
as unknow as Hoge
は最凶(強)。
- 投稿日:2019-12-04T19:14:42+09:00
JavaScriptのfor-in文がPythonなどと違う
JSの
for-in
文が思ったように動かない...素直に配列の中身を
for-in
文で取ろうとしたのだが、sample.jsvar a = ["hoge", "fuga", "piyo"]; for (var v in a) { console.log(v); } /* result 0 1 2 */なぜか、
0, 1, 2
と出る(泣)JSでは、
for-of
を使うsample.jsvar a = ["hoge", "fuga", "piyo"]; for (var v of a) { console.log(v); } /* result hoge fuga piyo */何かと、20分くらい悩んでた...。
PythonやC#などforeach文に慣れているとちょっと違和感wちなみに、
for-in
文の使い方は?JSでの
for-in
文はオブジェクトのプロパティを取得するみたいです。sample.jsvar pen = { name: "hoge", color: "black", price: 100 } for (var p in pen) { console.log(p); } /* result name color price */
- 投稿日:2019-12-04T19:14:42+09:00
JavaScriptにおけるfor-in文の動作
JSの
for-in
文が思ったように動かない...素直に配列の中身を
for-in
文で取ろうとしたのだが、sample.jsvar a = ["hoge", "fuga", "piyo"]; for (var v in a) { console.log(v); } /* result 0 1 2 */なぜか、
0, 1, 2
と出る(泣)JSでは、
for-of
を使うsample.jsvar a = ["hoge", "fuga", "piyo"]; for (var v of a) { console.log(v); } /* result hoge fuga piyo */何かと、20分くらい悩んでた...。
PythonやC#などforeach文に慣れているとちょっと違和感wちなみに、
for-in
文の使い方は?JSでの
for-in
文はオブジェクトのプロパティを取得するみたいです。sample.jsvar pen = { name: "hoge", color: "black", price: 100 } for (var p in pen) { console.log(p); } /* result name color price */
- 投稿日:2019-12-04T19:14:40+09:00
入社1年目のWEBエンジニアが、コードレビューで教わったことを振り返ってみる
アドベントカレンダー何書こうかな〜と考えていたところ、フロントエンドエンジニア1年目はコードレビューでどんな指摘を受けるのかという投稿を読み、私も良い機会なので書いてみることにしました。
私のステータス
- 昨年11月にqnoteに入社
- コールセンターからエンジニアにジョブチェンジ
- 学生時代にプログラミング未学習
- Laravel + Vue.js を使ったプロジェクトに参画
- フロントエンド / バックエンド共に行い、機能別に issue を立てて、issue 毎に担当。
初歩的なこと
typo
私はこれがひどい。
date
とdata
を間違える- 変数名が camelCase なのに snake_case で書いて値が取れないと騒ぐ
delete
をdetele
と書くエディタでスペルチェックをしてもらうのが良い。
Sublime Text なら、spell_check: true
にするかF6
でスペルチェックをしてくれる。
VSCode でもプラグインを入れればスペルチェックしてくれる。真偽値の統一
true
、'1'
、1
は全てtrue
と判断できるが、コーディング規則に従い統一するrand ではなく mt_rand
テストで乱数を生成するときに、
rand
を使用した。理由はググったら最初に出できたから。
しかし、rand
の上位互換であるmt_rand
を使用するように教わった。
理由はmt_rand
の公式ドキュメントにある。古い libc の多くの乱数発生器は、怪しげであるか特性が不明であったりし、 また低速でした。 mt_rand() 関数は、古い rand() の代替品となるものです。 この関数は、その特性が既知の乱数生成器 » メルセンヌ・ツイスター を使用し、 平均的な libc の rand()よりも 4 倍以上高速に乱数を生成します。
PHP: mt_rand - Manual命名規則に従う
変数名などの表記方法は一般的に以下の3つになる。
- camelCase キャメルケース
- PascalCase パスカルケース(キャメルケースの頭文字大文字版)
- snake_case スネークケース
参画したプロジェクトでは、変数名は
camelCase
、クラス名とファイル名はPascalCase
にすることになっていた。
これはプロジェクト毎で規則があると思うが、変数やファイル名などの規則がバラバラにならないように気をつける。エラーを読む
これ、よくやりがち。
エラーが出た
->怖くなって先輩に質問しに行く。
->先輩がエラー見る
->先輩『エラー文言読んだ?』
->私『読んでません。(汗』エラーは、ちゃんと問題点を書いてくれていることが多い。
先輩に聞きに行く前に、翻訳してみたり、ググってみればわかることは多い。
5分調べて、10分試して解決しなければ聞きに行く。くらいで良いと思う。公式ドキュメントを読もう(英語でも)
困った時、ググる。
すると Qiita とか はてブ とか teratail とか どっかのオンラインスクール とかが、検索上位に出てくる。
そういう記事は、タイトルが困ってる状態と似ていることが多いのだが、自分の環境と違ったり、間違っていたりすることもある。
で、結局解決せずに、先輩に聞きに行く
->先輩『公式ドキュメント読んだ?』
->私『読んでません(汗』内容によるが、基本的な使い方は公式ドキュメントに書いてある。
公式ドキュメントが英語のみってこともある。lodash とかね。
けど、そんな難しい言い回しはしていない。
翻訳しつつ、頑張って読んでいれば、だんだん読めるようになってくる。可読性向上
三項演算子やエルビス演算子で済むなら、if文は使わない。
最近の流行りというのもあるかもだけど、単純な条件分岐なら、if文を使わない方が読みやすい。
countによってメッセージを変える処理.jsmsg() { if (this.count > 10) { return 'カウントが 10回 を超えました。' } return '' }, // 三項演算子 msg() { return this.count > 10 ? 'カウントが 10回 を超えました。' : '' },一行を長くしすぎない(改行する)
一行の文字が多く、横に長すぎるとコードが読みにくい。
ESLintを導入していれば、エラーで教えてくれる。
max-len - Rules - ESLint - Pluggable JavaScript linterDRY
Don't repeat yourself(繰り返すな)。
同じような処理を複数回書くことは、修正が必要な時に修正漏れなどのリスクが増す。
また、同じような処理なら、別functionとして切り離せばテストもしやすい。テスト
DI を使う
以下のような機能のテストを書いているときに教わった。
DIを使用していない.phpclass Sample { public function execute(array $params) { if ($userCd = Arr::pull($value, 'user_cd')) { $userId = app(GetUser::class) ->getUserId($userCd); // 何かする } } }このテストを書く場合、GetUser::getUserId() が未実装だとテストが行えない。
そこで DI を使用する。
DIを使用した場合.phpclass Sample { private $getUser; public function __construct(GetUser $getUser) { $this->getUser = $getUser; } public function execute(array $params): string { if ($userCd = Arr::pull($value, 'user_cd')) { $userId = $this->getUser ->getUserId($userCd); // 何かする } } }このように DI を使用すると、この Sample クラスのテストをする際に、GetUser::getUserId() をモックすれば、テストが行える。
モックオブジェクトを使ったテスト.phpclass SampleTest extends TestCase { $getUser = $this ->getMockBuilder(GetUser::class) ->setMethods(['getUserId']) ->getMock(); $getUser->expects($this->once()) ->method('getUserId') ->willReturn(123); $sample = new Sample($getUser); $parames = [ 'user_cd' => 'hogehoge123' ]; $res = $sample->execute($parames); $this->assertEquals($res, '期待値'); }機能を細かく分ける
理想は、1クラスに1機能。
そうすることで、テストが容易になる。SOLID原則
敬愛なるゆいもっぷ先輩の記事をご参照ください?♂️
SOLID原則について簡単に書く猫
猫社員の見分け方
名前 特長 ふたば クリーム色 みるく 茶虎、口元が白い、尻尾が少し鍵しっぽ はな 茶虎、全体的にシュッとしてる ちまき 錆色、尻尾が縞様、おでこハゲ さくら 錆色、顔の桜色の部分多め りぼん 錆色、小さい、かわいい みぃ 白黒 猫社員たちをご覧になりたい方は、是非遊びにきてください!
株式会社qnote猫は腰を叩かれると喜ぶが、叩きすぎは良くない
叩きすぎるとヘルニアになる可能性がある。
最後に
いつもコードレビューや質問に応えていただいている @adwin さんや @poipoisaurus さんをはじめとする qnote の皆さんに本当に感謝しています。
ありがとうございます。
そして、来年もよろしくお願いします?
- 投稿日:2019-12-04T18:53:19+09:00
react-firebase-hooksを使ってみた(Firestore Hooks編)
はじめに
前回の記事でreact-firebase-hooksのAuth Hooksを使ってみたのですが、今回はFirestore Hooksに挑戦したいと思います。
react-firebase-hooks
リポジトリはこちらです。
https://github.com/CSFrequency/react-firebase-hooks
このライブラリは、4種類のAPIを提供しています。
- Auth Hooks
- Cloud Firestore Hooks
- Cloud Storage Hooks
- Realtime Database Hooks
今回対象とするのは2つ目のFirestore Hooksです。
テストデータの作成
最初に、Firestoreのコンソールでコレクションといくつかのドキュメントを作成します。コレクション名はtodosとしました。
コーディング
モジュールのimport
今回使うモジュールをimportします。
import React, { useState } from "react"; import ReactDOM from "react-dom"; import firebase from "firebase"; import { useCollectionData } from "react-firebase-hooks/firestore";TodoListコンポーネント
todosコレクションを表示するコンポーネントを作ります。
const TodoList = () => { const [values, loading, error] = useCollectionData( firebase.firestore().collection("todos"), { idField: "id" } ); if (loading) { return <div>Loading...</div>; } if (error) { return <div>{`Error: ${error.message}`}</div>; } return ( <ul> {values.map(value => ( <li key={value.id}>{value.title}</li> ))} </ul> ); };
idField
でidを取得するところがポイントです。NewTodoコンポーネント
todosコレクションに新たなドキュメントを追加するためのコンポーネントを作ります。
const NewTodo = () => { const [title, setTitle] = useState(""); const [pending, setPending] = useState(false); const add = async () => { setTitle(""); setPending(true); try { await firebase .firestore() .collection("todos") .add({ title }); } finally { setPending(false); } }; return ( <div> <input value={title} onChange={e => setTitle(e.target.value)} /> <button type="button" onClick={add}> Add </button> {pending && "Pending..."} </div> ); };エラー処理は省略しています。
Appコンポーネント
最後に、全体をつなげるAppコンポーネントとReactDOMのrenderです。
const App = () => { return ( <div> <TodoList /> <NewTodo /> </div> ); }; const rootElement = document.getElementById("root"); ReactDOM.render(<App />, rootElement);CodeSandbox
https://codesandbox.io/s/wonderful-browser-zh0wc
完成したものがこちらです、実際に動作させるためにはforkしてfirebaseConfigを置き換える必要がありますのでご注意ください。
おわりに
Firestoreのリアルタイム更新とReact Hooksはとても相性がいいと感じました。ドキュメントを追加したら、すぐに更新されます。Firestoreのコンソールから追加しても同様です。
今回は、useCollectionDataを使いましたが、用意されているhooksはさらにあります。
- useCollection
- useCollectionOnce
- useCollectionData
- useCollectionDataOnce
- useDocument
- useDocumentOnce
- useDocumentData
- useDocumentDataOnce
Once系は一度だけの取得なのですが、その場合はhookがどれだけ役立つかは微妙です。callbackから使うことになることが多い気がします。また、Data系のhookはTypeScriptの型が付けられますが、ソースコード上は単にアサーションしているだけなので、予期せぬランタイムエラーが発生する可能性がありそうです。結局、独自の拡張をしようと思うとcustom hooksを作ることになりそうですが、その先に本ライブラリのhooksから合成できるかはやってみないと分からないといった感じになりそうです。
- 投稿日:2019-12-04T18:37:59+09:00
ゆ、useEffectちゃん!初回に動かないで!
TL;DR
よいしょ……よいしょ……
useEffect便利ですよね。
stateの変化を監視し、そのstateの変化に伴うべき処理の流れを一元管理できます。import React, { useState, useEffect } from 'react' import ReactDOM from 'react-dom' function Counter(props) { const [count, setCount] = useState(0) const [lastUpdatedAt, setLastUpdatedAt] = useState(null) useEffect(() => {// 『count』 が更新された際に、それに伴い必ず実行される setLastUpdatedAt(new Date().toString()) }, [count]) return ( <div> <p>カウント {count} 回目</p> {/* 変な要件の機能だなぁ…? */} <p>?最終カウントアップ日時? {lastUpdatedAt || ''} </p> <p> <button onClick={() => setCount(count + 1)}>カウントアップ</button> </p> </div> ) } ReactDOM.render( <Counter />, document.getElementById('root') )かなしいところ
(上記例の様に)何も考えずそのまま使うと、対象のstateが変更されているか否かに関わらず、初回レンダー時『にも』必ず動いてしまう。
カウントまだ1回もしてないのに「最終カウントアップ日時」出てんのおかしいダルルォン!?
(うるさいですね・・・)かいけつ
useRefを使う。
import React, { useState, useEffect, useRef } from 'react' import ReactDOM from 'react-dom' function Counter(props) { const [count, setCount] = useState(0) const [lastUpdatedAt, setLastUpdatedAt] = useState(null) const isFirstRender = useRef(false) useEffect(() => { // このeffectは初回レンダー時のみ呼ばれるeffect isFirstRender.current = true }, []) useEffect(() => {// 『count』 が更新された場合『と』初回レンダー時に動くeffect if(isFirstRender.current) { // 初回レンダー判定 isFirstRender.current = false // もう初回レンダーじゃないよ代入 } else { setLastUpdatedAt(new Date().toString()) } }, [count]) return ( <div> <p>カウント {count} 回目</p> <p>?最終カウントアップ日時? {lastUpdatedAt || ''} </p> <p> <button onClick={() => setCount(count + 1)}>カウントアップ</button> </p> </div> ) } ReactDOM.render( <Counter />, document.getElementById('root') )かつてのクラスコンポーネントで言う所の「componentDidMount」と似た働きが期待出来ます。
けつろん
結果オーライ! 終わりやっぱりReactはたのしい。以上。
- 投稿日:2019-12-04T18:33:30+09:00
User JavaScript and CSS (Chrome 拡張機能) を使って Redmine をカスタマイズしてみた
User JavaScript and CSS とは
Chrome の拡張機能として提供されている,既存のウェブサイトに独自の Javascript や CSS を埋め込むライブラリです.
User JavaScript and CSS - Chrome ウェブストアこれを利用して,ダークモードがないウェブサイトにダークモードを実装してみたり, Yahoo! のサイトに自分の好きなキャラクターのイラストを表示させたり,いろいろなことができます.
今回は,社内のタスク管理に使用している Redmine で,チケットの説明に出てくるスニペットの内容をクリップボードにコピーする機能を Javascript で実装してみました.画面設計
スニペットの枠外,右下部分にコピーのアイコンを配置し,
アイコンをクリックすると「コピーしました!」のダイアログを表示することにします.
実装
アイコンを読み込む
今回は Font Awesome のアイコンを使用します.
Font Awesome についてはこちら流れは次の通りです.
- Font Awesome を読み込む link タグを作成する
- head タグを取得し,その子要素として先ほど作成した link タグを追加する
redmine.jsconst link = document.createElement('link'); link.setAttribute('rel', 'stylesheet'); link.setAttribute('href', 'https://use.fontawesome.com/releases/v5.6.3/css/all.css'); document.getElementsByTagName('head')[0].appendChild(link);スニペットの枠外,右下部分に要素を配置する
流れは次の通りです.
スニペット (pre タグ) を取得する -> 各スニペットが配列で取れます
各スニペットに対して,次の処理を行う
- コピーボタンを配置するための p タグを作成する (snippet)
- 後にコピーボタンとなる span タグを作成する (copyButton)
- (copyButton に title, innerHTML, className などの属性を設定する)
- copyButton にクリックのイベントリスナーを付与する (内容は下記)
- コピー対象となる textarea タグを作成 (textarea タグでなくても行けるかもしれません)
- スニペット内の文章を textarea タグに設定する
- 作成した textarea タグを body タグの子要素として配置 -> 選択 -> コピー
- textarea タグは不要になるので取り除く
- コピーが完了した旨のメッセージを表示
- copyButton を snippet の直後に配置する
redmine.js// スニペットのタグを選択 const snippets = document.getElementsByTagName('pre'); // console.log(snippets); // HTMLCollection // console.log(typeof snippets); // object // コピーボタンを配置 Object.keys(snippets).forEach(function (index) { let snippet = snippets[index]; let p = document.createElement('p'); p.className = 'copy-snippet'; let copyButton = document.createElement('span'); copyButton.title = 'Copy to Clipboard'; copyButton.innerHTML = '<i class="far fa-copy"></i> Copy'; copyButton.className = 'copy-button'; copyButton.addEventListener('click', function () { let copyTarget = document.createElement('textarea'); copyTarget.textContent = snippet.innerText.trim(); // 見た目に合わせて文末改行削る? let body = document.getElementsByTagName('body')[0]; body.appendChild(copyTarget); copyTarget.select(); document.execCommand("copy"); body.removeChild(copyTarget); // 後処理まで終わったらおまけ (function () { alert('コピーしました!'); })(); }); p.appendChild(copyButton); snippet.parentNode.insertBefore(p, snippet.nextSibling); });その他
画面右下にカエルのイラストを入れて,全体を CSS で整えます.
redmine.js// カエルがペコペコおじぎする GIF const imageUrl = 'https://sozai-good.com/download?id=2451&type=7&subnumber=0&extention=gif'; // 画像挿入処理 let img = document.createElement('img'); img.id = 'inserted-image'; img.height = 105; img.src = imageUrl; const wrapper = document.getElementById('wrapper'); wrapper.parentNode.insertBefore(img, wrapper.nextSibling);redmine.css#wrapper, #main { background-image: url("https://www.pakutaso.com/shared/img/thumb/mizuho1102dssd_TP_V.jpg"); background-repeat: no-repeat; background-size: cover; background-attachment: fixed; opacity: 0.96; } #top-menu { /*background-color: darkgreen;*/ background-color: rgba(10, 106, 10, 0.9); } #header, #footer { /*background-color: forestgreen;*/ background-color: rgba(0, 0, 0, 0.5); } #main-menu li a { /*background-color: darkgreen;*/ background-color: rgba(10, 106, 10, 0.6); } #main-menu li .selected { color: lightgreen; } #content { margin-bottom: 20px; } #sidebar { background-color: white; } #footer { position: fixed; background-color: black; right: 0; bottom: 0; } pre, .CodeRay { font-family: Monaco, monospace; } #inserted-image { position: fixed; right: 25px; bottom: 45px; } .copy-snippet { text-align: right; } .copy-button { cursor: pointer; background: none; border: 0; /* settings when hovered */ /*transition: color 0.1s linear;*/ } .copy-button:hover { color: red; }Redmine の公式ページを表示してみる
Defect #32525: CSV related tests fail with Rails 5.2.4 - Redmine
やったー!
効用
- 「コマンドはコピペして貼り付ける」のが暗黙のルールとなっている本番環境へのリリース作業において,逐一スニペットの中身をマウスで選択する手間が省けます.
- 自分の画面だけに実装されるので,他の人に自慢できます.
- フロント周りがちょっとだけ楽しくなります.
- 投稿日:2019-12-04T18:20:30+09:00
非Javascriptエンジニアのための「Javascriptの型」
ワガハイは非Javascriptエンジニアのアクマちゃんデモ!
この記事は非JavascriptエンジニアのためのJavascript入門 3日遅れのひとり Advent Calendar 2019の4日目の記事(3日遅れだから実質1日目)であると同時にワガハイのQiita初投稿記事デモ!読みにくいところや間違いがあるかもしれないけどよろしくデモ。
はじめに
この記事はJavaScript 「再」入門をベースとして、非Javascriptエンジニア(非エンジニアではなくJavascript以外の言語をメインで使っているエンジニア)に向けて要点をまとめたり、ワガハイが理解できなかった箇所を掘り下げて調べていったりして構成していく予定デモ。
ワガハイはJavaエンジニアなので、他の言語のことはあんまりわからないけど、プログラムの基本がわかっている人が読んだら多分理解できるはずデモ。
わかりにくいところや追記してほしいところがあったら連絡くれるとうれしいデモ。
それじゃあスタートデモ。Javascriptの型
Javascriptには以下の型があるデモ。
- Number
- Boolean
- Symbol
- Object
- String
- Function
- Array
- Date
- RegExp
- null
- undefined
nullとundefinedはちょっと特殊な型で、String, Function, Array, Date, RegExpはObject型の一種デモね。Error型もあるけどこの記事では触れられていないみたいデモ。
それじゃあ一つづつ見ていくことにするデモ!
数値
浮動小数点数
Javascriptの数値はNumber型で扱えるデモ。Number型は「倍精度 64 ビットフォーマット IEEE 754 値 (double-precision 64-bit format IEEE 754 values)」と定義されているらしいデモ。Javascriptには整数は存在しないみたいだから注意デモ。
浮動小数点については今度またお勉強しようと思うデモ。console.log(0.1 + 0.2) // 0.30000000000000004 console.log(0.1 + 0.2 == 0.30000000000000004) // trueparseInt()関数
文字列を吸い数に変換する組み込み関数として、
parseInt()
関数を紹介するデモ。parseInt('123', 10); // 123第2引数は変換の基数デモ。省略可能だけど指定推奨デモ。
なぜかというと古いブラウザでは先頭の0を8進数として認識してしまうからデモ!// 2013年以降のブラウザでの処理結果 parseInt('010', 10); // 10 // 古いブラウザでの処理結果 parseInt('010', 10); // 8 ←デモッ!?一方先頭が0xの文字列は現在でも16進数として認識されるデモ。
// 2013年以降のブラウザでの処理結果 parseInt('0x10'); // 16 // 古いブラウザでの処理結果 parseInt('0x10'); // 16同じように
parseFloat()
関数もあるデモ、こちらは基数は常に10が用いられるデモ。第2引数はないから注意デモ。単項演算子
+
での変換単項演算子
+
を使って値を数値に変換することもできるデモ。+ '42'; // 42 + '010'; // 10 + '0x10'; // 16便利デモね!
NaN
もし
parseInt()
や+
で変換しようとした値が数ではない場合はNan
(Not a Number)という特別な値が返ってくるデモ。
NaNを使って算術演算をしようとすると、その結果はすべてNaNになってしまうデモ。NaN + 5 // NaN組み込み関数
isNaN`を使えば、Numberに入っている値が
NaN``かどうか調べることができるデモ。isNaN(NaN) // true isNaN(1) // falseInfinity, -Infinity
Infinity
は名前の通り無限大を表す値デモ。1 / 0; // Infinity -1 / 0; // -Infinity組み込み関数
isFinite()
は与えられた値が有限(Finite)の時にtrueを返すデモ。
有限ではないと判定されるのはNaN
,Infinity
,-Infinity
だけデモ。isFinite(1 / 0); // false isFinite(-Infinity); // false isFinite(NaN); // false isFinite(1); // true文字列
JavascriptにはCharのような1文字を表す型はないデモ。すべてStringで表現するデモ。
文字列はオブジェクト
文字列はオブジェクトなのでプロパティを持っているデモ。
文字列の長さは
length
プロパティに入っているデモ。'hello'.length; // 5他にも文字列は、文字列の操作や文字列に関する情報へのアクセスを可能にするメソッドを持っているデモ。
'hello'.charAt(0); // "h" 'hello, world'.replace('hello', 'goodbye'); // "goodbye, world" 'hello'.toUpperCase(); // "HELLO"nullとundefined
Javascriptには
null
とundifined
があるデモ。Javaエンジニアのワガハイからするとなかなか理解が難しいところデモね。とりあえずドキュメントを引用するデモ。
nullnull 値は null というリテラルです。undefined のようなグローバルオブジェクトのプロパティではありません。代わりに、 null は識別の欠如を表し、変数がオブジェクトを指してないことを示します。API においては、通常はオブジェクトが返されるところで、関連したオブジェクトがない場合に null がよく渡されます。
undefined
undefined は、グローバルオブジェクトのプロパティであり、すなわちグローバルスコープ内の変数です。undefined の初期値はプリミティブ値である undefined です。
まだ値が代入されていない変数は undefined 型となります。評価しようとしている変数に値が代入されていない場合、メソッドや文も undefined を返します。値を return しない関数も undefined を返します。な、なるほどデモ?
つまり、そもそもnull
は型ではないがundefined
は型で、初期値にプリミティブ値のundefined
が入っているデモね?基本的に、値を代入しないで変数を宣言するとその変数の型が
undefined
になるみたいデモね。最後にnullのドキュメントにnullとundefinedの判定の違いが書いてあるから引用するデモ。
typeof null // "object" (歴史的の理由で "null" ではありません) typeof undefined // "undefined" null === undefined // false null == undefined // true null === null // true null == null // true !null // true isNaN(1 + null) // false isNaN(1 + undefined) // true真偽値
Javascriptでは、どんな値でも真偽値に変換することができるデモ。
つまり、if文の条件式に渡すとtrue, falseのどちらかに変換されるデモね。
変換のルールは以下デモ。
false
,0
,""
,NaN
,null
,undefined
はfalseになる- その他のすべての値はtrueになる。
この変換は
Boolean()
関数で明示的に行えるが、特に意味はないみたいデモ。シンボル
シンボルはちょっと難しいので、またの機会にするデモ。
とりあえず知らなくても平気デモ。あとがき
はじめてのQiita、大変だったデモ。
もっと掘り下げて各項目を書きたかったんだけど、ほとんど元の文書の焼き直しになってしまったデモ。
でも自分自身の理解は深まりそうだし、文章を書くのに慣れてくればもっと内容も濃くできるはずなので続けていこうと思うデモ。それじゃあまた明日デモ!
参考
- 投稿日:2019-12-04T18:13:37+09:00
君はVue,Reactの次に来るSvelteを知っているか?
はじめに
この記事はAteam Brides Inc. Advent Calendar 2019 5日目の記事です。
はじめまして、エイチームブライズ新卒1年目の@oekazumaです。最近僕がハマっているSvelteに関して書きたいと思います!
Svelteとは?
SvelteはRich Harris氏によって開発されたコンパイラーでVueやReactのようにブラウザー上でコンポーネント化をするフレームワークではなく*.svelteファイルをhtml, js, cssに変換します。
「すらりとした」という意味を持つ名の通り軽量で高速。
ベンチマークでReactの35倍、Vueの50倍速いです。Svelteの3つの魅力
公式にも書かれている下記の3つを中心に説明していきます!
1. Write less code (より少ないコードを書く)
2. No Virtual DOM (仮想DOMがない)
3. Truly reactive (本当に反応的)Write less code(記述量が少ない)
入力フォームで変数aとbに値を入力し、足して表示するプログラムを例にしてみると
React 442文字
import React, { useState } from 'react'; export default () => { const [a, setA] = useState(1); const [b, setB] = useState(2); function handleChangeA(event) { setA(+event.target.value); } function handleChangeB(event) { setB(+event.target.value); } return ( <div> <input type="number" value={a} onChange={handleChangeA}/> <input type="number" value={b} onChange={handleChangeB}/> <p>{a} + {b} = {a + b}</p> </div> ); };Vue 263文字
<template> <div> <input type="number" v-model.number="a"> <input type="number" v-model.number="b"> <p>{{a}} + {{b}} = {{a + b}}</p> </div> </template> <script> export default { data: function() { return { a: 1, b: 2 }; } }; </script>Svelte 145文字
<script> let a = 1; let b = 2; </script> <input type="number" bind:value={a}> <input type="number" bind:value={b}> <p>{a} + {b} = {a + b}</p>すごく記述量が少ないことがわかると思います。
書き方自体はVueに似ている部分もあるので既にVueを書いている方だとそんなに違和感なく開発できそうです。No Virtual DOM(仮想DOMがない)
仮想DOMはオーバーヘッドであると言っています。大きな理由としては「実DOMとの差分を計算するのって無料じゃないしオーバーヘッドだよね」というところにあります。
Svelteは仮想DOMを使用せずに同様のプログラミングモデルで十分なパフォーマンスで、状態遷移を考慮することなくアプリを構築できます。
以下の流れでいうとSvelteは1と4だけで済むということです。仮想DOMでHTMLが書き換わるまでの流れ
1. 現在の状態(state)が変わる
2.再レンダリング(仮想DOMの再構成)を実行する
3.実DOMとの差分を計算する
4.実際にHTML(=実DOM)を書き換えるTruly reactive(本当に反応的)
ReactおよびVueは、状態変数が変更されたときに更新する場所を追跡できず、その結果、状態変数が存在するコンポーネント全体とそのすべての子を更新します。
一方、Svelteはアプリケーションを介してデータを追跡し、更新された変数に依存する変数のみを更新できます。さいごに
日本では正直全然話題になっていませんが、海外のフロントエンド界隈では盛り上がっているようでこれから日本でも流行っていくのではないかなと勝手に思っています。
数年後にはVue,Reactと肩を並べて語られている気がする...(^ω^)
今は日本語文献がかなり少ないので盛り上げていってもっと身近にSvelteを感じられるようになれば嬉しいなと思います!
この記事では実践的な部分がなかったのですが、明日に@mkinがsvelte3でToDoリストをチュートリアルと照らし合わせて作るぞ! 【入門編】を書いてくれるので楽しみにしていてください!私たちのチームで働きませんか?
エイチームは、インターネットを使った多様な技術を駆使し、幅広いビジネスの領域に挑戦し続ける名古屋の総合IT企業です。
そのグループ会社である株式会社エイチームブライズでは、一緒に働く仲間を募集しています!上記求人をご覧いただき、少しでも興味を持っていただけた方は、まずはチャットでざっくばらんに話をしましょう。
技術的な話だけでなく、私たちが大切にしていることや、お任せしたいお仕事についてなどを詳しくお伝えいたします!Qiita Jobsよりメッセージお待ちしております!
- 投稿日:2019-12-04T18:10:52+09:00
【JavaScript】Promise は Either Monad?
イントロダクション
JavaScript における Promise は失敗か成功のどちらかを表し、どちらの場合も何らかの値を持つことができる。さらにメソッドチェーンによりパイプラインのようなものを作ることができ、失敗すると short-circuit する。この点において Promise は Either monad の async 版のような感じがする。ここでは JavaScript の Promise と Scala の Either の対応関係を示す。
Note: 筆者は関数型プログラミングに詳しくない。
constructor
Promise の constructor である
Promise.resolve
とPromise.reject
はそれぞれ Scala のRight
とLeft
に対応している。JavaScriptconst r = Promise.resolve(1) // Resolve(1) と表記する const l = Promise.reject(0) // Reject(0) と表記するScalaval r: Either[Int, Int] = Right(1) val l: Either[Int, Int] = Left(0)
promise.then(f)
(1) =either.map(f)
Promise の
then
メソッドに非 Promise を返す関数を渡したときの動作は Either のmap
と同じである。JavaScriptr.then(x => x + 10) // Resolve(11) l.then(x => x + 10) // Reject(0)Scalar.map(_ + 10) // Right(11) l.map(_ + 10) // Left(0)
promise.then(f)
(2) =either.flatMap(f)
Promise の
then
メソッドに Promise (一重)を返す関数を渡したときの動作は Either のflatMap
と同じである。JavaScriptr.then(x => Promise.resolve(3)) // Resolve(3) r.then(x => Promise.reject(3)) // Reject(3) l.then(x => Promise.resolve(3)) // Reject(0) l.then(x => Promise.reject(3)) // Reject(0)Scalar.flatMap((x: Int) => Right[Int, Int](3)) // Right(3) r.flatMap((x: Int) => Left[Int, Int](3)) // Left(3) l.flatMap((x: Int) => Right[Int, Int](3)) // Left(0) l.flatMap((x: Int) => Left[Int, Int](3)) // Left(0)
promise.catch(f)
(1) =either.left.flatMap(f andThen Right)
Promise の
catch
メソッドに非 Promise を返す関数を渡したときの動作は Either のLeft
に対してその関数を実行し、さらにその結果をRight
で包んでflatten
するのと同じである。JavaScriptr.catch(x => x + 10) // Resolve(1) l.catch(x => x + 10) // Resolve(10)Scalar.left.flatMap(((x: Int) => x + 10) andThen Right[Int, Int]) // Right(1) l.left.flatMap(((x: Int) => x + 10) andThen Right[Int, Int]) // Right(10)
promise.catch(f)
(2) =either.left.flatMap(f)
Promise の
catch
メソッドに Promise (一重)を返す関数を渡したときの動作は Either のLeft
に対してflatMap
を実行するのと同じである。JavaScriptr.catch(x => Promise.resolve(x + 10)) // Resolve(1) r.catch(x => Promise.reject(x + 10)) // Resolve(1) l.catch(x => Promise.resolve(x + 10)) // Resolve(10) l.catch(x => Promise.reject(x + 10)) // Reject(10)Scalar.left.flatMap(x => Right[Int, Int](x + 10)) // Right(1) r.left.flatMap(x => Left[Int, Int](x + 10)) // Right(1) l.left.flatMap(x => Right[Int, Int](x + 10)) // Right(10) l.left.flatMap(x => Left[Int, Int](x + 10)) // Left(10)まとめ
以上の比較から、Promise の
then
メソッドはmap
とflatMap
のオーバーロードのようなものであるということがわかる(ちなみに関数の返り値が何重もの Promise になっていたとしても、一気に flatten するという性質がある)。
catch
メソッドについてはキレイに Scala コードで表現できなかったが、Left から Right に戻すという操作は関数型言語ではあまりしないのだろうか、と感じた。
- 投稿日:2019-12-04T18:08:29+09:00
Visual studio 2019でさくっとNode.js
IDEには、好みがあると思いますが一つの選択肢としてVisual Studio選びました。
Visual studio 2019には、node.jsを開発するためのテンプレートが用意されています。
基本のNode.js Express アプリケーションを選択します。
プロジェクト名を決定します。
C:. │ app.js │ ExpressApp5.njsproj │ package-lock.json │ package.json │ README.md │ ├─public │ └─stylesheets │ main.css │ ├─routes │ index.js │ users.js │ └─views error.pug index.pug layout.pug上記のようなフォルダとファイルが作成されます。
app.js'use strict'; var debug = require('debug'); var express = require('express'); var path = require('path'); var favicon = require('serve-favicon'); var logger = require('morgan'); var cookieParser = require('cookie-parser'); var bodyParser = require('body-parser'); var routes = require('./routes/index'); var users = require('./routes/users'); var app = express(); // view engine setup app.set('views', path.join(__dirname, 'views')); app.set('view engine', 'pug'); // uncomment after placing your favicon in /public //app.use(favicon(__dirname + '/public/favicon.ico')); app.use(logger('dev')); app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: false })); app.use(cookieParser()); app.use(express.static(path.join(__dirname, 'public'))); app.use('/', routes); app.use('/users', users); // catch 404 and forward to error handler app.use(function (req, res, next) { var err = new Error('Not Found'); err.status = 404; next(err); }); // error handlers // development error handler // will print stacktrace if (app.get('env') === 'development') { app.use(function (err, req, res, next) { res.status(err.status || 500); res.render('error', { message: err.message, error: err }); }); } // production error handler // no stacktraces leaked to user app.use(function (err, req, res, next) { res.status(err.status || 500); res.render('error', { message: err.message, error: {} }); }); app.set('port', process.env.PORT || 3000); var server = app.listen(app.get('port'), function () { debug('Express server listening on port ' + server.address().port); });必要なモジュールをrequireで読み込んでいる。
viewsにある....pugは、pugをHTML化している。
Pugとは. HTMLを書くためのテンプレートエンジンです。
app.set('view engine', 'pug');実行すると
layout.pugdoctype html html head title= title link(rel='stylesheet', href='/stylesheets/main.css') body block contentindex.pugextends layout block content h1= title p Welcome to #{title}実行時には、下記のようなHTMLが生成される。
index.html!DOCTYPE html> <html> <head> <title>Express</title> <link rel="stylesheet" href="/stylesheets/main.css"> </head> <body> <h1>Express</h1> <p>Welcome to Express</p> </body> </html>index.pubを描画するためのindex.jsを見てみましょう。
index.js'use strict'; var express = require('express'); var router = express.Router(); /* GET home page. */ router.get('/', function (req, res) { res.render('index', { title: 'Express' }); }); module.exports = router;res.render('index', { title: 'Express' });は、index.pubにタイトルを渡し描画とています。
Pugの中にjsをコーディングすると
index.pugextends layout block content h1= title p Welcome to #{title} - for(var i=1;i<=6;i++) - var t="H"+i #{t} #{t} の文字の大きさ{t}は、変数を描画するときに使います。
次にlayout.pugを変更しbootstrap4を使えるようにします。
layout.pugdoctype html html(lang="ja") head title= title meta(name="viewport", content="width=device-width, initial-scale=1.0") link(rel="stylesheet", href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css",integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" ,crossorigin="anonymous") script(src="https://code.jquery.com/jquery-3.3.1.slim.min.js", integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo", crossorigin="anonymous") script(src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js", integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1",crossorigin="anonymous") script(src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js", integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM",crossorigin="anonymous") body block contentbootstrap4に必要なものは、CDNから取得します。
index.pugをbootstrap4に変更します。
index.pugextends layout block content .container.m-1 .jumbotron h1= title p Welcome to #{title} - for(var i=1;i<=6;i++) - var t="H"+i #{t} #{t} の文字の大きさコンテナとジャンボトロンが実装できました。
次にボタンを描画します。
index.pugextends layout block content style. .bsz { width: 137px; height: 70px; font-size: small; margin: 2px !important; } .container.m-1 .jumbotron h1= title p Welcome to #{title} - for(var i=1;i<=6;i++) - var t="H"+i #{t} #{t} の文字の大きさ .container.mt-4.body-content each c,i in 'primary,secondary,success,danger,warning,info,light,dark'.split(',') - var cx="bsz btn m-2 btn-"+c; button(class=cx,id='sw'+i) SW#{i} script. $(function(){ $('.btn').click(function(){ var id=$(this).attr('id'); alert(id); }); });上記のようにstyleやscriptも追加できます。
生成されるhtmlは、index.html<!DOCTYPE html> <html lang="ja"> <head> <title>Express</title> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous"> <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script> <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script> </head> <body> <style> .bsz { width: 137px; height: 70px; font-size: small; margin: 2px !important; } </style><div class="container m-1"><div class="jumbotron"><h1>Express</h1><p>Welcome to Express</p></div><H1>H1 の文字の大きさ</H1><H2>H2 の文字の大きさ</H2><H3>H3 の文字の大きさ</H3><H4>H4 の文字の大きさ</H4><H5>H5 の文字の大きさ</H5><H6>H6 の文字の大きさ</H6><div class="container mt-4 body-content"> <button class="bsz btn m-2 btn-primary" id="sw0">SW0</button><button class="bsz btn m-2 btn-secondary" id="sw1">SW1</button><button class="bsz btn m-2 btn-success" id="sw2">SW2</button><button class="bsz btn m-2 btn-danger" id="sw3">SW3</button><button class="bsz btn m-2 btn-warning" id="sw4">SW4</button><button class="bsz btn m-2 btn-info" id="sw5">SW5</button><button class="bsz btn m-2 btn-light" id="sw6">SW6</button><button class="bsz btn m-2 btn-dark" id="sw7">SW7</button></div></div> <script> $(function(){ $('.btn').click(function(){ var id=$(this).attr('id'); alert(id); }); });</script> </body> </html>
- 投稿日:2019-12-04T18:07:06+09:00
Google AnalyticsのLighthouse対策やってみた
はい!
ということで今日はですね。
弁護士ドットコム Advent Calendar 2019 - Qiitaの5日目の記事をですね。
書いていきたいと思います。tl;dr
- これは社内プロジェクトと関係ありません。個人的なプロジェクトで試したものです。
- GoogleAnalytics(以下GA)は2時間しかキャッシュされない。
- GA代替のga-liteはCDNも用意あるしnpmも公開している。
- ga-liteはGA完全互換ではないので独自の癖がある。が、PVやイベントトラッキングがメインな計測では問題ない。
- キャッシュはパフォーマンススコアとは関係ない。
ga-lite導入まで
https://github.com/jehna/ga-lite
はじめに
PageSpeed Insightsとかlighthouseで高得点&高速化していくの楽しいですよね!
なんでもオリンピックの正式種目になるとかならないとかならないとか…こちらの点数ですが、計測後は高速化へのアドバイスが表示されるので比較的容易に対策が可能です。
その中でもみんなを悩ませる、"GAのキャッシュが短い"
という問題を今回は対策してみたいと思います。ga-liteについて
GAの問題箇所を対策するためにググっていたら、KeyCDN社のパフォーマンス対策記事を見つけました。
こちらで、ga-liteというGAの代替案を紹介していました。
- npmを公開している
- Analyticsライクな記述
- ライセンスも問題なさそう
GAとの比較もありましたが良い感じです!
ライセンス
- ga-lite自体のライセンスはMIT
- Analyticsのライセンス的にも問題なさそうです。
※ ソースを追ったところ、GAの公開protocolであるMeasurement Protocolをga関数に似せてラップしている為ga-liteの注意点
- DoNotTrackに対応している。ちなみにGAは非対応。
- GA完全互換ではないので、関数がそのままつかえないものもある。
- 既知のbugというか問題点もある(issuesに書いてあったけど、表示にかかった時間が取得できていないなど)
野良のnodeパッケージを導入する方法
紹介しただけでもうこの記事も終えたという感じではあるんですが、おそらくGAとの乗り換えで使う人の方が多い(= want to track)と思うので、自分用にカスタムする方法もメモしておきます。
cloneして別リポジトリを用意して、DoNotTrack箇所を編集して魔改造してみます。
https://github.com/THETIMEINC/ga-lite
こんな感じで書き換えましたが、これはnpmには公開せずこのgithubリポジトリをそのまま読み込みこんで利用していきます。(下記は、私専用なので適宜書き換えてください)。
$ npm i @THETIMEINC/ga-lite@github:THETIMEINC/ga-lite#develop使い方
import galite from '@THETIMEINC/ga-lite' galite('create', 'UA-XXXXXXXX-X', 'auto') galite('send', 'pageview') galite('send', 'event', 'outbound', 'label', 'beacon')ga関数に近いです。pageviewとかeventをこんな感じで送れます。
ただし、fieldsObjectの記述方法だと非対応だったりしたので、google-analytics-debuggerでdebugをしたほうが良さそうです。所感
nodeのパッケージで、Analyticsの公開プロトコルをga関数みたいな感じで送信するっていうのが気軽で便利だなと思ったので紹介しました。
これで高得点&高速化できましたね……
ん?キャッシュの時間変更するだけでは、点数はあがらない…だと。。
(…ですが、ファイルサイズが減るなど別要因で点数は上がっているかと思います。結果オーライ自分。)みんなで100点を目指していきましょう!
こちらはちょっと前に社内勉強会で発表した内容でした。
弁護士ドットコムでは社内外の勉強会もおこなっています。楽しいですよ〜参考URL
https://developers.google.com/analytics/devguides/collection/protocol/v1/reference
https://developers.google.com/analytics/devguides/collection/analyticsjs/command-queue-reference
https://qiita.com/miyanaga/items/d38124cdd64a1999fed9
https://www.keycdn.com/blog/leverage-browser-caching
- 投稿日:2019-12-04T17:27:41+09:00
Rails:ajax通信の流れとデバッグの解説[超初心者編]
まずはじめに
ajaxを勉強中に簡易なrailsアプリを作りました。
その時大枠の処理の流れが大切だと感じたので今回復習も兼ねて解説してみようと思います。大枠の流れを理解しているとエラーが起きた際にどこでデバッグしてどの変数の中身を見たら良いか、どこまでは処理がうまく書けているか。という原因特定をする際に非常に便利です。
むしろ流れを理解していないとエラー解決は手探りでの作業となってしまい非効率です。
作ったアプリ内容
検索フォームからDBに保存してあるユーザー情報を表示する
アプリGIF
https://gyazo.com/ac90a773abec869ddb72d59037f62f46[したいこと]
検索フォームにキーワードを入力された毎にユーザー検索して該当するユーザー名を表示する[必要な手段]
検索フォームに入力されたら反応
フォームに入力されたキーワードを取得
コントローラでキーワードを元にユーザーテーブルを検索
検索結果をビューで表示する対象者
この記事では、ajax通信とは、
どのようにして送信先を決めているのか、
送ったデータはどのように処理されているのか、
どのように処理したデータを返すのか
エラーで詰まってしまった際にどう対処するのか
を学びます。よって概ね同じようなajax通信の流れを組む、インクリメンタルサーチや非同期通信、自動更新の実装にもこの記事で紹介する処理の流れやデバッグの方法は応用できます
大まかな処理の流れについて説明できる自信がない方に対してザックリと理解できるようにまとめました。
極力専門的な言い方や記述を省き、イメージしやすいように言い回しも変えています。
開発環境
Rails: 5.0.7.2
ruby: 2.5.1
jquery-rails: 4.3.3
mac: Mojave(10.14.4)まずは登場ファイルの紹介
・ edit.html ----------------- HTMLファイル(ビューファイル)
・ test.js -------------------- JavaScriptファイル
・ users_controller.rb -------- コントローラファイル
・ index.json.jbuilder --------- json.jbuilderファイルajax通信の流れ
ビューファイルが読み込まれる
(コントローラのアクションに紐づくビューファイルが読み込まれるということ)
edit.html<input class="name-form" placeholder="検索したいユーザー名" type="text"> <div class='append-user'>ビューと同時にJavaScriptファイルも読み込まれる
test.js$(function(){ //以下の処理を読み込ませるための必須記述 $(".name-form").on("keyup", function() { //this = $(".name-form") var input = $(this).val(); $.ajax({ type: 'GET', //type = HTTPメソッドを指定する url: '/users', //url = パス(URI or Prefix)を指定する data: { keyword: input }, //data = コントローラへ送りたいデータ dataType: 'json' //dataType = コントローラが返すファイルの形式 }) //変換完了 .done(function(datas) { if (datas.length !== 0) { //検索にヒットした情報が1件以上だったら //返されたjsonデータの個数分処理を繰り返す datas.forEach(function(data) { //一人一人のユーザー情報(data)をブラウザに表示する任意のメソッド appendHTML(data); }); } }) //変換失敗 .fail(function() { alert('失敗しました'); }) }); });ここで一旦test.jsファイルは何をしてくれるファイルなのかを解説
※1行目のfunctionの記述はJSファイルを読み込ませる必要最低限の記述のため解説割愛
JavaScriptファイルは、簡単に言ってしまうと「ビューファイルを監視して処理を実行してくれる」ファイルです。
画像の①関数を定義しておくとオレンジの範囲の処理を行なってくれます。
オレンジの範囲には②処理や②と関係する③・④の処理、⑤の処理が含まれています。② → ③ → ④ → ⑤の順番で処理が進んでいきます
ではどんな時に①関数が動き出すのか?これは①関数の最初に書かれている記述から読み取ることができます。
$(".name-form").on("keyup", function() { 直訳 「クラス名「name-form」の入力フォームにキー入力され、そのキーが離された瞬間」に動き出す関数 解説 ①:$(".name-form") → 読み込んだHTMLファイルの中でclass= "name-form"の要素 ②:.on → ①が ③:"keyup" → キーアップされたら(入力時のキーを離したら)
クラス名「name-form」といえば、
edit.html.hamlで生成した入力フォームのことですね。
このフォームに入力がされたら、関数が動くという仕組みです。
つまり、test.jsの
$(".name-form").on("keyup", function() {
という記述が、現在読み込まれているビューファイルの特定のクラス名の要素の「動き」を監視しているという言い方もできるわけです。
また、記述の各部分には名称がついているので、人へ伝える時や調べものをするときに下記のワードを用いて理解を深めましょう。$(".name-form").on("keyup", function() { 処理 }名称:セレクタ
$(".name-form")
・・・「動き」を監視する対象や要素名称:イベントハンドラ
.on
・・・セレクタに対して名称:イベント
"keyup"
・・・予め検知したい「動き」を定義する(例:キーアップイベントが発生したら)名称:無名関数
function() { 処理 }
・・・セレクタに検知したい「動き」が起こったら{処理}を行う
$(セレクタ).on(イベント名, イベントが発生したときに実行する処理)
ちなみに、JavaScriptファイル内で「$」マークで始まる記述はJavascriptのライブラリの一つである「jQuery」の記述です。
もちろんイベントには「送信されたら」、「クリックされたら」などたくさんの種類があるので
「jQuery イベントハンドラ」でググってみましょうjQueryイベント一覧 わかりやすい記事
http://www.jquerystudy.info/reference/events/index.html
test.jsファイルの解説は一旦終了です。イベントを発火させる
登場するファイルと処理の順番
【edit.html】 → ブラウザで入力操作 → 【test.js】それではいよいよtest.jsに書かれた①関数を動かします。
そのために入力フォームへ何か文字を入力するんでしたね
これで定義していた処理が実行されます
①関数定義が動く
登場するファイルと処理の順番
【test.js①】 → 【test.js②】入力イベントに反応して上記画像の1番上のfunction{}関数内の処理が実行されていきます
もちろん処理は上から下へ実行されるので②ajax通信の処理ブロックにたどり着くまでに書かれている記述を実行していきます。
途中にある
var input = $(this).val();この記述は、変数
input
に対してjQueryの記述で値を代入しています。$マークのカッコで囲んだものはjQueryオブジェクトとして扱うのでしたね!
ではカッコの中に記述されているthisとは何かというと現在処理されているfunctionのセレクタを指します。
※thisは使う場面によって色々な状態の情報が取得できるので一概に取得できる情報を明言できません。
①関数(function)内でthisと書くとfunctionのセレクタである$(".name-form")
が取得できます
よって「this」は書く場所によって結果が違う。ということです
この後解説するdone関数やfail関数のfunction内でthisを記述すると①関数内でthisを書いた時の情報とは違う状態の情報がthisの記述で取得できます
デバッグ作業の心構え
this情報の確認方法について、ここで一旦デバッグ作業の仕方についてサクッと解説です。
this情報の確認は簡単で、確認したい場所でconsole.log(this)
を記述するだけです。
例で下の画像のように3箇所にconsole.log(this)
を記述します
上記画像のようなconsole.log(this)の配置で様々な状況下のthisの値がコンソール画面で確認できます。
注意:console.log(〇〇)と書いたあとは①関数を動かす必要があるので必ずキーアップイベントを起こす必要あり。
console.log()
とはlogカッコ内に記述した変数の中身をブラウザの検証の「console」画面に表示するメソッドです。定義しておいた変数などをlog引数に記述すると変数の中身がコンソール画面に表示することができ、処理に使う変数が期待する値かどうかを確認するときに大活躍します。
var num = 10 + 5; console.log(num); //コンソール画面には「15」と表示されるこの作業こそ、まさに「デバッグ」ですね!!
デバッグ作業の重要なポイントとしては
①変数の中身を確認する(どの変数を確認すべきか)
②変数の中身を予想する(期待する答えを考える)この2点です!
たったコレだけですが、この2点ができるできないで作業効率は大きく変わります
普段こういったことを考えないで闇雲にデバッグしている人は、めちゃくちゃ損してます。以上、デバッグの心構えでした。
では、話は戻って
var input = $(this).val();この記述は
var input = $(".name-form").val();このように変換※でき、
.val()
は対象のvalue属性の値を取得するので
現状入力フォーム(クラス名name-formのHTML要素)には「a」が入力されているので
※ここでの「変換」とはわかりやすいようにイメージするならば。という意味var input = "a";と変換できることになります。
②関数が動く。ajax通信の設定
登場するファイルと流れ
【test.js②】 → ③【users_controller.rb】変数
input
を定義した状態で次に②関数のajax通信の設定が実行されます。ここでのajax通信は、railsのMVCの流れに割り込んだ形でビュー(HTML)ファイルからコントローラファイルへデータを渡すために記述されています。
この流れでファイルを読み込んでいきます。
ajax通信の設定は以下の内容で実行されます
$.ajax({ type: 'GET', //type = HTTPメソッドを指定する url: '/users', //url = パス(URI or Prefix)を指定する data: { keyword: input }, //data = コントローラへ送りたいデータ dataType: 'json' //dataType = コントローラが返すファイルの形式 })ajax通信の項目
・type
とurl
はルーティングに渡す情報を指定
・data
はルーティングが判断したコントローラファイルに渡す情報の指定
ここで先ほど定義したinput
変数を使っています。
・dataType
はdata項目を送ったり送り返してもらう際の通信形式を指定(値はjsonやhtmlなどが存在)ここでもajax通信のイメージを掴んでもらうために、たとえを用いると
ajax通信とは、「外国へ荷物を配達してくれる郵便屋さん」みたいな存在です。
ちょっとよく分からないとは思いますが黙って聞いていてください。②関数が動くと郵便屋さんが配達の準備を始めます
$.ajax({ type: 'GET', //type = 目的地情報その1 url: '/users', //url = 目的地情報その2 data: { keyword: input }, //data = 送る荷物 dataType: 'json' //dataType = 発送方法 })上記の情報をもとに目的地の設定や送る荷物の中身を決めます。
実行されたajax通信はまずルーティングに解析され、ルーティングではHTTPメソッドは
GET
、パスは/users
として判断され
users_controller.rbファイルのindexアクションが実行されます。発火させるべきコントローラとアクションの選定方法
ここで大事なのは発火させたいコントローラとアクションは何であるのかイメージしておくことです。
まず、なぜコントローラのアクションを発火させたいのでしょうか?
それは、コントローラではDBの情報を取得・登録・編集・削除などのアクションが実行でき、今まさにDBの情報を取得したいからです。まずは行いたい処理を大枠で思い出しましょう。
・したいこと
DBからキーワードに該当するユーザーを取得する・そのための手段
ajax通信を使う
キーアップされたごとに検索する
入力されたキーワード情報を取得する
キーワード情報をコントローラへ送るでは「したいこと」を実行するために最適なコントローラとは?
答えは簡単です。関係性のあるコントローラを選べば良いのです。例えば
・users_controller.rb
・groups_controller.rb
・messages_controller.rb
と3つのコントローラがあったら、コントローラそれぞれの役割を思い出します。・users_controller.rb:ユーザーに関わることを操作する
・groups_controller.rb:グループに関わることを操作する
・messages_controller.rb:メッセージに関わることを操作する「したいこと」はユーザー情報の取得です。
こう考えると、users_controller.rbの一択ですね。
では次に、users_controller内のどのアクションを発火させるか?
これも7つのアクションからひとつ当てはまるものを選べば良いのです。冷静に考えれば楽勝です
当てはまるものがわからなければ目的とは異なるものを排除していきましょう!・index・・・・・・一覧表示
・new・・・・・・新規作成画面
・create・・・・・DBに新規作成
・show ・・・・・・詳細画面
・edit・・・・・・・編集画面
・update・・・・・DBに編集内容を保存
・delete ・・・・・DBから削除すでに登録されているユーザー情報を取得する。という観点だけでも、
・index
・show
の2つに絞られます。・index・・・・・・一覧表示
・new・・・・・・新規作成画面 → ユーザーを新規作成するわけではない
・create・・・・・・DBに新規作成 → ユーザーを新規作成するわけではない
・show・・・・・・詳細画面
・edit・・・・・・・編集画面 → 既存のユーザー情報を書き換えたいわけではない
・update・・・・・DBに編集内容を保存 → 既存のユーザー情報を書き換えたいわけではない
・delete ・・・・・DBから削除 → ユーザー情報を削除したいわけではないここで重要なのは、キーワード検索して該当したユーザー情報を全て取得するという部分がポイントです。
「a」と検索したら「aaa」さんも「abc」さんの情報も該当する情報一覧を取得したいということです。indexは一覧情報。対して
editは一人のユーザー情報の詳細です。だからindexアクションが適切です。
users_controller#indexアクションです
これで発火させたいコントローラとアクションが選定できました!
ターミナルで「rails routes」コマンドを打って表示される一番右端に書いてあるコントローラとアクションに紐づくパスとHTTPメソッドを確認してみましょう!
railsにてページの遷移を行うには何かしらのコントローラのアクションを発火させなければいけません。
その場合、必ず「したいこと」を言語化し、発火させたいコントローラとアクションを決めてから細かい処理を組み立てていきましょう実行されたajax通信はまずルーティングに解析され、ルーティングではHTTPメソッドは
GET
、パスは/users
として判断され
users_controller.rbファイルのindexアクションが実行されます。その後はjson.jbuilderファイル→ test.jsファイルの⑤処理というふうに処理がされていきます。
郵便屋さんが
data
という荷物をコントローラに渡し、コントローラはもらったdata
を使って変数を生成します。最後に郵便屋さんがコントローラで生成された変数をdata
の送り主(test.js)に届けるという流れです。通常は荷物を届けた時点で郵便屋さんの仕事は終了ですが、今回はお届け先から送り主に対して送り返す荷物(情報)が発生するというお仕事になります。
一旦はこんなイメージで見ててください
③コントローラでの処理
登場するファイルと流れ
③【users_controller.rb】 → ④【index.json.jbuilder】users_controller.rbファイルのindexアクションではDBのusersテーブルからブラウザの入力フォームに入力された「a」のワードに該当するユーザー情報を
@users
に代入しています。※大枠の処理の流れが重要のため、コントローラ内の処理詳細は割愛します。
users_controller.rbclass UsersController < ApplicationController def index return nil if params[:keyword] == "" @users = User.where(['name LIKE ?', "%#{params[:keyword]}%"] ).where.not(id: current_user.id).limit(10) # ajax通信の記述:dataTypeの種類に応じて参照するファイルを切り替える respond_to do |format| format.html format.json # ajax記述には、dataType: 'json' と書かれているので # index.json.jbuilderファイルが読み込まれる end end end上記で記述されている
params[:keyword]
とは
ajax通信の設定で記述したdata項目(送る荷物)のハッシュデータが深く関わってきます。
params[:keyword]
とは、data項目に定義したハッシュのキー名を指定してバリューとなるinput (入力ワード「a」)
を取得する記述です。data: { keyword: input }, //data = コントローラへ送りたいデータなぜ送った
data
がparams
に取り込まれているのかajax郵便屋さんが言語の違う「外国」へ行っていることを思い出してイメージしましょう
日本語がアメリカでは通じないように、javascript語をrubyの言語内では使えないのでjsonという通信方法を使ってruby語の会話であるparamsに情報を混ぜてもらっているのです。
そうすると、javascript語で書いた情報でもruby国に籍を置くusers_controller.rbファイルでも読み取ることができるようになり、
test.jsから受け取った変数
input
の中身を使ってDBからユーザー情報を検索できるのです。また、検索結果を代入した
@users
変数はtest.js(javascript)ファイルにてユーザー検索結果を表示する際に使われる重要な変数です。
コントローラでtest.js(javascript)ファイルに送り返す変数@users
を定義できたら、通常のMVCの流れ同様コントローラ → ビューと処理が移るのですが、
ビューファイルの参照前に、ajax通信の設定で記述したdataTypeの値に応じて参照するファイルを選定する記述がコントローラには書かれています。
users_controller.rbrespond_to do |format| format.html format.json end今回はdataType:
json
でajax通信を行なっていますよねtest.js$.ajax({ type: 'GET', //type = HTTPメソッドを指定する url: '/users', //url = パス(URI or Prefix)を指定する data: { keyword: input }, //data = コントローラへ送りたいデータ dataType: 'json' //dataType = コントローラが返すファイルの形式 })なので「コントローラで処理されたアクション名.jsonファイル」の
views/users/index.json.jbuilderファイルが読み込まれます。※respond_toの記述がなければ、コントローラで処理されたアクション名.htmlファイルが参照されます。
④json.jbuilderファイルでの変換処理
登場するファイルと流れ
④【index.json.jbuilder】 → 【test.js⑤】
index.json.jbuilderファイルではコントローラで生成した変数
@users
の変換処理を行います。
ん?
なぜ変換するの?と思うかもしれませんが
コントローラで生成した変数ということは、ゴリゴリのruby語で書かれた情報ということになり、
このままの状態で変数をtest.js(javascript)ファイルへ持ち帰っても誰も解読できないよね。ということになります。
そこで荷物を届けに来るときにjson → paramsと変換した時同様に、
送り返す際もparams → jsonと変換をしてあげます。index.json.jbuilderjson.array! @users do |user| json.id user.id json.name user.name end一つ一つ解説すると、まず始めの記述
json.jbuilderjson.array! @users do |user|これは変数
@users
をruby言語でいうeach文で取り出しているような書き方ですね!
いわゆる繰り返し処理です。なぜ繰り返すかというと変数
@users
は複数情報が格納されている配列情報だからです。
配列情報とは1個以上の複数情報から成り立っています。
今回のコントローラでの処理では入力ワード「a」に該当するユーザー情報が変数
@users
に詰められていますが、
DBのusersテーブルにもし「abc」さんと、「aaa」さんの2人ユーザー情報が登録されていたら、どちらのユーザーも「a」という文字列を含むため、コントローラの処理で前述の2人分の情報が変数@users
に詰められてくる可能性があるからです。そうしたら「abc」さんにも「aaa」さんにも変換処理を行なってあげないと、test.js(javascript)ファイルで変数を受け取る際に解読できなくなってしまいます。
では次に、
json.jbuilder#javascript語 ← ruby語 #jsonデータ ← paramsデータ #パン ← bread json.id user.id json.name user.nameこの記述は左辺がjavascript語での呼び方、右辺がruby語での呼び方を定義している記述です。
左辺に定義した名前にどんなrubyの情報を定義するか。といった感じです。試しに、
json.jbuilderjson.n user.nameこう書けば、
javascriptファイルで◯◯.nと記述すると、ruby語でuser.nameの情報が取得できる。といった感じ
※「〇〇」はdone関数の引数名などが入る⑤変換結果に応じた処理(done & fail)
登場するファイルと流れ
【test.js⑤ done】 or 【test.js⑤ fail】
※appendHTML関数はブラウザにユーザー情報を表示する関数です
※この記事では大枠の処理の流れをメインに解説を行うため、doneメソッド内で使われているappendHTML関数の詳細は敢えて記述せず、解説もしません。
④index.json.jbuilderファイルでの変換処理を経て、test.jsファイルに返ってきたjsonデータ。
このjsonデータには入力ワード「a」に該当するユーザー情報が詰められているのですが、
④index.json.jbuilderの変換結果によって実行される関数が分岐します。変換結果
変換成功 → done関数
変換失敗 → fail関数failメソッドが実行される場合
変換失敗の際はfail関数が処理されます。
ではどのような時に変換失敗になるか
これはjson.jbuilderでの処理が以下のような時です。NG.json.jbuilderjson.array! @users do |user| json.id user.user.id #userが重複 json.nickname user.mickname #カラム名の間違い or 存在しないカラム名の指定(mickname) json.nickname @users.nickname #変換する変数名が違う endカラム名の記述ミスや存在しないテーブルの参照など、記述をよく観察すると発見できるミスが多いです。
他にもコントローラファイル → json.jbuilderへと参照させるためにコントローラ内に記述が必要なrespond_toが抜けていたりすると適切な変換ファイルが参照されずfailメソッドが実行されてしまいます。users_controller.rbrespond_to do |format| format.html format.json end予めfailメソッド内に
alert("通信失敗しました");
などの記述を配置してfailメソッドが呼ばれてしまったタイミングを見逃さないようにしておきましょう。json.jbuilderファイルの記述に間違いがなさそうであれば処理の流れを遡ってコントローラで定義した変数が怪しいと考えましょう
そうしたらコントローラ内に
binding.pry
を記述し、処理を止めて変数名を入力して期待通りの中身か確認しましょう!
このように原因箇所を処理の流れに沿って絞っていくことが大切です。doneメソッド内の処理で不具合が起こった場合
変換処理に問題がなければdoneメソッドが処理されます。
さらにdoneメソッドの引数にはjson.jbuilder内で変換されたjsonデータが入ります。
今回の例でいうとtest.js.done(function(datas) { 処理 })
datas
という引数がjsonデータです。引数名は自由に名付けられます!
注:これまでの処理順番画像のdoneメソッドの引数名が全て「data」で記述されています。ミスですsorryこの引数の中に変換されたユーザー情報が代入されています。
このdatas
引数の中身を展開してブラウザにユーザー情報を表示していくのですが、ここdoneメソッド内での処理が一番記述を間違いやすい箇所でもあるので、エラーが起こった際は
冷静にこれまでの処理の順番を遡り、確認すべき変数を見極めデバッグしていくことが求められます。よくある間違いの原因としては、
・doneメソッドの引数であるjsonデータを配列情報として扱っていないミス
・json.jbuilderで定義していない名前を展開しようとしている
・そもそもコントローラでの処理の時点で@users
変数の中身が正常ではない
・@users
変数を作るための材料であるinput
変数の中身がすでに正常ではないなどなど、
どの原因もconsole.logやbinding.pryを使えばすぐに割り出せる内容です。デバッグの使い分け
javascriptファイルでの変数確認 = console.log または debugger
使えるファイル例:test.jsファイルrubyファイルでの処理停止 = binding.pry
使えるファイル例:コントローラファイル、ビューファイル、語尾に.rbと付くファイルなら大概使える
よくある間違いへの対処
・doneメソッドの引数であるjsonデータを配列情報として扱っていないミス
このミスへの対処は下記のような
forEachメソッドで配列の各情報を取り出して個別に処理(appendHTMLなど)することを心がけましょうtest.js.done(function(datas) { if (datas.length !== 0) { //検索にヒットした情報が1件以上だったら //返されたjsonデータの個数分処理を繰り返す datas.forEach(function(data) { //一人一人のユーザー情報(data)をブラウザに表示する任意のメソッド appendHTML(data); }); } })コントローラでの処理にもよりますが、コントローラでwhereメソッドを使って配列情報を送ることが決定している以上は該当するユーザー情報が「aaa」さん一人分の情報であろうと配列情報に変わりはありません。
よって配列情報には必ずforEachを使って個別処理を行う必要があります。
そして、ここで取り出した変数(data)に対してようやくjson.jbuilderでの変換内容を展開できます。例:
data.id = ユーザーのid情報を展開
data.nickname = ユーザーの名前情報を展開またキーワード検索で何もヒットしなかった時 = 配列に何も情報が含まれて来ない時
の処理も考えておくとユーザビリティの向上に繋がります。
「該当するユーザーはいませんでした」などなど。。。
・json.jbuilderで定義していない名前を展開しようとしている
appendHTML関数でjsonデータを展開したら「Undefind」だった。
これはもう楽勝ですね。
json.jbuilderファイルをじっくり確認しましょう!!
変換名や変換内容、展開名が食い違っていないか確認しましょう!!
・そもそもコントローラでの処理の時点で@users
変数の中身が正常ではないjson.jbuilderファイルの記述にミスが見当たらなければ、もう一つ処理を遡ってコントローラを確認しにいきます
binding.pry
を記述してjson.jbuilderファイルで変換する変数@users
の中身を見てみましょう!users_controller.rbclass UsersController < ApplicationController def index return nil if params[:keyword] == "" @users = User.where(['name LIKE ?', "%#{params[:keyword]}%"] ).where.not(id: current_user.id).limit(10) binding.pry # @users変数を定義した直後に処理を止める、ターミナルに「@users」と入力して中身の確認 respond_to do |format| format.html format.json end end endターミナルに「@users」と入力する前に、
最初は間違ってもいいので、「おそらくこんな値が入っているはず」と仮説を立ててから中身を確認することが超重要です。
・@users
変数を作るための材料であるinput
変数の中身がすでに正常ではないコントローラファイル内で
binding.pry
を記述して@users
変数の中身を確認してもし値が崩れていたら、
尽かさずparams[:keyword]
とターミナルに入力しましょう!
params[:keyword]
と入力するとキーワード情報が取得できるはずです。もしキーワード情報が取得できない場合は、test.jsのajax通信のdata項目
test.js$.ajax({ type: 'GET', url: '/users', data: { keyword: input }, dataType: 'json' })ここの記述が原因です
input
変数を定義している記述を確認しましょう
ここでもconsole.logが大活躍です。尽かさず変数の中身を確認しましょう!
var input = $(this).val(); console.log(input);これでも値が崩れているのなら
input
変数を形成するthisを確認console.log($(this));このように処理の順番を遡って変数の中身を確認する。
もう分かってると思うんですが。。。
最初はとにかくデバッグなんです
繰り返してデバッグをしているとデバッグのポイントでもある
①変数の中身を確認する(どの変数を確認すべきか)
②変数の中身を予想する(期待する答えを考える)これが自然と身についてきます。
まずは手を動かすこと
コツとしては、
変数や引数があったのであれば、直後にconsole.logで確認。これでまずは手を動かしてみましょう
test.js$(function(){ $(".name-form").on("keyup", function() { var input = $(this).val(); console.log(input); //input変数の中身を確認 $.ajax({ type: 'GET', url: '/users', data: { keyword: input }, dataType: 'json' }) .done(function(datas) { console.log(datas); //引数datasの中身を確認 if (datas.length !== 0) { datas.forEach(function(data) { console.log(data); //引数dataの中身を確認 appendHTML(data); }); } }) .fail(function() { alert('失敗しました'); }) }); });中身の値の崩れが発見できたら、これまでの処理の順番を遡って変数の中身を確認していきましょう
以上、ajax通信の流れでした!!
- 投稿日:2019-12-04T17:25:38+09:00
react-firebase-hooksを使ってみた(Auth Hooks編)
はじめに
FirebaseとFirestoreをReact Hooksで使いたいと以前から思っていましたが、react-firebase-hooks v1はあまり納得がいかず、自作のcustom hooksを使っていました。その後v2が出たので、調べなければと思いつつ、半年くらい経ってしまいましたが、とうとう重い腰をあげることにします。
react-firebase-hooks
リポジトリはこちらです。
https://github.com/CSFrequency/react-firebase-hooks
今回はAuth Hooksを試してみようと思います。
コーディング
モジュールのimport
最初に必要なモジュールをimportします。
import React, { useState, useRef, useEffect } from "react"; import ReactDOM from "react-dom"; import firebase from "firebase"; import { useAuthState } from "react-firebase-hooks/auth";firebaseの初期化
次に、firebaseの初期化をします。
const firebaseConfig = { apiKey: "...", authDomain: "...", databaseURL: "...", projectId: "...", storageBucket: "...", messagingSenderId: "...", appId: "..." }; firebase.initializeApp(firebaseConfig);Loginコンポーネント
ログイン用のコンポーネントを作ります。
const Login = () => { const [email, setEmail] = useState(""); const [pass, setPass] = useState(""); const [error, setError] = useState(null); const [pending, setPending] = useState(false); const mounted = useRef(true); useEffect(() => { const cleanup = () => { mounted.current = false; }; return cleanup; }, []); const onSubmit = async e => { e.preventDefault(); setError(null); setPending(true); try { await firebase.auth().signInWithEmailAndPassword(email, pass); } catch (e) { console.log(e.message, mounted); if (mounted.current) setError(e); } finally { if (mounted.current) setPending(false); } }; return ( <div> <form onSubmit={onSubmit}> <input type="email" value={email} onChange={e => setEmail(e.target.value)} placeholder="Email..." /> <input type="password" value={pass} onChange={e => setPass(e.target.value)} placeholder="Password..." /> <button type="submit">Login</button> {pending && "Pending..."} {error && `Error: ${error.message}`} </form> </div> ); };ちょっと複雑になりましたが、やっていることは単純です。本来は、テキストフィールドを更新したところで、エラーメッセージをクリアすべきですが、そこは省略。
Logoutコンポーネント
ログアウト用のコンポーネントを作ります。
const Logout = () => { const [pending, setPending] = useState(false); const mounted = useRef(true); useEffect(() => { const cleanup = () => { mounted.current = false; }; return cleanup; }, []); const logout = async () => { setPending(true); await firebase.auth().signOut(); if (mounted.current) setPending(false); }; return ( <div> <button type="button" onClick={logout}> Logout </button> {pending && "Pending..."} </div> ); };Pending表示が短い場合はChrome Dev ToolsのNetwork TabでThrottlingをしましょう。
Appコンポーネント
最後に、全体をつなげるAppコンポーネントとReactDOMのrenderです。
const App = () => { const [user, initialising, error] = useAuthState(firebase.auth()); if (initialising) { return <div>Initialising...</div>; } if (error) { return <div>Error: {error}</div>; } if (!user) { return <Login />; } return ( <div> User: {user.email} <Logout /> </div> ); }; const rootElement = document.getElementById("root"); ReactDOM.render(<App />, rootElement);今回は、ログインしたらユーザのemailを表示するだけのシンプルなものです。
CodeSandbox
https://codesandbox.io/s/upbeat-chaum-vzpjg
完成したものがこちらです、実際に動作させるためにはforkして
firebaseConfig
を置き換える必要がありますのでご注意ください。おわりに
今までは、onAuthStateChangedをラップした独自custom hooksを使ってましたが、それがライブラリ化されることで、多少見通しはよくなったような気はします。しかし、loginやlogoutの機能を内包するcustom hooksは提供されていないため、今回のように長いコードになってしまいました。結局、そこには独自custom hooksが必要になりそうです。
- 投稿日:2019-12-04T17:19:31+09:00
TensorFlow.jsを用いた画像認識をNode-REDで行う手順
こんにちは、(株)日立製作所 研究開発グループ サービスコンピューティング研究部の横井一仁です。
今回は、Node-REDからTensorFlow.jsを用いて画像認識を行うフローをご紹介します。Node-REDが動くPCのカメラ画像やアップロードした画像に、何が映っているか(例えば、人物、犬、車、瓶など)を判定させてみます。
Node-REDの事前設定
今回は簡単にTensorFlow.jsを利用するため、学習済みモデルが入ったTensorFlow.jsモジュールをNode-REDのfunctionノードから呼び出してみます。functionノードで外部のnpmモジュールを利用する手順は、Node-REDの日本語サイトの「追加モジュールのロード」のページが参考になります。
(1) npmモジュールをインストール
コマンドプロンプトを起動し、Node-REDのホームディレクトリ(Windowsの場合はC:\Users\<ユーザ名>\.node-red)にて外部npmモジュール「max-image-segmenter」をインストールします。
cd cd .node-red npm install @codait/max-image-segmenter@0.1.4執筆時点の最新バージョンであるv0.1.12は仕様が変わり以降の手順で動作しないため、旧バージョンのv0.1.4を指定してください。
(2) Node-REDの設定ファイルを編集
Node-REDのホームディレクトリ(Windowsの場合はC:\Users\<ユーザ名>\.node-red)の中にあるsettings.jsをテキストエディタで開き、216行目辺りのfunctionGlobalContextセクションの中に以下の定義を記載します。
functionGlobalContext: { imageSegmenter: require('@codait/max-image-segmenter') }これによってfunctionノードのグローバルコンテキスト経由で外部npmモジュールを呼び出すことができる様になります。
(3) Node-REDを起動
node-redコマンドでNode-REDを起動します。もし起動中の場合はCtrl+cで終了し、再度Node-REDを起動してください。
node-redNode-REDフローエディタ( http://<Node-REDのIPアドレス>:<ポート番号> )にアクセスすると、functionノード上でTensorFlow.jsモジュールを利用できる様になっています。
画像認識を行うフローを作成
(1) 必要なノードをインストール
Node-REDフローエディタから画像ファイルをアップロードしたり、カメラで撮影をしたりするため、node-red-contrib-browser-utilsモジュールをインストールします。Node-REDフローエディタの右上のメニューから「設定」->「パレット」->「ノードを追加」を選択し、検索窓に「node-red-contrib-browser-utils」と入力してインストールしてください。
(2) フローを作成
file injectノード、functionノード、debugノードをワークスペース上に配置し、順にワイヤーで接続します。
(3) functionノードにコードを記載
functionノードに下記のJavaScriptコードを記載します。このJavaScriptコードは、前のノードから受け取った画像のバイナリデータを外部npmモジュールに渡し、画像認識結果を後続のノードに渡す処理をしています。
- コード:
var imageSegmenter = global.get('imageSegmenter'); imageSegmenter.predict(msg.payload).then(function (response) { msg.payload = response.objectsDetected; node.send(msg); });
- 名前: image segmenter
functionノードをライブラリに登録する際のトラブルを避けるため、名前は英数字と空白を用いた方が良いです。
デプロイボタンを押した後、file injectノードの左側のボタンをクリックすると、画像ファイルをアップロードできるダイアログが表示されます。試しに、人物が写った画像をアップロードしてみると、デバッグタブに"person"と出力されました。
その他、犬や猫の画像をアップロードすると、正しく区別してデバックタブに"dog"や"cat"を出力してくれます。file injectノードの代わりにcameraノードを用いると、PCのカメラで撮影した画像を用いた画像認識もできますので、試してみてください。
最後に
この様にNode-REDを用いて簡単に画像認識アプリを開発できました。今回ご紹介した手順の様にTensorFlow.jsを用いることで、Node-REDが搭載されているラズパイ等のデバイス上でエッジ分析ができる様になります。TensorFlowコミュニティでは、様々なモデルが公開されていますので、ぜひNode-REDと連携して遊んでみてください。
- 投稿日:2019-12-04T16:47:23+09:00
【React】マウント時に自動でfocusあてるhooks
useAutoFocus.tsimport * as React from 'react'; export default function useAutoFocus<RefType extends HTMLElement>() { const inputRef = React.useRef<RefType>(null); React.useEffect(() => { const node = inputRef.current; if (node) { node.focus(); } }, []); return inputRef; }使う側
function Hoge(props: Props) { const [code, setCode] = React.useState(''); const inputRef = useAutoFocus<HTMLInputElement>(); return ( <TextInput value={code} onChange={(e) => setCode(e.target.value)} ref={inputRef} /> ); }
- 投稿日:2019-12-04T16:24:29+09:00
条件によってテキストが変わるコンポーネントを分割して共通スタイルを適用する
こちらは、弁護士ドットコム Advent Calendar 2019 - Qiita の 21 日目の記事です。
要望
条件によって中身が変わるからコンポーネントは分けたいけど、スタイルは共通化したい。
JS によるロジックは特にない。具体的なケース
ユーザーの権限によってテキストが変わるコンポーネント。
共通のスタイルを使用したいのですが、一つのコンポーネントにまとめようとすると
v-if
の嵐になってしまい、可読性がかなり落ちてしまいます。イメージ<p v-if="condition" class="style1">◯◯権限を持っているので、☓☓が出来ます。</p> <p v-else class="style1">◯◯権限を持っていないので、☓☓が出来ません。</p> <p v-if="condition" class="style2">△△権限を持っているので、□□が出来ます。</p> <p v-else class="style2">ただし△△権限を持っていないので、□□が出来ません。</p> <p class="style3">どのユーザーも☆☆は出来ます。</p> <!-- どの権限でも同じ -->解決策
条件ごとにコンポーネントを分け、条件分岐・共通スタイル定義を親コンポーネントで行います。
スタイルを共通化させると、
v-if
をコンポーネントの切り替えの 1 つだけにできるので可読性が上がります。実装
親コンポーネントにスタイルを持たせて、条件によって子コンポーネントを切り替えるようにします。
親コンポーネント<template> <div class="wrapper"> <component :is="componentName" v-bind="propData" /> </div> </template> <script> import Component1 from './Component1.vue' import Component2 from './Component2.vue' export default { components: { Component1, Component2 }, props: { condition: { type: String, required: true }, username: { type: String, required: true } }, data() { return { propData: { username: this.username } } }, computed: { componentName() { switch (this.condition) { case 'cond1': return Component1 case 'cond2': return Component2 default: return null } } } } </script> <style scoped> .wrapper >>> .style1 { font-size: 20px; } .wrapper >>> .style2 { font-size: 16px; } .wrapper >>> .style3 { font-size: 16px; font-weight: bold; } </style>Component1.vue<template> <div> <h1>{{ username }}さん</h1> <p class="style1">閲覧権限を持っているので、プロパティの閲覧が出来ます。</p> <p class="style2">ただし編集権限を持っていないので、プロパティの編集が出来ません。</p> <p class="style3">どのユーザーもログインは出来ます。</p> </div> </template> <script> export default { props: { username: { type: String, required: true } } } </script>Component2.vue<template> <div> <h1>{{ username }}さん</h1> <p class="style1">閲覧権限を持っているので、プロパティの閲覧が出来ます。</p> <p class="style2">編集権限を持っているので、プロパティの編集が出来ます。</p> <p class="style3">どのユーザーもログインは出来ます。</p> </div> </template> <script> export default { props: { username: { type: String, required: true } } } </script>注意
>>>
(ディープセレクタ)は子孫要素すべてを対象とするので、scoped にしているからといって同じクラス名を使っているとスタイルがあたってしまいます。
コンポーネント名を prefix として付ける、BEM などの命名規則を適用する、などの対策が必要です。また、SCSS 等を使用している場合は、
>>>
ではなく/deep/
を使用する必要があります。他の選択肢
CSS を外部ファイル化して
@import
で読み込むHTML, CSS, JavaScript が一緒に管理できる SFC の利点が消えてしまうので見送りました。
「全く違う場所で利用するコンポーネントだけどスタイルは共通化させたい」というときは Minxin 的に使えるかもしれません。共通化しない
個々のコンポーネントとして取り扱えたほうがいいことが往々にしてあるので、選択肢としてはありだと思います。
今回は、共通に定義したスタイルを片方だけ変更するということが基本的にないことがわかっていたので、共通化したほうが後々楽だと判断しました。あとがき
自分が実装したケースではこのやり方がフィットしましたが、子コンポーネント側のコードが二重管理になってしまうので、ケースごとに検討することが必要だと思います。
他に同じような悩みを抱えている人の糧になれば幸いです。
参考
- 投稿日:2019-12-04T16:03:11+09:00
p5.js 初めてやってみる人に読んでもらえたら嬉しいです
はじめに
初めてp5.jsを始める方に向けて書いています。
p5.jsで最初に出てくる関数など紹介してきます。canvasの大きさや色
初めてp5のサイトを開くと、すでに画面の大きさやある程度までのコードが記入されて表示しています。
これを(▶︎)押して右の表示欄に表示させながらコードを書いていくことが出来るので、このサイトではHTMLやscriptの設定をせずにJavaScriptをどんどん書いていくことが出来ます。
下図のコード。。。mysketch.jsfunction setup() { createCanvas(windowWidth, windowHeight); background(100); }’createCanvas’で文字通りキャンバスを生成しています。
windowWidthで横幅を設定、windowHeightで高さを設定しています。ここの文字は他にも習得する方法がたくさんあります。上記の表にあるコードを自分のWEBサイト表示サイズに合わせて書き換えていくと良いです。
他にも収得方法はあり、下記のような記述にするとcanvasのサイズも自由自在です。mysketch.jsfunction setup() { createCanvas(800, 800);//800pxずつの正方形でcanvasが現れます。 background(100); }数値で入力すると画面全体ではなく、一部表示も可能になるので用途はいろいろになります。
backgroundの色指定
mysketch.jsfunction setup(){ createCanvas(windowWidth, windowHeight); background(100);//グレーが表示される }backgroundの色指定は、学生の頃に美術とかで習ったRGBやCMYK・HSBがありますが、p5も同じ方法で指定することが出来ます。
上記コード内に書かれているbackground(100);はRGBで指定されていて、このままだとグレーが表示されます。ここの数字をいろいろ調べて好きな色に変えることは可能なので、色彩のサイトで色を探して指定してもいいですし、無ければ自分でこの辺がこんな色かな?って考えながら入力しても楽しいと思います。
【参考サイト】
https://www.colordic.org/ここまでがsetup()関数になります。
描画する形はいろいろ
sketvh.jsfunction draw() { ellipse(mouseX, mouseY, 20, 20); }次に出てくるのは、draw()関数です。
こちらは、canvasに描画出来るものを指定することが出来ます。
上記コード内ではellipse(円)が指定されていますが、この他にも三角や四角・直線にしたり、繋げたりしてスライムみたいな形を描画することも出来ます。
JavaScriptと同じようにコードを書くと自動で描画したり、マウスに付いてきて描画させるなど他にも沢山の描画方法が思いついたコードやググったりして指定することが出来ます。p5のコードや参考のものはサイトを開いて検索できるようになっているので、他の人が書いているコードでわからないことがあってもrefarenceなどで調べて見るといいですよ。
【参考サイト】
https://p5js.org/reference/
https://developer.mozilla.org/ja/最後に
本当に初めてp5.jsをやってみようかなと思った方へ書いてみました。
ちょっとでも力になれたら幸いです。
p5.js以外でもJavaScriptに関連している〇〇.jsとかあるので、自分がやってみたいアニメーションのイメージと合うものをやっていくととても楽しいと思います。
最後までお読み頂きありがとうございました。
- 投稿日:2019-12-04T15:57:51+09:00
カスタムE で (((?)))
Akashic Advent Calendar 2019 七日目の記事です。
Akashic Engine では画面に画像や文字などを表示するために、様々なクラスが用意されています。それらはどれも
g.E
の派生クラスとして実装されています。この記事では、ちょっと変わったg.E
の派生クラスとして、append()
された要素をなんでもプリンのようにプルプルさせる E (以降カスタムE)を実装してみます。ゴールの確認
最初に完成形を確認しましょう。
プリンの画像は こちら のものを使用しています。
プリンがクリックされるとプルプルします。プリンの画像は
g.Sprite
で表示されていて、動きはカスタムEによるものです。二つの課題
プリンを実現するためには、「震える運動」と「画像の変形」の2つの機能が必要そうです。1つ1つ検討します。
震える運動
震える運動を考えます。プリンは変形すると元の形に戻ろうとします。これはバネの動きに似ています。
次のような、一方が固定され、もう一方に質点が繋がれたバネを考えてみます。質点を移動させると、振動して元の位置に戻ります。
+∂∂∂∂∂∂∂∂∂∂∂∂∂∂∂∂∂∂∂∂∂∂∂● |-------- len ---------|
- + : バネを固定した位置。
- ● : 質点
- len: バネの自然長(伸び縮みしていない、本来の長さ)。
そのほかにもバネの運動に必要なパラメータがあります。
- k: バネ係数。バネの伸び縮みに対する復元力を求めるための量。
- m: 質点の重さ。
- d: 質点の速度を減衰させる係数。
これらを用いて、バネの先端に取り付けられた質点の運動をコードの形で表現してみます。
/** * バネの先端に取り付けられた質点の位置と速度を求める。 * * @param dt 経過時間。 * @param x 質点の位置。 * @param v 質点の速度。 */ function massSpringSolver(dt: number, x: number, v: number): { x: number, v: number } { const k = 64; // バネ係数。 const len = 10; // バネの自然長。 const m = 1; // バネの先端に繋がれた質点の質量。 const d = 0.95; // 徐々に減速させるための値。 const dx = x - len; // バネの伸び縮みを求める。 const f = -dx * k; // 伸び縮みにバネ係数をかけて、質点に加わる力を求める。 const a = f / m; // 質点に加わっている力を質点の質量で割って、加速度を求める。 v += a * dt; // 質点を加速させる。 v *= d; // 質点が徐々に静止するよう、速度を減衰させる。 x += v * dt; // 速度に応じて質点の位置を更新する。 return { x, v }; // 新しい質点の位置と速度を返す。 }振動しながら徐々に元の位置に戻る、という処理が実現できそうです。
画像の変形
次に、質点の運動を用いて画像を変形することを考えます。
Akashic Engine のレンダリング機能を司る
g.Renderer
は画像の平行移動・回転・拡大縮小に行列を用います。行列は2行3列で、コード上は1次元配列で表されます。const matrix = [ a, b, c, d, e, f ];この6つの数値はどんな働きがあるのでしょうか。詳しい説明は省略しますが、 (a, b) はX軸を、(c, d)はY軸を変形させる働きがあります。
次の行列は平行移動・回転・拡大縮小しません。
const matrix = [ 1, 0, 0, 1, 0, 0 ];次の行列はY軸方向に2倍に拡大します。
const matrix = [ 1, 0, 0, 2, 0, 0 ];バネの力で運動する質点の位置を行列にうまく組み込めば、プルプルとした変形が実現できそうです。
完成
振動の方法と、それを変形に用いる方法がわかりました。これらを組み込んだカスタムE
Puddinizer
のコードは次のようになります。interface PuddinizerParameterObject extends g.EParameterObject { /** バネ定数 */ k: number; /** 質量 */ m: number; /** 減衰係数 */ d: number; } class Puddinizer extends g.E { private k: number; private m: number; private d: number; private px: number; private py: number; private vx: number; private vy: number; constructor(param: PuddinizerParameterObject) { super(param); this.k = param.k; this.m = param.m; this.d = param.d; this.px = 0; this.py = 0; this.vx = 0; this.vy = 0; // 毎フレーム自身を更新する。 this.update.add(() => this.onUpdate()); } /** * プリンを突っつく。 * * @param px 質点の位置(X成分) * @param py 質点の位置(Y成分) * @param vx 質点の速度(X成分) * @param vy 質点の速度(Y成分) */ poke(px: number, py: number, vx: number, vy: number): void { this.px = px; this.py = py; this.vx = vx; this.vy = vy; } renderSelf(renderer: g.Renderer, camera?: g.Camera): boolean { // ここで設定した行列が子の E に作用する。 renderer.transform([ 1, 0, // Y軸を表す成分に質点の位置を加味する。 0 + this.px, 1 + this.py, 0, 0 ]); return true; } private onUpdate(): void { const dt = 1 / g.game.fps; const ax = (-this.px * this.k) / this.m; const ay = (-this.py * this.k) / this.m; this.vx *= this.d; this.vy *= this.d; this.vx += ax * dt; this.vy += ay * dt; this.px += this.vx * dt; this.py += this.vy * dt; this.modified(); } }振動の計算は
onUpdate()
で、それを用いた行列の設定はrenderSelf()
で行われています。ここまでの検討とは次の点で違いがあるので注意してください。
- 平面上の振動を計算するため、質点の位置を px, py の2変数で表している。
- 位置 (0, 0) に固定された自然長 0 のバネに質点が繋がれている、として振動を計算している。
プルプルさせるカスタムEの実装は以上です。
Puddinizer
のデモはGitHubから入手できます。最後に
この記事では子要素に動きをつけるカスタムEを実装しました。質量やバネ定数を変更すると、色々な質感が表現できます。ゲームなら、スライムのダメージ表現といった使い方もできそうです。
- 投稿日:2019-12-04T15:46:52+09:00
Leaflet + Vue.js で 地図の表示位置を切り替えられるサンプルサイトを作ってみた
この記事は、 North Detail Advent Calendar 2019 の4日目の記事です。
弊社は最近地図を扱う業務が増えたので、ここ数ヶ月ほど学習がてらに "Leaflet" を使ってみてます。
もともと "Vue.js" でのサイト構築も多いので、両方を合わせるとどうなるか、というのを試してみたいと思います。デモサイト: https://sample-leaflet-tacck.netlify.com/
ソースコード: https://github.com/tacck/sample-leaflet画面でみると
初回は "位置情報提供の許可" が求められるので、 "許可" の方を選択してください。
「現在地」のボタンをクリックすると、ブラウザ経由で取得した位置情報の地点を地図上で表示します。(ここは弊社)
解説
Vue.js や Leaflet.js それぞれの詳しい扱い方は専用のサイトにお任せします。
ここでは、少し工夫したところを。
情報の更新
現在地取得と反映
Mainコンポーネントの中に、 Tabコンポーネント(ボタンのある領域) と Mapコンポーネント(地図のある領域) を持つ形にしています。
Mapコンポーネントは、Mainコンポーネントから与えられた緯度経度を中心に地図を表示する(変更があれば更新する)だけ、という作りです。緯度経度の変更されるタイミングは、次の二つになります。
- ボタンが押された場合
- ボタンが「現在地」の時にブラウザから与えられた位置情報が
navigator.geolocation.watchPosition()
経由で更新された場合ボタンが押された場合
Tabコンポーネント の中には TabButtonコンポーネント が二つ並んでいます。
TabButtonコンポーネント は、クリックされたら "クリックされたよ" というイベントを Tabコンポーネントに返すだけです。
どちらのボタンがクリックされたかは、 Tabコンポーネント の責任範囲として、ここからさらに Mainコンポーネント へイベントを投げます。
Mainコンポーネントへは "どちらのボタンが押されたか" がわかるものを引数として渡しています。Mainコンポーネントは、受け取った値を使って緯度経度を更新し、 Mapコンポーネントへ(props経由で)情報を渡します。
TabButton.vue(snip) <b-button @click="$emit('click')" size="lg" :variant="status"> (snip)Tab.vue<TabButton @click="clickStation" id="sation" :status="stationVariant" label="札幌駅" ></TabButton> (snip) clickStation() { this.stationVariant = "info"; this.hereVariant = ""; this.$emit("changeActive", "static"); },Main.vue(snip) <Tab @changeActive="setActive"></Tab> (snip) setActive: function(location) { console.log("active:" + location); this.lat = this.geosObject[location][0]; this.lon = this.geosObject[location][1]; }, (snip)地図へ反映
Mapコンポーネントでは、緯度経度の情報が更新されたこと(正確にはpropsで持つ 'lat' の値が更新されたこと)がわかるように、
watch
を使っています。Main.vue(snip) <Map :lat="lat" :lon="lon"></Map> (snip)Map.vue(snip) methods: { updateCurrentPosition: async function() { if (this.map) { this.map.panTo([this.lat, this.lon]); } if (this.currentCircle) { this.currentCircle.setLatLng([this.lat, this.lon]); } if (this.currentPoint) { this.currentPoint.setLatLng([this.lat, this.lon]); } } }, watch: { lat: function() { this.updateCurrentPosition(); } } (snip)まとめ
Vue.js のコンポーネントの一つとして、 Leaflet を使った地図を使うことが簡単にできました。
Mapコンポーネントの外から位置情報を与えられるので、各種サービスと連携して地図表示することも気軽にできそうです。
- 投稿日:2019-12-04T15:39:30+09:00
【初心者】javascriptでシャークネードのミニゲームを作りたい①
何を作るか
javascriptの勉強がてら、サメを迎撃するシャークネードのミニゲームを作ってみたいと思います。
イメージとしては、UNDERTALEのアンダイン戦の矢印が飛んでくるやつみたいな。↓
↓
―
→ ♥ ←↑こんなのです
さて、どこから手をつければいいことやら・・・実装すること
・中心にいる人(おなじみフィン・シェパード)
・キーボードの矢印キーで動くチェーンソー
・上下左右から飛んでくるサメ
・当たり判定(サメがチェーンソーに触れれば消滅、フィンに触れればゲームオーバー)
・スコア表示(1匹倒すごとに100点)できれば実装したいこと
・レアサメ(1匹倒すと500点)
・ハイスコア記録機能(10位くらいまで記録。
昔のアケゲーみたいにプレイヤー名も3文字くらいでのこせるようにしたい)こんなことをできるようになりたいな―と思います。
がんばります。