- 投稿日:2020-09-29T23:43:16+09:00
【非エンジニアによる業務の自動化大作戦!】契約終了日を営業チームのSlackに通知させるBotで契約継続率アップを狙います!!!
Qiitaの記事を書くようになって、2ヶ月経ちました。
が!!!私の本業はエンジニアではありません!!!
これまで勉強してきたプログラミングの技術を、どうにか本業にも活かしたい!!!
ということで、非エンジニアによる業務の自動化大作戦をここに決行いたします!!!
私が働いている会社の課題とは?
私は、創立3期目のHRベンチャーで営業企画とマーケティングを1人で担当しています。
社員も現在20名弱ということもあり、まだシステムを導入せず、顧客管理はスプレッドシートで行っています。
そんな弊社ですが、来月から大きく組織編制を変えることになりました!
これまで営業チームは、提案~受注~フォロー~継続提案まで一貫して担当していたのですが、
来月からカスタマーサクセスチームを立ち上げることに・・・!そのため、営業チームは提案~受注まで、
カスタマーサクセスはフォロー~継続提案まで、という風に担当フェーズが分割されます。なぜ、こうした組織編制にしたのでしょうか?
それは、契約継続率を上げるためです。ただ、この組織が力を発揮するには、2チームの連携が必要となります!
そこで私は、日ごろアナログで管理している契約日を自動的に通知するようにし、
両チームの工数削減、かつフォロー&継続提案の漏れがないようにできないか挑戦してみることにしました!Slackに通知を送れるようにする
弊社では、ほぼSlackで社員間のコミュニケーションをとっております。
そのため、契約日の通知もSlackに送れるようにしました。
Slackに送るためには、Webhookに繋げなきゃですが、その方法は以下の記事にまとめたので、ご覧ください♪▶SlackのIncoming WebhooksでWebhook URLを取得する方法
まず、これでSlackに送信できるようにするためのURLを取得できました!
GASを使って、スプレッドシートから契約終了日を取得し、Slackに送信する
次に、スプレッドシートから契約終了日を取得できるようにします!
Google Apps Scriptに、以下のようにコードを書きました!
AA列にある契約終了日を取得し、今日から30日以内だったら通知文を作成します。
そして、先ほど取得したSlackのWebhook URLへデータを送信すれば、指定したSlackチャンネルに通知文が送られます。function alertContract() { var mySheet = SpreadsheetApp.openById('SheetId').getSheetByName('シート名'); //スプレッドシートを取得 var lastRow = mySheet.getLastRow(); //スプレッドシートの最終行を取得 var today = new Date(); //今日の日付を取得 /* 契約終了日まで30日だったらSlack投稿 */ for (var i = 5; i <= lastRow; i++) { var Di = mySheet.getRange(i, 4).getValue(); //企業名(Di列)を取得 var dateAAi = new Date(mySheet.getRange(i, 27).getValue()); //契約終了予定日(AAi列)を取得 var period = Math.ceil((dateAAi - today) / (1000 * 60 * 60 * 24)); //今日から契約終了予定日までの期間を取得 if (period >= 0 && period <= 30) { //終了30日前の企業に通知 //通知文を作成 var strText = Di + "/契約終了日:" + dateAAi; //Slackへ投稿 postSlack(strText); } } } function postSlack(strText) { //payload var payload = { 'username' : "契約終了日を教えてくれる●●パイセンBot", 'text' : strText, // }; var options = { 'method' : 'post' , 'contentType' : 'application/json' , 'payload' : JSON.stringify(payload), }; // Webhook URL へPOSTする var url = 'https://hooks.slack.com/services/xxxxxxxxx'; //Incoming WebHooksのURL UrlFetchApp.fetch(url, options); }ところが・・・!このデータだと、正しい数値が取れず・・・。
Qiitaで質問を投げてみたところ、
@rf_p さんからすぐに回答をいただくことができました(大感謝)▶GASで契約終了日が今日から30日以内だったらSlackに通知が来るようにしたい
どうやら年月日ではなく、日付だけをとって計算していたため、数値が合っていなかったようです・・・。
そこで、教えていただいた通りにコードを修正。var period = Math.ceil((dateAAi - today) / (1000 * 60 * 60 * 24)); //今日から契約終了予定日までの期間を取得 if (period >= 0 && period <= 30) { //終了30日前の企業に通知これで、無事に契約終了まで30日以内の企業データを抽出することができました!!!
せっかくなので、Webhookを設定した画面で、アイコンもカスタマイズ♪
以下のように通知が来るようになりました♪
現場からのダメ出しにより改善
やった~~!完成した~~!嬉しくて、すぐさまカスタマーサクセスチームに共有♪
私『●●さん!見てください!契約終了30日以内の企業を通知できるようにしましたよ!』
●●さん『いや、30日だと遅い。それに何回も通知きても微妙だから、毎朝60日前と30日前と7日前の企業を通知させて』
私『・・・』
作る前にヒアリングしなかった私が悪いなと反省。何事もユーザーの声が一番大事だということに、改めて気づかされました。
ということで、作り直し。
if (period === 0) { //終了日の企業に通知 //通知文を作成 var strText = Di + "◆本日、契約終了します◆"; //Slackへ投稿 postSlack(strText); } else if (period === 7) { //終了7日前の企業に通知 //通知文を作成 var strText = Di + "は、あと7日で契約終了します!"; //Slackへ投稿 postSlack(strText); } else if (period === 30) { //終了30日前の企業に通知 //通知文を作成 var strText = Di + "は、あと30日で契約終了します!"; //Slackへ投稿 postSlack(strText); } else if (period === 60) { //終了60日前の企業に通知 //通知文を作成 var strText = Di + "は、あと60日で契約終了します!"; //Slackへ投稿 postSlack(strText); }少し追加で、本日終了する企業も出てくるように。
また、契約終了日ではなく、あと~~日とした方が見やすいので通知文も修正しました!完成したものがこちらです!
だいぶ見やすくなりましたね!先輩からもOKいただきました!!!良かった!!!まじで!!!
身につけたスキルを活かせる喜び
プログラミングを勉強して、これまでジェイソン・ステイサムのBotとか、ジェイソン・ステイサムの防犯システムとか、とにかく私利私欲に満ちたサービスしか作ってこなかった私。
今回おそらく初めて、本業の仕事に直結する仕組みを作りました。
もちろん、私得なサービス作りも、自分のスキル向上にとって有意義な時間です。
ただ、こうして身近な人に使ってもらえるサービスを作り、直接的に役に立てることは、また違った喜びを感じられるなと実感しました。プログラミングを勉強してから、めちゃくちゃ忙しくなったけど、
感じることが多くて、本当充実しているなぁと思うばかりです。もっともっとスキルアップして、自分のためにも、周りの人達のためにも、アウトプットを続けていくぞ~~~~!
最後までお付き合いいただき、ありがとうございます!
(*^^)v「よろしければLGTMもよろしくお願いします!」
- 投稿日:2020-09-29T23:30:03+09:00
Symbol from NEM のテストネットで着金を検知する(2020年9月時点)
前回、Symbol from NEM のテストネットで送金を体験する(2020年9月時点)にて、Symbol from NEM ブロックチェーンを使ってブラウザコンソールだけで送金を体験してみました。しかし、ブロックチェーンを実社会で利用するためには、入金をトリガーとして実行されるプログラムがあって初めて活躍します。
過去に書いたスマートコントラクトの基礎となる着金時のトリガープログラムを仮想通貨NEMで実装する。という記事が非常に多くの人に読んでいただけましたので、今回はこれの2020年9月Symbol from NEM版でお届けしたいと思います。
必要なもの
インターネット接続環境
Google Chromeブラウザ使用するツール・ライブラリ
nemtech レポジトリより公開されている symbol-sdk-typescript-javascript を browserify化して使用します。
- nemtech / symbol-sdk-typescript-javascript
- xembook / nem2-browserify / symbol-sdk-0.21.0.js
NGLより提供されるエクスプローラーとフォーセット(蛇口)を使用します。
- FAUCET
- エクスプローラー
解説の流れ
- 開発者コンソール起動とsymbol-sdkの準備
- 受信用アカウントの作成と着金トリガー実装
- 受信
開発者コンソール起動とsymbol-sdkの準備
開発者コンソールの起動
Google Chrome ブラウザを起動しF12キーを押して開発者コンソールを開きます。
google のトップページで起動させた場合以下のような画面になります。別の画面が見える場合はコンソールタブをクリックしてください。
設定値の定義とライブラリのインポート
コンソールに以下のスクリプトをコピーして貼り付けます。
NODE = 'https://sym-test.opening-line.jp:3001'; GENERATION_HASH = '6C1B92391CCB41C96478471C2634C111D9E989DECD66130C0430B5B8D20117CD'; (script = document.createElement('script')).src = 'https://xembook.github.io/nem2-browserify/symbol-sdk-0.21.0.js'; document.getElementsByTagName('head')[0].appendChild(script);
- NODE
- 接続するノードのURL(オープニングラインさんのHTTPS対応ノードをお借りします)
- GENERATION_HASH
- テストネットの一番最初のブロック生成時に出力されたハッシュ値
- script
- browserify化したsymbol-sdkを読み込むスクリプトタグを表示中のページに埋め込みます
スクリプトタグが無事に埋め込まれました。
このようにコンソールには最後に実行されたコマンドの結果が出力されるようになっています。ライブラリのインスタンス生成
symbol-sdkで定義されたfunctionをnem.Xxxxで呼び出せるようにします。
nem = require("/node_modules/symbol-sdk");受信用アカウントの作成と着金トリガー実装
アカウントの新規作成
alice = nem.Account.generateNewAccount(nem.NetworkType.TEST_NET);ノードへWebSocket接続
nsHttp = new nem.NamespaceHttp(NODE); wsEndpoint = NODE.replace('http', 'ws') + "/ws"; listener = new nem.Listener(wsEndpoint,nsHttp,WebSocket); listener.open().then(() => { listener.newBlock(); });リスナーを起動し、ノードとWebSocket接続を確立させます。ノードとのコネクションはデータのやりとりがないと1分で切断されてしまうので、ブロック生成時にデータを受信できるようにnewBlockを登録しておきます。
受信トリガー
listener.confirmed(alice.address) .subscribe(tx => { alert("トランザクション承認を受信しました") console.log("http://explorer-0.10.0.x-01.symboldev.network/accounts/" + alice.address.plain()); console.log("http://explorer-0.10.0.x-01.symboldev.network/transactions/" + tx.transactionInfo.hash); });listener.confirmedで作成したアカウントのアドレスを指定し、aliceへのトランザクションが承認された場合にトランザクションデータがsubscribeに処理が流れるようにします。この部分が着金トリガーとなり、subscribeの内部に処理を記述することで、着金時に起動するプログラムを記述することができます。
これで準備完了です。
受信
入金テスト
"http://faucet-0.10.0.x-01.symboldev.network/?recipient=" + alice.address.plain() +"&amount=1"フォーセットから作成したアカウントへトランザクションを送信します。
フォーセットにアクセスするためのURLを作成します。
出力されたURLをクリックしてフォーセット画面を表示してください。デフォルトで送金先が先ほど生成したアカウント、総金額が1XYMでセットされているので、そのままCLAIM!をクリックすれば作成したアカウントへトランザクションを送信できます。
トリガー発火
aliceへのトランザクションが承認されると、受信トリガー内に記述したスクリプトが実行されます。
コンソールにも出力されたURLもアクセスしてみましょう。アカウント
http://explorer-0.10.0.x-01.symboldev.network/accounts/TDM5BBEMEHY7R3IW6J23IHXVKZEG5XSUI6F5AGI
指定アカウントにsymbol.xymが届いています。
トランザクション
http://explorer-0.10.0.x-01.symboldev.network/transactions/697A7ACAFF35012697CF40FE1D796488E53E3A3E09A9A53949577B4E65BCCE06
指定トランザクションにアカウントへの送信記録が残されています。
- 投稿日:2020-09-29T23:24:47+09:00
エンジニアの本棚卸し
目的
自分がインプットしてきた技術関連知識の足跡整理。
文字にするとキリがないので、面倒ではない視覚的に集約されているという点で本棚の書籍写真でペタペタ整理。足跡
入り口はC言語
猫でも分かるC言語で自分が猫未満であることを悟り、苦しんで〜みたいな名前の書籍を読んだ記憶。
DXLibraryを使用してゲーム製作をしたり、遺伝的アルゴリズムでレースゲームのゴール時間を最適化したり、TCP/IPレイヤをイジったり、二度デストラクタを呼び壊したり、ダングリングポインタを参照して壊したり、壊したり。Webアプリケーションに興味が出てPHPあたり
Webアプリケーションを作りたくてPHP(時代を感じる)。
当時はLaravelではなくCakePHP,Symfonyとかの時代。Cakeを使ってWebアプリケーションを作っていたもののフレームワーク特有のブラックボックに薄気味悪さを覚えたため、一旦それは捨ててMVCフレームワークの自作に舵を切り替えた。
その後、数年してからREST APIでバックエンドとフロントエンドを接続するみたいなものが流行りだしたときにLaravelとReactでチーム開発した。オブジェクトモデリングに興味が出てJava
現実世界の写像(モデリング)ってなんぞ、と思いJava。
とやかく言われることの多い言語であるものの、複雑度の高いシステム構築においては安定している。エンジニアとして知識領域を開拓する礎になった恋のキューピッド的な言語。
OSが知りたいです
私達人間に何を隠しているのOSさん、ということでOS。
LINUXプログラミングインタフェースは学生のときに夏休みを使って読み切った(覚えてないけど)。CPUが知りたいです
演算あたりの機構が知りたくてパタヘネ本とか、CPUの創りかたとか。
左のほうにある本はAPI設計やら、正規表現エンジンやら、Androidアプリとかに興味を持ったときの。副作用のない言語?
保守性や疎結合などの文脈で関数型言語に興味を持った(ように記憶している)。
最初はCommonLisp。本屋でリスプエイリアンと目が合った。今はRustが好き。
メモリ安全、ゼロコスト抽象化、GCのない軽量なランタイム。機械学習やスクレイピング、デバッガ
脳内シナプスによるネットワークを模倣ってなにを言ってるの、と興味を持ち自作系のお魚本から。
テンソルフローの計算グラフが作れないので基本Keras(に逃げた)。その他、Pythonはスクレイピングやデバッガのカスタマイズなどでちょくちょく使う。
私はパケットになりたい
ネットワークってなにそれどうやって制御してるの、知りたい。
MPLSなにそれ、剥がされたい。攻撃は最大の防御
避けがたいクラッキングという中二病。
もちろん隔離された自分だけの環境で。コンピューティングリソースに対する抜本的な認識刷新
このあたりのコンピューティングリソースの変化はコペルニクス的転回(言いたいだけ)をもたらしましたね。マークアップとスタイル
あまり得意ではないものの、意思疎通を図るために必要なので最低限は知っておきたい。
右の砂川物理学は関係ない。DBさん腹のウチを見せてごらん
鳥になって鳥瞰したい
無限多味スルメ
実践UML、コード・クラフト、コードコンプリートあたりは読むたびに味が変わる。その他1
その他2
振り返った所感
まだ埋もれた書籍はクローゼットに大量にあるものの、とりあえず本棚にあるもので振り返ってみた。
結論:「頭でっかちになってはいけない」
物事の正しさというものは話し手5割・聞き手5割の双方の責任のもとに成立している。
書籍においても同様に書き手5割・読み手5割。誰かが言っていたらから正しいとか、あの本に書いてあったから正しいのような
話し手や書き手10割の責とするような姿勢でいてはいけないな、と最近は特に強く感じるようになった。
- 投稿日:2020-09-29T21:19:10+09:00
Nuxt.jsで認証認可入り掲示板APIのフロントエンド部分を構築する #6 記事投稿フォームの作成
←Nuxt.jsで認証認可入り掲示板APIのフロントエンド部分を構築する #5 ログイン・ログアウトの実装
はじめに
今回はNuxt編の最終回。
記事投稿フォームをindexに設置します。ログインしていないと書き込めないので、ログイン中のみフォームを表示します。
Railsのレスポンスコード修正
まず下準備として、Rails側の修正漏れ箇所を直します。
postのsave失敗時、ステータスが200(正常)で返ってきてしまっておりエラーがキャッチできないので、レスポンスコードを422で返すようにします。app/controllers/v1/posts_controller.rb... if post.save render json: post else - render json: { errors: post.errors } + render json: { errors: post.errors }, status: 422 end end ... if @post.update(post_params) render json: @post else - render json: { errors: @post.errors } + render json: { errors: @post.errors }, status: 422 end end ...postした時にNuxt側でtry catchを使っていますが、エラーが出てもレスポンスコードが200だとcatchブロックに行きません。ここは以前の設定ミスなのであらかじめ塞いでおきます。
pages/index.vueとstore/index.jsの修正
pages/index.vuesm8 md6 > + <form v-if="logged_in" @submit.prevent="newPost"> + <v-card> + <v-card-title class="headline"> + 新規投稿 + </v-card-title> + <v-card-text> + <ul v-if="errors"> + <li v-for="(message, key) in errors" :key="key"> + {{ key }} + <span v-for="(m, i) in message" :key="i"> + {{ m }} + </span> + </li> + </ul> + <v-text-field + id="subject" + v-model="subject" + label="subject" + name="subject" + prepend-icon="mdi-subtitles" + /> + <v-textarea + id="body" + v-model="body" + label="body" + name="body" + prepend-icon="mdi-comment-text" + /> + </v-card-text> + <v-card-actions> + <v-spacer /> + <v-btn color="primary" type="submit"> + 投稿 + </v-btn> + </v-card-actions> + </v-card> + </form> <v-card v-for="post in posts" :key="post.id"> <v-card-title class="headline"> <n-link :to="`/posts/${post.id}`"> ... <script> export default { + data () { + return { + subject: '', + body: '', + errors: [] + } + }, computed: { posts () { return this.$store.getters['posts/posts'] + }, + logged_in () { + return this.$store.getters.logged_in + } + }, + methods: { + async newPost () { + try { + await this.$store.dispatch('posts/newPost', { + subject: this.subject, + body: this.body + }) + this.subject = '' + this.body = '' + this.errors = [] + } catch (e) { + this.errors = e.data.errors + } + const posts = await this.$store.dispatch('posts/fetchPosts') + this.$store.commit('posts/setPosts', posts.posts) } } }エラーメッセージの表示箇所や、投稿後のposts再取得あたりがやっつけ感満載ですが。
やっていることはログインフォーム等と大差無いですね。最後にstore/posts.jsにnewPostを定義して終わりです。
store/posts.jsexport const actions = { async fetchPosts () { return await this.$axios.$get('/v1/posts') + }, + async newPost (_c, post) { + return await this.$axios.$post('/v1/posts', post) } }
終わりに
今回でNuxt.js編最終回です。
Rails編からご覧いただいていた場合、18+6で24章にも及ぶ連載です。ご覧いただいたり、プログラムを書いていただいた皆様ありがとうございました。Railsで実装したCRUD処理でNuxt側未実装の点も多いため、ここからは自力で実装に挑戦してみてください。
また、RailsもNuxtも(特にNuxtは)初心者でお見苦しいコードも多々あったかと思いますが、これをきっかけにオリジナルのアプリケーション構築への挑戦をオススメします。
終わり
【連載目次へ】
- 投稿日:2020-09-29T20:53:42+09:00
GASで大学のレポートテンプレ作成を自動化
自動化の意義
こんにちは、大学生で自動化エンジニアの山岡幹太です。
日々各種自動化ツールを作成し生活をより便利にすることに快感を得ています。大学のレポートの作成のとっかかりの障壁の一つに、レポートの雛形作成の手間が挙げられます。
例えば、学籍番号、所属、氏名、授業名などを手動で入力し、適切に右寄せ左寄せ、スタイルの設定をする必要があります。
しかし、こうした反復作業は必要なのにも関わらず、レポートの評価には寄与しません。
すなわち、人間がする必要のある作業ではありません。反復的な作業はプログラマーにやらせましょう。
そこで今回はGoogle Apps Scriptでのレポートテンプレ作成の自動化のユースケースを共有します。GAS, Google Apps Scriptとは?
Googleのサービス,e.g.,表計算、ドキュメント作成などを一貫してJavaScriptの形式で操作できるスクリプトエディタ&実行環境です。
自由度はきわめて高いです。前回は、下ネタを検知して叱責するLINE BotをGASとともに作成しましたが、意外と好評でした。
「GASとLINE Botで下ネタメッセージを取り締まるボットを作成」
https://qiita.com/kanta_yamaoka/items/02a2521f526f72126fac
興味がございましたらご覧ください流れ
大まかにこれだけです:
1.テンプレートドキュメントの作成
2.GASのスクリプトの記述
3.GASへの権限の付与
4.関数の実行&ファイル生成ここまでできれば、授業で先生がレポートの情報を喋った瞬間に
GASでレポートの情報を入力し、関数を実行するだけでドキュメントが作成できます
あとは、レポートの中身の作成に集中できますそれでは Let's go!
1.テンプレートドキュメントの作成
GASのスクリプトで後ほどこのようにしてテンプレートを作成し、中身の文字列を任意のものに書き換えます。
まず後ほど置換したい項目を{{}}で囲んで、フォントサイズや配置を決めます。
{{}}で囲んだのは、Vue.jsの表記を真似ただけなので、別に意図通りに置換できれば何でもいいのです。
「キン肉マン太郎」でもいいですが後ほど混乱を招くので、わかりやすい名前をつけましょう。
これのいいところは、GUIでテンプレートの編集が可能な点です。
*GASでスタイルをゴリゴリ編集するのは、面倒なので今回はやりません.次に、テンプレートドキュメントのドキュメントのIDを取得しましょう
テンプレートドキュメントのURLを開き,下の{document ID}の部分を取っておきましょう
このIDを次の段階で使いますhttps://docs.google.com/document/d/{document ID}/edit#2.GASのスクリプトの記述
Google Driveを開き、左上の「新規」ボタンを力を込めてクリック
そして、「その他」を力強くクリックして、"Google Apps Script"を選びましょう
以下のスクリプトをコピペして、先ほど取得したテンプレートファイルのドキュメントIDを置き換えましょう
function createReportBoilerplate(){ //ここだけを毎回変更してレポートを作成します let courseTitle='ジャガイモ入門IA'; let reportTitle='最終レポート'; let teacher='ジャガイモ先端研究機構 田中マイケルジャクソン'; let deadline='昭和32年4月45日'; let formatDescription='B3棟の研究室にA4サイズでレポート提出必要, ジャガイモの画像を最低一枚添付する必要ありと講義スライドにあった'; //たまにファイル名を指定してくる講義があるので、その場合はここにそれを入れ、指示通りのファイル名のドキュメントを出力します let fileNameRequirements=''; //そうでなければ基本、ファイル名は // `${fileNameRequirements}締め切り:${deadline},${courseTitle} ` // のファイル名で保存します。締め切りは何よりも大事だからです。 //テンプレートファイルのドキュメントIDを以下に記述します // https://docs.google.com/document/d/{document ID}/edit# let docID = '1grjjqEkpBQCcYkZ84gDjml8afyioRwhq1C2WWOGdSHQ'; //テンプレートからコピーの作成 let templateDoc = DriveApp.getFileById(docID); //ファイル名を識別しやすい形で指定 let createdDoc = templateDoc.makeCopy(`${fileNameRequirements}締め切り:${deadline},${courseTitle} `); //コピー後のドキュメントの本文を取得します let body = DocumentApp.openById(createdDoc.getId()).getBody(); //本文中の{{}}, mustache tagsを任意の文字列に置換します body.replaceText('{{courseTitle}}',courseTitle); body.replaceText('{{reportTitle}}',reportTitle); body.replaceText('{{teacher}}',teacher); body.replaceText('{{deadline}}',deadline); body.replaceText('{{formatDescription}}',formatDescription); }そのあと、スクリプトをctrl+sで保存し、以下の要領で実行します
3.GASへの権限の付与
Googleアカウントへの権限許可の画面に飛ぶので、「許可を確認」を力強くクリックしましょう
なお、スクリプトファイルはGoogleアカウントでほとんどのことができてしまうので、
取り扱いに十分気を付けましょう
先への進み方がわかりづらいですが、画像のように下の「詳細」をクリックしましょう
さらにわかりづらいですが、画像のように「(プロジェクト名)安全ではないページに移動」を力を込めてクリックしましょう!
そのあとは、画面にしたがって権限付与を完了してください
3.関数の実行&ファイル生成
Voila! Google Driveを開くと、お望み通りのドキュメントが新規作成されています
いったんこのスクリプトを作成してしまえば、甘美な大学生活が確約されたようなものです。
レポート作成の時の障壁となる毎回の学生情報の入力や、スタイルの変更の手間が省けて、
迅速にレポートに集中して取り組むことができます。
そして、余った可処分時間でより一層大学生活をサークルと恋愛で埋め尽くせます!(理論上)ハマったところ
Arrow演算子を使うと、GASの実行メニューに関数が表示されない
Arrow演算子ファンの皆さん、Arrow演算子かっこいいですよね
let warabimochi = (kome, yasai, kinoko)=>{ ... }でもGASでは関数定義はできても、GASの実行メニューに関数が表示されず使えないみたいです...
ランタイムがV8エンジンになったらしいですし、今後改善されるといいですね!とりあえず、従来の関数定義の形で書きましょう!
function warabimochi(kome,yasai,kinoko){ ... }応用の可能性は多岐にわたる
例えば、会社で顧客の名前などが入ったドキュメントなどを作成する時に、
いちいちファイルをコピーし名前をつけて、開き、該当部分を見つけて手動で入力などしていませんか?
GASを使えばこの手間を削減でき、ヒューマンエラーも減らせます。
特にスプレッドシートと連携すれば、より効率的にテンプレートからの文書作成ができます。スプレッドシートとの連携の私の大学生活でのユースケースを次回は紹介いたします。
それではお楽しみに!あなたはGASでどんなことをを便利にしますか?コメントどしどしお待ちしています!
- 投稿日:2020-09-29T20:47:31+09:00
Tyハロトレ30日目
CSS
iPhone frame
JavaScriptとは、インタプリタ型のプログラミング言語である。
インタプリタ型・・・プログラム実行時にオブジェクトコード(コンピュータが実行できる形式)に変換していくため、実行速度が遅いです。
例)同時通訳;喋ってる本人と翻訳してる人で時差があるようなイメージ。
①直接Webページに埋め込む
<head>xxx</head>
または
<body>xxx</body>
②外部JSファイル
<script src="xxx.js"></script>
注意点
・命令文の最後には「;」(セミコロン)をつけて区切る
・文字列は引用符で囲みます。""、''基本文
ウインドウオブジェクトを操作して、文字を表示させます。
訳:ウインドウオブジェクトの書類を書き出して、(この内容文字列)
document.write("<br>")
ダブルクォーテーションは2回まで
JS計算
算術演算子
割り算は永遠に計算できないので、途中で計算を止めてしまいます。
ダブルクォーテーションの位置で変わる
変数とは
データを一時的に保管するメモリ領域
メモリ領域を準備することを「変数を宣言する」という。JavaScriptでは変数に、数値、文字列、真偽値、オブジェクトを格納できます。
代入
「=」は「等しい」ではない
演算子と呼ばれる記号
age = 20;代入は上書きされる
計算式の代入
CSS
表とフォーム
input type="file"
reset・submit
required
好物にある、チェックボックスはスペースで選択でき、
性別にある、ラジオボックスは左右の矢印キーで選択でき、
年齢にある、プルダウンメニューは上下の矢印キーで選択できます。autofocus
placeholder
Photoshop
明るさを調整したいとき
ヒストグラム
レベル補正
イメージ>色調補正>レベル補正
下記画像は、明るいところに偏っている画像です。
レベル補正方法3パターン
①2つのスポイトを選ぶ
下記画像のように2つのスポイトを選ぶと、
プレビューで白みがかっていたのが消えるのがわかります。
レベル補正をやり直したい時は、レベル補正パネルでAltを押すと、キャンセルの部分が初期化に変わります。
②暗いところをドラッグ
③自動補正
機械なので、思い通りに行かないこともあります。
緑かぶり
緑っぽく色かぶりすることを「緑かぶり」と言います。
蛍光灯は色が付いていて、画像の色が変わるので、写真を撮る時は、自然光かカメラの光で撮るのが良いです。トーンカーブ
色相環
色彩計画を立てる上で、①中心となる色と、②それを補完する色があるとバランスが良いです。
参考URL
例)IKEAのコーポレートカラー 青と黄色
IKEAのコーポレートカラー「青」と「黄色」は、
金十字旗と呼ばれるスウェーデンの国旗の配色から採られているといわれてます。
参考URL
アンシャープマスク(USM)
フィルター>シャープ>アンシャープマスク
完全にぼけているのは加工が難しいですが、ぼけているのを若干くっきりさせるのに向いています。
ぼかし
フィルター>ぼかし>ぼかし(移動)
車が臨場感溢れるスピードで走っているのに使ったりします。
コピースタンプツール
覆い焼きツール
イン画紙にフイルムの画紙を光が覆うため、明るくなります。
例)夏にTシャツ焼けして、ノースリーブになったらダサいやつ焼きツールは覆い焼きツールの逆です。
境界をぼかして、色々模様ができます。
Illustrator
裁ち落とし
裁ち落とし・・・印刷物やチラシに用います。
仕上がるサイズよりも3mm大きく作って、裁ち落とします。
これにより、紙の地色が出なくなります。また、裁ち落としのため、B4とかA3でA4のデザインをする必要があります。
拡大・縮小ツールで25%にすると、A4のサイズになります。
1辺が40mmの正三角形
適当な三角形を作る
↓
ウインドウ〉変形
↓
鎖マークがつながっている状態にする(幅・高さを固定)
↓
幅を40mmにする底辺が50mm、高さ60mmの直角三角形
幅が50mm、高さ60mmの長方形を作る
↓
ダイレクト選択ツールで右上の頂点だけ選んで取り除く
↓
ダイレクト選択ツールで右下と左上を選ぶ
↓
オブジェクト>パス>連結1辺が40mmの正五角形
長さ40mmの線を書く
↓
回転ツールで108度回転を5回行ってコピーする
↓
線を5つくっつけてグループ化
- 投稿日:2020-09-29T20:44:20+09:00
Vue 映画情報をTMDb APIを使って取得
はじめに
完成イメージはこんな感じ
はじめてVueを触ってみた。。。Angular以外jsフレームワーク知らんから、これからはVueを本格的に学んでいく!
— 高卒プログラマーげんと (@gento34165638) September 29, 2020
ってことで、映画情報をVue(v2系)で取得してみた。#プログラミング #エンジニア転職 pic.twitter.com/UvEHYFiirR環境
Vue v2系です。
どうやら2020/09現在、vue v3系ではvuetifyが使えないようなので、あえてv2系にしました。。
https://github.com/vuetifyjs/vuetify-loader/issues/127使えるようになってら、コメントより教えてください
別に「vuetifyなんて使わない」と言う方は、特にバージョンを気にしなくて良いかと!
vuetifyを入れる
コマンドプロンプトかターミナルより、プロジェクトvueの階層へ移動
vue add vuetify
TMDbのAPI keyを取得
【Ionic + Angular】TMDb APIで映画の一覧を取得して表示する
上記リンクのリンクより、取得できます。映画情報を取得
axiosを入れる
npm install axios
まぁ映画上を取得するだけならaxiosを使う必要はなさそうですが、後々に検索機能とかもやってみたいので
一覧を表示してみる
Home.vue<template> <v-container> <v-row> <v-col v-for="movie in movies" :key="movie.id"> <v-card> <v-img v-bind:src="'http://image.tmdb.org/t/p/w300/' + movie.poster_path"></v-img> <v-card-title>{{ movie.title }}</v-card-title> <v-card-subtitle>{{ movie.release_date }}</v-card-subtitle> <v-card-text>{{ movie.overview }}</v-card-text> </v-card> </v-col> </v-row> </v-container> </template> <script> import axios from 'axios' export default { name: 'Home', data() { return { movies: [], // 自分のkeyに置き換える apiKey: '***', } }, methods: { getMovies() { // &language=jaで日本語に(日本語版がないものは空欄になるけど、、) axios.get(`https://api.themoviedb.org/3/movie/now_playing?api_key=${this.apiKey}`) .then(response => { this.movies = response.data.results console.log(this.movies); }) console.log(this.movies); }, }, created() { this.getMovies(); } } </script>https://developers.themoviedb.org/3/movies/get-now-playing
ドキュメントはこちら現在上映中の映画を取得しました〜。
- 投稿日:2020-09-29T19:41:47+09:00
フィッシャー・イェーツのシャッフル
- 投稿日:2020-09-29T19:32:08+09:00
leaflet.jsの日本語チュートリアルをやってみた。
leaflet.js
を業務で使う事になったので調査。埼玉大学谷謙二研究室さんが公開してくれている日本語のチュートリアル( http://ktgis.net/service/leafletlearn/index.html )が分かりやすかったので、やってみた。
なお、公式チュートリアル( https://leafletjs.com/examples.html )も存在するが、今回は触れてない。
learn_leaflet.html<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>埼玉大学谷謙二研究室さんが公開してくれているleaflet.jsの日本語チュートリアルをやってみた。</title> <link rel="stylesheet" href="https://unpkg.com/leaflet@1.3.0/dist/leaflet.css" /> <script src="https://unpkg.com/leaflet@1.3.0/dist/leaflet.js"></script> <style> /*紫色のアイコンのCSS */ .icon1 { width: 20px !important; height: 20px !important; border-radius: 10px; border: 3px solid #fdfdfd; box-shadow: 1px 1px 4px rgba(0, 0, 0, 0.8); background-color: rgb(204, 51, 166); } /*茶色のアイコンのCSS */ .icon2 { width: 20px !important; height: 20px !important; border-radius: 10px; border: 3px solid #fdfdfd; box-shadow: 1px 1px 4px rgba(0, 0, 0, 0.8); background-color: rgb(204, 125, 51); } /* 作成するdivのCSS */ .infostyle { border: solid 1px; background-color: azure; border-radius: 10px; opacity: 0.8; padding: 5px; font-size: 15px; color: black; } </style> <script> // 定数:地図URL const MAP_URL_STD = 'https://cyberjapandata.gsi.go.jp/xyz/std/{z}/{x}/{y}.png'; // 地理院地図・標準地図タイル const MAP_URL_PALE = 'http://cyberjapandata.gsi.go.jp/xyz/pale/{z}/{x}/{y}.png'; // 地理院地図・淡色地図タイル const MAP_URL_OPEN = 'http://tile.openstreetmap.jp/{z}/{x}/{y}.png'; // オープンストリートマップのタイル const MAP_URL_RELIEF = 'http://cyberjapandata.gsi.go.jp/xyz/relief/{z}/{x}/{y}.png'; // 地理院地図・色別標高図タイル const MAP_URL_HILL = 'http://cyberjapandata.gsi.go.jp/xyz/hillshademap/{z}/{x}/{y}.png'; // 地理院地図・陰影起伏図タイル // 定数:地図URLに紐づくaタグの属性 const ATTR_URL_STD = "<a href='https://maps.gsi.go.jp/development/ichiran.html' target='_blank'>地理院タイル</a>"; // 地理院地図 const ATTR_URL_PALE = "<a href='http://portal.cyberjapan.jp/help/termsofuse.html' target='_blank'>地理院タイル</a>"; // 地理院地図 const ATTR_URL_OPEN = "<a href='http://osm.org/copyright' target='_blank'>OpenStreetMap</a> contributors"; // オープンストリートマップ // 定数:座標 const COORD_BASE = [35.40 , 136]; // 初期位置 const COORD_SAITAMA_UNIV = [35.8627 , 139.6072]; // 埼玉大学 const COORD_SAKURA_KUYAKUSHO = [35.8561 , 139.6098]; // 桜区役所 const COORD_KITAURAWA_STA = [35.871986 , 139.645908]; // 北浦和駅 const COORD_ROUTE = [[35.865465, 139.60734], [35.870404, 139.6249], [35.870195, 139.6320], [35.871047, 139.6447], COORD_KITAURAWA_STA]; // 北浦和駅~埼玉大学の道案内 const COORD_SAITAMA_UNIV_SITE = [[35.864891, 139.605503], [35.865969, 139.6088], [35.865378, 139.6097], [35.863309, 139.609559], [35.858996, 139.609709], [35.858248, 139.608722], [35.859813, 139.6053], [35.864248, 139.6056], [35.864891, 139.605503]]; // 埼玉大学の敷地 const COORD_STOREs = [ // コンビニ一覧 { pos: [35.8645472, 139.6048663], name: "セブンイレブン浦和埼玉大学店" }, { pos: [35.8689857, 139.6086909], name: "セブンイレブンさいたま大久保店" }, { pos: [35.871305, 139.6128431], name: "ファミリーマート浦和上大久保店" }, { pos: [35.8665389, 139.6133905], name: "ミニストップさいたま上大久保店" }, { pos: [35.8650306, 139.6070633], name: "ローソン埼玉大学店" } ]; // クラス:オーバレイするグリッド線(タイル座標(z,x,y))をGridLayerで表示するためのクラス var GridLayerClass = L.GridLayer.extend({ createTile: function (coords) { //div要素でタイルを作成 var tileDiv = L.DomUtil.create('div', ''); tileDiv.setAttribute("style", "border: solid 1px"); //タイル要素の中にzxyを表示するdiv要素を作成 var coordsDiv = L.DomUtil.create('div', '', tileDiv); coordsDiv.setAttribute("style", "position:absolute; background-color:white; padding:5px; border:solid 1px; left:10px; top:10px; font-size:15px;"); coordsDiv.innerHTML = "z / x / y = " + coords.z + " / " + coords.x + " / " + coords.y; return tileDiv; }, }); // 変数:地図オブジェクトと線オブジェクトは各関数で使うためグローバル変数にしておく var map; var pline; // // 画面表示時の初期処理 // - 各チュートリアルの内容を本関数に集約したよ // function init() { // -------------------- 基本オブジェクトの作成 -------------------- map = L.map('mapcontainer', { zoomControl: false }); // -------------------- 初期状態(地図の中心・ズームレベル) -------------------- map.setView(COORD_BASE, 5); // -------------------- レイヤー(基本地図・切替地図・オーバレイ地図) -------------------- //基本地図:標準地図タイル L.tileLayer(MAP_URL_STD, { attribution: ATTR_URL_STD }).addTo(map); //切替地図:標準地図タイル、淡色地図タイル、オープンストリートマップ) var gsi = L.tileLayer(MAP_URL_STD , { attribution: ATTR_URL_STD }); var gsipale = L.tileLayer(MAP_URL_PALE, { attribution: ATTR_URL_PALE }); var osm = L.tileLayer(MAP_URL_OPEN, { attribution: ATTR_URL_OPEN }); var baseMaps = { "地理院地図": gsi, "淡色地図": gsipale, "オープンストリートマップ": osm }; //オーバレイ地図:色別標高図タイル、陰影起伏図タイル、グリッド線 var gsirelief = L.tileLayer(MAP_URL_RELIEF, { opacity: 0.7, maxNativeZoom: 15, attribution: ATTR_URL_STD }); var gsirehillshademap = L.tileLayer(MAP_URL_HILL , { opacity: 0.5, maxNativeZoom: 16, attribution: ATTR_URL_STD }); var zxyLayer = new GridLayerClass(); var overlayMaps = { "色別標高図": gsirelief, "陰影起伏図": gsirehillshademap, "XYZ": zxyLayer }; // 画面にセット L.control.layers(baseMaps, overlayMaps).addTo(map); gsi.addTo(map); // ラジオボタンを選択状態にするために必要 // -------------------- スケール(右下) -------------------- L.control.scale({ maxWidth: 200, position: 'bottomright', imperial: false }).addTo(map); // -------------------- ズーム(左下、基本オブジェクト作成時にzoomControl:falseとする必要あり) -------------------- L.control.zoom({ position: 'bottomleft' }).addTo(map); // -------------------- マーカー・ポップアップ・アイコン -------------------- // アイコンオブジェクト var myIcon1 = L.divIcon({ className: 'icon1', iconAnchor: [13, 13] }); // アンカー:中央 var myIcon2 = L.divIcon({ className: 'icon2', iconAnchor: [0, 0] }); // アンカー:左上 // ポップアップあり、アイコンあり var popup1 = L.popup({ maxWidth: 550 }).setContent("埼玉大学です<br><img src='su.jpg' width='500' height='375'>"); var popup2 = L.popup().setContent("桜区役所です"); L.marker(COORD_SAITAMA_UNIV, { icon: myIcon1 }).bindPopup(popup1).bindTooltip("埼玉大学").addTo(map); L.marker(COORD_SAKURA_KUYAKUSHO, { icon: myIcon2 }).bindPopup(popup2).bindTooltip("桜区役所").addTo(map); // ポップアップなし、アイコンなし //L.marker(COORD_SAITAMA_UNIV, {title:"埼玉大学", draggable:true}).addTo(map); //L.marker(COORD_SAKURA_KUYAKUSHO, {title:"桜区役所"}).addTo(map); // 事前作成した複数のマーカーを表示 var bound = L.latLngBounds(COORD_STOREs[0].pos, COORD_STOREs[0].pos); for (var num in COORD_STOREs) { var mk = COORD_STOREs[num]; var popup = L.popup().setContent(mk.name); L.marker(mk.pos, { title: mk.name }).bindPopup(popup).addTo(map); bound.extend(mk.pos); } // map.fitBounds(bound); // このマーカー群を初期表示位置にしたい場合はfitBoundsを使う(使わない場合はsetViewで設定した座標とズームが適用される) // -------------------- ベクタレイヤー(円・線・多角形、表示非表示) -------------------- // 円(北浦和駅を中心に1000mごとの円を4つ作成する) var circleGroup = L.layerGroup(); for (var i = 0; i < 4; i++) { var r = i * 1000 + 1000; circleGroup.addLayer(L.circle(COORD_KITAURAWA_STA, { radius: r, color: "#FF5555", fill: false, weight: 3 }).addTo(map) ); } // 円(地図の大きさに関係なく北浦和駅を円で囲む) var circleMarker = L.circleMarker(COORD_KITAURAWA_STA, { radius: 20, color: "#5555ff", weight: 2, fill: true, fillColor: "#ffffff", opacity: 0.5 }).addTo(map); // 線(北浦和駅から埼玉大学までの道路) polyline = L.polyline(COORD_ROUTE, { color: 'blue', weight: 5, bubblingMouseEvents: false // イベントがmapオブジェクトに連鎖するのを防ぐ }).addTo(map); // 線(onLineClick関数で使うので空座標で一旦登録しておく) pline = L.polyline([], { color: 'blue', weight: 5, bubblingMouseEvents: false // イベントがmapオブジェクトに連鎖するのを防ぐ }).addTo(map) // 多角形(埼玉大学の敷地) var polygon = L.polygon(COORD_SAITAMA_UNIV_SITE, { color: 'green', weight: 2, fill: true, fillColor: 'green', opacity: 0.5 }).addTo(map); // 各ベクタレイヤをオブジェクトにまとめて、画面にセット var overlay = { "polyline": polyline, "Circle": circleGroup, "CircleMarker": circleMarker, "polygon": polygon } L.control.layers(null, overlay).addTo(map); // -------------------- 地図画面上にDiv要素の追加(左上、この例ではマウス移動した時の座標を表記) -------------------- var latloninfo = L.control({ position: "topleft" }); latloninfo.onAdd = function (map) { //divを作成(最初は非表示、div上のとmousemoveイベントがmapに連鎖しないように設定) this.ele = L.DomUtil.create('div', "infostyle"); this.ele.id = "latlondiv"; this.ele.style.visibility = "hidden"; this.ele.onmousemove = function (e) { e.stopPropagation() }; return this.ele; }; latloninfo.addTo(map); // -------------------- イベント(click・mousemove) -------------------- map.on('mousemove', onMapMousemove); map.on('click', onMapClick); pline.on('click', onLineClick); } // // 地図上のクリック // - クリック地点の座標にマーカーを追加 // - マーカーがクリックされた場合は、onMarkerClick()を呼び出し // - クリックを繰り返すと線を引き続ける // function onMapClick(e) { L.marker(e.latlng).on('click', onMarkerClick).addTo(map); pline.addLatLng(e.latlng); } // // マーカーのクリック // - クリックされたマーカーを地図のレイヤから削除する // function onMarkerClick(e) { map.removeLayer(e.target); } // // 線のクリック // - クリックされた線を削除 function onLineClick(e) { pline.setLatLngs([]); } //地図上を移動した際にdiv中に緯度経度を表示 function onMapMousemove(e) { var box = document.getElementById("latlondiv"); var html = "緯度:" + e.latlng.lat.toFixed(6) + "<br>" + "経度:" + e.latlng.lng.toFixed(6); box.innerHTML = html; box.style.visibility = "visible"; } </script> </head> <body onload="init()"> <!-- 全画面表示するためにstyle設定 --> <div id="mapcontainer" style="position:absolute; top:0; left:0; right:0; bottom:0;"></div> </body> </html>
- 投稿日:2020-09-29T18:40:34+09:00
簡単にGoogle Maps API を実装してみよう!
1.Google Maps APIとは?
「Google Maps API」とは、Google社が提供している高機能で世界中の地図データを扱っているGoogleマップを、さまざまなサービスで利用できるようにしたものです。AndroidやiOS向けアプリやWebサービスにGoogleマップを使用することができます。
Google Maps APIを使用することで、Google Mapの機能をウェブサイトやアプリに埋め込むことができます。
Google Maps APIについての詳細は、公式ホームページに記載されています。公式ホームページ
https://developers.google.com/maps/?hl=ja
今回は、Google Maps APIを使って、ウェブブラウザに地図を表示させる方法について学習していきます。
2.APIキーの取得方法
Google Maps APIを使用するには、APIキーを取得しなければなりません。
APIキーを取得するために、あらかじめGoogleアカウントを準備しておいてください。Google アカウントの作成
https://accounts.google.com/signup/v2/webcreateaccount?hl=ja&flowName=GlifWebSignIn&flowEntry=SignUp&nogm=true2-1.Google Cloud Platform にアクセス
APIキーを取得するために、まずはGoogle Cloud Platformを開きます。
まずは、以下リンク先を開いてください。
Google Cloud
https://cloud.google.com/[コンソール]ボタンをクリックすると、次のような画面が表示されます。
2-2. プロジェクトを作成
次に、新規でプロジェクトを作成します。
画面左上にある、[プロジェクトの選択]ボタンをクリックすると、プロジェクトの選択画面が表示されます。
新規プロジェクトを作成するには、[新しいプロジェクト]ボタンをクリックします。
新規プロジェクト作成画面が表示されます。
プロジェクト名の欄に任意の名前を入力し、[作成]ボタンをクリックします。プロジェクトが完成しました!
2-3. プロジェクトの選択
作成したプロジェクトを選択します。
再び、画面左上の[プロジェクトの選択]ボタンをクリックします。
プロジェクト選択画面が表示されるので、先ほど作成したプロジェクトを選択し、[開く]ボタンをクリックします。
これで、プロジェクトが選択されました。
2-4.APIの有効化
選択したプロジェクト内で、APIを有効化します。
画面左メニューから、[APIとサービス]にカーソルを合わせ、さらに、[ライブラリ]をクリックします。APIライブラリが表示されます。
"Maps JavaScript API"を検索し、クリックします。
Maps JavaScript APIの概要画面が表示されます。
[有効にする]ボタンをクリックし、APIを有効化します。これで、APIが有効化されました。
2-5. APIキーを取得
APIの有効化が完了すると、プロジェクトの[APIとサービス]の[ダッシュボード]に、有効化したAPIが追加されます。
ダッシュボードに表示されたAPI一覧から、"Maps JavaScript API"をクリックすると、次のような画面が表示されます。
[認証情報]タブをクリックします。
画面が表示されると、認証情報作成のウィンドウが開きます。[認証情報を作成]ボタンをクリックし、さらに[APIキー]ボタンをクリックします。
Maps JavaScript APIのAPIキーが作成されました。
ウィンドウにて、取得したAPIキーを確認することができます。
[キーを制限]ボタンをクリックし、次の設定に進みます。APIキーの制限設定画面が表示されます。
ここでは、そのままの設定で[保存]ボタンをクリックします。Maps JavaScript APIの[認証情報]タブに、取得したAPIキー情報が表示されます。
これで、APIキーを取得できました。
3.コーディング
取得したMaps JavaScript APIのAPIキーを用いて、コーディングを行なっていきます。
今回は、簡単なサンプルを用いて、Google Maps APIによる地図の表示方法を紹介します。3-1.サンプルコード
最も簡単な、マップを表示するだけのサンプルコードを紹介します。
以下のサンプルコードでは、ブラウザに指定した座標位置のマップを表示します。・ APIをロード
index.html<head> <script src="http://maps.google.com/maps/api/js?key={APIキー}&language=ja"></script> </head>index.htmlのhaedタグに上記のコード記入し、Maps JavaScript APIをロードを行います
{APIキー}の部分には、先ほど取得したAPIキーを入れてください。・ マップの表示
bodyタグの中にdivで、マップ(id:map)をブラウザ画面上に配置します。
index.html<body> <div id="map"></div> </body>・ スタイルの指定
マップ(id:map)のスタイルを、高さ(height)500px、幅(width)100%に設定します。
style.css#map{ height:500px; width:100%; }・ スクリプトの記述
マップ(id:map)を表示するためのスクリプトを、bodyタグの一番下にscriptタグで記述します。
index.html<script> var MyLatLng = new google.maps.LatLng(35.6811673, 139.7670516); var Options = { zoom: 15, //地図の縮尺値 center: MyLatLng, //地図の中心座標 mapTypeId: 'roadmap' //地図の種類 }; var map = new google.maps.Map(document.getElementById('map'), Options); </script>以下に、スクリプトの中身を解説します。
・ 座標指定 LatLngクラスのインスタンスより、座標を取得します。 ここでは、緯度:35.6811673、経度:139.7670516 を指定し、取得した座標情報を変数MyLatLngに入力しています。
var MyLatLng = new google.maps.LatLng(35.6811673, 139.7670516);・ オプション設定 表示するマップのオプションを設定します。 設定した内容は、変数Optionsに入力しています。
var Options = { zoom: 15, //地図の縮尺値 center: MyLatLng, //地図の中心座標 mapTypeId: 'roadmap' //地図の種類 };ここでは、zoom、center、mapTypeidの3項目について設定をしています。
center項目については、先ほど座標指定にて取得した座標情報(変数:MyLatLng)を指定しています。
設定項目 説明 zoom 必須項目。マップの縮尺値を設定する。 center 必須項目。マップの中心座標地を設定する。 map Typeid 初期に表示されるマップの種類を設定する。デフォルトはROADMAP。 上記以外にも、設定項目は存在します。
オプションの設定項目について詳細は、公式サイトのリファレンスで確認できます。Google Maps Platform - Map Options interface
https://developers.google.com/maps/documentation/javascript/reference/3.exp/map?hl=ja#MapOptions・ マップ作成 設定したオプション設定(変数:Options)を指定し、Mapクラスを利用して、マップを作成します。
var map = new google.maps.Map(document.getElementById('map'), Options);3-2 画面
ブラウザで確認すると、下図のような画面が表示されます。
Googleマップの画面が表示されれば成功です。
なお、誤ったAPIキーを入力して、htmlファイルを実行すると、以下のようにエラー画面が表示されます。この場合はhtmlファイルを見直してください。
* 自分はAPIの使うにあたり支払い設定をしていませんでした!
最後に注意しておきたいのが、新しい料金体系では、毎月200ドル分は無料で使用することが可能ですが、それを超えると利用料金を支払う必要があります。
Google Maps Platform - お客様のニーズを満たす柔軟な価格プラン
https://cloud.google.com/maps-platform/pricing/?hl=ja個人で検証用に利用する範囲であれば、無料枠を超えることはあまりなさそうですが、
念のため、APIの割り当で上限を設定しておくとよいかもしれません。
- 投稿日:2020-09-29T17:54:33+09:00
【React】styled-componentsを導入から適応まで
styled-componentsとは?
React
内でCSSを適用させるための一つの手段です。直感的で使いやすいので、今回は
styled-components
まとめてみます。基本的な構文
import * as React from "react"; import styled from "styled-components"; const Title = styled.h1` color: blue; `; const Header = <Title>ブログ</Title>;解説していきますね。
import styled from "styled-components";
上記で
styled-components
の機能をimport
します。const Title = styled.h1` color: blue; `;上記で、
<Title>ブログ</Title>
をスタイリングしています。下記に構文をまとめておきます。
const <要素名> = styled.<タグ名>` // ここにあてたいCSSを書く。 `;導入
npm i styled-components @types/styled-componentsnpmでインストールします。僕の環境では
typescript
を導入しているので、型定義も一緒にインストールしています。あとは、最初の方に解説したような形でスタイリングできます。
以上です。
- 投稿日:2020-09-29T17:19:52+09:00
長すぎるURLのWikipedia記事を、簡潔なpageID形式のURLで読み込み直すブックマークレット【2020年版】
はじめに
長いURLは引用する時、ちょっと厄介です。
そこで今回はウィキペディアのURLをシンプルにするやつを作りました。
このコード自体が、拍子抜けするぐらいシンプルです。javascript:location.href=location.origin+'/?curid='+RLCONF.wgArticleId;説明
Wikipediaのscriptタグを覗くと、
RLCONF
というobjectの中に、それらしきIDが見つかったので拝借しました。
ページ情報については、開発者ツールのコンソールで次のように入力すると確認できます。console.log(RLCONF);あとがき
javascript:location.href=location.origin+'/?curid='+wgArticleId;
RLCONF.
の部分を省略しても動くという噂がありますが検証していません。
試してみる価値はありそうです。よろしくお願いします。本日もありがとうございました。
参考
https://qiita.com/yubessy/items/16d2a074be84ee67c01f
https://ja.wikipedia.org/wiki/Category:長大な項目名
https://developer.mozilla.org/en-US/docs/Web/API/Location/origin
- 投稿日:2020-09-29T15:32:52+09:00
【ハイドラ】RPGツクールMZ プラグイン入門、そして画面に額縁を作成した【エメドラ】
RPGツクールMVを始めようと思い、はや3年。
何かモチベが上がらんわーと思っていたところRPGツクールMZが発売されました。今回はATB(アクティブタイムバトル)が追加されたということで、
「よっしゃ、やるで」と思った訳ですが。何か物足りない。
そう、画面に額縁が無いのだ。画面に額縁が無い限り、オレのやる気はゼロだ。
プラグイン作ればいいじゃん。
あれやろ、HTML5だろ?オレReactだよヨユーじゃん。
自分もそう思ってた時期がありました。あかん、全く解らん。
ここからスタート。
プラグインの情報もほとんどない。
なるほど、これは私が入門書を書くしかないのですね。というわけで、流行りのZennに入門書を書いてみました。
RPGツクールMZ 「伝統的RPGデザイン」プラグイン作成入門が、Qiitaのほうが気楽に書ける気がするので
プラグイン作成入門をこちらにも書いてみます。
ネタ投稿はQiitaのほうが良いですよね。∧∧∧∧∧∧∧∧∧∧∧∧∧∧∧∧∧∧∧∧ < Qiita! 圧倒的SEO! Qiita! 圧倒的SEO! > ∨∨∨∨∨∨∨∨∨∨∨∨∨∨∨∨∨∨∨∨ ( ゚∀゚)o彡°( ゚∀゚)o彡°( ゚∀゚)o彡°画面の枠を作成するにはどうすればいいのか
幸い、デフォルトでRPGツクールMZはタイトル画面のみ枠を付ける機能がありました。
ここを改造してみましょう。rmmz_scenes.jsScene_Title.prototype.createBackground = function() { this._backSprite1 = new Sprite( ImageManager.loadTitle1($dataSystem.title1Name) ); this._backSprite2 = new Sprite( ImageManager.loadTitle2($dataSystem.title2Name) ); this.addChild(this._backSprite1); this.addChild(this._backSprite2); };このloadTitle2というのがデフォルト枠画像のようです。
ImageManager.loadTitle2($dataSystem.title2Name)こいつを
ImageManager.loadTitle2("titleframe")こうします。
では、これでSamplePlugin.jsを作成してみましょう。
SamplePlugin.js//============================================================================= // RPG Maker MZ - Sample Plugin //============================================================================= /*: * @target MZ * @plugindesc A sample for practice. * @author *** * * @help SamplePlugin.js * * Here's a description. */ /*:ja * @target MZ * @plugindesc 練習用のサンプルです。 * @author *** * * @help SamplePlugin.js * * ここに説明文を書きます。 */ (() => { Scene_Title.prototype.createBackground = function() { this._backSprite1 = new Sprite( ImageManager.loadTitle1($dataSystem.title1Name) ); this._backSprite2 = new Sprite( ImageManager.loadTitle2("titleframe") ); this.addChild(this._backSprite1); this.addChild(this._backSprite2); }; })();画像はこちらに置いておきました。
titleframe.pngこのSamplePlugin.jsとtitleframe.pngをプロジェクトに配置しましょう。
- [プロジェクトフォルダ]/js/plugins/SamplePlugin.js
- [プロジェクトフォルダ]/img/titles2/titleframe.png
ツール > プラグイン管理 > プラグインリスト > プラグインの設定
画面解像度も変更しましょう。
名前 値 画面の幅 1280 画面の高さ 800 UIエリアの幅 1180 UIエリアの高さ 700 それではテストプレイしてみましょう。
やはり格調高い。そしてこれをゲーム画面に実現したい訳です。
今回は、流行りのZennに記事を書いてみたご報告と
改めてQiitaの良さを実感した所感のご報告でした。
- 投稿日:2020-09-29T15:04:11+09:00
レガシー環境でJavaScript初心者が強くなれた瞬間
JavaScript初心者が強くなれた瞬間
現在の常駐先に来て、JavaScriptを書かせていただく機会がとてもありました。
そこで、私が「JavaScript初心者」から「少しは分かる経験者」になった(と信じたい)と思うので
理解が深まったきっかけとなるエピソードを振り返りたいと思います。常駐先と業務内容
- 業態:某大手WEBメディア
- 使用技術:HTML,CSS,jQuery(1系)。動的ページはバックエンドエンジニアがJSPで生成。
- 業務内容:ページを0から作成ということはあまりやらない。既存ページをoverrideするような形でJavaScriptをゴリゴリ書いて、 ABテストのためにUIやデザインを改造することが多い。
JavaScriptの理解度
常駐開始前
- フロントエンジニア歴半年(エンジニアになるまでは営業職)
- HTML,CSSはなんとなくであるが自分でも書けた。
- JavaScriptはコピペをもとにしてしか書けないコピペエンジニア (コピペした内容もなんとなくしかわからない)
常駐開始後1年
- フロントエンジニア歴1年半年
- 他のエンジニアが書いたコードをレビューできるレベル
- コピペはするが、内容も理解できるし自分で考えて0から書くこともできるようになった
なぜ強くなれたのか。
既存ソースを触らず、JavaScriptでぺージの見た目を変える必要があったから。
通常のWEB制作では既存ソースを触れることが多いのではないでしょうか?
要件がカセットの新デザイン化であれば、HTML,CSSで基本完結し、もしも動的要素の追加があればPHPなどのバックエンド言語を使うなど。ただ、僕の常駐先では、既存ソースをを一切触らずに様々な要件をJavaScript一本で実装する必要がありました。
上記の様なカセットの新デザイン化であれば
- 対象となる要素の取得
- CSSを当てるためにクラスの追加。
- CSSファイルの追加読み込み
- レイアウト変更があれば、DOMの改造
- 追加となる動的要素があれば「同ページのどこかから拾ってくる」「拾えなければajaxでAPIからデータをとってくる」
などの実装を既存ソースをを一切触らず、追加したJavaScriptソースだけで行う必要がありました。
なので、自然と様々な処理を書く機会ができ、
そうすると「JavaScriptでできること」から逆算し、「ここはこういう風にすればできるのではないか。」
といった思考が身につきました。
それが強くなれた一因かなと思います。レガシーだったから。
僕の常駐先ではreactやvueといったモダンな環境ではなく。
JavaScriptはどんなに複雑な要件でもjQueryの1系で書き切っていました。内容はあまりは言えませんが、要件的にググって全部のコードが出てくる様な単純なものはなく
複雑な要件のものが多かったです。ReactやVueでは意識せずにかける事も
jqueryだと、JavaScriptできることを理解できていないと
書けないです。もしも、僕がいきなりReactやVueを使う常駐をしていたら
JavaScriptの基本的な部分は理解できていなかった気がします。その中で理解が深まった瞬間
処理の実行順序を意識してかけるようになった時
JavaScriptの実行順序は基本的に「上から順番」ですが、例外あります。
それが
* document.ready
* window.load
* 非同期処理
です。常駐開始直後の僕は上記の概念を全く理解していませんでした。
通常の静的なサイト制作にあたっては、
1人のフロントエンジニアがJavaScriptを書き切ることが多いのでは
ないでしょうか。僕も常駐開始前までは、基本自分だけで完結するような制作して来なかったので、
「上から順番」という原則だけしか理解していなくてもなんとかなったのではないかと思います。ただ常駐を開始してからは「既存js処理にさらに自分の処理を追加して書く」ということが当然となり、
jsの実行順序を意識しないと、意図通りの挙動にならないということがよくありました。既存JavaScript処理と共存していくためには 既存JavaScript処理がどのタイミングでなにをしているのか。を正しく理解しましょうということです。
どのように使えば良いかは以下の記事が参考になります。
https://qiita.com/katsunory/items/3ba4683629333b94b2be
処理を区切って関数化する。returnをうまく使う。高階関数を使う。
常駐前までに書いていたjsはどちらかというとググれば出てくる「スライダー処理」「文字きり」などがメインでした。
しかし常駐先ではどちらかというと「JavaScriptの基本」を組み合わせて要件を実装する。ということ必要でした。「JavaScriptの基本」というのは
* 文字列や、属性値をDOM要素から取得する
* 配列の中身を捜査、加工したもの配列を生成する
* 真偽値によって処理を分岐させるなど、です。
これらを組み合わせて、処理を書いていく時に大切なことは処理を区切って関数化する。
returnをうまく使う。
高階関数を使う。です。
例えば以下のような処理です。(わかりづらかったらすみませんmm)
returnされた値や高階関数を使っています。
関数caliculate
は引数にとったisInit
がtrueなら、
引数にとったコールバック関数result
に引数num
の値を引き渡し実行させます。jsをかける人からしたら当然の概念かもしれませんが、
僕はこれらを使えるようになってから、かなりできることの幅が広がったし、
わかりやすくかける様になったと思います。
なので、初学者の方に、ぜひ理解しておいていただきたい部分です。
高階関数についてはこちらが参考になります。https://qiita.com/may88seiji/items/8f7e42353b6904af5e9aconst pass = 'GO' const baseNum = 20 // 引数が'GO'ならtrueを返す const validatePass = (pass) => { return pass === 'GO' } // 2倍する const double = (num) => { return num * 2 } // 3倍にして出力 const result = (num) => { console.log('result is ' + num * 3 ) } // 条件が真ならコールバックを実行 const caliculate = (isInit, num,callBack) => { if (isInit) { callBack(num) } } caliculate(validatePass(pass), double(baseNum),result) // result is 120まとめ
上記の概念を理解した瞬間が自分のJavaScriptへの理解が深まったな。と感じる瞬間でした。
JavaScriptがゴリゴリかける方には当然の概念かもしれませんが、
駆け出しエンジニアの方の参考になればと思います。
- 投稿日:2020-09-29T13:58:50+09:00
YellowfinのAPIとwebserviceを使ってダッシュボードのレポートを全てExcelでダウンロードする
やりたいこと
またまたタイトル通りなのですが、前回の記事FetchでExcelファイルがダウンロードできるページに条件をPOSTしてブラウザダウンロードするでExcelのダウンロードをできるようになったので、Yellowfinのダッシュボードにある全てのレポートをExcelでダウンロードしたいってところです。
検証しているのはversion9.2.2ではBaseAPI,reportAPI,filterAPI,DashboardAPIとコードモードでいろいろなことができるようになっているので、そこで各レポートの情報を取得し、foreachで回す感じです。前準備をします
ダッシュボードを作成後に、コードwidgetからボタンのwidgetをドラッグ・アンド・ドロップして名前をつけます。例)export
これは次のJSタブに記述する部分で使います。また、Excelをエクスポートするwebservice用スクリプトを/Yellowfinインストールディレクトリ/appserver/webapps/ROOT/の下に配置します。
これはExcel以外にも対応してるのでPOSTするキーのformatを他のPDF・CSVに変更してもformatをそれぞれに合わせれば使用できます。
また何もしてしなければPDFファイルで出力されます。output.jsp<%@ page language="java" contentType="text/html; charset=UTF-8" %> <%@ page import="java.util.*, java.text.*" %> <%@ page import="com.hof.mi.web.service.*" %> <%@ page import="java.net.URLEncoder" %> <% String host = "localhost"; Integer port = 8080; String userid = "admin@yellowfin.com.au"; String password = "test"; String orgid = "1"; String uuid = request.getParameter("uuid"); String fname = request.getParameter("fname"); String path = "/services/ReportService"; String suffix = ""; String format = request.getParameter("format"); String contentType = "application/octet-stream"; if (format == null) format = "PDF"; if (format.equals("CSV")) { contentType = "text/comma-separated-values"; suffix = ".csv"; } else if (format.equals("PDF")) { contentType = "application/pdf"; suffix = ".pdf"; } else if (format.equals("XLS")) contentType = "application/vnd.ms-excel"; else if (format.equals("XLSX")) { contentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"; suffix = ".xlsx"; } else if (format.equals("RTF")) contentType = "application/rtf"; else if (format.equals("TEXT")) contentType = "text/tab-separated-values"; HashMap filters = new HashMap(); boolean cleared = filters.size() == 0; Iterator f = request.getParameterMap().keySet().iterator(); while (f.hasNext()) { String key = (String) f.next(); if (key.startsWith("filter")) { if (!cleared) { filters.clear(); cleared = true; } String value = request.getParameter(key); int pipeIndex = value.indexOf("|"); if (pipeIndex == -1) continue; key = value.substring(0, pipeIndex); value = value.substring(pipeIndex + 1); filters.put(key, value); } } ReportServiceClient rsc = new ReportServiceClient(host, port, userid, password, path); i4Report report = rsc.loadReportForUser(uuid, userid, password, orgid); f = filters.keySet().iterator(); while (f.hasNext()) { String key = (String) f.next(); String value = (String) filters.get(key); report.setFilter(key, value); } HashMap elementStorage = new HashMap(); report.run(elementStorage, format); response.setContentType(contentType); fname = fname + suffix; String encodedFilename = URLEncoder.encode( fname , "UTF-8"); response.setHeader("Content-Disposition","attachment;" + "filename=\"" + encodedFilename + "\""); java.io.BufferedOutputStream o = new java.io.BufferedOutputStream(response.getOutputStream(), 32000); o.write(report.renderBinary()); o.flush(); %>ダッシュボードのJSタブ
ここに前回やった部分を少し改造して全てのレポートをエクスポートできるようにDashboardAPIを駆使します。
let button = this.apis.canvas.select('export');の部分でダッシュボード上に設置したボタンの名前を指定することでこれをeventlistenerで取得できます。
他の前回の記事で解説していないこととしては、こちらの部分です。
var dash = this.apis.dashboard;
var allrep = dash.getAllReports();
これはYellowfinのdashboardAPIを呼び出し、getAllReports()でダッシュボード上のレポート情報を全て取得しています。
該当のYellowfinのwiki部分
それぞれのレポートをfoeachで回し、uuidとformatをbodyで渡してリンクを生成させてます。レポート名も取得したい場合はさらにwebservice用のjsp作らないといけないのでまた次の機会に。
JSタブthis.onRender = function () { // ここにコードを記述します。これは、イベントリスナーを設定するのに理想的な場所です let button = this.apis.canvas.select('export'); button.addEventListener('click', () => { var dash = this.apis.dashboard; var allrep = dash.getAllReports(); allrep.forEach( item => { var uuid = item.reportUUID; var obj = { fname: uuid, uuid: uuid, format: 'XLSX' }; var method = "POST"; var body = Object.keys(obj).map((key)=>key+"="+encodeURIComponent(obj[key])).join("&"); var headers = { 'Accept': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8', 'responseType' : "blob", }; fetch("./output_file.jsp", {method, headers, body}) .then((res)=> res.blob()) .then(blob => { let anchor = document.createElement("a"); anchor.href = window.URL.createObjectURL(blob); anchor.download = uuid+".xlsx"; anchor.click();}) .then(console.log) .catch(console.error); }); }); };結果
ダッシュボードに設置しているレポートがどんどん落ちてきます。たぶん初回はブラウザ側でダウンロードの許可を求めるダイアログが出るので許可してあげれば大丈夫です。
※今の所レポートにダッシュボードで使用していたフィルターの内容を渡せないようなので、これがやりたい場合は、動的ではなく予め条件毎にダッシュボードを作っておく必要があります。。。この辺がなんとかなればかなり使えると思うんですけどね。
- 投稿日:2020-09-29T13:06:29+09:00
連想配列のメンバ
メソッド 概要 size 要素数が返り値 set(key.val) キーとバリューのペアをオブジェクトにセット get(key) 指定したキーの値を取得する delete(key) 指定したキーの値を削除する has(key) 指定したキーの要素の有無を確認する keys() すべてのキーの値を取得する values() すべての値を取得する entries すべてのキー、値のペアを取得する
- 投稿日:2020-09-29T13:06:29+09:00
map連想配列のメンバ関数リスト
メソッド 概要 size 要素数が返り値 set(key,val) キーとバリューのペアをオブジェクトにセット get(key) 指定したキーの要素を取得する delete(key) 指定したキーの要素を削除する has(key) 指定したキーの要素の有無を確認する keys() すべてのキーの値を取得する values() すべての値を取得する entries すべてのキー、値のペアを取得する
- 投稿日:2020-09-29T12:49:50+09:00
nodenvをupdateして新しいバージョンのNode.jsをインストールする
nodenvで欲しいバージョンのnodeが見つからない……
プロジェクトで指定されたバージョンのNode.jsをインストールしようとして
nodenv install --listとうっても、欲しいバージョンがリストに出てこないときってありませんか?
どうやらnodenvはインストール時に登録されているバージョンしか表示してくれないようです。
調べてみても、nodenvを消してから再インストールするみたいな、ええっ?という情報が多いです。
nodenvをアップデートする一番簡単な方法
anyenvのプラグインであるanyenv-updateを使いましょう。
※ そもそもanyenv使っていない人は、こちらなどが参考になります。anyenv、オススメです。
anyenv-updateのインストール
anyenvのルートディレクトリ(デフォルトでは
~/.anyenv
)の下に、pluginsディレクトリを作り、リポジトリをクローンします。mkdir -p $(anyenv root)/plugins git clone https://github.com/znz/anyenv-update.git $(anyenv root)/plugins/anyenv-updatenodenvのアップデート
コマンドをうつだけです。かんたんですね?
anyenv update※ anyenvで管理しているnodenv以外のツールもアップデートしてくれます
anyenv使いたくないよって人は……
手動でリストをアップデートする
公式のREADMEに方法が載っていました。
node-buildというデフォルトのプラグインのリポジトリを最新にすればよいらしいです。
cd ~/.nodenv/plugins/node-build git pull
nodenv-updateを使う
nodenv-updateというプラグインがあるようです。
# インストール mkdir -p "$(nodenv root)"/plugins git clone https://github.com/nodenv/nodenv-update.git "$(nodenv root)"/plugins/nodenv-update # アップデート nodenv updateおわりに
参考になれば幸いですっ。
- 投稿日:2020-09-29T12:25:11+09:00
Mochaで実行時に特定のテストをスキップする
こんな感じで
this.skip()
を使えばOK。const assert = require('assert'); const os = require('os'); suite('テスト', function(){ test('Winの場合はスキップ', function(){ if (os.platform() == 'win32') this.skip(); assert.ok(1); }); });
this
が必要なので、アロー関数内のテストだとダメ。関数で囲うべし。
- 投稿日:2020-09-29T12:16:38+09:00
コールバック関数
- 投稿日:2020-09-29T12:16:38+09:00
JavaScriptの関数
コールバック関数
何らかの動作後に実行したい関数を実装する手段。
引数として関数を渡すことで、任意のタイミングで関数の処理実行を実現している。test.jsfunction func(callback) { console.log('word1'); callback(); } func (function(){ console.log('word2'); }); //実行結果は word1 word2と順に表示される。巻き上げ
verを使用して関数内で変数を宣言したばあい、全てその関数の先頭で宣言されたものとみなされる。
これは、関数内のどこでも変数を宣言できる代わりに受ける制約。test.jsver a = 'word1'; function func() { console.log(a); //undefined ver a = 'word2'; console.log(a); //word2 } f();実際の動作
test.jsver a = 'word1'; function func() { ver a; console.log(a); //undefined a = 'word2'; console.log(a); //word2 } f();即時関数
関数の定義と同時に実行する手段。
再利用しない場合などに使用できる。test.js(function(a, b) {console.log(a + b);}(1, 2)); //3
- 投稿日:2020-09-29T11:55:08+09:00
JavaScript: プラグインシステムとプロトタイプ汚染攻撃
この投稿では、JavaScriptで実装されたプラグインシステムにおいて脅威になりうる、プロトタイプ汚染攻撃の手法を説明します。
対策についてはまだ検証中なので説明しませんが、プラグインシステムを設計するにあたって、プロトタイプ汚染攻撃が脅威であることを一度整理しておきたかったので記事にしました。
プロトタイプ汚染攻撃とは
プロトタイプ汚染攻撃(prototype pollution attack)とは、ざっくり言うと、攻撃者がJavaScriptオブジェクトのプロトタイプを書き換えることで、情報を盗んだり、オブジェクトの振る舞いを変更したりする攻撃のことです。
JavaScriptの言語的特徴を巧妙に悪用した手口
JavaScriptでは、オブジェクトを生成すると、プロトタイプオブジェクトと呼ばれる生成元のオブジェクトへの関連を持つことになります。
例えば、
Foo
オブジェクトを定義した場合、class Foo { doSomething() { console.log('Hello World!') } }
Foo
オブジェクトにはprototype
というプロパティが生えて、そこにメソッドが入ります:console.log(Foo.prototype.doSomething) //=> [Function: doSomething] Foo.prototype.doSomething() //=> "Hello World!"この
Foo
オブジェクトをnew
すると、新しいオブジェクトを生成でき、const foo = new Foo()そのオブジェクトは
doSomething
メソッドを提供するようになりますが、foo.doSomething() //=> "Hello World!"このメソッドは、実は
Foo.prototype
のメソッドと同じものです:console.log(Foo.prototype.doSomething === foo.doSomething) //=> true以上の動作を見ると分かりますが、
Foo.prototype
とfoo
には関連があり、foo.doSomething
を呼び出した際には、その処理はFoo.prototype.doSomething
に移譲されているのです。もしここで、
Foo.prototype.doSomething
を置き換えると、Foo
をnew
して作られたオブジェクトのdoSomething
メソッドをまるっきし別の処理にすることができてしまいます:// 置き換える処理 Foo.prototype.doSomething = function () { console.log('Goodbye World!') } foo.doSomething() //=> "Goodbye World!"
foo
はメソッド置き換え前にnew
されたものですが、それにも関わらず、後付で行われた置き換えの影響を受けているのが分かります。以上のデモで分かったように、JavaScriptはプロトタイプを書き換えることが可能で、プロトタイプの書き換えは生成されたオブジェクトにも影響を与えることができるといった言語的特徴があるのです。
これは良く使えば、pollyfillなどに活用できる便利でいい機能ですが、見方を変えると、特定の処理をぶんどることであらゆる悪用が可能な危険な機能でもあるのです。プロトタイプ汚染攻撃は、この言語的特徴を悪用した手口なのです。
プロトタイプ汚染攻撃はプラグインシステムで脅威になる
典型的なプロトタイプ汚染攻撃は、脆弱性があるサーバに対して一定のJSONを送ることで実現します。この典型的な攻撃についての解説は、他の文献に譲りたいと思います。
- Node.jsにおけるプロトタイプ汚染攻撃とは何か - ぼちぼち日記
- JavaScriptのプロトタイプ汚染攻撃対策は難しい - Qiita
- __proto__の除去でNode.jsのプロトタイプ汚染を防げないケース - knqyf263's blog
この典型的なプロトタイプ汚染攻撃を実現するためには、プロトタイプ汚染を引き起こしうる脆弱性がないとなりません。したがって、JSONをサーバに送りつけるような典型的な攻撃は、対応が難しいにしても、脆弱性を塞いでしまえば防衛できるはずです。
この投稿でテーマとしたいのは、プラグインシステムでのプロトタイプ汚染攻撃です。
プラグインシステムを持ったシステムはどいうものかというと、例えば、WordPressやATOMエディタです。WordPressやATOMなどは、コアの機能をプラグインを追加することで拡張することができます。
そして、プラグインは誰もが開発することができます。そして、ユーザは配布されたプラグインを自由にインストールすることができます。
すべてのプラグインが善良なものならいいですが、悪意を持ってプラグインを実装することも考えられます。プラグインは普通、制限なしに任意のコードが実行できるようになっているので、悪意を持ってプラグインを開発すると、プロトタイプ汚染攻撃は高い自由度を持って実現できてしまいます。(もちろん可能な悪用はプロトタイプ汚染だけに限られませんが、ここでは他の攻撃については取り上げません)
悪意を持ったプラグインに、プロトタイプ汚染攻撃のコードが仕込んであると、それに気づかずインストールしたユーザは機密情報を盗まれたりといった被害に遭うことが考えられます。
ユーザが信頼できるプラグインのみをインストールすることで、こうした攻撃を未然に防ぐことができるかも知れません。ユーザによる自衛です。ただ、これはユーザに一定の見定め能力を求めるので、どんなユーザが使うか分からないシステムでは、なかなか根本的な解決になりにくいと考えられます。
プラグインシステムを備えたアプリでは、
- プラグイン開発者が信頼できるとは限らず、
- ユーザの自衛に100%頼ることが難しい
そういったシステムは決して少なくないと思います。こうした環境があいまって、プラグインシステムではプロトタイプ汚染が脅威となりうると考えています。
プラグインシステムにおけるプロトタイプ汚染攻撃のデモ
プラグインシステムでどのようにプロトタイプ汚染攻撃が実現されるのか、単純なデモをやってみようと思います。
まず標的となる、機密データを扱う善良なオブジェクト
Innocent
があるとします。innocent.jsclass Innocent { handleSecretData(secret) { console.log('善良なコードが機密データを受け取りました: %o', secret) } } exports.Innocent = Innocentシステム本体ではこの
Innocent
に機密データを渡す処理と、プラグインをロードする処理があるとします:system.jsconst { Innocent } = require('./innocent') // プラグインをロードする処理 const fs = require('fs') const files = fs.readdirSync('./plugins') for (const file of files) { require('./plugins/' + file) } // 機密データを扱う部分 const innocent = new Innocent() innocent.handleSecretData('ひみつ')かなりプラグインシステムを簡略化してはいますが、このコア機能では
./plugins
に置かれたJSファイルをrequire
する形でプラグインのロードを行います。なので、ユーザは./plugins
にJSファイルを置くことで、システムにプラグインを追加することができます。続いて、ユーザが何も知らずに、悪意あるプラグインをインストールしてしまったとします。そのプラグインのコードは次のように、
Innocent
オブジェクトのプロトタイプのhandleSecretData
メソッドを上書きする形で、プロトタイプ汚染するようになっています:plugins/evil-plugin.jsconst { Innocent } = require('../innocent') // Innocentに対してプロトタイプ汚染するコード const originalFunction = Innocent.prototype.handleSecretData Innocent.prototype.handleSecretData = function (...args) { console.log('悪意あるプラグインが機密データを盗みました: %o', args[0]) return originalFunction.apply(this, args) }この状態で、システム本体を動かすと、悪意あるプラグインが
Innocent
オブジェクトの処理に割り込んで、機密データを盗んでいくようになります:$ node system.js 悪意あるプラグインが機密データを盗みました: 'ひみつ' 善良なコードが機密データを受け取りました: 'ひみつ'プラグインシステムにおけるプロトタイプ汚染の対策の理想形
プラグインシステムを設計する立場として理想形を考えると、
- ユーザの能力に頼ることもなく
- プラグインの開発者を認可制度などで制限することもなく
- プラグインのマーケットプレイスなどが安全性を評価することもなく
プロトタイプ汚染攻撃を最小化できる技術的な仕組みが欲しいところです。
そういう仕組みがあると、プラグインの開発が誰でも行え、自由に配布できるといった発展性があるエコシステムを維持したまま、ユーザはより安心してプラグインをインストールできるようになると思います。
具体的な対策については、考えているものがありますが、もう少し研究してから記事にしたいと思います。
もし何か対策アイディアがあればコメントなどで頂けると嬉しいです。
- 投稿日:2020-09-29T11:37:31+09:00
Rails+JavaScript(jQuery)でタイムレスな検索を
記事で扱う検索機能
こんな感じの検索機能です。
今回は、shopsというテーブルからnameというカラムを抽出してビューに表示していきます。
※店舗情報を複数登録済み。そこから任意の店舗を任意のカラムで検索します。
- 非同期で行う
- 文字を打つごとに検索を行い、描画する
- 検索結果をクリックで該当のページにリンクする
おそらくですが、初学者のプログラミングにおける知識レベルや常識レベルって経験者の方が考えるそれよりもずっと低い水準にあると思うんです。
なので、自身にできる範囲内で「そんな事まで説明する必要あるの?」という事も書いて行こうかと思います。開発環境
mac
Ruby 2.5.1
Rails 5.2.3
jQuery必要なファイル
まず、実装に必要となるファイルを以下にまとめます。
- 検索を行うアクションを定義したコントローラーの準備
app/controllers/shops_controller.rbdef search end
- 上記アクションに対応するjson.jbuilderの準備
views/shops/search.json.jbuilder#上記で作成したコントローラー及びアクションに対応するようデイレクトリ関係とファイル名に注意 #この場合、views/shops(コントローラー名に対応)/search(アクション名に対応).json.jbuilder
- 上記アクションに対するルーティングの準備
config/route.rbresources :shops do #shopsコントローラー内の collection do get 'search' #searchアクション end end
- 検索フォームと結果のを表示するビューファイルの準備
app/views/shops/index.html.haml#今回は、shops一覧画面に検索を表示します。 #hamlを使って書いていきますが、通常のhtmlでもslimでも構いません。
- jsファイルの準備
app/assets/javascripts/search.js#.jsで終わっていれば名前はなんでも構いません
以上、5つのファイルを使って検索機能を実装していきます。
どう動くの?
検索処理の流れは以下の通りです。
検索フォームで起こったイベントを.jsファイルで受け取る。
○○.html.haml(○○.html.erb) → ○○.js
受け取った情報をjson形式でコントローラへ流す。
○○.js → ○○_controller
コントローラーでDBから必要な情報を抽出し、その情報を.js内でも使えるようjbuilderで変換。
○○_controller → ○○.json.jbuilder
再度js側に送り、htmlへの描画を行う。
○○.json.jbuilder → ○○.js → ○○.html.haml
処理の流れが分かっていないと、エラーが起きた際に対処できず1時間も2時間も無駄にすることになってしまいます。
これから記載していく実際のコードには可能な限り説明を入れますので、ぜひ上記の処理の流れを頭に入れた状態で読み進めてみて下さい。では、始めていきましょう。
ルーティングを行う
今回は例として、shops_controller内にsearchアクションを設定します。
app/controllers/shops_controller.rbdef search endconfig/route.rbresources :shops do #shopsコントローラー内の collection do get 'search' #searchアクション end end検索フォームと結果表示欄の準備
app/views/shops/index.html.haml.search-field .fas.fa-search #虫眼鏡のfontawesomeを使用しています。 .shop_search = f.text_field :text, placeholder: "店舗・住所で検索", id: "shop_search" #テキスト入力欄を設置。 #shop_search--result ←コメントアウトではなくshop_search--resultというid名です。検索結果を表示。 .shop-search-list検索フォームに入力された情報を受け取り、受け取った情報をコントローラへ送る
app/assets/javascripts/search.js$("#shop_search").on("keyup", function(){ let input = $("#shop_search").val(); $.ajax({ type: 'GET', url: '/shops/search', data: {keyword: input}, dataType: 'json' })まず、検索フォーム(hamlファイル内のf.text_field)に入力された情報をjsファイルで受け取ります。
┗検索フォームにつけたid名を指定し、文字入力の際にキーボードから指を話した瞬間にkeyupで情報を取得。ajax以下でその情報を
・どのような方法で
・どこに
・何を
・どのような状態で
送るかを指定します。この場合、GETメソッドでshopsコントーラ内のsearchアクションにinputをjson方式で送るということになるかと思います。
コントローラーでアクションの設定、DBとのやりとりを記載
app/controllers/shops_controller.rbdef search return nil if params[:keyword] == "" @shops = Shop.where('name LIKE ? OR location LIKE ?', "%#{params[:keyword]}%", "%#{params[:keyword]}%").limit(10) respond_to do |format| format.html format.json end end今回私は、shopsテーブル作成の際にname(店舗名)カラムとlocation(所在地)カラムを設定していますので、この2情報で検索できるように設定していますが、
例えば店舗名のみで検索する場合は、app/controllers/shops_controller.rbdef search return nil if params[:keyword] == "" @shops = Shop.where('name LIKE ?, "%#{params[:keyword]}%").limit(10) respond_to do |format| format.html format.json end endでOKです。
アクション内のparams[:keyword]は先のjsファイル内ajax以下のdata:{keyword: input}から来ています。
分かりづらいかもしれませんが、検索欄に入力した文字をjsファイル内ではinputとして扱い、コントローラファイル内ではkeywordとして扱っているということです。
検索欄に何も入力されていない場合(2行目"")はnilを返しています。また、3行目ではshopsテーブルではそのkeyword(検索欄に入力された文字)を基に、DBから情報を引っ張り出します。
今回はparams[:keyword]の量端を%で囲って部分一致検索を行う仕様です。
他の検索方法については、この記事を参考にしてみて下さい。・Rails - LIKE句を使った文字のあいまい検索(特定の文字を含む語句を曖昧検索したい場合)
私もこの記事を参考にさせていただきました。ありがとうございます。そして、今回入力された文字データはjson形式でコントローラへ入って来ている為、jbuilderへと移ります。
json.jbuilderで情報変換
search.json.jbuilderjson.array! @shops do |shop| json.name shop.name json.location shop.location endコントローラー内でDBから情報を抽出しましたがこのデータをjson形式に変換する必要があります。
それをjbuilderで行うのです。htmlファイルへの検索結果描画
まず先ほどのjsファイルajax以下にコントローラーでの処理が終わり、戻ってきた際の処理を追記します。
app/assets/javascripts/search.js$("#shop_search").on("keyup", function(){ let input = $("#shop_search").val(); $.ajax({ type: 'GET', url: '/shops/search', data: {keyword: input}, dataType: 'json' }) .done(function(shops){ $("#shop_search--result").empty(); if (shops.length !== 0) { shops.forEach(function(shop){ addShop(shop); }); } else if (input.length == 0){ return false; } else { addNoShop(); } }); });.done以下がその処理です。
そしてそれぞれaddShop、addNoShopとしたものをhtmlへ描画します。
以下がjsファイル全文です。
app/assets/javascripts/search.js$(function(){ function addShop(shop) { let html = ` <a href="/shops/${shop.id} class="shop_search-list"> <div>${shop.name} - ${shop.location}</div> </a> `; $("#shop_search--result").append(html); }; function addNoShop(){ let html =`ショップがありません` $("#shop_search--result").append(html); }; $("#shop_search").on("keyup", function(){ let input = $("#shop_search").val(); $.ajax({ type: 'GET', url: '/shops/search', data: {keyword: input}, dataType: 'json' }) .done(function(shops){ $("#shop_search--result").empty(); if (shops.length !== 0) { shops.forEach(function(shop){ addShop(shop); }); } else if (input.length == 0){ return false; } else { addNoShop(); } }) }); });let htmlとして描画する際の定型を指定します。
今回は検索結果をクリックするとその詳細ページにリンクする形で記載してみました。hamlファイル内の検索結果を表示する箇所(#shop_search--result)にappendを使用して作成した定型文(html)を挿入します。
これで非同期の検索機能(インクリメンタルサーチ)の完成です。
やってみて
当内容はスクールで学んだ内容に自身で挑戦してみて、理解を深めながら作成したものです。
学習当初は半端な理解で出来ればいいや位の気持ちで進めていましたが、いざ1から自分の力でやるとなると手が止まってしまう事が多々ありました。反省です。ファイルが変わるごとに、どこがどのように対応しているのかを把握する事がポイントかと思います。
また、どんな機能でもそうですが、今回の場合はconsole.log等デバッグしながら書き進めていくことを強くお勧めします。
- 投稿日:2020-09-29T11:11:37+09:00
JavaScriptのスコープ基本事項
見本
test.jsclass Test { constructor() { this.test = 'グローバルスコープ'; } } const word1 = 'グローバルスコープ'; function test(){ let word2 = '関数スコープ'; if(.....){ let word3 = 'ブロックスコープ' } }グローバールスコープ
どこからでもアクセス可能なもの。
また、let、constを省略した場合
は関数の中であってもグローバルスコープ。ローカルスコープ
関数スコープ
関数の中だけ有効となるもの。
ただし、letまたはconstを用いて宣言したものだけ
が、ブロックスコープ。
let、constを省略した場合
は関数の中であってもグローバルスコープ。ブロックスコープ
{}の中
だけで有効なもの。
- 投稿日:2020-09-29T10:22:58+09:00
Nem Symbol-sdk(testnet) を使ったアカウントの作り方
はじめに
NEM-Symbolは非常に簡単に扱うことができるブロックチェーンです。
本来はNODE.JSを使いますが今回はブラウザーで実行させる方法です。
ここではWEB初心者向けにSDKを使ったアカウントの作成方法を簡単に分かりやすく解説していきます。公式はこちら
※testnet版になります。準備するもの
WEBサーバー
XAMPPなどローカルWEBサーバー環境を用意しておいてください。Symbol-sdk のブラウザー版(browserify)
https://github.com/xembook/nem2-browserify
symbol-sdk-0.21.0.js をgithubからダウンロードします。ダウンロードしたら
作業ディレクトリにindex.htmlを作成してsymbol-sdk-0.21.0.jsを置いてください。
symbol-sdk-0.21.0.jsは名前をsymbol_sdk.jsにリネームしておきます。作業ディレクトリ
|-index.html
|-symbol_sdk.js(symbol-sdk-0.21.0.js)SDKの読み込み
symbol_sdk.jsを読み込みます。
<script src="symbol_sdk.js"></script>次にSymbol-SDKをモジュールとしてインポートします。
<script> var symbol_sdk_1 = require("/node_modules/symbol-sdk"); </script>全体としてはこんな感じ。。。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Symbol-sdk_test</title> <script src="/symbol_sdk.js"></script> </head> <body> <script> const symbol_sdk_1 = require("/node_modules/symbol-sdk"); </script>アカウントを作成する
次に公式からアカウント作成させるスクリプトをコピペします。
const account = symbol_sdk_1.Account.generateNewAccount(symbol_sdk_1.NetworkType.TEST_NET); console.log('Your new account address is:', account.address.pretty(), 'and its private key', account.privateKey);全体はこんな感じ
<html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Symbol-sdk_test</title> <script src="/symbol_sdk.js"></script> </head> <body> <script> var symbol_sdk_1 = require("/node_modules/symbol-sdk"); const account = symbol_sdk_1.Account.generateNewAccount(symbol_sdk_1.NetworkType.TEST_NET); console.log('Your new account address is:', account.address.pretty(), 'and its private key', account.privateKey); </script> </body> </html>以上でブラウザーからindex.htmlにアクセスするとアカウントが作成されます。
しかし、これではアクセスする毎にアカウントが作られるので、アカウントの作成ボタンをつけましょう。アカウント作成ボタンを作る
先ほどのindex.htmlにボタンを作るコードを追加します。
まずコピペしたアカウント作成コードをfunction()に入れます。
関数名はget_accountにします。function get_account(){ const account = symbol_sdk_1.Account.generateNewAccount(symbol_sdk_1.NetworkType.TEST_NET); console.log('Your new account address is:', account.address.pretty(), 'and its private key', account.privateKey); }つぎにアカウント作成ボタンを作ります。
onclickに先ほど作成したget_account関数を指定します。<button onclick="get_account()">アカウント作成</button>全体ではこんな感じです。
<html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Symbol-sdk_test</title> <script src="/symbol_sdk.js"></script> </head> <body> <script> var symbol_sdk_1 = require("/node_modules/symbol-sdk"); function get_account(){ const account = symbol_sdk_1.Account.generateNewAccount(symbol_sdk_1.NetworkType.TEST_NET); console.log('Your new account address is:', account.address.pretty(), 'and its private key', account.privateKey); } </script> <button onclick="get_account()">アカウント作成</button> </body> </html>以上で完成しました。
あとはブラウザーからアクセスしてアカウント作成ボタンを押します。
コンソールログ(F12押すもしくはメニューからその他のツール-デベロッパーツールを押す。)
にアドレスと秘密鍵が表示されていれば成功です!秘密鍵をコンソールログからコピペしてSymbolWalletにインポートしましょう。
SymbolWalletのアカウントの作成から秘密鍵を使って作成でインポートすることができます。以上Symbol-sdkを使ってブラウザーからアカウントを作る方法でした。
- 投稿日:2020-09-29T07:16:05+09:00
MJPG-streamerのストリーミング映像をcanvasタグに描画する
はじめに
MJPG-streamerのストリーミング映像をcanvasタグに描画するスクリプトを書いたので忘れないようメモ
実行画面
実行画面 pic.twitter.com/G5o0GgftJl
— j4amountain (@zsipparu) September 28, 2020ソース
作ったソース
sample.html<html> <head> <title>(sample) MJPG-streamer -> CANVAS Tag</title> </head> <body> <canvas id="canvas" width="400" height="400"></canvas> <script> const canvas = document.getElementById('canvas'); const context = canvas.getContext('2d'); function render() { const image = new Image(); image.onload = function(){ canvas.getContext("2d").drawImage(image, 0, 0, 400, 400); } // ↓ MJPG-streamerの静止画像をロード image.src = "http://xxxxxx:xxxx/?action=snapshot"; requestAnimationFrame(render); } render(); </script> </body> </html>補足
- MJPG-streamerは、ラズパイで実装。
- 先ほどのソース(sample.html)は、別にWebサーバーを立ててそこに置く
- ラズパイにMJPG-streamerをインストールする手順 → リンク
- ↓ は、MJPG-streamerのストリーミング実行のコマンド
$ /usr/local/bin/mjpg_streamer -i "input_raspicam.so -x 400 -y 400 -fps 15 -q 80" -o "output_http.so -p 8090 -w /usr/local/share/mjpg-streamer/www"
- 投稿日:2020-09-29T05:38:41+09:00
CSS セレクターでの大文字小文字区別について
はじめに
ちょっと気になったので実際に調べてみました。
用意した HTML とその結果
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Document</title> </head> <body> <div id="target" class="target" data-target="target">target</div> <script> console.log('tag', document.querySelector('div')); console.log('TAG', document.querySelector('DIV')); console.log('tAg', document.querySelector('dIv')); console.log('id', document.querySelector('#target')); console.log('ID', document.querySelector('#TARGET')); // null console.log('iD', document.querySelector('#tArGeT')); // null console.log('class', document.querySelector('.target')); console.log('CLASS', document.querySelector('.TARGET')); // null console.log('cLaSs', document.querySelector('.tArGeT')); // null console.log('attribute name', document.querySelector('[data-target]')); console.log('ATTRIBUTE NAME', document.querySelector('[DATA-TARGET]')); console.log('aTtRiBuTe NaMe', document.querySelector('[dAtA-tArGeT]')); console.log('attribute value', document.querySelector('[data-target="target"]')); console.log('ATTRIBUTE VALUE', document.querySelector('[data-target="TARGET"]')); // null console.log('aTtRiBuTe VaLuE', document.querySelector('[data-target="tArGeT"]')); // null </script> </body> </html>結果まとめ
- タグ名は大文字小文字を区別しない
- 属性名は大文字小文字を区別しない
- 属性値(
id
の値・class
の値を含む)は大文字小文字を区別するおわりに
考えてみれば当たり前でした…。
ちなみに
[data-target="tArGeT" i]
とすると取得可能です。ignore_case
ですね。
詳しくは: 属性セレクター - CSS: カスケーディングスタイルシート | MDN
- 投稿日:2020-09-29T05:20:34+09:00
【JavaScript】`scrollIntoView()`とかいうすごいやつ
はじめに
久しぶりに User Script でスクロール操作をしようと MDN を見たら、
scrollIntoView()
とかいうすごいやつを見つけたのでメモ。
Element.scrollIntoView()
とはウェブページを「要素をもとにした特定の箇所」までスクロールするもの。
Element インターフェイスの scrollIntoView() メソッドは、 scrollIntoView() が呼び出された要素がユーザーに見えるところまで、要素の親コンテナーをスクロールします。
MDNより
document.body.scrollIntoView(); document.body.scrollIntoView(true); document.body.scrollIntoView({ block: 'start' });ページトップへ移動します。
(正確には、body 要素の上辺が画面の上辺にくるまでスクロールします)。
document.body.scrollIntoView(false); document.body.scrollIntoView({ block: 'end' });body 要素の下辺が画面の下辺にくるまでスクロールします。
document.body.scrollIntoView({ behavior: 'smooth', });
behavior
にsmooth
を設定することで、簡単にスムーズスクロールが実装できます!
すごい!ここすきポイント
- 簡単にスムーズスクロールができる
- 要素を画面の真ん中へ持ってくることもできる(ユースケースは限られそうですがここぞというときに役立ちそう)
document.body.scrollIntoView({ behavior: 'smooth', block: 'center', });おわりに
そういえば、CSS にも
scroll-behavior: smooth;
とかいうものがありましたっけ。
スクロール系が充実するのはとてもうれしいです。
- 投稿日:2020-09-29T02:13:49+09:00
constの再代入禁止とオブジェクトの凍結
変数宣言時に
const
を使えば変数への再代入を防ぐことができます。sampleconst con = 1000; con = 500; // エラー発生 Uncaught TypeError: Assignment to constant variable.
const
であってもオブジェクトのプロパティの変更や追加削除を防ぐことはできません。const con = { prop1: 'p1', prop2: 'p2', }; con.prop1 = 'p3'; con.prop3 = 'p3'; console.log(con); // {prop1: "p3", prop2: "p2", prop3: "p3"}
Object.freeze
を使うことでプロパティに変更や追加を防ぐことができます。const con = { prop1: 'p1', prop2: 'p2', }; // オブジェクトを凍結 Object.freeze(con); con.prop1 = 'p3'; con.prop3 = 'p3'; console.log(con); // {prop1: "p1", prop2: "p2"}
Object.freeze
の凍結は対象のオブジェクトだけです。より深いオブジェクトは凍結されないので注意。const con = { prop1: 'p1', prop2: 'p2', deep: { prop4: 'p4' } }; Object.freeze(con); con.deep.prop4 = 'p5'; console.log(con); // {prop1: "p1", prop2: "p2", deep: { prop4: "p5"}}