- 投稿日:2020-11-21T23:39:07+09:00
【Vue.js】カウンター作成記録
はじめに
vueの練習したいけど何を作ればいいのか。
そうだ、カウンターを作ろう。とりあえず作った
コード
counter.html<template> <div> <b-button v-on:click="countUp" variant="success">カウントアップ</b-button> <b-button v-on:click="countDown" variant="primary">カウントダウン</b-button> <b-button v-on:click="countClear" variant="danger">カウントクリア</b-button> <h1>{{displayCount}}</h1> <div v-if="displayCount > 10"> {{successCount()}} </div> </div> </template>counter.js<script> export default { data() { return { displayCount: 0 }; }, methods: { countUp() { this.displayCount++; }, countDown() { this.displayCount--; }, countClear() { this.displayCount = 0; }, successCount() { alert('great!!') } } }; </script>さいごに
マウスをカチカチするだけでも楽しくなっちゃう自分としては
かなり良い暇つぶしグッズを作ってしまった。
vue.jsで作った意味があるのかわからない感じだけど、記法とかをおぼえる練習としては良かったかな。
ここまで読んでくれた方ありがとうございました!おまけ
下記のリンクで遊べます。
ちなみにCodeSandboxはかなり優秀な開発環境ですので是非使ってみてほしいです。
https://codesandbox.io/s/counter-practice-xlquf
- 投稿日:2020-11-21T22:21:05+09:00
Nuxt.jsビギナーズガイドで「Request failed with status code 401」にハマった
- 投稿日:2020-11-21T21:40:51+09:00
JavaScriptで2段階に連動するプルダウンの作成と要素の表示、非表示
備忘録として残しておきます
HTML
choice.html<select id="parent" onchange="date()" class="form"> <option value="和食">和食</option> <option value="洋食">洋食</option> <option value="中華">中華</option> <option value="" disabled selected>選択してください</option> </select> <select id="children" class="form"> <option value="" disabled selected>選択してください</option> <option value="味噌汁">味噌汁</option> <option value="肉じゃが">肉じゃが</option> <option value="天ぷら">天ぷら</option> <option value="グラタン">グラタン</option> <option value="オムライス">オムライス</option> <option value="ナポリタン">ナポリタン</option> <option value="麻婆豆腐">麻婆豆腐</option> <option value="餃子">餃子</option> <option value="春巻">春巻</option> </select>CSS
choice2.css#children option:disabled{ display:none; } #children option{ display:block; }JavaScript
choice3.jsfunction date() { //id="course"の値を取得 var parentlement = document.getElementById( "parent" ) ; //id="seminardate"を取得 var childrenDate = document.getElementById( "children") ; //disabledをtrueに for (var i = 0; i < childrenDate.length; i++){ childrenDate.options[i].disabled = true; } // disabledに代入 if( parentlement.value == "和食") { childrenDate.options[1].disabled = false;//味噌汁 childrenDate.options[2].disabled = false;//肉じゃが childrenDate.options[3].disabled = false;//天ぷら } else if( parentlement.value == "洋食") { childrenDate.options[4].disabled = false;//グラタン childrenDate.options[5].disabled = false;//オムライス childrenDate.options[6].disabled = false;//ナポリタン } else if ( parentlement.value == "中華") { childrenDate.options[7].disabled = false;//麻婆豆腐 childrenDate.options[8].disabled = false;//餃子 childrenDate.options[9].disabled = false;//春巻 } else if ( parentlement.value = "") { childrenDate.disabled = true; } }和食を選択します。
和食を選択すると「味噌汁」「肉じゃが」「天ぷら」以外にdisabledが付きます。disabledが付いたoptionは非表示になります。
choice2.css#children option:disabled{ display:none; }まとめ
このやり方でも一応動きますが、これではメンテナンスがしにくいのでJavaScriptの改善が必要みたいです。
参考リンク
- 投稿日:2020-11-21T21:35:42+09:00
【初心者でもわかる】セレクトボックスの選択で、リンク先を変更する方法
どうも7noteです。セレクトボックスで選択された内容ごとにリンク先を変える方法
シンプルなスクリプトで対応が可能です。
検索ボタンを押したときに、リンクする方法と、セレクトの値を変更した瞬間にリンクする方法の2種類のサンプルソースです。※jQueryを使用しています。jQueryってなんだという方は[こちら](https://qiita.com/7note/items/45bd8efbedd557667218)
「検索」ボタンありバージョン
index.html<select name="preflist"> <option value="/hokkaido/">北海道</option> <option value="/okinawa/">沖縄</option> </select> <button class="btn">検索</button>script.js$(".btn").on("click",function(){ const selected = $("select[name=preflist]"); window.location.href = selected.val(); });検索ボタンなしバージョン(選択を切り換えたらすぐリンク)
index.html<select name="preflist"> <option value="/hokkaido/">北海道</option> <option value="/okinawa/">沖縄</option> </select>script.jsconst selected = $("select[name=preflist]"); selected.on("change",function(){ window.location.href = selected.val(); });解説
window.location.href
でリンク先を指定することができます。
タイミングはボタンありの場合はボタンを押した時、ボタンなしならセレクトの値が変更された時に発火するように作ります。
とび先のリンクを各optionタグのvalueに入れておくことで、指定のリンク先へ飛ばすことができます。参考
https://awesome-linus.com/2019/05/09/select-option-href/おそまつ!
~ Qiitaで毎日投稿中!! ~
【初心者向け】HTML・CSSのちょいテク詰め合わせ
- 投稿日:2020-11-21T21:30:30+09:00
簡単 (電子) レシート receiptline の新機能を試してみた
マークダウン言語で紙のレシートや電子レシートを簡単に作れる receiptline。
https://github.com/receiptline/receiptline
https://www.npmjs.com/package/receiptline少し前にマイナーバージョンアップがあったので、新機能を試してみました。
(2020年11月21日時点で Verion 1.1.1)Web フォントで表示
前回の記事で試していた Google Fonts の「Kosugi Maru」が採用されました。
これで iOS, Android, Windows, Linux, Mac どの環境でも同じ表示になるはずです。
データは過去の記事から。
ReceiptLine{image:iVBORw0KGgoAAAANSUhEUgAAASAAAAAwAQMAAACL7DXsAAAABlBMVEUAAAD///+l2Z/dAAABqElEQVQ4y+3VMW7bMBQGYLIqwqUo10ymj5AxBQwouUmO0NEFBFOCBo05Qs7QG5DV0K1noOGhKwMvLEDw7yOVALEUIB46BdUofJTe+x8pMZxxsXeFol4gJucm1HMUopght0SPmCO2RCOGGTJLNBB783UShzladidxfBMluYjpFaT+FYqEKE4O/8VtRA7WrRO8Sl+pEIPWq2Nd0LGPHvc81CJYYGgTfslwdy2wql1UP1RBv+2foFVHaO8AaYAHYW4uq1hLF1dsQj/tY9wpS2j0hAgq4TZVXkVP+jChwfrU1BPSSTkkJfxO2KAHQv1U04P1OhbUMh0z0oQk3e0zmrpD50UoqKdWCSGdopxT4oGfojBDNBZvIztF4ynKA95bsG1B3RMaFugw2qPfqmeUI5AzNODQU06NzMgkqpm6m9BTBGX7jn33PTYUy0qYBqki9PmiIApzW9Aeol1fxcR45MJxmjlDYm1GNBb/saDQCHO3/QZ2GyvuK6BdK/AuI6yU/yT6cjjVPqQK1kKqSIE42vPDwVxWTBvEzY69POb2nG/Bf4T2HMT0u/sj/AWoqMYMK2VNkgAAAABJRU5ErkJggg==}| ^^領収書| 2020/07/20 01:23| #NGC17TH| 缶ビール | ¥211~ シュークリーム | ¥129* - 小計 | ¥340~ (10%対象 | ¥211) ( 8%対象 | ¥129) (内消費税等 | ¥28) ^合計 | ^¥340 お 預 り | ¥1,000 お 釣 り | ¥660 *印は軽減税率対象商品です。USB (Bluetooth) プリンターで印刷
仮想シリアルポート対応 USB (Bluetooth) プリンターでテスト印刷できるようになりました。
そこで、ネットオークションで新たに SII RP-E10 と CITIZEN CT-S255 を入手。
どちらも USB 接続モデルで、店舗で予備機として保管されていたものだそうです。
仮想シリアルポートの設定
RP-E10 用「SII RP Series Communication Software for Windows」をインストール。
「RP通信設定ユーティリティ」で仮想シリアルポートドライバーに切り替え。
CT-S255 用「シチズンサーマルプリンタ仮想 COM ポートドライバー」をインストール。
デバイスマネージャーで確認。RP-E10 は「COM3」、CT-S255 は「COM7」になりました。
Serial-LAN Converter の設定
servers.json
で、COM3
(またはCOM7
) と TCP9100
ポートをつなぎます。servers.json"serial": { "host": "127.0.0.1", "port": 9100, "device": "COM3" }ReceiptLine Designer の設定
printers.json
に、RP-E10 と CT-S255 を登録。localhost
がプリンターになります。printers.json{ "rp_e10": { "host": "127.0.0.1", "port": 9100, "cpl": 48, "encoding": "cp932", "gamma": 1.8, "upsideDown": false, "spacing": false, "cutting": true, "command": "sii" }, "ct_s255": { "host": "127.0.0.1", "port": 9100, "cpl": 48, "encoding": "cp932", "gamma": 1.8, "upsideDown": true, "spacing": false, "cutting": true, "command": "escpos" } }テスト印刷
Node.js でサーバーを起動。ReceiptLine Designer で印刷できました。
この開発ツールの使い方はこちら。$ node designer.js
新しい印刷オプション
printers.json
で行間隔と用紙カットの有無が選択できるようになりました。行間隔
spacing
をtrue
にすると、行間が空いて読みやすくなります。
ただし、罫線もしくは縦倍角以上の拡大文字がある行の間隔は変わりません。
ちなみに、前々回の記事は、上下反転印刷に対応できない中途半端な改造でした…用紙カット
cutting
をfalse
にすると、印刷完了後に自動カットされなくなります。
また何か作ったら投稿します。ではまた!
- 投稿日:2020-11-21T21:03:42+09:00
効果的なTypeScriptを読んで学んだ4つのこと
こちらの記事は、Kaylie Kwon 氏により2020年1月に公開された『 4 Things I Learned from Reading Effective TypeScript 』の和訳です。
本記事は原著者から許可を得た上で記事を公開しています。効果的なTypeScriptを読んで学んだ4つのこと
Photo by Siora Photographyon Unsplash
数日前、私は長時間のフライトでダン・ヴァンダーカム氏の効果的なTypeScript(以下TSとする)を読み終えました。(正確には8時間!)
その本は非常に実践的なアドバイスで簡潔に書かれています。本を読んでいただくことをお勧めしますが、ここに私のお気に入りのコツがいくつかあるのでご紹介します。1.エラーによるオブジェクトの変更を防ぐため、“ readonly “を使用すること
配列またはタプルがreadonlyとしてマークされている場合、ユーザーがそれらのオブジェクト内の項目を追加、削除、または更新しようとするとTSはエラーが発生。これは副作用を回避するための関数型プログラミングで特に役立つ。
2.型の再利用性については関数宣言よりも関数式を優先すること
同じ関数シグネチャを持つユーティリティを書き込むと少し繰り返しになる可能性がある。
以下に、入力タイプと出力タイプを毎回再度書き出す必要がある3つのカート関連関数を示す。
DRY原則を維持するにはシグネチャ全体を関数型に割り当て、関数式の使用に切り替えることができる。3.より良い型安全性を得るためにどれよりもunknownを好むこと
anyを使用すれば便利なエスケープハッチになる可能性があるが、それを回避する正当な理由もある。どんな型安全性も保証するものではなく、コールチェーンの上位のコンシューマーからエラーを隠す。
型がunknownの場合にはTSは存在しないメソッドを呼び出す等のエラーを正しく表示する。開発者がコンパイラよりも型についての知識が豊富な場合は意図を明確にするためにダブルアサーションと共にunknownを使用することもできる。
4.より良い開発経験のためにJSDocもしくはTSDocスタイルのコメントに傾倒すること
ほとんどの場合においてコメントに型情報を含めることは避けるべき。ただし、移行し、JSを使用してTSを有効にする場合、または関数に関する追加情報を提供したい場合にはJSDocもしくはTSDocが型チェックを行い、IDEと統合するための優れた方法だ。
注意:VSCodeは適切にフォーマットされたコメントをツールチップに表示する。
これでおしまい!
これらのコツを選択したのは既存のコードベースの改善にすぐに適用できるから。お役に立てば幸いです。翻訳協力
Original Author: Kaylie Kwon
Original Article: 4 Things I Learned from Reading Effective TypeScript
Thank you for letting us share your knowledge!ご意見・ご感想をお待ちしております
今回の記事は、いかがだったでしょうか?
・こうしたら良かった、もっとこうして欲しい、こうした方が良いのではないか
・こういったところが良かった
などなど、率直なご意見を募集しております。
いただいたお声は、今後の記事の質向上に役立たせていただきますので、お気軽にコメント欄にてご投稿ください。Twitterでもご意見を受け付けております。
みなさまのメッセージをお待ちしております。
- 投稿日:2020-11-21T19:07:39+09:00
【DroidScript】スマホでスマホアプリを作る シリーズまとめ
概要
DroidScriptというアプリを使うと、スマホからjavascriptベースの記述でスマホアプリを作れてしまうらしい。
DroidScriptでググっても日本語のサイトはあまりヒットしないし、Qiitaの記事もゼロ・・・面白そうなアプリなのに(´・ω・`)
以前にWEBアプリ導入の練習で「電子レンジワット数で加熱時間を換算するアプリ」を作り、今回も同じお題で練習してみたので、まとめてみた。
参照:【GAS】電子レンジワット数で加熱時間を換算するwebアプリを作ってみた
これまでのまとめ
スマホでスマホアプリを作る① Hello World!
DroidScriptの紹介と、サンプルアプリの実行。スマホでスマホアプリを作る② 背景やテキストの追加
オリジナルアプリ作成開始。背景画像やテキストの追加。スマホでスマホアプリを作る③ スピンボタンとシークバー
計算用の数字を取得するためのボタン設置。スマホでスマホアプリを作る④ 実行ボタン
取得した数字を元に計算実行。アプリ完成!自作アプリの動作
アプリの画面がこちら。
実際に動かしてみたところ。
自分のレンジのワット数を設定。温める対象のラベルに記載されたワット数と加熱時間を設定。計算ボタンをタップすると、自分のレンジで温める場合の加熱時間を教えてくれる。GIFには出てこないけど、ドロイド君をタップするとアプリの情報がポップアップする。
コード全文
function OnStart() { lay = app.CreateLayout( "linear", "VCenter,FillXY" ); lay.SetBackground( "/Sys/Img/BlueBack.jpg" ); txt = app.CreateText( "HOW MANY MIN" ); txt.SetTextSize( 22 ); lay.AddChild( txt ); img = app.CreateImage( "/Sys/Img/Icon.png", 0.1 ); img.SetMargins( 0, 0.02, 0, 0 ); img.SetOnTouchDown( img_OnTouchDown ); lay.AddChild( img ); txt = app.CreateText( "Your Microwave" ); txt.SetMargins( 0,0.02,0,0 ); txt.SetTextSize( 20 ); lay.AddChild( txt ); my = app.CreateSpinner( "730,700,680", 0.3 ); my.SetOnTouch( spin_my ); lay.AddChild( my ); txt = app.CreateText( "Label Microwave" ); txt.SetMargins( 0,0.02,0,0 ); txt.SetTextSize( 20 ); lay.AddChild( txt ); label = app.CreateSpinner( "500,1500", 0.3 ); label.SetOnTouch( spin_label ); lay.AddChild( label ); txt = app.CreateText( "Label heating time" ); txt.SetMargins( 0,0.02,0,0 ); txt.SetTextSize( 20 ); lay.AddChild( txt ); skb = app.CreateSeekBar( 0.8 ); skb.SetOnTouch( skb_OnTouch ); skb.SetRange( 600 ); skb.SetValue( 180 ); lay.AddChild( skb ); btn = app.CreateButton( "EXECUTE", 0.4 ); btn.SetMargins( 0, 0.05, 0, 0 ); btn.SetOnTouch( btn_OnTouch ); lay.AddChild( btn ); app.AddLayout( lay ); } function img_OnTouchDown( ev ) { app.ShowPopup( "I will calculate the heating time!" ); app.Vibrate( "0,100,30,100,50,300" ); } function spin_my( item ) { app.ShowPopup( "Set " + item + "W" ); app.Vibrate( "0,30" ); } function spin_label( item ) { app.ShowPopup( "Set " + item + "W" ); app.Vibrate( "0,30" ); } function skb_OnTouch( value ) { value = Math.round( value / 5 ) * 5; var min = Math.floor( value / 60 ); var sec = Math.floor( value % 60 ); sec = ( "0" + sec ).slice( -2 ); var settime = min + ":" + sec; app.ShowPopup( "Set Time = " + settime ); app.Vibrate( "0,10" ); } function btn_OnTouch() { var my_wave = my.GetText(); var label_wave = label.GetText(); var time = skb.GetValue(); time = Math.round( time * 0.2 ) / 0.2; var ratio = ( label_wave / my_wave ); var after = time * ratio; var remain = after % 60; var newmin = ( after - remain ) / 60; var newsec = Math.round( remain ); newsec = ( "0" + newsec ).slice( -2 ); var ans = newmin + ":" + newsec; app.ShowPopup( "Heat for " + ans ); app.Vibrate( "0,100,30,100,50,300" ); }DroidScriptの操作Tips
PCでコード編集
左上のWiFiマークをタップすると、画面のような表示が表れる。
http://androidscript.org/ide
からアクセスして、伏字のIPアドレスとパスワードを入力すればPCからコードを編集できる。
スマホでコード編集
感覚的には、スマホでコピペしようと思ったらカーソル長押しなんだけど、こちらのアプリだと操作が異なるのが最初分からなくて苦労した・・・。
カーソルを操作する丸いアイコンをダブルタップすると、コピペなどのメニューが開く。これでサンプルコードから必要な部分をコピーすることができる。
以上!
おしまい
シリーズ
DroidScript電子レンジアプリ作成
スマホでスマホアプリを作る① Hello World!
スマホでスマホアプリを作る② 背景やテキストの追加
スマホでスマホアプリを作る③ スピンボタンとシークバー
スマホでスマホアプリを作る④ 実行ボタン
スマホでスマホアプリを作る シリーズまとめ
- 投稿日:2020-11-21T18:58:01+09:00
【Jest】テスト用のAPIとDBとうまく付き合っていく方法
始めに
apiのnodeのバージョンを8から14にしました(まだリリースはしてない)!!
14への移行自体は簡単だったもののテストの移行でかなり苦戦しました。。。
テストは古のライブラリmocha-coで書かれており、仕方なくmocha-coのシンタックスをjestに置き換える作業を開始しました。これが予想以上にめんどくさくその時のHow toを解決する方法がこの記事になります。
以下の問題にお悩みの方はこちらを参考していただけるかと思います。またいい方法をご存知の方はお教えください!!
- TooManyConnectionとなってしまう
- 毎回のテストでサーバー立ち上げとクローズをするのめんどくさい
- --ranInBandつけたけどなんか挙動がよくわからない・・・。
- mochaからのjestに移行したい
正確にjestのランタイムの仕様が分かっているわけではなく、調べたり試したりした結果なので間違いやもっと詳細が分かる方がいらっしゃいましたらぜひお教えください!!
また正確なコードを書くと記述量が多いので雰囲気で書いているところもあるのでご了承ください。前提条件
- テスト用のDBを立ててデータを流しこみながら行っている
- モジュール単位のテストではなくApiに対してリクエストを投げてそのレスポンスとDBのデータを確認してテストを行う
といったことを前提とします。データベースはインメモリではなくデータベースサーバーを立てる前提です。ですのでテストは順次実行をしていく必要があります。(もともとそうなっていたのが理由としては大きいですが)実際の環境に近い環境を立てることができることがこの作りのメリットだったりします。逆にインメモリにすると並列でテストを実行することができるので、実行速度をあげることができます。
では、データベースサーバーを立てるテストの場合、順次実行をしていかないといけない理由としては、
こんな感じに一斉に更新がかかると他のテストに影響が出ることがあります。
たとえば、テスト前にテーブルを消したり、データの更新をしたりとか・・・。すると想定されていた状態にないデータが生まれテストが落ちたします。しかも恐ろしいことにそれが処理時間によって変わるのでテストガチャが生まれます。
そのためこの場合は、順次実行をしていくのがよいです。こうすればテストファイルが一斉に更新されることによっておこるデータベースの不整合を防ぐことができます。
そのためのオプションとして、jestでは--runInBand
オプションを利用します。jest --runInBandただし、これだけでは問題が発生します。
テスト用のAPIサーバーが立ち上がらない or 立てたり切ったりし続けると遅い問題
具体的にどういう問題かについての前にAPIテストで行うことを書きます。
APIテスト
APIのテストでは
- モジュール単位でテストする方法
- APIサーバーを立ててリクエストを送ることによって
があります。後者の方法で有名なライブラリとしては
supertest
が挙げられます。
例えばexpress
とsupertest
の両方を使ってサーバーを立てるコードは下記のように書けます。// app.js const express = require('express'); const app = express(); app.get('/', function (req, res) { res.send('Hello World'); }); module.exports = app;// test-request.js const supertest = require('supertest'); const app = require('./app'); module.exports = supertest.agent(app.listen(3000));// index.test.js const testRequest = ('./test-request'); describe('test', () => { it('200?', () => { testRequest .get('/') .expect(200); }); });またテスト用のデータを流し込むためのモジュールも下記のように定義します。
// test-model.js const Sequelize = require('sequelize'); const sequelize = new Sequelize('test', 'test', 'test', { host: '127.0.0.1', dialect: 'mysql', }); const models = {...}; module.exports = { sequelize: sequelize, models: models };mochaの場合はtest-requestやtest-modelを読み込んでテストをすればそれでよく、問題は発生しません。
しかし、jestの場合はそうはいきません。mochaにおける挙動
といった形になります。そのため先ほどのコードで一度しかコネクションはできないですし、一度しかサーバーは立ち上がりません。
jestにおける挙動と問題
jestにおいてはどのようになるのかというと、
といった形になります。そのためmochaのときのような書き方はやめてテストファイルごとに『立てて、切って』とする必要があります(書き換えの際はめっちゃ大変です)。
とはいえ、毎回そのようにするのは結構大変ですし、毎回『起動、コネクト、停止、切断』と実行していくとテスト完了にかかる時間が増えてしまいます。
- 毎回のテストでサーバーの起動・停止、dbへのコネクション・停止をする必要がある
- 記述量が増え、closeを忘れるとテストが落ちる
- 処理が増えるため必然的に実行時間が長くなる
- エラーハンドリングをしてcloseしなくても動くようにする
- 大量のコネクションが貼られることになりtoo many connectionとなってしまう
といった問題が発生します。
そのためには、globalSetupとtestEnvironmentを使います。
globalSetupを使い一度のみ起動する
globalSetupを使います。
jestではこういった形で各ファイルが実行されるため、一度しか実行されないglobalSetupでサーバーの起動をなどを行うのが良いかと思われます。
// globalSetup const supertest = require('supertest'); const app = require('./app'); global.__request = supertest.agent(app.listen(3000));// test-utils/request.js module.exports = global.__request;// test.ts import request from 'test-utils/request'; describe('test', () => { it('200', () => { request.get('/').expect(200); }); });気持ち的にこんな感じにglobalにロードしたモジュールを保存させてテストで利用できるようにしたいんですが、ここにはここで落とし穴があります。その落とし穴はsetUpFilesにあります。
setUpFilesでは何を行っているのかというか『あたらしい環境のセットアップです』具体的にはglobalをはじめとしたテスト実行用の環境を生成するタイミングになり、新しくglobalを作っているため、下記のようなコードの場合、// globalSetUp global.__global = 'I am from global setup';// setUpFiles console.log(global.__global); // <- 新しいコンフィグになるためundefined global.__setUp = 'I am from setup files'; // <- ただしここで追加したglobalメンバーにはテストファイルがアクセス可能となります。そのため、実際のテストファイルでも
// test.js describe('test', () => { console.log(global.__global); // undefined console.log(global.__setUp); // I am from setup files;となります。
親玉のランナーの実行環境とは別の環境が作られるというところがポイントになります。
setUpAfterEnvもタイミングは異なれど同じ挙動をします。そこでどうするのかというとtestEnvironmentを利用します。
testEnvironment
testEnvironmentを使うことで柔軟にテスト環境を構築することができます。
// package.json "testEnvironment": "./my-custom-environment.js",// my-custom-environment const NodeEnvironment = require('jest-environment-node'); class CustomEnvironment extends NodeEnvironment { constructor(config, context) { super(config, context); } async setup() { this.global.__request = global.__request; await super.setup(); } } module.exports = CustomEnvironment;このようにsetupメソッドで
global.__request
をthis.global__request
に設定することによってテストファイルの方でglobal.__request
にアクセスすることができます。まとめ
最終的にはこのようになります。
アプリケーションコード
// app.js const express = require('express'); const app = express(); app.get('/', function (req, res) { res.send('Hello World'); }); module.exports = app;// model.js const Sequelize = require('sequelize'); const sequelize = new Sequelize('test', 'test', 'test', { host: '127.0.0.1', dialect: 'mysql', }); const models = {...}; module.exports = { sequelize: sequelize, models: models };テストユーティリティコード
// test-request.js module.exports = global.__request;// test-model module.exports = global.__db;// global-setup.js const supertest = require('supertest'); const app = require('./app'); const db = require('model'); global.__request = supertest.agent(app.listen(3000)); global.__db = db;// my-custom-environment const NodeEnvironment = require('jest-environment-node'); class CustomEnvironment extends NodeEnvironment { constructor(config, context) { super(config, context); } async setup() { this.global.__request = global.__request; this.global.__db = global.__db; await super.setup(); } } module.exports = CustomEnvironment;// jest.config.json { "testEnvironment": "./global-setup.js", "globalSetup": "./my-custom-environment.js" }テストコード
// test.js const testRequest = ('./test-request'); const testModel = ('./test-model'); describe('test', () => { beforeAll(async () => { await testModel.reset(); }); it('200?', () => { testRequest .get('/') .expect(200); }); });全体図
問題点
ただし、jestのmockと相性が悪いという問題点があります・・・。
というのもglobal上にサーバーコードがロードされているため、それらを実行したい時は各ファイルごとに一回サーバーを落としてもう一度ロードする必要があります。
そのため、globalにこれらをやるための入り口を開けてあげる必要がありました。
ここら辺みなさんどうしてるんですかね?いろいろとご意見伺いたいのもあって書かせていただきました。
- 投稿日:2020-11-21T18:47:15+09:00
webStorageとcookie
はじめに
今までも何度かlocalStorageやsessionStorageは、見てきました。そしてそれらに対してブラウザ側でいくつかデータを保持できるものという認識でしかなかったのですが、なんとなくの違いなどがわかりましたので、記事にしました。
WebStorage
5メガバイトまで保持できる
sessionStorageとlocalStorageの総称
2つの違い
基本的に機能や用意されているメソッドは変わりません。
しかしこれら二つの中で具体的な違いは、その有効範囲になります。
sesstionStorageは、タブやウィンドウを変えてしまうと、データは保持できなくなります
localStorageは、タブやウィンドウを変えても、データを保持できます(半永久的)同じオリジン内であればオリジン: プロトコルとドメイン名とポート番号の組み合わせ(URL)
主なメソッド
setItem()・・・これは、実際にブラウザでデータを保持させるときのメソッド
getItem()・・・データを呼び出すときに使う
clear()・・・全削除
どちらも機能やメソッドは変わらない、変わるのは範囲だけなのでよりlocalStorageが使うのが妥当なのかな?
cookie
機能はほぼwebStorageと変わらない
webStorageとの違い
・http通信で情報を付与してサーバへリクエストできる。
・cookieは有効期限がある。
・cookieは拒否もできる機能もある
・4KBまでしか保持できない通常はwebStorageを使い、サーバーへのリクエストで情報を付与させたい時などはcookieを使う
注意点
基本的にこれらの保持されたデータは、デベロッパーツールで見ることができるので、大切な情報や見られたくない情報は、保持させておくべきでない
- 投稿日:2020-11-21T18:25:33+09:00
【DroidScript】スマホでスマホアプリを作る④ 実行ボタン
あらすじ
DroidScriptというアプリを使うと、javascriptベースの記述でスマホアプリが作れるらしいので、練習として「電子レンジワット数で加熱時間を換算するアプリ」を作る。
前回までのおさらい
スピンボタンとシークバーを実装し、計算に必要なデータ入力が揃った。あとは計算するだけ。
スマホでスマホアプリを作る① Hello World!
スマホでスマホアプリを作る② 背景やテキストの追加
スマホでスマホアプリを作る③ スピンボタンとシークバーボタンの設置
btn = app.CreateButton( "EXECUTE", 0.4 ); btn.SetMargins( 0, 0.05, 0, 0 ); btn.SetOnTouch( btn_OnTouch ); lay.AddChild( btn );
app.CreateButton
でボタンを作成。括弧内でボタンに表示する文字と、画面幅に対するサイズを設定。.SetMargins
で直前に設置したシークバーとの間隔を設定。.SetOnTouch
でタップしたときに呼び出す関数btn_OnTouch
を設定している。そのコードがこちら。
function btn_OnTouch() { var my_wave = my.GetText(); var label_wave = label.GetText(); var time = skb.GetValue(); time = Math.round( time * 0.2 ) / 0.2; var ratio = ( label_wave / my_wave ); var after = time * ratio; var remain = after % 60; var newmin = ( after - remain ) / 60; var newsec = Math.round( remain ); newsec = ( "0" + newsec ).slice( -2 ); var ans = newmin + ":" + newsec; app.ShowPopup( "Heat for " + ans ); app.Vibrate( "0,100,30,100,50,300" ); }スピンボタンやシークバーで操作した数字をどうやって拾ってくるのか分からなかったけど、操作後の状態から単純に参照できた。
スピンボタンで選んだ選択肢は
.GetText()
で、シークバーで合わせた数字は.GetValue()
で参照。シークバーを操作した時に表示されるのが5秒間隔なので、計算するためにシークバーから参照した数字も5刻みに丸める。
あとはワット数の比で加熱時間を換算し、m:ss表示に加工して
"Heat for " + ans
でポップアップ、仕上げにapp.Vivrate
でスマホを振動させ達成感を味わう。
補足
スピンボタンの選択肢を
.GetText
で参照しているので、受け取った数字が文字列になっていると考えて、数字型に変更するためにNumber()
メソッドを使ったところエラーになってしまった。結局、そんなことしなくてもそのまま計算できてしまうみたい^^)b
次回
アプリ作成はこれにて終了。
次回はシリーズ目次的なまとめと、コード全文、DroidScriptのちょっとした使い方を紹介。
スマホでスマホアプリを作る シリーズまとめつづく
シリーズ
DroidScript電子レンジアプリ作成
スマホでスマホアプリを作る① Hello World!
スマホでスマホアプリを作る② 背景やテキストの追加
スマホでスマホアプリを作る③ スピンボタンとシークバー
スマホでスマホアプリを作る④ 実行ボタン
スマホでスマホアプリを作る シリーズまとめ
- 投稿日:2020-11-21T18:08:22+09:00
Express×Helmetでウェブセキュリティを学ぶ
この記事は エニプラ Advent Calendar 2020 の 11 日目の記事です。
はじめに
Express 公式の『実稼働環境における Express のセキュリティーに関するベスト・プラクティス』というドキュメントでは、「脆弱性対策のために Helmet を使ってね(要約)」と書いているけれど、どんなリクエストヘッダが設定されて、それによってどんな効果があるのか具体的に把握していなかったので、勉強がてらに調べてみました。
私自身セキュリティには勉強中の身であり、ことセキュリティに関しては内容の正確性が求められると思いますので、誤りや勘違いなどがございましたらどうぞご指摘頂ければ幸いです
Helmet とは??
Helmet は Express で作成された Web アプリケーションに対して HTTP ヘッダを設定することで、webアプリケーションをセキュリティ的に堅牢化するライブラリです。
(Express に限らず Koa 用のラッパーもあるようです)ただ、公式で
It's not a silver bullet, but it can help!
とあるように、必ずしもセキュリティ的な問題を一挙に解決してくれる万能薬ではありません。
実際、おそらく CSRF やセッションハイジャック、SQL インジェクションなどへの対策としては有効でないと思われますが、XSS やクリックジャッキングなどの対策にはなると思われます。
Helmet を実際に試してみる
それでは、さっそく Helmet を使ってみましょう。
環境は以下の通りです。
Node.js npm Express Helmet v14.15.0 6.14.8 4.17.1 4.2.0 Express と Helmet をインストール
新しいフォルダを作り、Express と Helmet をインストールします。
> mkdir express-helmet-sample > cd express-helmet-sample > npm init > npm install --save express helmetHelmet を使う前のレスポンスヘッダを見てみる
新しく作ったフォルダ内に、
app.ts
ファイルを作成し、公式を参考にコードを記述します。const express = require('express') const app = express() const port = 3000 app.get('/', (req, res) => { res.send('Hello World!') }) app.listen(port, () => { console.log('listening on port:', port) })サーバーを起動して、ポート
3000
で接続を待ち受けるコードです。
ルートURL(/
)に対して HTTP GET リクエストを投げてみます。
app.js
を実行してサーバを起動した後、> node app.js
--dump-header
付きのcurl
コマンドを実行し、起動したサーバのルート URL から HTTP GET レスポンスヘッダを取得します。> curl --dump-header - http://localhost:3000取得したレスポンスヘッダは以下の通りです。
HTTP/1.1 200 OK X-Powered-By: Express Content-Type: text/html; charset=utf-8 Content-Length: 12 ETag: W/"c-Lve95gjOVATpfV8EL5X4nxwjKHE" Date: Fri, 13 Nov 2020 15:05:20 GMT Connection: keep-alive Keep-Alive: timeout=5特に何の変哲もなさそうですが、
X-Powered-By
ヘッダを見ると、このレスポンスが Express によって作られたアプリから返されていることがが分かります。この場合、攻撃者はサーバが Express によって起動されていると分かるので、攻撃前のひと手間が省かれてしまうという懸念になります。
ただ、単に Express だと分かったところで何ができるかは微妙なところではないかな?と思ってしまいますが……。しかし、MDN でも以下のように注意書きがなされているため、
X-Powered-By
ヘッダは無くしたほうが無難だと思われます。ホスティング環境やその他のフレームワークによって設定される可能性があり、アプリケーションや訪問者に有益ではない情報を含みます。潜在的な脆弱性が発現することを防ぐために、このヘッダーは設定しないでください。
ここでは、単に Express サーバを起動しただけでは、
X-Powered-By
のようなリスクのあるヘッダがそのままレスポンスに含まれてしまうことに注意してみます。Helmet を被ってみる
ここでヘルメットを使用してみます。
以下のようにコードを変更します。const express = require('express') const helmet = require('helmet') const app = express() const port = 3000 app.use(helmet()) // ヘルメットを使用する app.get('/', (req, res) => { res.send('Hello World!') }) app.listen(port, () => { console.log('listening on port', port) })サーバを再起動し、改めて
curl
コマンドによりレスポンスヘッダを取得します。> node app.js > curl --dump-header - http://localhost:3000レスポンスヘッダの中身を見てみると、先ほどとは様変わりしていることが伺えます。
先ほど言及したX-Powered-By
が消えていますね。HTTP/1.1 200 OK Content-Security-Policy: default-src 'self';base-uri 'self';block-all-mixed-content;font-src 'self' https: data:;frame-ancestors 'self';img-src 'self' data:;object-src 'none';script-src 'self';script-src-attr 'none';style-src 'self' https: 'unsafe-inline';upgrade-insecure-requests X-DNS-Prefetch-Control: off Expect-CT: max-age=0 X-Frame-Options: SAMEORIGIN Strict-Transport-Security: max-age=15552000; includeSubDomains X-Download-Options: noopen X-Content-Type-Options: nosniff X-Permitted-Cross-Domain-Policies: none Referrer-Policy: no-referrer X-XSS-Protection: 0 Content-Type: text/html; charset=utf-8 Content-Length: 12 ETag: W/"c-Lve95gjOVATpfV8EL5X4nxwjKHE" Date: Fri, 13 Nov 2020 15:08:04 GMT Connection: keep-alive Keep-Alive: timeout=5 Hello World!以上の新たに Helmet によって付加されたヘッダはセキュリティに関するヘッダであり、これらをレスポンスに設定することで様々な恩恵が得られます。
Helmet で対策される脆弱性
Helmet で追加されたヘッダーとそれにより対策される脆弱性を一覧化すると、次のようになります。
(ただし、必ずしもすべての対策がデフォルトで設定されるわけではありません)
ヘッダー 対策される脆弱性 Helmet API Content-Security-Policy1 XSS攻撃 helmet.contentSecurityPolicy(options) X-DNS-Prefetch-Control2 情報漏洩 helmet.dnsPrefetchControl(options) Expect-CT3 不正なSSL証明書 helmet.expectCt(options) X-Frame-Options4 クリックジャッキング攻撃 helmet.frameguard(options) Strict-Transport-Security5 中間者攻撃 helmet.hsts(options) X-Download-Options フィッシング helmet.ieNoOpen() X-Content-Type-Options6 MIME スニッフィング helmet.noSniff() X-Permitted-Cross-Domain-Policies7 XSS攻撃 helmet.permittedCrossDomainPolicies(options) Referrer-Policy8 情報漏洩 helmet.referrerPolicy(options) X-XSS-Protection9 XSS攻撃 helmet.xssFilter() 一つ一つ簡単に見ていきたいと思います。
Content-Security-Policy (CSP)
Content-Security-Policy: default-src 'self';base-uri 'self';block-all-mixed-content;font-src 'self' https: data:;frame-ancestors 'self';img-src 'self' data:;object-src 'none';script-src 'self';script-src-attr 'none';style-src 'self'
https: 'unsafe-inline';upgrade-insecure-requestsこれは主に XSS 攻撃を緩和する役目を果たします。
実行を許可するコンテンツのホワイトリストを作成し、その参照元のリソースだけを実行及びレンダリングするようにブラウザに指示します。
これにより、信頼できないコンテンツを事前に弾くことができ、不正なコードが注入されることを防ぎます。
Content-Security-Policy
ではホワイトリストの一行一行をポリシーとして定義し、以下のようにセミコロン区切りで一つ一つ設定することになっています。Content-Security-Policy: *policy*; *policy*; ...;ポリシーについて一つ一つ見ていくと脱線するので、ここでは省略します。
X-DNS-Prefetch-Control
X-DNS-Prefetch-Control: off
ブラウザが事前にドメイン名の解決を実行する機能を制御します。ここでは DNS 先読みを無効にしています。MDN2には、
これはページのリンクを制御しない場合や、ドメインに情報漏洩させたくないと分かる場合に有用です。
とあり、Helmet の README でも
helmet.dnsPrefetchControl sets the X-DNS-Prefetch-Control header to help control DNS prefetching, which can improve user privacy at the expense of performance. See documentation on MDN for more.
とあるので、パフォーマンスを犠牲にしてでもユーザーのプライバシーを守るために
off
としているようですが、正直なぜ??という感じでいまいち理解できなかったのでもう少し調べてみました。
すると、同じような疑問を抱いていた方がほかにもいました。私の理解の及ぶ限りで要約すると、DNS 先読みが有効であると、そのページで参照される全ての外部リソースに対してホスト解決のための DNS リクエストが発行されてしまい、それらが第三者によって盗聴される可能性があることを含めると、むやみにホスト解決を行うことは情報漏洩を招くことになる……ということだと思います。
Expect-CT
Expect-CT: max-age=0
不正な SSL 証明書を検知するための仕組みです。
以下の記事がお詳しいです。デフォルトでは
max-age
が0
になっており、特に何も設定していません。
ユーザが意図的に有効にする必要があるようです。X-Frame-Options
X-Frame-Options: SAMEORIGIN
ブラウザがページを
<frame>
,<iframe>
,<embed>
,<object>
の中に表示することを許可するか示すために使用されます。
CSP のframe-ancestors
に似ており、実際 CSP で代替できますが、古いブラウザ向けにまだ有効のようです。
ここではSAMEORIGIN
を指定することで、ページ自体と同じオリジンのフレーム内でのみ表示できるようになります。Strict-Transport-Security
Strict-Transport-Security: max-age=15552000; includeSubDomains
ウェブサイトがブラウザに対して HTTP の代わりに HTTPS を用いて通信を行うように指示します。
MDN5 の以下の解説がとても分かりやすいです。もし、訪問者が http://www.foo.com/ または単に foo.com と入力したとき、ウェブサイトが接続を HTTP で受け付け、 HTTPS にリダイレクトするようになっていると、訪問者はリダイレクトされる前にまず、暗号化されないバージョンのサイトと通信する可能性があります。これは中間者攻撃の機会を作ってしまいます。リダイレクトは訪問者を、本来のサイトの安全なバージョンではなく、悪意のあるサイトに導くために利用される可能性があるからです。
HTTP の Strict Transport Security ヘッダーは、ブラウザーに対してサイトを HTTP を使用して読み込まず、サイトへのすべてのアクセスを、自動的に HTTP から HTTPS リクエストに変換するよう指示することができます。
ヘッダと直接関係はありませんが、リダイレクト自体、オープンリダイレクト脆弱性などを引き起こす可能性があるので注意したほうがよさそうです。
X-Download-Options
X-Download-Options: noopen
ブラウザ(IE8)がアプリケーションからの安全でない可能性のあるダウンロードを必ず保存させるようにします。
これにより、サイトのコンテキストで HTML の実行を抑止します。X-Content-Type-Options
X-Content-Type-Options: nosniff
Content-Type
ヘッダーで示された MIME タイプを変更せずに従うようにします。
これは MIME スニッフィング対策に有効です。X-Permitted-Cross-Domain-Policies
X-Permitted-Cross-Domain-Policies: none
Adobe 製品に特有のクロスドメインポリシーファイル(
crossdomain.xml
)を許可するかどうかを指定します。
crossdomain.xml
は同一ドメインポリシーによって制限されているドメイン間のデータを処理する許可を与えるポリシーを定義することができ、none
に設定することですべてのポリシーファイルを不許可にしています。Referrer-Policy
Referrer-Policy: no-referrer
リファラ情報をリクエストに含めないように設定しています。
これは情報漏洩を未然に防ぐのに有効でもあり、例えば URL にセッション ID などを埋め込んでいて悪意のある外部サイトへのリンクを踏んでしまうと、外部サイトへセッション ID が流出してしまい、セッションハイジャックを引き起こしてしまう可能性があります(徳丸本参照)。X-XSS-Protection
X-XSS-Protection: 0
X-XSS-Protection による XSS フィルタリングを有効にすると、XSS 攻撃を検出したときにページの読み込みを停止します。
ここでは0
にして XSS フィルタリングを無効化していますが、無効化にしている理由は以下の議論が参考になります。ちゃんと理解できているか自信ないですが、
X-XSS-Protection
による XSS フィルタを利用することで、むしろ悪用されて XS-Leak 攻撃を引き起こす危険性があるため、無効にした方がいいらしい?まとめ
今回調べてみて、DNS 先読みや Certificate Transparency、
X-XSS-Protection
の悪用については全然知らなかったので、勉強になりました。ただ、今回は表層を撫でてみただけで、「完全に理解した」には至っていないので、今回調べたことをもとに各種ヘッダーや Web の脆弱性そのものについてそれぞれ更に深く調べていきたいと思います。
余談
余談になりますが、先日ウェブ・セキュリティ試験(徳丸基礎試験)を受験して、無事に合格することができました。
この試験では今回見たようなウェブ・セキュリティの基礎的な知見を問うもので、とてもよい試験でした。試験については以下の記事がとても参考になります。恥ずかしながら、今まで漠然とした不安の中で Web アプリケーションを開発していたのですが、この試験のために『体系的に学ぶ 安全なWebアプリケーションの作り方』(通称徳丸本)を何度か読み直して勉強したことで、少なからぬ根拠を持って実装に当たれるようになりました。
昨今ウェブ・セキュリティの需要が高まっている(と勝手に思っています)中、皆さんも年末年始楽しんで勉強してみてはいかがでしょうか。
出典
- Helmet
- 実稼働環境における Express のセキュリティーに関するベスト・プラクティス
- HTTP ヘッダー - HTTP | MDN
- コンテンツ セキュリティ ポリシー | Web | Google Developers
- web browser - What security implications does DNS prefetching have? - Information Security Stack Exchange
- Certificate Transparency - Web security | MDN
- CT対応を示すExpect-CTヘッダとは - ASnoKaze blog
- 投稿日:2020-11-21T17:42:41+09:00
【DroidScript】スマホでスマホアプリを作る③ スピンボタンとシークバー
あらすじ
DroidScriptというアプリを使うと、javascriptベースの記述でスマホアプリが作れるらしいので、練習として「電子レンジワット数で加熱時間を換算するアプリ」を作る。
前回までのおさらい
アプリ画面の作成に着手。
前回は画像の上から2行目まで作成。タイトルテキストとドロイド君を設置、ドロイド君をタップするとスマホが振動してアプリの説明がポップアップするようにした。スマホでスマホアプリを作る① Hello World!
スマホでスマホアプリを作る② 背景やテキストの追加スピンボタン(上下ボタン)の設置
txt = app.CreateText( "Your Microwave" ); txt.SetMargins( 0,0.02,0,0 ); txt.SetTextSize( 20 ); lay.AddChild( txt ); my = app.CreateSpinner( "730,700,680", 0.3 ); my.SetOnTouch( spin_my ); lay.AddChild( my ); txt = app.CreateText( "Label Microwave" ); txt.SetMargins( 0,0.02,0,0 ); txt.SetTextSize( 20 ); lay.AddChild( txt ); label = app.CreateSpinner( "500,1500", 0.3 ); label.SetOnTouch( spin_label ); lay.AddChild( label );スピンボタンは2つ。自分のレンジのワット数と、温める対象のラベルに記載されたワット数。それぞれスピンボタンの前にテキストとマージンを挿入している。
app.CreateSpinner("選択肢,選択肢,・・・",幅);
で設定。幅は画面幅を1.0として記述する。自分のレンジは、自宅で使っている730Wと、職場の700W、たまに使う680Wを選択できるように設定。ラベルのワット数は500Wと1500Wを用意した。
それぞれ、スピンボタンでワット数を選択した時の関数を作成し.SetOnTouch()
で呼び出している。呼び出される関数がこちら。function spin_my( item ) { app.ShowPopup( "Set " + item + "W" ); app.Vibrate( "0,30" ); } function spin_label( item ) { app.ShowPopup( "Set " + item + "W" ); app.Vibrate( "0,30" ); }選択したワット数が引数になる。例えば、
700
を選ぶとSet700W
とポップアップし、app.Vibrate
で少し振動する。シークバーの設置
txt = app.CreateText( "Label heating time" ); txt.SetMargins( 0,0.02,0,0 ); txt.SetTextSize( 20 ); lay.AddChild( txt ); skb = app.CreateSeekBar( 0.8 ); skb.SetOnTouch( skb_OnTouch ); skb.SetRange( 600 ); skb.SetValue( 180 ); lay.AddChild( skb );
app.CreateSeekBar
でシークバーを作成。括弧内で画面に対する幅を設定。
.SetRange
でシークバーの最大値、.SetValue
で初期値を設定している。ここで設定するのは電子レンジの加熱時間なので、最大値を600秒、初期値を180秒にしてみた。シークバーを操作した時に呼び出される関数がこちら。
function skb_OnTouch( value ) { value = Math.round( value / 5 ) * 5; var min = Math.floor( value / 60 ); var sec = Math.floor( value % 60 ); sec = ( "0" + sec ).slice( -2 ); var settime = min + ":" + sec; app.ShowPopup( "Set Time = " + settime ); app.Vibrate( "0,10" ); }スピンボタンの時と同じように選択している秒数をポップアップさせるのに、受け取った数字をそのまま表示させるのではなく少し加工してみた。
1秒単位で細かく刻む必要はないので5秒単位に数字を丸め、m:ss表示ができるよう分と秒に分け、10秒未満でも2桁で表示するように加工した。
次回
計算実行ボタンを作成する。
スマホでスマホアプリを作る④ 実行ボタンつづく
シリーズ
DroidScript電子レンジアプリ作成
スマホでスマホアプリを作る① Hello World!
スマホでスマホアプリを作る② 背景やテキストの追加
スマホでスマホアプリを作る③ スピンボタンとシークバー
スマホでスマホアプリを作る④ 実行ボタン
スマホでスマホアプリを作る シリーズまとめ
- 投稿日:2020-11-21T17:39:18+09:00
Optional Chaining と Nullish Coalescing
Optional Chaining と Nullish Coalescing
ES2020, TypeScript 3.7 から導入された記法。
node では 13系からオプション指定をすることで (--harmony)、14系からは標準で使用できるようになっている。記法
項目 記法 Optional Chaining ?.
Nullish Coalescing ?? Optional Chaining
挙動: プロパティを取るチェーンの途中でエラーを出さず、最終的に取れればそれを、取れなければ
undefined
を返す例えば以下のコードにおいて
optionalChaining.jsconst users = [ { id: 1, name: 'sample user', career: { university: 'Hoge大', }, }, { id: 2, name: 'invalid user', }, ] users.forEach(user => console.log(user.name, user.career.university));
users[1]
のuser.career.university
にアクセスしようとするとTypeError: Cannot read property 'university' of undefinedというエラーが起こる。
user.career
までならundefined
が帰ってくるが、user.career.university
までアクセスしようとすると怒られる。
これを何重に行っても怒られず、そのチェーン先プロパティが存在しない場合に、常にundefined
を返してくれるようにするのが Optional Chaining である。
最後の行の.
を?.
とするだけで、その挙動になる。optionalChaining.jsusers.forEach(user => console.log(user.name, user?.career?.university)); // 実行結果 // sample user Hoge大 // invalid user undefinedNullish Coalescing
挙動:
null
かundefined
の場合のみ右側を評価し、そうでない場合は左側の被演算子を返す例えば以下のようなコードがあったとする。
nullishCoalescing.jsconst user = { id: 1, name: 'sample user', career: { university: 'Hoge大', high_school: null, }, } console.log(user.career.high_school || '高校登録なし')ただし、
||
はfalsy
な値すべてをfalse
扱いしてしまう。false "" 0 undefined nullそうではなく、純粋に左側の被演算子が
null
(存在しないという意味) かundefined
(定義されていない) の場合に、右側を評価したい場合 Nullish Coalescing が有効である。
そしてそれは先ほどのコードの||
を??
に書き換えるだけで完了する。nullishCoalescing.jsconst user = { id: 1, name: 'sample user', career: { university: 'Hoge大', high_school: null, }, } console.log(user.career.high_school ?? '高校登録なし') // => 高校登録なし結論
今後ネストしたプロパティにアクセスするときは Optional Chaining
null または undefined という判定をしたいなら Nullish Coalescing
を使うと、短く簡潔にかける。
- 投稿日:2020-11-21T16:28:11+09:00
【DroidScript】スマホでスマホアプリを作る② 背景やテキストの追加
あらすじ
DroidScriptというアプリを使うと、javascriptベースの記述でスマホアプリが作れるらしいので、練習として「電子レンジワット数で加熱時間を換算するアプリ」を作る。
前回のおさらい
ボタンをタップすると「Hollo World!」と表示されるサンプルアプリのコードを見た。
スマホでスマホアプリを作る① Hello World!どうやら
lay
にパーツを放り込むことでアプリの画面をレイアウトできそう。function OnStart() { lay = app.CreateLayout( "linear", "VCenter,FillXY" ); app.AddLayout( lay ); }と言うわけで、自作アプリに向けてレイアウトを作成していく。
まずは完成画面
絶望的な英語力や配置センスは気にしないでいただきたい(ToT)
上から
・タイトル
・ドロイド君
・自分のレンジのワット数
・温める対象のラベルに記載されたワット数
・温める対象のラベルに記載された加熱時間
・計算ボタン
となっている。背景とタイトル
lay = app.CreateLayout( "linear", "VCenter,FillXY" ); lay.SetBackground( "/Sys/Img/BlueBack.jpg" ); txt = app.CreateText( "HOW MANY MIN" ); txt.SetTextSize( 22 ); lay.AddChild( txt );1行目で
lay
の基本設定。括弧内の記述はサンプルコードのコピペなのでよくわからん^^;
lay.SetBackgroud
で背景を設定。最初から用意されているBlueBack.jpg
を使用。タイトルの「HOW MANY MIN」というテキストを作りAddChildした。
ドロイド君
img = app.CreateImage( "/Sys/Img/Icon.png", 0.1 ); img.SetMargins( 0, 0.02, 0, 0 ); img.SetOnTouchDown( img_OnTouchDown ); lay.AddChild( img );こちらも最初から用意されているドロイド君のアイコン
Icon.png
をAddChild。.SetMargins
で、その前にAddChildしたパーツとの間に間隔を設ける。.SetOnTouchDown
で、パーツをタップした時に呼び出す関数img_OnTouchDown
を設定した。ドロイド君をタップしたら呼び出される関数がこちら
function img_OnTouchDown( ev ) { app.ShowPopup( "I will calculate the heating time!" ); app.Vibrate( "0,100,30,100,50,300" ); }何のアプリか簡単な説明をポップアップさせ、
app.Vibrate
でスマホを振動させる。
app.Vibrate
の括弧内は、("休止,振動,休止,振動,・・・")
で、数字はミリ秒となっているみたい。次回
ワット数や加熱時間を入力する部分を作成する。
スマホでスマホアプリを作る③ スピンボタンとシークバーつづく
シリーズ
DroidScript電子レンジアプリ作成
スマホでスマホアプリを作る① Hello World!
スマホでスマホアプリを作る② 背景やテキストの追加
スマホでスマホアプリを作る③ スピンボタンとシークバー
スマホでスマホアプリを作る④ 実行ボタン
スマホでスマホアプリを作る シリーズまとめ
- 投稿日:2020-11-21T16:26:39+09:00
【Electron入門】GithubのIssueを一瞬で確認できるアプリを作って業務効率化してみた #2 GithubAPI編
はじめに
前回はwindowの作成をしました。
今回はGithubAPIを導入して完成させようと思います。
長くなってしまうのでJavaScriptでDOMの作成をする部分やCSSの説明は省きます。
気になる方はコードをご確認ください!完成品
option(alt) + Spaceで表示/非表示が切り替えられます。
現状
Access Tokenを生成する
それではやっていきましょう。
Githubにログインし、
Settings > Developer settings > Personal access tokens
と進みます。
Generate new token
を押すとtoken作成画面に進めます。・
Note
にはtokenを識別するために用途を書きます。自分はgit-electron
としました。・
Select scopes
でtokenの権限を選択できます。今回はrepo
の権限全てにチェックをつけます。
Generate token
を押すとAccess Tokenが生成されるので、
後ほど使うので、赤く囲まれている部分のAccess Tokenをコピーしておきます。AccessTokenを使ってAPIにアクセスする
実際にGithubAPIを叩いてみます。
通信には
axios
を使用しました。
Tokenの管理にはdotenv
を使用しました。1 . アプリのルートパスで
.env
ファイルを作成して以下のように編集します。.envGH_ACCESS_TOKEN=[先ほどコピーしたAccess Token] GH_BASE_URL=https://api.github.com2 . main.jsに以下を追記して
.env
ファイルを読み込みます。main.jsrequire("dotenv").config({ path: __dirname + "/.env" });※ AccessTokenは外部に見られたくないので、Githubで公開する際には
.gitignore
を作成してremoteに送らないようにしましょう。.gitignore.env
3 . axiosを拡張するaxiosの設定を共通化するために拡張します。
自分はplugins
ディレクトリを作成して、配下にaxios.js
を作成しました。plugins/axios.jsconst axiosBase = require("axios"); const axios = axiosBase.create({ baseURL: process.env.GH_BASE_URL, headers: { Authorization: "Bearer " + process.env.GH_ACCESS_TOKEN, "Content-Type": "application/json", }, }); module.exports = axios;これ以降、axiosを呼び出すときはこのaxios.jsを呼び出します。
これでGithub APIを呼び出す準備は整いました!
フロント(Window)からAPIを呼び出す
Electronには
メインプロセス
とレンダラープロセス
があります。メインプロセス内(ここで言うmain.js)でBroeserWindowインスタンスを作成し、そのBroeserWindowインスタンスがレンダラープロセス内でWebページ(html)を表示します。
公式ドキュメントよってhtmlから呼び出したJavaScriptなどはレンダラープロセスで実行する事になります。
ここで困ることが一点あります。
main.js
はメインプロセス上で動くので、Node.jsの文法が使えますが、レンダラープロセスでは使えません。
require
などNode.js上で使えるメソッドをフロントからの要求に応じて使えるようにするには、
contextBridgeを使って、メイン⇔プロセス間の通信をセキュアにする必要があります。
詳しく知りたい方は以下の記事がうまくまとまっていると思うので参考にしてみてください!ElectronでcontextBridgeによる安全なIPC通信 - Qiita
ElectronでcontextBridgeによる安全なIPC通信(受信編) -QiitacontextBridgeを実装する
contextBridgeを使用する為の
preload.js
をルートディレクトリに作成します。preload.jsconst { contextBridge } = require("electron"); contextBridge.exposeInMainWorld("api", { github: { getIssues: () => { // メインプロセスで行いたい処理を書く const axios = require("./plugins/axios"); axios .get("/issues") .then((res) => { console.log(res); }) .catch((e) => console.log(e)); }, }, });次にmain.jsを修正して、BrowserWindowでpreload.jsを読み込むよう設定します。
main.jsconst win = new BrowserWindow({ webPreferences: { nodeIntegration: false, contextIsolation: true, preload: __dirname + "/preload.js", }, // .....省略...... })最後にindex.htmlにscriptタグと発火用のボタンを追加して、preload.jsで定義した関数を呼び出せるようにします。
index.html<body style="-webkit-app-region: drag"> <div style="background: #fff; height: 300px"> <h1>Hello World!</h1> <button onclick="fetchAPI()">get issues</button> </div> </body> <script> function fetchAPI() { window.api.github.getIssues(); } </script>ボタンを押して、Electron上でDeveloper toolを開くとIssueのデータが取れていると思います!
デスクトップアプリとして使いたい場合は、
npm run build
でelectron-builderが起動し、dist
配下にアプリが生成されます。DOMの生成やCSSはリポジトリを参考にしてみてください!
最後に
今回はElectroとGithubAPIを組み合わせて業務効率化アプリを作ってみました。
かなり手軽にデスクトップアプリを作成することができた一方、いくつか制限もあったので大規模開発での導入には検討が必要かもしれません。(と言ってもVSCodeなどもelectronで開発されているのである程度安心していいと思っています)
こんな感じで週1~2でアウトプットを記事にしていこうと思います。是非LGTM、フォローの方お願い致します!
Twitterのフォローもお願い致します!
https://twitter.com/1keiuu
- 投稿日:2020-11-21T16:25:31+09:00
【Electron入門】GithubのIssueを一瞬で確認できるアプリを作って業務効率化してみた #1 Alfredっぽいwindow作成編
完成品
option(alt) + Spaceで表示/非表示が切り替えられます。
ソースコード
https://github.com/ikkei12/git-app.pub
はじめに
Alfred最高ですよね。
彼のおかげでマウスを使う頻度が激減して助かっています。Alfredと同じ手軽さで、自分がアサインされているissueを確認できるデスクトップアプリが作りたいなと思ったので、Electronで作ってみようと思いました。
Electronには
- JavaScriptで作れる
- Chromiumベースなのでwebエンジニアにも優しい
といった利点があり、HTML/CSS/JavaScriptを学んだ初心者の方でも扱いやすいかと思います!
Electronのはじめかた
基本的に公式ドキュメントを参考にElectronのセットアップをするだけですが、一部変更を加えています。
1. 以下のコマンドを実行
mkdir git-app && cd git-app npm init -y npm i --save-dev electron npm i --save-dev electron-builder2. html/jsファイルを作成
標準的なNode.jsアプリと同様に以下のような構成で始めます。
git-app/ ├── package.json ├── main.js └── index.htmlmain.jsconst { app, BrowserWindow } = require('electron') function createWindow () { const win = new BrowserWindow({ webPreferences: { nodeIntegration: false, contextIsolation: true, }, frame: false, alwaysOnTop: true, useContentSize: true, transparent: true, }) win.loadFile('index.html') win.on("blur", (e) => { // window外をクリックしたタイミング(blur)で閉じる app.hide(); }); } app.whenReady().then(createWindow) app.on('window-all-closed', () => { if (process.platform !== 'darwin') { app.quit() } }) app.on('activate', () => { if (BrowserWindow.getAllWindows().length === 0) { createWindow() } })index.html<!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <title>Hello World!</title> <meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-inline';" /> </head> <body style="-webkit-app-region: drag"> <div style="background: #fff; height: 300px"> <h1>Hello World!</h1> </div> </body> </html>3. package.jsonを修正
package.jsonは以下のように書き換えます。
package.json{ "name": "git-app", "version": "1.0.0", "description": "", "main": "main.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "start": "NODE_ENV=development electron .", "build": "NODE_ENV=production electron-builder" }, "build": { "appId": "git-app", "mac": { "category": "git-app" } }, "keywords": [], "author": "", "license": "ISC", "devDependencies": { "electron": "^10.1.5" } }4. 起動
npm start
でこのようなwindowが表示されると思います。どこからでもショートカットでアプリを呼び出せるようにする
このままではwindowの表示/非表示を切り替えられないし、PCの起動時に毎回アプリを立ち上げる必要があります。
Alfredのような挙動を実現するには
1. electronアプリを常駐で起動させておいて、
2. 表示/非表示を切り替えるショートカットを登録する
必要があります。1. electronアプリを常駐で起動させる
main.jsapp.dock.hide(); if (process.env.NODE_ENV !== "development") { app.setLoginItemSettings({ openAtLogin: true, path: app.getPath("exe"), }); }解説
・
app.dock.hide()
常駐のアプリをDockに表示したくないので、Dockから非表示にします。・
app.setLoginItemSettings()
でログイン時に自動で起動してくれるようになります。
ローカルでは起動したくないので、NODE_ENV
で判定しています。・macではログイン時に起動されるアプリは
ユーザーとグループ > ログイン項目
に追加されます。
2. 表示/非表示を切り替えるショートカットを登録する
main.jsapp.on("ready", function () { globalShortcut.register("alt+space", function () { // 現在focusしているwindowを取得 const window = BrowserWindow.getFocusedWindow(); // windowが存在すればhide、なければshow window ? hideWindow(window) : showWindow(); }); }); app.on("will-quit", function () { // 終了するタイミングで全てのglobalShortcutを解除 globalShortcut.unregisterAll(); }); function showWindow() { // focusさせる事でBrowserWindowのblurイベントを検知させる app.focus({ steal: true }); app.show(); } function hideWindow(window) { // center()する事でshowする時に中央で表示される window.center(); app.hide(); }解説
・アプリが
ready
状態になった段階で、ショートカットキーを登録しています。自分はalt+space
で起動したいのでregister
の第一引数に渡しています。・第二引数ではショートカットキーが押されたタイミングで実行する関数を登録しています。現在focusされているwindowを取得し、存在する場合はhideメソッド、しなければshowメソッドを実行する事で表示を切り替えています。
・
globalShortcut
と言うのがポイントで、普通のショートカット(localShortcut)ではElectronアプリがフォアグラウンドになっている場合にしか反応しません。
このように動くと思います!
Tips
Windowをドラッグできるようにする
<body style="-webkit-app-region: drag">参考: https://www.electronjs.org/docs/api/frameless-window#draggable-region
まとめ
ご覧いただきありがとうございました!
次回はGithub APIからIssueを取得して表示する部分を作成していきます!Twitterのフォローもお願い致します!
https://twitter.com/1keiuu
- 投稿日:2020-11-21T13:46:09+09:00
【DroidScript】スマホでスマホアプリを作る①
概要
DroidScriptと言うスマホアプリがある。
javascriptを記述して、オリジナルアプリが作れてしまうらしい。しかもスマホで(そりゃあスマホアプリなんだからそうなんだろうけど何か衝撃)。
GASを覚えたことで、javascriptなら行けんじゃね?と、ちょっと触ってみることにした。
DroidScript
Playストアから入手可能。
サンプルコードが大量にあるためヒントは多いけど、コードの記述の方法とか、専用のメソッドを解説しているサイトが少なくて手探りになりそう・・・。
サンプルコードを見ると、横スクロールのゲームやらメディアプレイヤーやらある他、カメラ、メール、USBなどなど、出来ることがかなり多い。
アプリの画面をPCに表示させる方法もあって、スマホでコチコチやらなくてもコード入力できるみたい。
基本操作は全て無料!プラグインが豊富で、APKファイルへのビルドやブルートゥース対応など、一部のプラグインが有料になっている。
目的
以前に、WEBアプリ導入の練習で「電子レンジワット数で加熱時間を換算するアプリ」を作ったので、今回も同じお題で練習してみる。
参照:【GAS】電子レンジワット数で加熱時間を換算するwebアプリを作ってみた
Hello World!
何事もハローワールド!から。
アプリをインストールしたところ、最初にサンプルアプリが用意されていたので起動してみた。
画面にはドロイド君と「Press Me」と書かれたボタンが1つ(左画像)。ボタンをタップすると「Hello World!」と画面に表示された(右画像)。コードを表示させたところこんな感じ。
//Called when application is started. function OnStart() { //Create a layout with objects vertically centered. lay = app.CreateLayout( "linear", "VCenter,FillXY" ); //Create image 1/5 of screen width and correct aspect ratio. img = app.CreateImage( "Img/Hello World.png", 0.2, -1 ); lay.AddChild( img ); //Create a button 1/3 of screen width and 1/10 screen height. btn = app.CreateButton( "Press Me", 0.3, 0.1 ); btn.SetMargins( 0, 0.05, 0, 0 ); lay.AddChild( btn ); //Set function to call when button pressed. btn.SetOnTouch( btn_OnTouch ); //Add layout to app. app.AddLayout( lay ); } //Called when user touches our button. function btn_OnTouch() { //Show a popup message. app.ShowPopup( "Hello World!" ); }英語は読めないけど、どうやら
OnStart()
がアプリを開いたときに呼び出される関数みたい。
app.CreateLayout
で宣言したlay
に.AddChild
で表示させたいアイテムを放り込み、app.AddLayout
でアプリに渡してるんだなぁ・・・とか、
btn.SetOnTouch( btn_OnTouch )
でボタンがタップされた時の関数を呼び出してるみたいだなぁ・・・とか、
function btn_OnTouch()
では、app.ShowPopup
でメッセージがポップアップするんだろうなぁ・・・など、何とか分かりそう。
次回
自作アプリの初期画面を作ってみる。
つづく
- 投稿日:2020-11-21T13:46:09+09:00
【DroidScript】スマホでスマホアプリを作る① Hello World!
概要
DroidScriptと言うスマホアプリがある。
javascriptを記述して、オリジナルアプリを開発できるらしい。しかもスマホで!そりゃあスマホアプリなんだからそうなんだろうけど何か衝撃。
GASを覚えたことで、javascriptなら行けんじゃね?と、ちょっと触ってみることにした。
DroidScript
Playストアから入手可能。
サンプルコードが大量にあるためヒントは多いけど、ググっても日本語のサイトはあまりヒットしないし、Qiitaの記事もゼロ・・・操作は手探りになりそう(´・ω・`)
サンプルコードを見ると、横スクロールのゲームやらメディアプレイヤーやらある他、カメラ、メール、USBなどなど、出来ることはかなり多い。
アプリの画面をPCに表示させる方法もあって、スマホでチマチマやらなくてもキーボードでコード入力できるみたい(ちな、今回は全てスマホから操作)。
基本操作は全て無料!プラグインが豊富で、APKファイルへのビルドやブルートゥース対応など、一部のプラグインが有料になっている。
目的
以前に、WEBアプリ導入の練習で「電子レンジワット数で加熱時間を換算するアプリ」を作ったので、今回も同じお題で練習してみる。
参照:【GAS】電子レンジワット数で加熱時間を換算するwebアプリを作ってみた
Hello World!
何事もハローワールド!から。
アプリをインストールしたところ、最初にサンプルアプリが用意されていたので起動してみた。
画面にはドロイド君と「Press Me」と書かれたボタンが1つ(左画像)。ボタンをタップすると「Hello World!」と画面に表示された(右画像)。コードを表示させたところこんな感じ。
//Called when application is started. function OnStart() { //Create a layout with objects vertically centered. lay = app.CreateLayout( "linear", "VCenter,FillXY" ); //Create image 1/5 of screen width and correct aspect ratio. img = app.CreateImage( "Img/Hello World.png", 0.2, -1 ); lay.AddChild( img ); //Create a button 1/3 of screen width and 1/10 screen height. btn = app.CreateButton( "Press Me", 0.3, 0.1 ); btn.SetMargins( 0, 0.05, 0, 0 ); lay.AddChild( btn ); //Set function to call when button pressed. btn.SetOnTouch( btn_OnTouch ); //Add layout to app. app.AddLayout( lay ); } //Called when user touches our button. function btn_OnTouch() { //Show a popup message. app.ShowPopup( "Hello World!" ); }英語は読めないけど、どうやら
OnStart()
がアプリを開いたときに呼び出される関数みたい。
app.CreateLayout
で宣言したlay
に.AddChild
で表示させたいアイテムを放り込み、app.AddLayout
でアプリに渡してるんだなぁ・・・とか、
btn.SetOnTouch( btn_OnTouch )
でボタンがタップされた時の関数を呼び出してるみたいだなぁ・・・とか、
function btn_OnTouch()
では、app.ShowPopup
でメッセージがポップアップするんだろうなぁ・・・など、何とか分かりそう。
次回
次回から自作アプリの作成。
スマホでスマホアプリを作る② 背景やテキストの追加つづく
シリーズ
DroidScript電子レンジアプリ作成
スマホでスマホアプリを作る① Hello World!
スマホでスマホアプリを作る② 背景やテキストの追加
スマホでスマホアプリを作る③ スピンボタンとシークバー
スマホでスマホアプリを作る④ 実行ボタン
スマホでスマホアプリを作る シリーズまとめ
- 投稿日:2020-11-21T13:16:41+09:00
1ファイルのHTML(+JavaScript)で、QRcodeを生成する
1ファイルのHTML(+JavaScript)で、QRcodeを生成する
・ライブラリ等を利用せず、javascriptを持つ1ファイルのHTML文書で、QRコードを生成できる
・javascript内で、スクラッチ(ゼロ)からQRcodeを書き出している
・型番は1~25まで対応
・最大718byteまでデータ容量を持てる。日本語(UTF-8)で、約230文字ほど
・訂正レベル「Q」、マスクパターンは「011」で固定外観
動作サンプル
http://thomas.cranky.jp/js_QR/QRcode_javascript.html
github
https://github.com/santarou6/QRcode_javascript/blob/main/QRcode_javascript.html
参考
QRコードの8種類のマスクを図示する
https://qiita.com/santarou6/items/f254f9fa5ea32d83ae27エクセルの、ワークシート関数のみでQRコードを作成する
https://qiita.com/santarou6/items/dda2f88f42c55cd2118fEXCELのVBAだけでQRコード。一部修正して日本語(全角)でも作成可能に。サンプルのエクセルファイルあり。
https://qiita.com/santarou6/items/d623417ea8ba33756108
- 投稿日:2020-11-21T09:55:17+09:00
【Vue.js】Vue.jsとcssアニメーションで作るスライドショー
Vue.js
とCSS
を用いてスライドショーを実装するメモです。目的
実装
テンプレート
Imageslide.vue<template> <div> <transition-group tag="ul" class="images"> <li v-for="(image, index) in demoimages" v-show="currentImage == index + 1" :key="index" > <img :src="image.image" alt="" /> </li> </transition-group> </div> </template>画像を
v-for
でリストしてv-show
で表示を切り替えています。
v-show
はv-if
とは違い、DOMを消したり追加したりはせず、display:none
を付与して要素を非表示にします。そのため非表示の画像をもう一度表示する場合も、再度画像が読み込まれるわけではありません。スクリプト
Imageslide.vue<script> export default { data() { return { currentImage: 1, } }, computed: { demoimages() { return [ { image: '/images/1.jpg' }, { image: '/images/2.jpg' }, { image: '/images/3.jpg' }, ] }, }, watch: { currentImage() { this.autoSlide() }, }, mounted() { this.autoSlide() }, methods: { async autoSlide() { const wait = (ms) => new Promise((resolve) => setTimeout(() => resolve(), ms)) await wait(2000) if (this.currentImage > this.images.length - 1) { this.currentImage = 1 } else { this.currentImage++ } }, }, } </script>表示されている画像のインデックスを示す
currentImage
をウォッチしてautoSlide()
を実行しています。currentImageが画像のリストを超えた場合に初期化することで無限ループになります。スタイル
Imageslide.vue<style lang="scss" scoped> .images { width: 100%; height: 100%; padding: 0; margin: 0; position: relative; li { width: 100%; height: 100%; max-height: 627px; position: absolute; top: 0; left: 0; } } img { width: 100%; height: 100%; max-height: 627px; margin: auto; z-index: 2; } .v-enter-active, .v-leave-active { transition: opacity 0.5s; } .v-enter, .v-leave-to { opacity: 0; } </style>リストの親要素に
position:relative
を指定し、子要素にposition:absolute top:0 left:0
を指定することで同じ位置に画像を重ねることができます。
.v-enter-active,.v-leave-active,.v-enter,.v-leave-to
はtransition
によってVueから自動的に付与されるクラスです。まとめ
Vue.jsとCSSを使用したスライドショーの実装方法について簡単に説明してみました。私はこのように実装しましたが、おそらく別の方法もあるかと思います。いろいろ試してみてください!
- 投稿日:2020-11-21T09:52:24+09:00
v-bind:keyは並び替えるためのものじゃない
結論
v-bind:keyはそれぞれの要素に一意なキーを与えるだけ。並べ替えはしません。
並べ替えるなら関数使って新しく作りましょう。例
お恥ずかしいことに、下記のようにキーに数字を与えると自動で並べ替えられると思っていました。
<span v-for="num in [3, 2, 1]" :key="num"> {{ num }} </span> <!-- 期待したのは出力はこっち--> <!-- 1 2 3 --> <!-- 実際の出力は以下になる --> <!-- 3 2 1 -->具体的な並び替え方は以下が参考になりました。
Vue.jsのv-forでの並び順を指定したキーの値順にしたり、昇順、降順を切り替えたりするまとめ
keyは一意に定めるものでそれ単体で並び替えに使えるものではありません。並び替えるならcomputedプロパティなどを使って並び替えたデータを用意しましょう。
私のように変な勘違いを起こした方が路頭に迷わないためにここに残しておきます。
- 投稿日:2020-11-21T08:52:18+09:00
途中から「fixed」で、上スクロールでふわっと降りてきて、下スクロールで消えるトップメニュー
上スクロールでふわっと降りてきて、下スクロールで消えるトップメニュー
を作ってみました。
See the Pen Topmenu that appear-scrollup & hide-scrolldown by sarap422 (@sarap422) on CodePen.
よくあるやり方と若干違うのは、
最初からposition: fixed;
でなく、
スクロール量に応じて、途中からposition: fixed;
にしています。つまり、どういうことかと言いますと、
スマホのときに、最初のトップメニューはそのまま使いたい、
途中から、上スクロールで下タブ表示されるようにしたい、
という動きになります。あと最初が
position: static;(初期値)
のままだから、
レイアウト調整が楽とかもあるかもしれないですね。解説部分
scroll量計算・グローバル変数
javascript
$(function () { // グローバル変数 previous_scrY = 0; // window 'scroll' があったとき $(window).on("scroll", function () { // scroll量計算 let current_scrY = window.pageYOffset; let diff_scrY = current_scrY - previous_scrY;
current_scrY(現在スクロール量) - previous_scrY(前回スクロール量)
で、上スクロールか下スクロールかと判定します。
「var」や「let」だと上手く行かなかったのですが、「グローバル変数」というのがあるらしいですね。これだと上手くいきました。「position:fixed」に変更、「.hide」クラスで一旦隠す
javascript
// 現scrollY > 150 (※「pos:fixed」に変更、「.hide」で一旦隠す) if (current_scrY > 150) { // 現scrollYが、前scrollYより大きい場合 if (diff_scrY > 0) { // id '#' に、class'rolldown-hide'を追加 $("#Meltlilith").removeClass("rollup-PC rollup-Mbl"); $("#Meltlilith").addClass("rolldown-hide-PC rolldown-hide-Mbl"); } // 現scrollYが、前scrollYより小さい場合 if (diff_scrY < 0) { // id '#' に、class'rollup-hide'を追加 $("#Meltlilith").removeClass("rolldown-PC rolldown-Mbl"); $("#Meltlilith").addClass("rollup-hide-PC rollup-hide-Mbl"); }css
@media only screen and (max-width: 500px) { .rollup-Mbl .toplogo, .rollup-Mbl .toplogo { display: none; } .rolldown-Mbl, .rolldown-hide-Mbl { position: fixed; z-index: 999; width: 100%; justify-content: center; bottom: -100%; }途中で、
position: fixed;
に変更する場合
cssのtransition
でふわっとならずに、すぐにドンと表示されたんですね。それとスマホのときの、「最初は上表示 → 途中は下から表示」も上手く行かなかったので、一旦隠す処理を入れました。
隠していた「fixed」メニューを表示
javascript
// 現scrollY > 200 if (current_scrY > 200) { // 現scrollYが、前scrollYより大きい場合 if (diff_scrY > 0) { // id '#' を、class'rolldown-hide' → 'rolldown'に移行 $("#Meltlilith").removeClass("rolldown-hide-PC rolldown-hide-Mbl"); $("#Meltlilith").addClass("rolldown-PC rolldown-Mbl"); } // 現scrollYが、前scrollYより小さい場合 if (diff_scrY < 0) { // id '#' を、class'rollup-hide' → 'rollup'に移行 $("#Meltlilith").removeClass("rollup-hide-PC rollup-hide-Mbl"); $("#Meltlilith").addClass("rollup-PC rollup-Mbl"); } } /*ƒ /if (current_scrY > 200) */css
.rollup-Mbl { position: fixed; z-index: 999; width: 100%; justify-content: center; bottom: 0; }あとは「.roll-hide」クラスを削除して、「.roll」で表示というだいたい同じような作りですね。
最後に「現在のスクロール量」を次回スクロール時の比較値として記録
javascript
} else { /* not(current_scrY > 150) */ // "roll"classを全削除 $("#Meltlilith").removeClass( "rolldown-hide-PC rollup-PC rolldown-hide-PC rolldown-PC rolldown-hide-Mbl rollup-Mbl rolldown-hide-Mbl rolldown-Mbl" ); } // 処理後に、現scrollYを、前scrollYとして記録 previous_scrY = current_scrY;上に来たときは、
position: static;(初期値)
に戻るよう「.roll」クラスを全削除、
処理が終わった最後に、現在のスクロール量を、次回比較値として記録します。下タブ時はアイコンも表示されるようにしてみる
css
.rollup-Mbl [id^='Top__li'] i:before { font-family: 'Font Awesome 5 Free'; font-weight: 900; font-style: normal; font-size: 1.5rem; /* content内での改行ありにする */ white-space: pre; } .rollup-Mbl #Top__li-Home i:before { content: '\f015\a'; } .rollup-Mbl #Top__li-Works i:before { content: '\f0b1\a'; } .rollup-Mbl #Top__li-About i:before { content: '\f007\a'; }疑似要素で、下タブ時はアイコンも表示されるようにもしてみました。
white-space: pre;
、content: '\a';
でアイコン改行です。まとめ
ちなみにふわっと降りてくるだけなら、もう少しシンプルに。
既存のトップメニューのままでも、id付け加えるだけでもある程度は動くんじゃないでしょうか? 以上です。css
/* scrollPeraFix-falling.js (上スクロールでTopbar表示、下スクロールで消去) =================================================================== */ #Meltlilith { width: 100%; transition: all 0.3s ease-in-out; } .rolldown, .rollup-hide { position: fixed; z-index: 999; width: 100%; top: 0; } .rollup { position: fixed; z-index: 999; width: 100%; top: -100%; }javascript
/* scrollPeraFix-falling.js (上スクロールでTopbar表示、下スクロールで消去) =================================================================== */ $(function () { // グローバル変数 previous_scrY = 0; // window 'scroll' があったとき $(window).on("scroll", function () { // scroll量計算 let current_scrY = window.pageYOffset; let diff_scrY = current_scrY - previous_scrY; // 現scrollY > 150 (※「pos:fixed」に変更、「.hide」で一旦隠す) if (current_scrY > 150) { // 現scrollYが、前scrollYより大きい場合 if (diff_scrY > 0) { // id '#' に、class'rolldown-hide'を追加 $("#Meltlilith").removeClass("rollup"); $("#Meltlilith").addClass("rolldown-hide"); } // 現scrollYが、前scrollYより小さい場合 if (diff_scrY < 0) { // id '#' に、class'rollup-hide'を追加 $("#Meltlilith").removeClass("rolldown"); $("#Meltlilith").addClass("rollup-hide"); } // 現scrollY > 200 if (current_scrY > 200) { // 現scrollYが、前scrollYより大きい場合 if (diff_scrY > 0) { // id '#' を、class'rolldown-hide' → 'rolldown'に移行 $("#Meltlilith").removeClass("rolldown-hide"); $("#Meltlilith").addClass("rolldown"); } // 現scrollYが、前scrollYより小さい場合 if (diff_scrY < 0) { // id '#' を、class'rollup-hide' → 'rollup'に移行 $("#Meltlilith").removeClass("rollup-hide"); $("#Meltlilith").addClass("rollup"); } } /*ƒ /if (current_scrY > 200) */ } else { /* not(current_scrY > 150) */ // "roll"classを全削除 $("#Meltlilith").removeClass( "rollup-hide rollup rolldown-hide rolldown" ); } // 処理後に、現scrollYを、前scrollYとして記録 previous_scrY = current_scrY; }); });
- 投稿日:2020-11-21T04:54:36+09:00
[PlayCanvas] Button Elementには気をつけよう
なにがあったのか
Button ElementにはHover, Pressed, InactiveのTintが設定できるようになっています。
その中でもInactiveはButton ComponentのActiveのチェックボックスが外れていると適応されるTintで、文字通り「非活性状態」のボタンを表現できます。
これを使用して「条件が満たされるまで押せないボタン」を実装しようとしましたが動画の通り。
非活性にしているはずのボタン(Black)をクリックしたらイベントが発火しました。正直に申し上げると、Unityの感覚が抜けていなかったので混乱しました。Activeのチェック外してるのになんで処理が走るのかと。
なので公式リファレンスのactiveの項目を確認したら、このように書いてあった。
If set to false, the button will be visible but will not respond to hover or touch interactions.
訳) falseに設定した場合、ボタンは表示されますが、ホバーやタッチのインタラクションには反応しません。イベントの発火について触れられてなかったorz
あくまでこのActiveのチェックボックスはボタンのビジュアルに影響を与えるだけで、イベントの発火には関わっていないとの事でした。対処
押された時の処理部分を下記の様に修正しました。
var Button = pc.createScript('button'); Button.prototype.onPress = function() { /* ボタンコンポーネントの active が true なら実行 */ if( this.entity.button && this.entity.button.active ){ /* 処理内容 */ } };非常にシンプルですが、Button Componentのactiveがtrueになってる時だけ処理するようにしました。
結果はこの通り、非活性状態のボタン(Black)をクリックしても動作しなくなりました。おわりに
これも以前の記事同様、Unityライクに書けるがゆえに起きたミスではあるんですが、正直予想外でした。
入力イベントの登録方法を見た時に気がつくべきではあったのですが…