- 投稿日:2020-06-03T22:03:46+09:00
PA-API v5で商品情報を取得する
PA-APIの利用
PA-API を使ってアマゾン商品情報をとってみよう。そこからアフィリエイトなどで収益化ができたら良いね。
PA-APIはアマゾンで一ヶ月以内に売上が発生していないと使えません。なので、アマゾンの商品リンク作成ツールや、ブログのアフィリエイトツールなどで売上を得られるように頑張ろう。ここが一番難易度が高い気がする。
アフィリエイトで売上が発生したら次に、ISBNやASINが必要となる。ただ、ASINを具体的にどのように取るかの選択が少ないので、アマゾンのサイトをクロールしている。
Amazonの検索結果からasinをスクレイピング
https://qiita.com/99nyorituryo/items/c5d53a3ca8a4967b5927ASINから商品情報のJSONを取得する
ここでは、ISBNやASINという本の情報から、アマゾンの表紙画像や、タイトル著者名、出版社、価格、本の種類などの情報を取得してサイトとして出力する方法を書く。
PAAPIでは、様々な言語用にツールが配布されているが、私はnode.jsが一番慣れているので、node.jsを用いた方法について書いていきます。node.jsのやり方は公式のページから見てみると良いのだけど。
https://affiliate.amazon.co.jp/help/node/topic/GZH32YX29UH5GACM
ここで得られたISBNをsampleGetItemsApi.jsを使ってアマゾンから書誌情報を取得する。
sampleGetItemsApi.jsは下記アドレスのnode.jsからダウンロードできる。
https://webservices.amazon.com/paapi5/documentation/quick-start/using-sdk.html
- paapi5-nodejs-sdk-and-samplesをダウンロードして展開して
- さっきのファイルを解凍したフォルダーで、npm install paapi5--nodejs-sdk --saveでインストール
- アクセスキーやシークレットキーをsampleGetItemsApi.jsなどのサンプルに追加
- またアフィリエイト用のパートナータグも追加(-22で終わるやつ)
- sampleGetItemsApi.jsを実行してみよう
というようなことが書かれています。
sampleGetItemsApi.jsを自分の目的に合うように書き換える。
初期状態では、amazon.co.jpになっていないので修正する必要がある。アクセスキーやシークレットキーを入力しないと実行できない。それから一度に取得できるasinは10個までになっている。それと待ち時間を設定しないとすぐエラーを吐くようである。
- 100個の情報を取得しようとすると、待ち時間を設定して取得している。
- key.jsonにアクセスキーやシークレットキー、アソシエイトタグにまとめている。
- dateが開始日でtodateが終了日になっていてまとめて取得できる。
- asinの配列は下の方法で作っている。
Amazonの検索結果からasinをスクレイピング
https://qiita.com/99nyorituryo/items/c5d53a3ca8a4967b5927{"accessKey":"アクセスキー","secretKey" :"シークレットキー","PartnerTag":"パートナータグ"}fs = require('fs'); var json = fs.readFileSync(__dirname + '/key.json', 'utf-8'); key = JSON.parse(json); function sleep(second) { return new Promise(resolve => { setTimeout(() => { resolve() }, second * 1000) }) } var ProductAdvertisingAPIv1 = require('./src/index'); var defaultClient = ProductAdvertisingAPIv1.ApiClient.instance; defaultClient.accessKey =key.accessKey// defaultClient.secretKey = key.secretKey// defaultClient.host = 'webservices.amazon.co.jp'; defaultClient.region = 'us-west-2'; var api = new ProductAdvertisingAPIv1.DefaultApi(); var getItemsRequest = new ProductAdvertisingAPIv1.GetItemsRequest(); getItemsRequest['PartnerTag'] =key.PartnerTag// getItemsRequest['PartnerType'] = 'Associates'; getItemsRequest['Resources'] =[ "BrowseNodeInfo.BrowseNodes", "BrowseNodeInfo.BrowseNodes.Ancestor", "BrowseNodeInfo.BrowseNodes.SalesRank", "BrowseNodeInfo.WebsiteSalesRank", "Images.Primary.Small", "Images.Primary.Medium", "Images.Primary.Large", "ItemInfo.ByLineInfo", "ItemInfo.ContentRating", "ItemInfo.Classifications", "ItemInfo.ExternalIds", "ItemInfo.ManufactureInfo", "ItemInfo.ProductInfo", "ItemInfo.Title", "Offers.Listings.Price"]; var callback = function (error, data, response) { if (error) { console.log('Error calling PA-API 5.0!'); console.log('Printing Full Error Object:\n' + JSON.stringify(error, null, 1)); console.log('Status Code: ' + error['status']); if (error['response'] !== undefined && error['response']['text'] !== undefined) { console.log('Error Object: ' + JSON.stringify(error['response']['text'], null, 1)); } } else { // console.log('API called successfully.'); var getItemsResponse = ProductAdvertisingAPIv1.GetItemsResponse.constructFromObject(data); console.log(JSON.stringify(getItemsResponse, null, 1)); jsondata=jsondata.concat(getItemsResponse.ItemsResult.Items); fs.writeFileSync(__dirname + '/json/kindle_paapi/'+filename, JSON.stringify(jsondata, null, 1),'utf-8') //console.log("test"+jsondata) if (getItemsResponse['Errors'] !== undefined) { console.log('\nErrors:'); console.log('Complete Error Response: ' + JSON.stringify(getItemsResponse['Errors'], null, 1)); console.log('Printing 1st Error:'); var error_0 = getItemsResponse['Errors'][0]; console.log('Error Code: ' + error_0['Code']); console.log('Error Message: ' + error_0['Message']); } } }; (async function(){ date=20200602 todate=20200602 for (date; date <= todate; date++) { var json = fs.readFileSync(__dirname + '/json/kindle_asin/'+date+'k.json', 'utf-8'); asinarry = JSON.parse(json); //c=a.ItemsResult.Items.concat(b.ItemsResult.Items); jsondata=[]; filename=date +'.json' for (let i = 0; i < asinarry.length; i += 10) { await sleep(3) asin =asinarry.slice(i, i+10) getItemsRequest['ItemIds'] = asin try { api.getItems(getItemsRequest, callback); } catch (ex) { console.log("Exception: " + ex); } }} })();これらを利用して作ったのが下のサイトである。
- 投稿日:2020-06-03T22:03:46+09:00
PA-API v5でAmazonの商品情報を取得する
PA-APIの利用
PA-API を使ってアマゾン商品情報をとってみよう。そこからアフィリエイトなどで収益化ができたら良いね。
PA-APIはアマゾンで一ヶ月以内に売上が発生していないと使えません。なので、アマゾンの商品リンク作成ツールや、ブログのアフィリエイトツールなどで売上を得られるように頑張ろう。ここが一番難易度が高い気がする。
アフィリエイトで売上が発生したら次に、ISBNやASINが必要となる。ただ、ASINを具体的にどのように取るかの選択が少ないので、アマゾンのサイトをクロールしている。
Amazonの検索結果からasinをスクレイピング
https://qiita.com/99nyorituryo/items/c5d53a3ca8a4967b5927ASINから商品情報のJSONを取得する
ここでは、ISBNやASINという本の情報から、アマゾンの表紙画像や、タイトル著者名、出版社、価格、本の種類などの情報を取得してサイトとして出力する方法を書く。
PAAPIでは、様々な言語用にツールが配布されているが、私はnode.jsが一番慣れているので、node.jsを用いた方法について書いていきます。node.jsのやり方は公式のページから見てみると良いのだけど。
https://affiliate.amazon.co.jp/help/node/topic/GZH32YX29UH5GACM
ここで得られたISBNをsampleGetItemsApi.jsを使ってアマゾンから書誌情報を取得する。
sampleGetItemsApi.jsは下記アドレスのnode.jsからダウンロードできる。
https://webservices.amazon.com/paapi5/documentation/quick-start/using-sdk.html
- paapi5-nodejs-sdk-and-samplesをダウンロードして展開して
- さっきのファイルを解凍したフォルダーで、npm install paapi5--nodejs-sdk --saveでインストール
- アクセスキーやシークレットキーをsampleGetItemsApi.jsなどのサンプルに追加
- またアフィリエイト用のパートナータグも追加(-22で終わるやつ)
- sampleGetItemsApi.jsを実行してみよう
というようなことが書かれています。
アクセスキーやシークレットキーは下から取得する。
https://affiliate.amazon.co.jp/assoc_credentials/homesampleGetItemsApi.jsを自分の目的に合うように書き換える。
初期状態では、amazon.co.jpになっていないので修正する必要がある。アクセスキーやシークレットキーを入力しないと実行できない。それから一度に取得できるasinは10個までになっている。それと待ち時間を設定しないとすぐエラーを吐くようである。
- 100個の情報を取得しようとすると、待ち時間を設定して取得している。
- key.jsonにアクセスキーやシークレットキー、アソシエイトタグにまとめている。
- dateが開始日でtodateが終了日になっていてまとめて取得できる。
- asinの配列は下の方法で作っている。
Amazonの検索結果からasinをスクレイピング
https://qiita.com/99nyorituryo/items/c5d53a3ca8a4967b5927{"accessKey":"アクセスキー","secretKey" :"シークレットキー","PartnerTag":"パートナータグ"}fs = require('fs'); var json = fs.readFileSync(__dirname + '/key.json', 'utf-8'); key = JSON.parse(json); function sleep(second) { return new Promise(resolve => { setTimeout(() => { resolve() }, second * 1000) }) } var ProductAdvertisingAPIv1 = require('./src/index'); var defaultClient = ProductAdvertisingAPIv1.ApiClient.instance; defaultClient.accessKey =key.accessKey// defaultClient.secretKey = key.secretKey// defaultClient.host = 'webservices.amazon.co.jp'; defaultClient.region = 'us-west-2'; var api = new ProductAdvertisingAPIv1.DefaultApi(); var getItemsRequest = new ProductAdvertisingAPIv1.GetItemsRequest(); getItemsRequest['PartnerTag'] =key.PartnerTag// getItemsRequest['PartnerType'] = 'Associates'; getItemsRequest['Resources'] =[ "BrowseNodeInfo.BrowseNodes", "BrowseNodeInfo.BrowseNodes.Ancestor", "BrowseNodeInfo.BrowseNodes.SalesRank", "BrowseNodeInfo.WebsiteSalesRank", "Images.Primary.Small", "Images.Primary.Medium", "Images.Primary.Large", "ItemInfo.ByLineInfo", "ItemInfo.ContentRating", "ItemInfo.Classifications", "ItemInfo.ExternalIds", "ItemInfo.ManufactureInfo", "ItemInfo.ProductInfo", "ItemInfo.Title", "Offers.Listings.Price"]; var callback = function (error, data, response) { if (error) { console.log('Error calling PA-API 5.0!'); console.log('Printing Full Error Object:\n' + JSON.stringify(error, null, 1)); console.log('Status Code: ' + error['status']); if (error['response'] !== undefined && error['response']['text'] !== undefined) { console.log('Error Object: ' + JSON.stringify(error['response']['text'], null, 1)); } } else { // console.log('API called successfully.'); var getItemsResponse = ProductAdvertisingAPIv1.GetItemsResponse.constructFromObject(data); console.log(JSON.stringify(getItemsResponse, null, 1)); jsondata=jsondata.concat(getItemsResponse.ItemsResult.Items); fs.writeFileSync(__dirname + '/json/kindle_paapi/'+filename, JSON.stringify(jsondata, null, 1),'utf-8') //console.log("test"+jsondata) if (getItemsResponse['Errors'] !== undefined) { console.log('\nErrors:'); console.log('Complete Error Response: ' + JSON.stringify(getItemsResponse['Errors'], null, 1)); console.log('Printing 1st Error:'); var error_0 = getItemsResponse['Errors'][0]; console.log('Error Code: ' + error_0['Code']); console.log('Error Message: ' + error_0['Message']); } } }; (async function(){ date=20200602 todate=20200602 for (date; date <= todate; date++) { var json = fs.readFileSync(__dirname + '/json/kindle_asin/'+date+'k.json', 'utf-8'); asinarry = JSON.parse(json); //c=a.ItemsResult.Items.concat(b.ItemsResult.Items); jsondata=[]; filename=date +'.json' for (let i = 0; i < asinarry.length; i += 10) { await sleep(3) asin =asinarry.slice(i, i+10) getItemsRequest['ItemIds'] = asin try { api.getItems(getItemsRequest, callback); } catch (ex) { console.log("Exception: " + ex); } }} })();これらを利用して作ったのが下のサイトである。
- 投稿日:2020-06-03T21:21:04+09:00
Kinx ライブラリ - Getopt
Kinx ライブラリ - Getopt
はじめに
「見た目は JavaScript、頭脳(中身)は Ruby、(安定感は AC/DC)」 でお届けしているスクリプト言語 Kinx。言語はライブラリが命。ということでライブラリの使い方編。
今回は Getopt です。SpecTest で内部的に実装して使っていたのですが、標準ライブラリのほうに移動させました。
- 参考
- 最初の動機 ... スクリプト言語 KINX(ご紹介)
- 個別記事へのリンクは全てここに集約してあります。
- リポジトリ ... https://github.com/Kray-G/kinx
- Pull Request 等お待ちしております。
ロングオプションにも対応しました(
まだリリースしてませんが...リリースしました)。Getopt -
System.getopt
使い方
以下のように、while 文の条件式の場所にオプションの配列とオプション文字列、ロングオプション・オブジェクトを指定する。ロングオプション・オブジェクトは省略可能。
var opt, add, check; while (opt = System.getopt($$, "a:df", { add: 'a', delete: 'd', help: null, "do-check": '=' })) { switch (opt.type) { case 'a': // '--add' でも 'a' が返る。 add = opt.arg; // ':' 指定は引数があることを示す。 System.println('-a with "%{add}"'); break; case 'd': // '--delete' でも 'd' が返る。 System.println('-d'); break; case 'f': // '-f' で返る。 System.println('-f'); break; case 'help': // '--help' で返る。 System.println('--help'); break; case 'do-check': // '--do-check' で返る。 check = opt.arg; // '=' 指定は引数があることを示す。 System.println('--do-check with "%{check}"'); break; case '-': // オプションではなかった場合、ここに来る。 list.push(opt.arg); break; } } // オプション以外の表示 System.println("Program options: ", list);オプション文字列の詳細
- 引数有りを指定した場合、引数を指定しなかった時には ArgumentException 例外が送出される。
- 引数が無いオプションの場合、同じオプション内に次のオプションを指定できる。例えば、
-d -f
は-df
と書いても良い。- 引数があるオプションの場合、次に文字が続いていれば引数とみなされる。例えば、
-a ARG
は-aARG
と書いてよい。- 上記 2 つを組み合わせると、
-d -a ARG
は-da ARG
とも-daARG
とも書くことができる。ロングオプションの詳細
- ロングオプションでオプション文字を指定した場合、引数の有無もオプション文字の指定に従う。
- 引数有りを指定した場合、引数を指定しなかった時には ArgumentException 例外が送出される。
- ロングオプションの場合は
--long-option=argument
の形式で引数を指定する。また、ロングオプションの場合は空文字列の引数が許容される。サンプルの実行
先ほどのサンプルを動作させると次のようになる。
$ ./kinx examples/option.kx -d -a arg -d -a with "arg" Program options: ["examples/option.kx"] $ ./kinx examples/option.kx -da arg -d -a with "arg" Program options: ["examples/option.kx"] $ ./kinx examples/option.kx -daarg -d -a with "arg" Program options: ["examples/option.kx"] $ ./kinx examples/option.kx --help something --help Program options: ["examples/option.kx", "something"] $ ./kinx examples/option.kx --do-check= --do-check with "" Program options: ["examples/option.kx"] $ ./kinx examples/option.kx --do-check=abc --do-check with "abc" Program options: ["examples/option.kx"] $ ./kinx examples/option.kx -a Uncaught exception: No one catch the exception. ArgumentException: Needs an argument for -a Stack Trace Information: at <main-block>(examples/option.kx:2) $ ./kinx examples/option.kx --unknown Uncaught exception: No one catch the exception. ArgumentException: Unknown option: --unknown Stack Trace Information: at <main-block>(examples/option.kx:2)おわりに
オプション解析の方法も色々あって、
getopt
も歴史が古いですが今でも現役ですね。Most fitting in C programmers の観点ではgetopt
は使いやすいんじゃないかな、と思います。ヘルプを表示できるという意味では、
boost::program_options
も捨てがたい。まずは最低限のことができるという意味でSystem.getopt
のサポートです。今後もっと便利なものが出てくるかもしれない(どこから?)。ではまた、次回。
- 投稿日:2020-06-03T20:59:47+09:00
ブックマーク.htmlを初心者でもJavaScriptで整理整頓できるのか
iPhone3G-5sまでアップル漬け。
それ以降Android所有。
Googleを信用せずマイクロソフトを信じてる人。古いiPhoneのバックアップデータ(10年前~4年くらい前)をiBackupbotで読み取り、SafariブックマークをHTMLに書き出す。
iPhoneを定期的に初期化し清潔を保っていたため毎度UUIDが変わるためバックアップもちらかっていた。
およそ20個ほどのUUIDから20個のHTMLを作成。この時点で奇人。PC上のとっちらかってるエクスポートブックマーク.htmlをEverythingで30個ほど見つける。
Edge, Firefox, Chrome, Ironの最新ブックマークをHTMLに書き出す。
type *.html > merge.txt
で適当に連結。12万行になる。
テキストエディタで掃除、整理整頓、ソート、フォルダ削除。簡易的な重複削除。1万行。
JavascriptでURLが全く同じであれば重複削除。5000行まで圧縮。
10年間で5000ブックマーク。暇人すぎる。
閑話休題。
目標1は
・フォルダ構造をぶっ壊してしまったため機会的にフォルダ分けして動作を軽くしなければならない。
・サブドメインを含めないドメイン名主体で整理。
・互換性のあるフォーマットでHTML出力。ブックマークバー/アーカイブ/ドメイン頭文字/ドメイン/固有のブックマークという構造
(例)
ブックマークバー/アーカイブ/a/apple.com/<a href="https://music.apple.com">Apple Music</a>
目標2は
フェッチAPIでDNS解決可否、サーバからお返事があるのか聴く。
なければ
ブックマークバー/インターネット・アーカイブ/a/apple.com/<a href="https://web.archive.org/web/*/https://music.apple.com">[IA] Apple Music</a>
のリンクに差し替える。
(フェッチAPIが有用なのかはわからないけれど)目標は今月末まで。
ざっと調べた限りでは
Location, URLインターフェイス他ネイティブで用意されているパーツも使えそうであるがサブドメインやら扱うのが難しい。
正規表現でやってみようと思ったが汚くなるのでやめた。document.querySelectorAll("a")[50].hostname; "foo.bar.com"見立てでは初心者でもかんたんで少量の記述量で済むjQuery, URI.jsに頼って書くが楽そうだ。
プログラミングに関することも書いておかないと規約上いけませんので
let uri = new URI("http://abcd.efgh.example.org/foo/hello.html"); uri.subdomain(); "abcd.efgh" uri.domain(); "example.org" uri.tld(); "org" uri.domain()[0]; "e"JR東海のポスターのごとく「ラクだ!」
プロなら30分の仕事かもしれない。
- 投稿日:2020-06-03T20:48:11+09:00
【お試し】クイック スタート:Visual Studio Code を使用して Azure で関数を作成する(JavaScript)
概要
クイック スタート:Visual Studio Code を使用して Azure で関数を作成するの内容を参考に、AzureFunctionsを触ってみます。
環境情報
2020/6/3時点
- MacOS 10.15.5
- Node.js v12.18.0 (最新は14.4.0でしたがLTSがおすすめらしいのでLTSのバージョンにしました。)
事前準備
- Microsoft Azureアカウント(無料)を作成しておく
- アカウント作成時にGitHubアカウントでログインもしくは新規でメールアドレスを登録してアカウントを作成
- 電話番号、クレジットカード情報の登録も必要
- クレジットカード情報は、認証に利用し、アップグレードを叫ばない限り費用が発生することはないようです。
- Visual Studio Codeを使うのでインストールしておく
- 拡張機能としてAzure Functionsを使うのでインストールしておく
- Node.jsをインストールしておく
- MacにNode.jsをインストールが参考になります。
ローカルプロジェクトを作成する
ローカルプロジェクトを作成するの内容をトレースしていきます。
上記記事と、最新の環境とで差があるようですが、概ね手順の通り進められます。
新しいプロジェクトの作成
プロジェクトワークスペースのディレクトリを指定
これらの手順は、ワークスペースの外部で実行するように設計されています。 ここでは、ワークスペースに含まれるプロジェクト フォルダーは選択しないでください。
という注意書きがあるので、どういうことなのかやってみました。
もともと別のソースを管理するワークスペース「SRC」のワークスペースをそのまま選択して進めてみたところ、
既存のワークスペースに「HttpExample」というディレクトリだけでなく、そのディレクトリの外側に.gitignore
やhost.json
などいろいろなファイルが生成されてしまいました。なので、既存のワークスペースがごちゃごちゃするから、新しいフォルダを指定してくださいね。
という注意書きのようですね。
新規フォルダを作成してそこを指定すると、きれいに関連するファイルだけがまとまって作成されました。
プロンプトで次の情報を入力する
Select a language
でJavaScript
を選択する。
Select a template for your project's first function
でHTTP trigger
を選択する。
Provide a function name
で任意のプロジェクト名を入力します。
ここでは、クイックスタートのサイトで指定されているHttpExample
という名前を指定していきます。
Authorization level
でAnonymous
を指定する。
Anonymousに設定することで、APIキーが不要な単純なHTTPリクエストによるAzure Functionを体験できるようです。
これだとPublicに公開された誰でも実行できるものになってしまうということであれば、別の承認レベルを指定する必要がありそうですね。
関数をローカルで実行する
なんと、ここでAzure Functions Core Toolsというツールを使って、関数のテストを行うようです。
事前準備で、VSCodeやらNode.jsをセットアップさせておきながら、まだ必要な環境があるとは。。。
Azure Functions Core Toolsのセットアップ
Macの環境なので、Terminalを起動して
brew tap azure/functions
を実行。$ brew tap azure/functions Updating Homebrew... ==> Auto-updated Homebrew! Updated 1 tap (homebrew/core). ==> New Formulae bombadillo coredns ==> Updated Formulae abcmidi aws-cdk awscli@1 composer dnscontrol elektra flow ghq imagemagick liblouis node@10 rbspy simple-scan aliyun-cli awscli buildifier csvq doctl eureka geoserver grin-wallet ktlint node node@12 semgrep ==> Deleted Formulae baidupcs-go ==> Tapping azure/functions Cloning into '/usr/local/Homebrew/Library/Taps/azure/homebrew-functions'... remote: Enumerating objects: 71, done. remote: Counting objects: 100% (71/71), done. remote: Compressing objects: 100% (42/42), done. remote: Total 338 (delta 48), reused 41 (delta 29), pack-reused 267 Receiving objects: 100% (338/338), 50.35 KiB | 305.00 KiB/s, done. Resolving deltas: 100% (184/184), done. Tapped 4 formulae (31 files, 93.6KB).続いて
brew install azure-functions-core-tools@3
を実行。$ brew install azure-functions-core-tools@3 ==> Installing azure-functions-core-tools@3 from azure/functions ==> Downloading https://functionscdn.azureedge.net/public/3.0.2534/Azure.Functions.Cli.osx-x64.3.0.2534.zip ######################################################################## 100.0% Telemetry --------- The Azure Functions Core tools collect usage data in order to help us improve your experience. The data is anonymous and doesn't include any user specific or personal information. The data is collected by Microsoft. You can opt-out of telemetry by setting the FUNCTIONS_CORE_TOOLS_TELEMETRY_OPTOUT environment variable to '1' or 'true' using your favorite shell. ? /usr/local/Cellar/azure-functions-core-tools@3/3.0.2534: 5,114 files, 564.9MB, built in 21 secondsVSCodeでF5キーを押下
F5キーを押下すると、こんな感じになります。
Azure Functions Core Tools
をインストールしたからこんな感じになるのか、インストールしなくても実は動くのか。。。インストールする前に試せばよかったです。とりあえず
TERMINAL
に出力された結果としては以下のような感じでした。> Executing task: npm install < npm notice created a lockfile as package-lock.json. You should commit this file. npm WARN azurefunctions@1.0.0 No description npm WARN azurefunctions@1.0.0 No repository field. npm WARN azurefunctions@1.0.0 No license field. up to date in 0.653s found 0 vulnerabilities Terminal will be reused by tasks, press any key to close it. > Executing task: func host start < %%%%%% %%%%%% @ %%%%%% @ @@ %%%%%% @@ @@@ %%%%%%%%%%% @@@ @@ %%%%%%%%%% @@ @@ %%%% @@ @@ %%% @@ @@ %% @@ %% % Azure Functions Core Tools (3.0.2534 Commit hash: bc1e9efa8fa78dd1a138dd1ac1ebef97aac8d78e) Function Runtime Version: 3.0.13353.0 [2020/06/03 11:08:07] FUNCTIONS_WORKER_RUNTIME set to node. Skipping WorkerConfig for language:python [2020/06/03 11:08:07] FUNCTIONS_WORKER_RUNTIME set to node. Skipping WorkerConfig for language:java [2020/06/03 11:08:07] FUNCTIONS_WORKER_RUNTIME set to node. Skipping WorkerConfig for language:powershell [2020/06/03 11:08:07] Building host: startup suppressed: 'False', configuration suppressed: 'False', startup operation id: 'd6b60201-83fa-46d6-8a79-67bd2d429035' [2020/06/03 11:08:08] Reading host configuration file '/Users/you_name_is_yu/Develop/AzureFunctions/host.json' [2020/06/03 11:08:08] Host configuration file read: [2020/06/03 11:08:08] { [2020/06/03 11:08:08] "version": "2.0", [2020/06/03 11:08:08] "extensionBundle": { [2020/06/03 11:08:08] "id": "Microsoft.Azure.Functions.ExtensionBundle", [2020/06/03 11:08:08] "version": "[1.*, 2.0.0)" [2020/06/03 11:08:08] } [2020/06/03 11:08:08] } [2020/06/03 11:08:08] Reading functions metadata [2020/06/03 11:08:08] 1 functions found [2020/06/03 11:08:08] Looking for extension bundle Microsoft.Azure.Functions.ExtensionBundle at /var/folders/pf/mbns7g5n6nlfj48466cy3kj40000gn/T/Functions/ExtensionBundles/Microsoft.Azure.Functions.ExtensionBundle [2020/06/03 11:08:08] Fetching information on versions of extension bundle Microsoft.Azure.Functions.ExtensionBundle available on https://functionscdn.azureedge.net/public/ExtensionBundles/Microsoft.Azure.Functions.ExtensionBundle/index.json [2020/06/03 11:08:09] Downloading extension bundle from https://functionscdn.azureedge.net/public/ExtensionBundles/Microsoft.Azure.Functions.ExtensionBundle/1.1.1/Microsoft.Azure.Functions.ExtensionBundle.1.1.1.zip to /var/folders/pf/mbns7g5n6nlfj48466cy3kj40000gn/T/62e2229d-4a18-4dca-92f3-7f12aef4f16e/Microsoft.Azure.Functions.ExtensionBundle.1.1.1.zip [2020/06/03 11:08:13] Completed downloading extension bundle from https://functionscdn.azureedge.net/public/ExtensionBundles/Microsoft.Azure.Functions.ExtensionBundle/1.1.1/Microsoft.Azure.Functions.ExtensionBundle.1.1.1.zip to /var/folders/pf/mbns7g5n6nlfj48466cy3kj40000gn/T/62e2229d-4a18-4dca-92f3-7f12aef4f16e/Microsoft.Azure.Functions.ExtensionBundle.1.1.1.zip [2020/06/03 11:08:13] Extracting extension bundle at /var/folders/pf/mbns7g5n6nlfj48466cy3kj40000gn/T/Functions/ExtensionBundles/Microsoft.Azure.Functions.ExtensionBundle/1.1.1 [2020/06/03 11:08:13] Zip extraction complete [2020/06/03 11:08:13] Loading Extention bundle from /var/folders/pf/mbns7g5n6nlfj48466cy3kj40000gn/T/Functions/ExtensionBundles/Microsoft.Azure.Functions.ExtensionBundle/1.1.1 [2020/06/03 11:08:13] FUNCTIONS_WORKER_RUNTIME set to node. Skipping WorkerConfig for language:python [2020/06/03 11:08:13] FUNCTIONS_WORKER_RUNTIME set to node. Skipping WorkerConfig for language:java [2020/06/03 11:08:13] FUNCTIONS_WORKER_RUNTIME set to node. Skipping WorkerConfig for language:powershell [2020/06/03 11:08:13] Initializing Warmup Extension. [2020/06/03 11:08:14] Initializing Host. OperationId: 'd6b60201-83fa-46d6-8a79-67bd2d429035'. [2020/06/03 11:08:14] Host initialization: ConsecutiveErrors=0, StartupCount=1, OperationId=d6b60201-83fa-46d6-8a79-67bd2d429035 [2020/06/03 11:08:14] LoggerFilterOptions [2020/06/03 11:08:14] { [2020/06/03 11:08:14] "MinLevel": "None", [2020/06/03 11:08:14] "Rules": [ [2020/06/03 11:08:14] { [2020/06/03 11:08:14] "ProviderName": null, [2020/06/03 11:08:14] "CategoryName": null, [2020/06/03 11:08:14] "LogLevel": null, [2020/06/03 11:08:14] "Filter": "<AddFilter>b__0" [2020/06/03 11:08:14] }, [2020/06/03 11:08:14] { [2020/06/03 11:08:14] "ProviderName": "Microsoft.Azure.WebJobs.Script.WebHost.Diagnostics.SystemLoggerProvider", [2020/06/03 11:08:14] "CategoryName": null, [2020/06/03 11:08:14] "LogLevel": "None", [2020/06/03 11:08:14] "Filter": null [2020/06/03 11:08:14] }, [2020/06/03 11:08:14] { [2020/06/03 11:08:14] "ProviderName": "Microsoft.Azure.WebJobs.Script.WebHost.Diagnostics.SystemLoggerProvider", [2020/06/03 11:08:14] "CategoryName": null, [2020/06/03 11:08:14] "LogLevel": null, [2020/06/03 11:08:14] "Filter": "<AddFilter>b__0" [2020/06/03 11:08:14] } [2020/06/03 11:08:14] ] [2020/06/03 11:08:14] } [2020/06/03 11:08:14] FunctionResultAggregatorOptions [2020/06/03 11:08:14] { [2020/06/03 11:08:14] "BatchSize": 1000, [2020/06/03 11:08:14] "FlushTimeout": "00:00:30", [2020/06/03 11:08:14] "IsEnabled": true [2020/06/03 11:08:14] } [2020/06/03 11:08:14] SingletonOptions [2020/06/03 11:08:14] { [2020/06/03 11:08:14] "LockPeriod": "00:00:15", [2020/06/03 11:08:14] "ListenerLockPeriod": "00:00:15", [2020/06/03 11:08:14] "LockAcquisitionTimeout": "10675199.02:48:05.4775807", [2020/06/03 11:08:14] "LockAcquisitionPollingInterval": "00:00:05", [2020/06/03 11:08:14] "ListenerLockRecoveryPollingInterval": "00:01:00" [2020/06/03 11:08:14] } [2020/06/03 11:08:14] HttpOptions [2020/06/03 11:08:14] { [2020/06/03 11:08:14] "DynamicThrottlesEnabled": false, [2020/06/03 11:08:14] "MaxConcurrentRequests": -1, [2020/06/03 11:08:14] "MaxOutstandingRequests": -1, [2020/06/03 11:08:14] "RoutePrefix": "api" [2020/06/03 11:08:14] } [2020/06/03 11:08:14] Starting JobHost [2020/06/03 11:08:14] Starting Host (HostId=younameisyupc-237003343, InstanceId=309bf51b-da3c-4db1-be93-1afdc15b677e, Version=3.0.13353.0, ProcessId=5908, AppDomainId=1, InDebugMode=False, InDiagnosticMode=False, FunctionsExtensionVersion=(null)) [2020/06/03 11:08:14] Loading functions metadata [2020/06/03 11:08:14] 1 functions loaded [2020/06/03 11:08:14] Loading proxies metadata [2020/06/03 11:08:14] Initializing Azure Function proxies [2020/06/03 11:08:16] 0 proxies loaded [2020/06/03 11:08:16] Starting worker process:node --inspect=9229 "/usr/local/Cellar/azure-functions-core-tools@3/3.0.2534/workers/node/dist/src/nodejsWorker.js" --host 127.0.0.1 --port 50181 --workerId c50406b7-d0c6-48fa-a29d-c66ec65f8d4f --requestId 83f76573-42dc-499a-80b9-a44874849ea9 --grpcMaxMessageLength 134217728 [2020/06/03 11:08:17] node process with Id=5919 started [2020/06/03 11:08:17] Generating 1 job function(s) [2020/06/03 11:08:17] Found the following functions: [2020/06/03 11:08:17] Host.Functions.HttpExample [2020/06/03 11:08:17] [2020/06/03 11:08:17] Initializing function HTTP routes [2020/06/03 11:08:17] Mapped function route 'api/HttpExample' [get,post] to 'HttpExample' [2020/06/03 11:08:17] [2020/06/03 11:08:17] Host initialized (2741ms) [2020/06/03 11:08:17] Host started (2750ms) [2020/06/03 11:08:17] Job host started [2020/06/03 11:08:17] Debugger listening on ws://127.0.0.1:9229/d7c34f05-e633-4dec-88f4-140ed939a894 [2020/06/03 11:08:17] For help, see: https://nodejs.org/en/docs/inspector Hosting environment: Production Content root path: /Users/you_name_is_yu/Develop/AzureFunctions Now listening on: http://0.0.0.0:7071 Application started. Press Ctrl+C to shut down. Http Functions: HttpExample: [GET,POST] http://localhost:7071/api/HttpExample [2020/06/03 11:08:17] Worker c50406b7-d0c6-48fa-a29d-c66ec65f8d4f connecting on 127.0.0.1:50181 [2020/06/03 11:08:22] Host lock lease acquired by instance ID '000000000000000000000000354A045E'. [2020/06/03 11:08:23] Debugger attached.気になるポイントとしてはこんな感じで、HttpFunctionが動き始めたようなメッセージが出力されている部分でしょうか。
http://localhost:7071/api/HttpExampleただ、このURLのままアクセスしてもダメみたいで、以下のように、クエリパラメータとして
name=Functions
を指定する必要があるようです。
http://localhost:7071/api/HttpExample?name=Functions
アクセスしてみると、よくある
Hello World
のような感じで、Hello Functions
という文字が出力されました。ブラウザにアクセスした時点でTERMINALに出力される内容としては以下のような内容が出力されています。
[2020/06/03 11:13:27] Executing HTTP request: { [2020/06/03 11:13:27] "requestId": "816dc4fe-2914-4e86-99a5-7820ab231808", [2020/06/03 11:13:27] "method": "GET", [2020/06/03 11:13:27] "uri": "/api/HttpExample" [2020/06/03 11:13:27] } [2020/06/03 11:13:27] Executing 'Functions.HttpExample' (Reason='This function was programmatically called via the host APIs.', Id=32b32a60-2b58-4988-8fd5-329484b2a50d) [2020/06/03 11:13:28] JavaScript HTTP trigger function processed a request. [2020/06/03 11:13:28] Executed 'Functions.HttpExample' (Succeeded, Id=32b32a60-2b58-4988-8fd5-329484b2a50d) [2020/06/03 11:13:28] Executed HTTP request: { [2020/06/03 11:13:28] "requestId": "816dc4fe-2914-4e86-99a5-7820ab231808", [2020/06/03 11:13:28] "method": "GET", [2020/06/03 11:13:28] "uri": "/api/HttpExample", [2020/06/03 11:13:28] "identities": [ [2020/06/03 11:13:28] { [2020/06/03 11:13:28] "type": "WebJobsAuthLevel", [2020/06/03 11:13:28] "level": "Admin" [2020/06/03 11:13:28] } [2020/06/03 11:13:28] ], [2020/06/03 11:13:28] "status": 200, [2020/06/03 11:13:28] "duration": 1095 [2020/06/03 11:13:28] } [2020/06/03 11:13:28] Executing HTTP request: { [2020/06/03 11:13:28] "requestId": "ec5ffac0-d06e-4795-ae24-e2bb5b2f9cd7", [2020/06/03 11:13:28] "method": "GET", [2020/06/03 11:13:28] "uri": "/favicon.ico" [2020/06/03 11:13:28] } [2020/06/03 11:13:28] Executed HTTP request: { [2020/06/03 11:13:28] "requestId": "ec5ffac0-d06e-4795-ae24-e2bb5b2f9cd7", [2020/06/03 11:13:28] "method": "GET", [2020/06/03 11:13:28] "uri": "/favicon.ico", [2020/06/03 11:13:28] "identities": [], [2020/06/03 11:13:28] "status": 404, [2020/06/03 11:13:28] "duration": 352 [2020/06/03 11:13:28] }
デバッガーを終了
Disconnectボタンを押下するか、Ctrl+Cを押下することでデバッグを終了します。
Azureへのサインイン
Azure Functionsのアプリを発行するには、Azureへのサインインが必要です。
ブラウザでのログインが求められたので、私の場合はGitHubアカウントでサインインします。
サインインしてしまえば、表示されたブラウザは閉じてOKです。
Azure にプロジェクトを発行する
Create new Function App in Azure
を選択する。
※Advancedは選択しないでとのこと。
今回はNode.jsのバージョンを12.18.0としているため、Node.js 12を選択します。
Japan East
を選択します。パフォーマンスを向上させるために、お近くのリージョンを選択してください
完了すると以下のようになります。
View output
ボタンを押下することで、作成とデプロイの結果を確認することができます。
8:30:46 PM: Creating resource group "yuyamaguchifunctionshttp" in location "japaneast"... 8:30:46 PM: Successfully created resource group "yuyamaguchifunctionshttp". 8:30:46 PM: Creating storage account "yuyamaguchifunctionshttp" in location "japaneast" with sku "Standard_LRS"... 8:31:18 PM: Successfully created storage account "yuyamaguchifunctionshttp". 8:31:18 PM: Verifying that Application Insights is available for this location... 8:31:18 PM: Creating Application Insights resource "yuyamaguchifunctionshttp"... 8:31:21 PM: Successfully created Application Insights resource "yuyamaguchifunctionshttp". 8:31:21 PM: Creating new function app "Yu-Yamaguchi-Functions-HttpExample"... 8:32:09 PM: Successfully created function app "Yu-Yamaguchi-Functions-HttpExample": https://yu-yamaguchi-functions-httpexample.azurewebsites.net 8:32:13 PM Yu-Yamaguchi-Functions-HttpExample: Creating zip package... 8:32:13 PM Yu-Yamaguchi-Functions-HttpExample: Starting deployment... 8:32:22 PM Yu-Yamaguchi-Functions-HttpExample: Updating submodules. 8:32:22 PM Yu-Yamaguchi-Functions-HttpExample: Preparing deployment for commit id 'a320bfa83a'. 8:32:23 PM Yu-Yamaguchi-Functions-HttpExample: Skipping build. Project type: Run-From-Zip 8:32:23 PM Yu-Yamaguchi-Functions-HttpExample: Skipping post build. Project type: Run-From-Zip 8:32:23 PM Yu-Yamaguchi-Functions-HttpExample: Triggering recycle (preview mode disabled). 8:32:27 PM Yu-Yamaguchi-Functions-HttpExample: Syncing 2 function triggers with payload size 158 bytes successful. 8:32:28 PM Yu-Yamaguchi-Functions-HttpExample: Deployment successful. 8:32:42 PM Yu-Yamaguchi-Functions-HttpExample: Started postDeployTask "npm install". 8:32:43 PM Yu-Yamaguchi-Functions-HttpExample: Querying triggers... 8:32:48 PM Yu-Yamaguchi-Functions-HttpExample: HTTP Trigger Urls: HttpExample: https://yu-yamaguchi-functions-httpexample.azurewebsites.net/api/HttpExampleAzure で関数を実行する
コピーしたURLは以下の通りです。
https://yu-yamaguchi-functions-httpexample.azurewebsites.net/api/HttpExampleこれにローカル環境での動作確認でも指定したクエリ文字列
?name=Functions
を指定したURLを生成し、ブラウザでアクセスします。
https://yu-yamaguchi-functions-httpexample.azurewebsites.net/api/HttpExample?name=Functions
このURLをみる限り、ユニークなFunctionの名前とは、グローバルでのユニークとなるように指定する必要があるようですね。
無闇に作らず、命名には注意が必要そうです。Azureのポータルサイトにアクセスすると、デプロイしたFunctionが確認できます。
Azure Functionsはリクエスト数が多いとその分費用?がかかるような従量課金だと思いますので、ここで作ったFunctionは消しておきますw
なので、ここに載せているFunctionのURLにアクセスしても意味が無いので悪しからずm(_ _)m以上で、クイックスタートの内容は完了です。
まだ何ができるかわかっていませんが、できればOffice365とか会社で使っているので、TeamsとかOneDriveのドキュメントとか、その辺りといい感じのことができると嬉しいなと思っています。
- 投稿日:2020-06-03T19:35:46+09:00
Javascriptで値が数値かどうかチェック→スペースかどうかもチェック
Wano株式会社で社内のもろもろを担当しているakibinです。
ヒゲ男ばかり聴いて、少しでも若返ったつもりでいる今日このごろです。でも好きだなーこのちょっと縦ノリっぽい楽曲と声。
今回やってみたこと
Javascriptで値が数値かどうかチェックするのにこちらの記事を参照させてもらいました!(分かりやすかったです!!)
がしかし!
スペースを数字と認識してしまうので、スペースかどうかもチェックするようにしてみました。こんな感じ
正規表現の\S(空白文字以外の文字)と、!で論理値を反転させてif文で判定。当てはまった(true)場合はtry…catch文を使用して終了する。(JSにはexitが無いので)
※htmlは参照させていただいた記事と変わりないのですが、念の為載せておきます。check.html<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <title>check</title> <script src="check.js"></script> </head> <body> 数値を入力して下さい: <input type="text" id="input01"> <input type="button" value="check" onclick="check();"> </body> </html>check.js'use strict'; var check = function() { var num = document.getElementById('input01').value; try { if (!num.match(/\S/g)){ alert("スペースが入ってますよ!"); throw new Error("スペースありのため終了します"); } if(!isNaN(num)){ alert("入力された数は" + num + "です。"); } else { alert("数値以外が入力されています"); } } catch(e) { console.log(e.message); } }こちらもよろしくです。
****************************************
◆ Twitterアカウント
@AkibinMusic◆ Youtubeチャンネル
https://www.youtube.com/channel/UC-JOpwEnJn3gCrUA4NdCYgg
- 投稿日:2020-06-03T19:35:46+09:00
Javascriptで値が数値かどうかチェック→スペースかどうかも
Wano株式会社で社内のもろもろを担当しているakibinです。
ヒゲ男ばかり聴いて、少しでも若返ったつもりでいる今日このごろです。でも好きだなーこのちょっと縦ノリっぽい楽曲と声。
今回やってみたこと
Javascriptで値が数値かどうかチェックするのにこちらの記事を参照させてもらいました!(分かりやすかったです!!)
がしかし!
スペースを数字と認識してしまうので、スペースかどうかもチェックするようにしてみました。こんな感じ
正規表現の\S(空白文字以外の文字)と、!で論理値を反転させてif文で判定。当てはまった(true)場合はtry…catch文を使用して終了する。(JSにはexitが無いので)
※htmlは参照させていただいた記事と変わりないのですが、念の為載せておきます。check.html<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <title>check</title> <script src="check.js"></script> </head> <body> 数値を入力して下さい: <input type="text" id="input01"> <input type="button" value="check" onclick="check();"> </body> </html>check.js'use strict'; var check = function() { var num = document.getElementById('input01').value; try { if (!num.match(/\S/g)){ alert("スペースが入ってますよ!"); throw new Error("スペースありのため終了します"); } if(!isNaN(num)){ alert("入力された数は" + num + "です。"); } else { alert("数値以外が入力されています"); } } catch(e) { console.log(e.message); } }こちらもよろしくです。
****************************************
◆ Twitterアカウント
@AkibinMusic◆ Youtubeチャンネル
https://www.youtube.com/channel/UC-JOpwEnJn3gCrUA4NdCYgg
- 投稿日:2020-06-03T19:18:22+09:00
JavaScriptでhtmlのTableをいじる
初めに
今回はjQueryをメインでDOM操作しながらTableをあれこれする方法を展開します。
コード
セルを押下できるようにする
セルを押下するとイベントが発火して処理される仕組み(今回はアラート表示)
まずはhtml側にTableをセットする(今回はタイトルなしです)test.html<table border="1"> <tr> <td id="a"><a onclick="onClick(this);">Test1</a></td> <td id="b"><a onclick="onClick(this);">Test2</a></td> <td id="c"><a onclick="onClick(this);">Test3</a></td> </tr> <tr> <td id="d"><a onclick="onClick(this);">Test4</a></td> <td id="e"><a onclick="onClick(this);">Test5</a></td> <td id="f"><a onclick="onClick(this);">Test6</a></td> </tr> </table>main.jsfunction onClick(link){ alert("Test"); }押下したセルの文字を入れ替え
main.jsfunction onClick(link){ $(link).html("入れ替え") }押下したセルの文字を抽出
main.jsfunction onClick(link){ $(link).parent().text() }押下したセルの列番号を抽出
main.jsfunction onClick(link){ $(link).parent().index() }押下したセルのIDを抽出
idを振っている前提です
main.jsfunction onClick(link){ $(link).parent().attr('id') }終わりに
今回はTableについての展開でした。
すごく簡単ですね!楽しい!
- 投稿日:2020-06-03T17:27:58+09:00
難読化されたJavaScriptを解読するツールを作った
Obfuscator.ioというJavaScriptコードを難読化できるツールがあります。
例えば以下のような感じ
正直難読化されてても慣れればコードを読むのはそんなに苦じゃないのですが、自動ですべて解読するツールを作ったらおもしろそうだと思ってやってみました。
難読化されたJavaScriptを半自動で解読するツール
Vue.js(サイト側)とCloud Functions(API側)で作ってみました。
公開URL: https://sigr.io/deobfuscator/
残念ながら完全自動化までは至りませんでしたが、最初にターゲットとなる関数名を入力すれば後は自動で解析してくれます。
ツールの使い方
- 解読したいコードをInputに貼り付ける。
- 貼り付けたコードの中に以下のような部分があるので関数名をコピーする(3つのうちどれかに似てるはず)
_0x439c[103]
_0x439c('0x4')
_0x439c('0x2','f]Xg')
これの場合は_0x439cが対象(関数名)。Target function nameの欄にコピーしたのを貼り付ける
3.「Deobfuscate」をクリック
4.ちゃんと解読されればResultに結果が表示されるちゃんと解読されると難読化された部分が読めるようになるはずです。
さいごに
GitHubにフロント側のソースコードをうpしています。
https://github.com/LostMyCode/javascript-deobfuscatorCloudFunctionsで動いているAPI側のコードもいつか公開するかもしれません。
- 投稿日:2020-06-03T17:14:58+09:00
google.script.run が Promise を返すようにする
期待を返せ
google.script.run は withSuccessHandler や withFailureHandler でコールバックを登録する仕様となっており、 Promise を返してくれません。
async / await ですっきり書きたいですよね。理想(async ()=> { const data1 = await google.script.run.myFunction(); const data2 = await google.script.run.myFunction2(data1); console.log(data1); console.log(data2); })();現実google.script.run .withSuccessHandler( data1 => { google.script.run .withSuccessHandler( data2 => { console.log(data1); console.log(data2); }) .withFailureHandler(console.error) .myFunction2(data1); }) .withFailureHandler(console.error) .myFunction();Promiseを返さないなら自作関数で包んでしまえ
というわけで google.script.run を元に Promise を返す関数を動的に定義します。
const ServerScript = new class { constructor() { const createAsyncFunction = (methodName) => function() { return new Promise( (resolve, reject) => { google.script.run .withSuccessHandler(resolve) .withFailureHandler(reject) [methodName] .apply(null, arguments); }); }.bind(this); for (const methodName in google.script.run) { const type = google.script.run[methodName].prototype.constructor.name; if (type != methodName) { this[methodName] = createAsyncFunction(methodName); } } } }; (async ()=> { const data1 = await ServerScript.myFunction(); const data2 = await ServerScript.myFunction2(data1); console.log(data1); console.log(data2); })();for 文の中の if は withSuccessHandler や withFailureHandler を弾くためのものなので、別に無くても問題ないです。呼ばなければ良い話ですから。
また、この条件でも doGet などが入ってきてしまうのですが、呼んでもエラーなどは起きずに undefined が返ってきます。元々の google.script.run からそのような動きをするようなので、こちらも特に気にしないことにします。
以上、快適なGASライフを!
- 投稿日:2020-06-03T16:41:24+09:00
webpack3からwebpack4へバージョンアップ
保守できていなかったwebpackのバージョンをあげた際の記録です。
メモレベルで恐縮ですが、よかったら参考にしてください。モチベーション
- 最新のTypeScriptを導入したい
- ビルドを速くしたい
移行手順
基本的にはこちらのマイグレーションガイド通りです。
https://webpack.js.org/migrate/4/
- package.jsonの修正
- modeの追加
- pluginの設定見直し
- loaderのバージョンアップ
移行手順詳細
package.jsonの修正
https://github.com/webpack/webpack/releases
こちらを参考にwebpackの箇所のバージョンを修正しました。package.json"webpack": "^4.42.0"npm install
yarnの方は
yarn install
してください。modeの追加
production
とdevelopment
の二つがありwebpack.config.jsに記載しないと警告がでます。
本番用と開発用ですね。
圧縮されるかどうかなどの違いがあります。webpack.config.jsmodule.exports = { //... mode: 'production', //... }pluginの設定見直し
3rd partyのプラグインのバージョンアップや一部機能がデフォルトになったため(modeの記述でOKになったため)見直しをしました。
before
webpack.config.jsmodule.exports = { //... plugins: [ new webpack.optimize.UglifyJsPlugin({}) ] //... }after
webpack.config.jsmodule.exports = { //... mode: 'production' //... }loaderのバージョンアップ
Cannot read property 'babel' of undefinedビルドすると、上記のログが出たので
babel-loader
をバージョンアップしました。
最新版にしたかったのですが、babel
のバージョンアップとは切り分けたかったのでpackage.json"babel-loader": "^7.1.5"で対応しました。
babel-loader
の8系移行を使うには
https://koukitips.net/if-you-raise-the-babel-loader-to-v8-the-solution-is-when-you-get-an-error/
この辺りを参考にする必要がありそうです。参考
https://webpack.js.org/migrate/4/
https://thebrainfiles.wearebrain.com/moving-from-webpack-3-to-webpack-4-f8cdacd290f9
https://auth0.com/blog/webpack-4-release-what-is-new/#L7--Faster-Build-Times
- 投稿日:2020-06-03T16:37:51+09:00
webpack1からwebpack3へバージョンアップ
保守できていなかったwebpackのバージョンをあげた際の記録です。
メモレベルで恐縮ですが、よかったら参考にしてください。モチベーション
- TypeScriptを導入したい(1だとts-loaderが対応していない)
- ブラウザでのjs実行を速くしたい
なぜ2ではなく、3なの?
https://webpack.js.org/migrate/3/
公式でNote that there were far fewer changes between 2 and 3, so that migration shouldn't be too bad. If you are running into issues, please see the changelog for details.
となっていて、移行ガイドも
To v2 or v3 from v1
となっているから。また、
https://medium.com/webpack/webpack-3-official-release-15fd2dd8f07bMigrating from webpack 2 to 3, should involve no effort beyond running the upgrade commands in your terminal. We marked this as a Major change because of internal breaking changes that could affect some plugins.
とも。
使用ライブラリ
- Grunt
- Webpack
- Bower
移行手順
- package.jsonの修正
- bower対応
- DedupePlugin,OccurenceOrderPlugin
- grunt-webpackバージョンアップ
- moduleDirectories
- resolve
- module
- debug
移行手順詳細
package.jsonの修正
webpackの箇所のバージョンを修正
package.json"webpack": "^3.12.0"npm install
yarnの方は
yarn install
してくださいbower対応
bowerとは、公式で非推奨になっていますが、フロントエンド周りのパッケージマネージャーです。
将来的使わないようにしたいですが、今回はwebpackのバージョンアップに専念しています。webpack.config.jsnew webpack.ResolverPlugin( new webpack.ResolverPlugin.DirectoryDescriptionFilePlugin("bower.json", ["main"]) )上記はwebpackでbower管理のファイルを読み込む際のお作法のようなものです。
https://github.com/webpack/webpack/issues/2324
It was removed. There is now a option resolve.plugins. But for bower you don't need a plugin anymore, as the resolve options has been expanded.
See: https://gist.github.com/sokra/27b24881210b56bbaff7#resolving-optionsに書かれていますが、
webpack.config.jsdescriptionFiles: ["package.json", "bower.json"]をwebpack.config.jsのresolveに書いて対応しました。
参考
http://js.studio-kingdom.com/webpack/getting_started/using_plugins
DedupePlugin,OccurenceOrderPlugin
https://teratail.com/questions/93933
OccurenceOrderPluginは、Webpack2でOccurrenceOrderPlugin(rが2つ)と改名されています(Issue)。
DedupeはWebpack2ではデフォルトの動作となったので、プラグインとしては不要です。とのこと。
grunt-webpackバージョンアップ
ビルドすると以下のエラーがでました。
configuration has an unknown property 'failOnError'. These properties are valid:こちらはバージョンアップで対応しました。
package.json"grunt-webpack": "^3.1.3"参考
https://github.com/webpack-contrib/grunt-webpack/issues/81
moduleDirectories
ビルドすると以下のエラーがでました。
configuration.resolve has an unknown property 'modulesDirectories'. These properties are validhttps://webpack.js.org/migrate/3/#resolveroot-resolvefallback-resolvemodulesdirectories
公式ガイドのresolve.modulesDirectoriesを参考に修正しました。before
webpack.config.jsresolve: { root: path.resolve(__dirname, "../js/dirname/"), modulesDirectories: [ path.resolve(__dirname, "node_modules"), path.resolve(__dirname, "bower_components") ] },after
webpack.config.jsresolve: { modules: [ path.resolve(__dirname, "../js/dirname/"), path.resolve(__dirname, "node_modules"), path.resolve(__dirname, "bower_components") ], descriptionFiles: ["package.json", "bower.json"] },descriptionFilesは前述したbower対応です。
同様にresolveloaderでもmodulesDirectoriesを使用していたので、modulesにリネームしました。module.loaders
https://webpack.js.org/migrate/3/#moduleloaders-is-now-modulerules
こちらに沿って変更しました。https://webpack.js.org/migrate/3/#automatic--loader-module-name-extension-removed
また、ドキュメントにあるようにwebpack.config.jsloader: "babel"ようにサフィックスを省略してはいけなくなり
webpack.config.jsloader: "babel-loaderのようにしました。
debug
https://webpack.js.org/migrate/3/#debug
こちらに沿って変更しました。before
webpack.config.jsdebug: true,after
webpack.config.jsplugins: [ new webpack.LoaderOptionsPlugin({ debug: true }) ]参考
公式移行ガイド
https://webpack.js.org/migrate/3/#moduleloaders-is-now-modulerules
他にも
https://qiita.com/uggds/items/2ee337c5843aae28a34a
https://blog.hiroppy.me/entry/2017/02/03/212817
- 投稿日:2020-06-03T16:10:57+09:00
【Javascript】 2つの配列に格納されているオブジェクト内部要素を比較し、新しいオブジェクト要素を追加
背景
vueでaxios使ってAPI情報取得したときに、使うことが多いかもなーってことで、もっと良い方法がありそうだが、ひとまず、自分用にメモで取っておきます。
vueでAPI叩いたときに、配列内部にオブジェクトを抱えたものが返ってくる前提です。
もっとこのほうがいいなど、あればアドバイスいただければ幸いです。
内容
結論
内容// axios叩いたときに返って来るdataの文字列 var obj = "data" var test02 = [ { "site_no": "00002", "term_id": "00002", }, { "site_no": "00001", "term_id": "00001", }, { "site_no": "00003", "term_id": "800003", }, { "site_no": "00004", "term_id": "00004", } ] // axios叩いたときに返ってくる配列(配列に格納されたオブジェクト一覧が返ってくる前提) var test08 = [ { "config":{ "test01": "hoge1" }, "data": { "image_url": "http://test01", "term_id": "800001", }, "header": { "aafa": "http://test01", "gdsa": "800001", }, "status": 200 }, { "config":{ "test01": "hoge1" }, "data": { "image_url": "http://test03", "term_id": "800003", }, "header": { "aafa": "http://test01", "gdsa": "800001", }, "status": 200 }, { "config":{ "test01": "hoge1" }, "data": { "image_url": "http://test02", "term_id": "800002", }, "header": { "aafa": "http://test01", "gdsa": "800001", }, "status": 200 } ] // 比較する配列内部のオブジェクトに一致する文字列があるかを確認し、ある・ないで処理を分ける // (2つ配列の内部どちらにもオブジェクトがあり、オブジェクト内部の特定文字列が存在することを確認する) // ある:比較対象側オブジェクト(端末一覧情報オブジェクト)に、要素を追加 // ない:特に処理をせず終了 test08.some((val01)=>{ test02.some((val02) => { console.log("val01: ", val01) if(obj !== ""){ if( ( "term_id" in val01[obj] && "term_id" in val02 ) && ( val01[obj]["term_id"] === val02["term_id"] ) ){ // console.log("存在します。") val02.image_url = val01[obj].image_url }else{ // console.log("存在しません。") } }else{ // オブジェクト内部に、検査したい要素があること確認 && それぞれのオブジェクトで要素が一致すること確認 if( ( "term_id" in val01 && "term_id" in val02 ) && ( val01["term_id"] === val02["term_id"] ) ){ // console.log("存在します。") // 条件に合致した場合に、片方の配列のオブジェクト要素に代入 val02.image_url = val01.image_url // 合致したタイミングで処理を抜ける return true; }else{ // console.log("存在しません。") } } }) }) console.log("result: ", test02)結果result: [ { site_no: '00002', term_id: '00002' }, { site_no: '00001', term_id: '00001' }, { site_no: '00003', term_id: '800003', image_url: 'http://test03' }, { site_no: '00004', term_id: '00004' } ]
- 投稿日:2020-06-03T15:59:14+09:00
javascriptでインターバルタイマーを作ってみた。
javascriptでインターバルタイマーを作ってみた。
仕様
・1秒ごとに音がなる。
・フォームに秒数を入れると、入力された秒数毎に別の音がなる。<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>インターバルタイマー</title> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous"> <style> h1{ font-size:24px; } #PassageArea{ font-size:42px; border:1px solid #666; padding:20px; width:100px; margin:0px auto; } </style> </head> <body> <div class="container mt-5" style="text-align:center;"> <h1>インターバルタイマー</h1> <div class="container mt-5"> <div id="PassageArea">0</div> </div> <div class="container mt-5"> <input type="number" id="number" min=1 value=5 style="width:80px;"> </div> <div class="container mt-5"> <button class="btn btn-primary" id="startcount" onclick="startShowing();">カウント開始</button> <button class="btn btn-primary" id="endcount" onclick="stopShowing();">カウント停止</button> </div> </div> <script> var msg; var number; var count = 0; var timer_id; function startShowing() { count = 0; number = document.getElementById("number").value; document.getElementById("startcount").disabled = true; countup(); } function stopShowing() { count = 0; clearTimeout(timer_id); document.getElementById("startcount").disabled = false; } function countup() { count ++; document.getElementById("PassageArea").innerHTML = count; console.log(count % number); if(count % number == 0){ sound1(); }else{ sound2(); } timer_id = setTimeout(countup,1000); } function sound1(){ var id = 'sound-file1'; if( typeof( document.getElementById( id ).currentTime ) != 'undefined' ){ document.getElementById( id ).currentTime = 0; } document.getElementById( id ).play() ; } function sound2(){ var id = 'sound-file2'; if( typeof( document.getElementById( id ).currentTime ) != 'undefined' ){ document.getElementById( id ).currentTime = 0; } document.getElementById( id ).play() ; } </script> <audio id="sound-file1" preload="auto"> <source src="beep1.mp3" type="audio/mp3"> </audio> <audio id="sound-file2" preload="auto"> <source src="beep2.mp3" type="audio/mp3"> </audio> </body> </html>
- 投稿日:2020-06-03T15:51:52+09:00
【JS】複数の二次元配列から、重複していない配列を抽出【GAS】
要旨
例: 2つの二次元配列から、重複していない配列を取得
GASで使う かもしれないconst ary01 = [ [1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12] ]; const ary02 = [ [1, 2, 3], [100, 99, 55], [4, 5, 6], [7, 8, 9] ]; // [10, 11, 12],[100, 99, 55]だけ取りてえ! const output = [...ary01, ...ary02].filter((value) => !ary01.some(ar => JSON.stringify(ar) === JSON.stringify(value)) || !ary02.some(ar => JSON.stringify(ar) === JSON.stringify(value)) ); console.log(output); // [[10, 11, 12], [100, 99, 55]]備考:配列の比較,JSON.stringify()とString()
[1,2,3] === [1,2,3] // false // js 配列 比較 で検索してね! JSON.stringify([1,2,3]) // "[1,2,3]" String([1,2,3]) // "1,2,3"GAS余談
Spreadsheetにて、onEdit()やonChange()は、IMPORTRANGEしているシートについては、IMPORTRANGEの元シートに編集or変更があっても発動しないようです。
IMPORTRANGEしているシートはA1とかA2あたりにIMPORTRANGE関数書いてあるだけだからね…発動したらたまったもんじゃないですね…参考
JavaScript:filter()を使って配列内の重複要素を削除・取得したり、2つの配列から共通要素を取得する方法
配列同士で重複する値があるか確認する
2次元配列中の重複削除
- 投稿日:2020-06-03T14:00:44+09:00
[Rails]フォームのすべての値をワンクリックで初期化する(helperメソッド定義+JavaScript)
はじめに
様々な検索条件を付けられる検索フォームを実装している中で、検索条件をワンクリックでリセットできる方法がないかと試行錯誤しました。
結果、下記の方法で実装できたのでまとめます。
環境
- Ruby2.5.1
- Rails5.2.4
手順
概要を簡単に説明すると、
1. ヘルパーメソッドにリセットボタンタグを生成するメソッドを定義
2. ビューでそれを呼び出す
3. チェックボックスをjsでクリアする処理を書く
という3本でお送りする感じです1. ヘルパーメソッドの定義
どのファイルでもいいですが、今回は
helpers/application.rb
に定義します。helpers/application.rbmodule ApplicationHelper def reset_tag(value = "Reset form", options = {}) options = options.stringify_keys tag :input, { type: "reset", value: value }.update(options) end end2. ビューファイルで呼び出し
search.html.haml%div = reset_tag 'クリア', id: 'js_clear_btn' %div = f.submit '完了'本来Railsに
reset_tag
はありませんが、ヘルパーメソッドで定義したので、この書き方で呼び出せます。3. チェックボックスをJavaScriptでクリアする記述
私の場合は、リセットボタンだとチェックボックスをクリア(チェックを外す)ことができなかったので、そこはJavaScript書きました。
コードは環境に大きく依存してしまうので、割愛します。
結果
こんな感じで、text_fieldもnumber_fieldも、selectもcheckboxもすべて初期化するボタンを作成できました!参考
- 投稿日:2020-06-03T13:12:46+09:00
Angular.jsでタスク管理用スクリプトを作る
Angular.jsでタスク管理スクリプト
時間があったのでJavascriptとAngular.jsを練習
前提
- ツールが概ねダウンロードできない(sublimeすら入らない)
- 作業報告が主なタスク
機能
- タスク追加
- タスク削除
- タスク更新
- フォーマット化して確認
- 日付の自動表示
画面のイメージ
実際の画面は下記です。上部の「編集」「確認」ボタンで画面が切り替わります。
編集画面
確認画面
ソースコード
ダウンロードボタンは実装検討中(置物)です。
本当はボタンを押すだけで設定ファイルが更新されるようにしたい。HTML
nippo.html<!doctype html> <html ng-app="myNippo"> <head> <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.4/angular.min.js"></script> <script src="./defaultSettings.js"></script> <script src="./nippo.js"></script> <link rel="stylesheet" href="nippo.css"> </head> <body> <div ng-controller="myController as myCtrl"> <button class="btn primary-btn" ng-click="isEdit = true">編集</button> <button class="btn primary-btn" ng-click="isEdit = false">確認</button> <hr/> <div ng-if="isEdit"> <input ng-model="startTime" ng-change="startChange()" /> <input ng-model="endTime" ng-change="endChange()" /> <div> <span>勤務時間: {{workingHours}}H</span> <span ng-if="workingMinutes">{{workingMinutes}}m</span> </div> <div>作業時間: {{executionHour}}<span ng-if="executionMinute && executionMinute == 30">.5</span>H<span ng-if="executionMinute && executionMinute != 30">{{executionMinute}}m</span> </div> <hr/> <div ng-repeat="tValue in taskValues"> <div ng-if="myCtrl.hasTask(tValue)"> {{tValue}} <div ng-repeat="task in tasks"> <div ng-if="task.value==tValue"> <input type="checkbox" ng-model="task.checked"> <input type="text" class="name-input" ng-model="task.name"> <input type="number" class="time-input" ng-model="task.hour" ng-change="myCtrl.calcHoursFromTasks()">H <span ng-if="task.minute"><input type="number" class="time-input" ng-model="task.minute" ng-change="myCtrl.calcHoursFromTasks()">m</span> </div> </div> </div> </div> <button class="btn alert-btn" ng-click="myCtrl.deleteTasks()">削除</button> <div class="block"> <input ng-model="myCtrl.addTaskName" type="text" placeholder="タスク"> <input ng-model="myCtrl.addTaskHour" type="number" placeholder="時" class="time-input">H <input ng-model="myCtrl.addTaskMinute" type="number" placeholder="分" class="time-input">m <select ng-model="myCtrl.addTaskValue" ng-options="value for value in taskValues" ng-required="true"></select> <div> <textarea ng-model="myCtrl.addTaskText"></textarea> </div> <button class="btn primary-btn" ng-click="myCtrl.onAdd()" >追加</button> </div> <div> <hr/> <button class="btn normal-btn" ng-click="myCtrl.downloadTasks()">ダウンロード</button> <button class="btn normal-btn" ng-click="displayTasks = !displayTasks">defaultSettings表示切替</button> <div ng-if="displayTasks"> <pre> var defaultTasks = {{tasks|json}}; var defaultStartTime="{{ startTime }}"; var defaultEndTime="{{ endTime }}"; var defaultTaskValues={{taskValues}}; </pre> </div> </div> </div> <div ng-if="!isEdit"> <label class="normal-label">日付: </label> {{current | date: 'yyyy/MM/dd'}}<br/> <hr/> <br/> 〇本日の作業<br/> <br/> トータル時間:{{executionHour}}<span ng-if="executionMinute && executionMinute == 30">.5</span>H<span ng-if="executionMinute && executionMinute != 30">{{executionMinute}}m</span><br/> <br/> <div ng-repeat="tValue in taskValues"> <div ng-if="myCtrl.hasTask(tValue)"> {{tValue}} <div ng-repeat="task in tasks"> <div ng-if="task.value==tValue">・{{task.name}}: {{task.hour}}<span ng-if="task.minute && task.minute == 30">.5</span>H<span ng-if="task.minute && task.minute != 30">{{ task.minute }}m</span> <div ng-if="task.text" class="task-text-area">{{task.text}} </div> </div> </div> <br/> </div> </div> </div> </div> </body> </html>Javascript
nippo.jslet app = angular.module('myNippo', []); app.controller('myController', function($scope) { const restHour = 1; $scope.startTime = defaultStartTime; $scope.endTime = defaultEndTime; $scope.current = new Date(); $scope.isEdit = true; $scope.displayTasks = false; $scope.taskValues = defaultTaskValues; $scope.tasks = defaultTasks; // 就業時間から勤務時間計算 let calcHoursFromStoE = function(startTime, endTime) { $scope.workingHours = 0; $scope.workingMinutes = 0; let startTimeArray = startTime.split(':'); const startHour = startTimeArray[0]; const startMinute = startTimeArray[1]; let endTimeArray = endTime.split(':'); const endHour = endTimeArray[0]; const endMinute = endTimeArray[1]; let workingHour = endHour - startHour - restHour; let workingMinute = endMinute - startMinute; if (workingMinute == 30) { $scope.workingHours = workingHour + 0.5; } else if (workingMinute < 0) { workingHour--; let resultMinute = (60 - parseInt(startMinute)) + parseInt(endMinute); if (resultMinute == 30) { $scope.workingHours = workingHour - 1 + 0.5; $scope.workingMinutes = null; return; } $scope.workingHours = workingHour; $scope.workingMinutes = resultMinute; } else{ $scope.workingHours = workingHour; $scope.workingMinutes = workingMinute; } } // タスクから勤務時間計算 this.calcHoursFromTasks = function() { let hours = 0; let minute = 0; $scope.tasks.forEach(task => { if (task.hour != null) { hours = hours + parseFloat(task.hour); } if (task.minute != null) { minute = minute + parseInt(task.minute); } if (minute >= 60){ hours++; minute = minute - 60; } }); $scope.executionHour = hours; $scope.executionMinute = minute; } calcHoursFromStoE($scope.startTime, $scope.endTime); this.calcHoursFromTasks(); // 開始時間変更 $scope.startChange = function() { calcHoursFromStoE($scope.startTime, $scope.endTime); } // 終了時間変更 $scope.endChange = function() { calcHoursFromStoE($scope.startTime, $scope.endTime); } // タスク追加 this.onAdd = function() { // 時刻が空で分のみ入力されたとき、時刻に0を入力する if (this.addTaskMinute != null && this.addTaskHour == null) { this.addTaskHour = 0; } if (this.addTaskValue == null) { alert('タスク種別が選択されていません'); return; } $scope.tasks.push({ name: this.addTaskName, hour: this.addTaskHour, minute: this.addTaskMinute, value: this.addTaskValue, text: this.addTaskText }); this.addTaskName = ""; this.addTaskHour = null; this.addTaskMinute = null; this.addTaskValue = null; this.addTaskText = ""; this.calcHoursFromTasks(); } // タスク削除 this.deleteTasks = function() { for (let i = 0;i < $scope.tasks.length; i++) { if ($scope.tasks[i].checked) { $scope.tasks.splice(i--, 1); } } } // defaultTasksダウンロード(TBD: 実装方法検討中) this.downloadTasks = function() { let array = ["let defaultTasks = " + JSON.stringify($scope.tasks)] let blob = new Blob(array ,{type:"text/json"}); let link = document.createElement('a'); link.href = URL.createObjectURL(blob); link.download = 'defaultSettings.txt'; link.click(); } this.hasTask = function(value) { for (let i = 0; i < $scope.tasks.length; i++){ if ($scope.tasks[i].value == value) { return true; } } return false; } });CSS
気持ちばかりのCSS
nippo.css.time-input { width: 50px; } .name-input { width: 200px; } .task-text-area { white-space: pre-line; } .block { padding: 10px 0px 10px 5px; } .btn { text-align: center; margin: 5px; padding: 2px 7px 2px 7px; border: 1px solid transparent; border-radius: .25rem; } .normal-btn { color: white; background-color: lightseagreen; border-color: lightseagreen; } .primary-btn { color: white; background-color: cornflowerblue; border-color: cornflowerblue; } .alert-btn { color: white; background-color: crimson; border-color: crimson; } .normal-label { color: white; background-color: lightseagreen; text-align: center; padding: 2px 7px 2px 7px; border-radius: .25em; font-weight: bold; font-size: small; }設定用ファイル
defaultSettings.jsvar defaultTasks = [ { "name": "リファクタリング", "hour": 0.5, "value": "【開発】" }, { "name": "メールチェック", "hour": 0.5, "value": "【その他】" } ]; var defaultStartTime="9:00"; var defaultEndTime="17:30"; var defaultTaskValues=["【開発】","【その他】"];できるならコンポーネント化したりしたいところ。
script指定するだけで使えるのは助かりました。
- 投稿日:2020-06-03T12:30:08+09:00
[備忘メモ]JavaScript の Date を「YYYY-MM-DD」の String に変換する関数
vuejs-datepicker
でカレンダーから日付を選択するコンポーネントを作っていたのですが、選択した月が1ヶ月ずれるという事象に出会いまして。。
結局、その問題は解決したんですが、JS の Date に関する、わかりづらい仕様を覚えておくためのメモです。Date を String に変換する関数
作成していたカレンダーのコンポーネントでは、得られた日付(Date)を「YYYY-MM-DD」形式の文字列(String)に変換する処理を書いていたのですが、その関数が以下になります。
init() { const now = new Date(Date.now()); formatDate(now); }, formatDate(val) { const year = val.getFullYear(); const month = val.getMonth() + 1; const date = val.getDate(); const formatDate = String(year) + '-' + String(month) + '-' + String(date); return formatDate; }
formatDate
のところが実際の関数です。const month = val.getMonth() + 1;気をつけないといけないのは上記の部分。
なんとJSの
getMonth()
が得られる月は「0~11」の間です。
なので、+ 1
しないと 1 ヶ月ずれてしまいます。それにしてもこんな罠があるなんて。。
参考文献
- 投稿日:2020-06-03T12:08:37+09:00
ReactNativeで指定した文字全てを置換する方法
個人用にメモ
置換メソッドreplaceについて記述してみる使い方はこんな感じらしい
[配列名].replace(/`変換対象の文字`/,` 変換後の文字`)配列に入ってる
,
だけ取り除いてみる。//配列生成 const str = "apple,banana,orange"; // 「,」を全て取り除く const replaced = str.replace(/,/g, ' ') //出力結果 console.log(replaced) apple banana orange
- 投稿日:2020-06-03T11:37:43+09:00
iOS,Android input="date" JavaScriptイベント取得まとめ
はじめに
Javascriptでinput="date"のイベント取得をiOS,Androidで調べたのでまとめ
検証環境
iOS 13.3
ブラウザ SafariAndroid 8.0.0
ブラウザ ChromeiOS
操作 イベント フォーカス onchange 日付切り替え onchange 消去 onchange 完了 onblur 完了を押下するとフォーカスが外れるため'onblur'が発火するっぽい
Android
操作 イベント フォーカス - 日付切り替え - 削除 onchange (日付が入力済みの場合) キャンセル - 設定 onchange 比較
ios Android フォーカス onchange - 日付切り替え onchange - 消去・削除 onchange onchange キャンセル - - 完了・設定 onblur onchange sample
alertだとダイアログを消した際に、blurが発生し続ける見たいだったので、HTML出力
See the Pen sample date by shotaabe (@shotaabe) on CodePen.
- 投稿日:2020-06-03T09:50:10+09:00
Notification API + Laravel + Vue.js、 PWAで メッセージ機能を作る 作例編
概要
以前の 製作事例公開内容となりますが
会員メンバー間で、メッセージを送受信できる機能を
Laravel+ Vue でPWA対応、実装しました。
新着の自動更新は、JSタイマで起動し、
Notification API での通知等の仕組みとなります。参考のコード / GitHub
https://github.com/kuc-arc-f/lara58a_7message
構成
Progressive Web Apps / PWA
Notification API
Laravel 5.8
Vue.js
javascript
nginx
mysqlmigrations
画像
・受信一覧、
メールのような、受信、送信タブで切替表示としました実装など
・JSタイマー
定期実行、自分宛の送信メッセージを監視し、
新着があれば、トリガー発火して。新着通知等の処理を実行します。function set_time_text(){ var data = { 'user_id': USER_ID, 'type': 1, }; axios.post('/api-1234' , data).then(res => { var item = res.data if(item.id != null){ $("input#time_text").val( item.id ); $("input#message_title").val( item.title ); }else{ $("input#time_text").val( 0 ); } console.log( item ); }); } set_time_text(); var timer_func = function(){ set_time_text(); }; var TIMER_SEC = 1000 * 600; setInterval(timer_func, TIMER_SEC );・新着の通知
function display_notification(title, body ){ if (!('Notification' in window)) {//対応してない場合 alert('未対応のブラウザです'); } else { // 許可を求める Notification.requestPermission() .then((permission) => { if (permission === 'granted') {// 許可 var options ={ body: body, icon: 'https://hoge.net/icon.png', tag: '' }; var n = new Notification(title,options); console.log(n); setTimeout(n.close.bind(n), 5000); } else if (permission == 'denied') {// 拒否 } else if (permission == 'default') {// 無視 } }); } }参考のページ
https://knaka0209.hatenablog.com/entry/lara58_26message
.
- 投稿日:2020-06-03T09:38:38+09:00
【5分で理解】JavaScriptのユーザー定義関数について
こんにちは!
本日はユーザー定義関数について解説していきたいと思います。
JavaScriptの基本を学びたい初学者の方は是非読んでいってください!? 関数のイメージ
関数ってよく聞くけど数学苦手マンだからよくわからん!というような私みたいな方もいると思うので、
最初に関数のイメージについてお話します。関数を英語にすると function となります。
functionだけだとピンときませんが、意味を調べると関数の他に機能などの意味があります。関数のフローは自動販売機によく例えられます。
ジュースのボタンを押す
お金を入れる→→選んだジュースが出てくるコレを関数に比喩すると
お金を入れる = 引数
ボタンを押す = 処理の実行
ジュースが出る = 戻り値というような形になります。
とりあえず、関数は自動販売機みたいな流れで動くというイメージだけ掴んでいただければOKです。? ユーザー定義関数って何?
関数は主に2種類あります。
- 組み込み関数 → 既にプログラムで準備されている関数
- ユーザー定義関数 → 自身で自由に作れる関数
その名の通り、ユーザーが定義した関数のことをユーザー定義関数と呼びます。
基本的な形としては
function test(引数){ //処理 return 戻り値; //戻り値 }testという自販機に対して、引数を入れて処理を行い、戻り値(return)が帰ってきます。
実際には引数がなかったり、引数が複数あったりするパターンもありますので、そちらを解説していこうと思います。? 関数のパターン
上記で書いたとおり関数の記載には複数のパターンが存在します。
何パターンか紹介していこうかと思います。パターン1 引数なし、戻り値なし
function test(){ //引数は無いので()の中には何も書かない console.log('test'); //戻り地も無いのでreturnを書かない } test();コレで実行すると、コンソール上に test と表示されます。
シンプルにconsole.logだけが動いている状態ですパターン2 引数あり、戻り値なし
const text = '春日部防衛隊ファイヤー!!!' const text_2 = '救いのヒーローぶりぶりざえもん参上' function getText(string){ //stringを引数に console.log(string); //ここの()の中身は必ず引数と同じ名前にする必要がある。 } getText(text); //勘違いしやすいですが、ここの()の中身は引数と同じにする必要がありません。コレを実行すると、春日部防衛隊ファイヤー!!!と出力されます。
このように、変数を代入して使うことが多いです。
またgetText(text_2);とすることで、救いのヒーローぶりぶりざえもん参上と出力されます。パターン3 引数なし、戻り値あり
このパターンは少し複雑になります。
下記のパターンで処理を実行した場合、どうなるでしょうか?function textNumber(){ return 5 ; } textNumber();一見するとreturnの5が、コンソール上に反ってきそうにも見えますが、
このコードに出力する処理が書かれていないので、何も表示はされません。
しかし、処理自体は行われているのでtextNumberに5という数字自体は入っています。function textNumber(){ return 5 ; } console.log(textNumber());このようにconsole.logでかこってあげると5と出力できます。
あるいはfunction textNumber(){ return 5 ; } textNumber(); //この段階で5がtextNumberに入ってる。 const TextNumber = textNumber(); //textNumberを変数TextNumberに代入する console.log(TextNumber) //変数を出力するこのように変数化して出力することも可能です!
これでも5が出力されます。パターン4 インプット2つ、戻り値あり
今回は足し算の合計を出力するコードを作ります。
function sumNumber(int1,int2){ //int1=4,int2=6が代入される。下記の第一引数、第二引数がそれぞれint1,int2似自動で振り分けられる。 let int3 = int1 + int2; return int3; } const total = sumNumber(4,6); //第一引数に4を第二引数に6を入れる console.log(total);この出力は、10となるのがわかるかと思います。
ポイントは引数と処理の中の文字をあわせることに注意するくらいだと思います。非常に簡単でしたが、ユーザー定義関数について簡単にパターンと使い方について解説していきました。
今回は以上となります!? 次回予告
次回は、組み込み関数についても解説できたらなと思います。
私事ですが、今後はAWS認定ソリューションアーキテクト アソシエイトに挑戦する予定ですので更新頻度が下がるか、備忘録的にそちらの記事を書いていくかと思われます。
記事を読んでいただきありがとうございました。
間違えている部分などありましたらご指摘のほどお願い致します
- 投稿日:2020-06-03T09:24:07+09:00
GAS:リストをもとに、テンプレートシートを複製し、シート名をリネームする
qiita arrayPractice.jsconst ThisSpread = SpreadsheetApp.getActiveSpreadsheet(); function createSheet(){ const name_list = createNameList(); Logger.log(name_list); const TemplateSheet = ThisSpread.getSheetByName('template'); for (let name of name_list){ const create_sheet = TemplateSheet.copyTo(ThisSpread); create_sheet.setName(name); } } function createNameList() { const name_list = []; const NamesSheet = ThisSpread.getSheetByName('names'); for (row = 1; row <= 5; row ++){ name_list.push(NamesSheet.getRange(row, 1).getValue()); } //Logger.log(name_list); return name_list; }
- 投稿日:2020-06-03T09:13:18+09:00
Nuxt.js で npm モジュールをプラグイン化するサンプル(IMI コンポーネントツール)
概要
- Nuxt.js で npm モジュールをプラグイン化して使う例をメモとして残す
- ついでに最近リリースされた IMI コンポーネントツールを使ってみる
手順
npm モジュール追加
yarn add https://info.gbiz.go.jp/tools/imi_tools/resource/imi-enrichment-contact/imi-enrichment-contact-1.0.0.tgzプラグイン作成
- plugins 以下に javascript ファイルを作成
- モジュールを import してコンテキストと Vue コンポーネントにインジェクトする
plugins/contact.jsimport contact from 'imi-enrichment-contact'; export default (ctx, inject) => { ctx.$contact = contact; inject('contact', contact); };nuxt.config.js を修正してロード
nuxt.config.jsplugins: ['@/plugins/contact'],.vue ファイルから使用する
xxx.vueasyncData({ $contact }) { console.log($contact('117')); }, methods: { normalize() { this.normalized = JSON.stringify(this.$contact(this.tel), null, 4); } }動作サンプル
- https://yoshihiro-hirose.github.io/nuxt-vuetify-sample/samples/contact
- https://github.com/Yoshihiro-Hirose/nuxt-vuetify-sample
補足
- IMI コンポーネントツールはブラウザで動かないものもある(住所とか)
- プラグインを TypeScript から使う場合は型定義なども別途必要
参考リンク
- 投稿日:2020-06-03T09:07:01+09:00
GAS:javaScriptでリスト化されたタイトルを各シートのrange(1,1)にsetValue()する
環境
GAS
前提
criteriaシートのA1からB4まで、シート名とタイトルが記載されています。
qiita arrayPractice.jsconst this_spread = SpreadsheetApp.getActiveSpreadsheet(); function Main() { const name_map = createMap(); const error_list = []; for (let name in name_map){ //Logger.log(name); //Logger.log(name_map[name]); try{ const target_sheet = this_spread.getSheetByName(name); Logger.log(name + ':' + name_map[name]); target_sheet.getRange(1,1).setValue(name + ':' + name_map[name]); }catch(error){ error_list.push(name); } } Browser.msgBox('error_list is ' + error_list); } function createMap() { const criteria_sheet = this_spread.getSheetByName('criteria'); let name_map = new Map(); for(let count = 1; count <= 4; count ++){ name_map[criteria_sheet.getRange(count,1).getValue()] = criteria_sheet.getRange(count,2).getValue(); } return name_map }
- 投稿日:2020-06-03T08:56:01+09:00
【Vue.js】半日でできる、ビデオチャット上で動くオリジナルゲームの開発から公開まで。
Vue.jsを使って簡単なゲーム開発し、完成したものをwh.imというゲームのプラットフォームに公開するまでをまとめてみました。
この記事を読めば、Vue.js未経験の方であっても数時間あればオリジナルゲームを開発・公開し、友達と遊べるようになります!
できる限りわかりやすく書いたので長くなりましたが、ぜひ最後まで読んでみてください(記事の完成に3日かかりました? 服装の変化を楽しんでください)。ご質問はコメントまで!対象読者
- html / css はある程度わかる方
- Vue.jsに入門したい人
- ビデオチャット上でゲームを作ってみたい方
はじめに
wh.im というサービスの開発している@aitaroです。Vue.jsを使い始めてかれこれ1年半になります。今でこそ wh.im を開発できるぐらいの知識がつきましたが(wh.imはVue.jsとそのフレームワークであるNuxt.jsを使っています)、Vue.jsを始めたときは右も左もわからない状態でした。そこで、同じようにVue.js初心者の方に対して、ゲームの作成といった一つのプロダクトの完成とデプロイを目指した入門記事を今回書こうと思いました。wh.im要素が多めはなりますが、Vue.jsの基礎も理解できるようになっているので、是非最後まで走りきってみてください。
また、Vue.js経験者の方にも、wh.imの始め方から公開の仕方までが一通りまとまっているので、ビデオチャットで遊べるでゲームを作ってみたいときは是非参考にしてください。
構成
このエントリは以下の6つのパートと21の章から成ります。
Ⅰ. wh.imとは?
Ⅱ. Vue.jsの環境構築
- 1. node.jsをインストール
- 2. npmでvue-cliをインストール
- 3. Vue.jsのプロジェクトを作る
- 4. ソースコードを変更してみる。
Ⅲ. wh.imでの開発のための環境設定
- 5. wh.im上でvueを動かしてみる
- 6. wh.im上で開発の準備
Ⅳ. ゲームの実装
- 7. main.jsのでライブラリ読み込み
- 8. vue.jsのコンポーネントの追加
- 9. じゃんけん画像の設置
- 10. グーを選択するメソッドを追加しよう!
- 11. 選んだ手のstateの登録
- 12. チョキやパーを選択できるようにしよう!
- 13. 関数を抽象化しよう!
- 14. 選択後の画面を作ろう
- 15. 結果の画面を作ろう
- 16. 結果の画面に全員の選んだ手を表示してみよう!
- 17. 最後に見た目を整えよう!
Ⅴ デプロイして公開する
- 18. githubにコードを上げる
- 19. netlifyの設定をする
- 20. wh.imのdevelop画面でゲームの登録をする
- 21. 遊んで見る!
Ⅵ. まとめ
wh.imとは?
wh.imとは、ビデオチャットしながら遊べるゲームのプラットフォームです。wh.imにアクセスすると、じゃんけんやワードウルフなどのゲームが友達と遊べます。さらに、wh.imの特徴はなんと言ってもオリジナルゲームを投稿できること!自分で作ったゲームを登録すれば、今日中にでもそのゲームで友達と遊ぶことができます。今回はこの wh.im を題材に使って、Vue.jsを用いたゲームを作っていこうと思います!
wh.imについて詳しくしりたい方は、新型コロナの自宅待機中に、ビデオチャットしながらゲームで遊べるサービスを作った話を是非読んでみてください。
Vue.jsの環境構築
1. node.jsをインストール
まずはMacにnode.jsをインストールしてください。インストールの方法はこちらの記事に簡潔にまとまっています。
Macにnode.jsをインストール2. npmでvue-cliをインストール
node.jsをインストールしたあなたは、npm使えるようになっているはずです。npmとは、node package managerの略で、JavaScript系のパッケージを管理するためのツールです。
以下の方法で、npmを使ってvue-cliをインストールしましょう。
-gをつけることで、グローバルにインストールされるので、どこからでもvueコマンドを呼び出せるようになります。$ npm install -g @vue/cli3. Vue.jsのプロジェクトを作る
ここから、簡単なじゃんけんアプリの開発を通じてVue.jsを使ったゲーム開発を学んでいきましょう。
まずはVue.jsのプロジェクトを作成してみます。今回はじゃんけんゲームを作っていくので、jankenとしますが、好きな名前で大丈夫です。
$ vue create janken
すると、次のような選択画面になります。
? Please pick a preset: (Use arrow keys) ❯ default (babel, eslint) Manually select featuresこれはそのままEnterを押してしまいましょう。
最後にこのような画面ができたら成功です!? Successfully created project janken. ? Get started with the following commands: $ cd jaknen $ npm run serve作成されたら、画面の指示に従ってそのjankenというディレクトリに移動してみましょう。
この段階で試しに起動してみましょう。$ cd janken $ npm run serve起動は成功しているので、Chrome等ブラウザのアドレスバーに
http://localhost:8080
と入力してみてください。
このような画面が出てくると思います。
これで環境構築はひとまず終了です!お疲れ様でした?4. ソースコードを変更してみる。
これで、Vue.jsの起動は完了しました。
Vue.jsを初めて触る人向けに、コードの雰囲気を掴んでもらいます。
まず、vue create で作成されたサンプル画面を見ていきましょう。
このサンプル画面のコードは src > components > HelloWorld.vue にあります。
ではそのうちこの文章を変えてみましょう。src/components/HelloWorld.vue<p> For a guide and recipes on how to configure / customize this project,<br> check out the <a href="https://cli.vuejs.org" target="_blank" rel="noopener">vue-cli documentation</a>. </p>これを適当に変えてみます。
src/components/HelloWorld.vue<p> 100日目にVue.jsをマスターする俺<br> check out the <a href="https://cli.vuejs.org" target="_blank" rel="noopener">vue-cli documentation</a>. </p>変えたら、command + S(winはctrl + S)で変更を保存してみましょう。
すると、ブラウザでみると自動で反映されていると思います!
これはVue.jsのうちのWebpackというライブラリで、HotReloadという機能です!変更が保存されると、自動で反映されます!ただ時々バグるので、もしバグったなというときはおとなしくブラウザの画面をリロードしましょう。wh.imでの開発のための環境設定
5. wh.im上でvueを動かしてみる
wh.im上でVueを動かしてみましょう。
wh.imから遊び場を作成します。
次に、wh.imを開発モードにします。
URLの最後に&develop=true
を追加して、リロードすると、wh.imが開発モードに変わります。
4開発モードになると、右上のボタンからアプリを選択するときに、開発用(port:8080)が出てきます。
この開発用(port:8080)を選択し、プレイすると先程の画面になります。
これで、開発の準備は整いました。
6. wh.im上で開発の準備
では、実際にwh.imを使ったゲームを作っていきましょう。wh.imはVueで開発しやすいように、ライブラリを用意しています。(ライブラリとは拡張機能みたいなもので、追加することでVueでやれることが広がります)
一旦、ターミナルにいき、 command + C (windows の場合は ctrl + C)でサーバーを止めましょう。
その後、次のコマンドを打ち込んでライブラリを追加します。$ npm install whim-client-vueこれで、ライブラリが入りました!
package.jsonを開いてみると、追加されているのがわかります。(バージョンは多少ことなる場合があります。)package.json{ "name": "janken", "version": "0.1.0", "private": true, "scripts": { "serve": "vue-cli-service serve", "build": "vue-cli-service build", "lint": "vue-cli-service lint" }, "dependencies": { "core-js": "^3.6.5", "vue": "^2.6.11", "whim-client-vue": "^1.1.4" // ←ここ }, "devDependencies": { "@vue/cli-plugin-babel": "~4.4.0", "@vue/cli-plugin-eslint": "~4.4.0", "@vue/cli-plugin-vuex": "~4.4.0", "@vue/cli-service": "~4.4.0", "babel-eslint": "^10.1.0", "eslint": "^6.7.2", "eslint-plugin-vue": "^6.2.2", "vue-template-compiler": "^2.6.11" } }そして次に、Vueの設定を変更します。janken(ルートディレクトリ)の直下にvue.config.jsを作り、以下のコードを加えましょう。
これはクロスドメインでアプリを呼び出すときに、host名を明示的にlocalhostにするためです。vue.config.jsmodule.exports = { devServer: { host: "localhost" }, };これでもう一度、Vueを起動します。
$ npm run serve
vueを再起動したときは、wh.imの方でも一回、ゲームを終了して再度選択する必要があります。
ゲームの実装
7. main.jsのでライブラリ読み込み
ここからは実際にコードを書いていきます。
まず、src/main.jsを開き、whim-client-vueを使うように設定します。
3,4行目を増やしました。src/main.jsimport Vue from 'vue' import App from './App.vue' import whimClientVue from "whim-client-vue"; Vue.use(whimClientVue); Vue.config.productionTip = false new Vue({ render: h => h(App), }).$mount('#app')これは、whim-client-vueのライブラリを呼び出すコードです。
src/main.jsimport whimClientVue from "whim-client-vue";
Vue.use
を使うことで、さきほど呼び出した、whim-client-vueのライブラリを、Vueに登録します。src/main.jsVue.use(whimClientVue);8. vue.jsのコンポーネントの追加
次に、App.vueを編集します。これは、ページにアクセスすると最初に表示される画面です。
これをデフォルト画面から変更してみます。変更点は、divタグの中身を消したこと、HelloWorldコンポーネントは今回使わないので、componentsを消したことです。src/App.vue<template> <div id="app"> Player: {{ $whim.accessUser.name }} </div> </template> <script> export default { name: "App", }; </script> <style> #app { font-family: Avenir, Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; margin-top: 60px; } </style>すると、自分の名前が表示されていると思います。
template内の {{ }} は、そこの部分を式評価するという意味です。
今回は、そこに $whim.accessUser.name と記述することで、アクセスしてるユーザーの名前がとれました。
ここで言う、アクセスしているユーザーとはそのゲームをプレイしている人のことです。wh.imはビデオチャットサービスなので、複数人でゲームをプレイします。そのとき、このブラウザからアクセスしている人はだれかということをゲーム側で把握しなければなりません。
実際に確認してみましょう。プライベートブラウザや、違うブラウザで、wh.imの同じルームにアクセスします。すると今度は、 Player:モナリザ と表示されました。
このようにアクセスする人によって、表示を変えたい場合は、accessUserを使うことで可能になります。
9. じゃんけん画像の設置
次に、ゲーム上にじゃんけんの選択肢を配置しましょう。
まず、じゃんけんの画像を用意します。
私は素材ライブラリーさんの画像を使わせていただきました。ダウンロードした画像を、 src/assetsの中に配置します。
ファイル構成は以下のようになると思います。janken └── assets ├── paper.png ├── rock.png └── scissors.pngそしてこれを先程のApp.vue画面に配置します。
先程の、Player: {{ $whim.accessPlayer.name }}
を下のように書き換えてください。src/App.vue<div> <h2> 選択してください! </h2> <div> <img src="@/assets/rock.png" width="150" height="150" /> <img src="@/assets/scissors.png" width="150" height="150" /> <img src="@/assets/paper.png" width="150" height="150" /> </div> </div>
App.vue全体
src/App.vue<template> <div id="app"> <div> <h2> 選択してください! </h2> <div> <img src="@/assets/rock.png" width="150" height="150" /> <img src="@/assets/scissors.png" width="150" height="150" /> <img src="@/assets/paper.png" width="150" height="150" /> </div> </div> </div> </template> <script> export default { name: 'App' } </script> <style> #app { font-family: Avenir, Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; margin-top: 60px; } </style>そして、wh.imの画面を確認します。すると、以下のような選択画面が現れていると思います!
10. グーを選択するメソッドを追加しよう!
次に、グーチョキパーを選択できるようにします。
目標は、画像をクリックすると文字が選択済み!に変わって、データベースに選択した手が書き込まれることです。
ではまず、データベースに書き込む処理をしましょう。グーから行きます。
グーの画像に@click="selectRock"
を付け足します。以下のような感じです。これはclickしたときにselectRockという関数を実行するという意味です。src/App.vue<img src="@/assets/rock.png" width="150" height="150" @click="selectRock" />そしてselectRock関数をvueに登録します。
を以下のように編集します。src/App.vue<script> export default { name: 'App', methods: { selectRock() { // ここに処理を書いていく console.log('selectされた!') } } } </script>
App.vue全体
src/App.vue<template> <div id="app"> <div> <h2> 選択してください! </h2> <div> <img src="@/assets/rock.png" width="150" height="150" @click="selectRock" /> <img src="@/assets/scissors.png" width="150" height="150" /> <img src="@/assets/paper.png" width="150" height="150" /> </div> </div> </div> </template> <script> export default { name: 'App', methods: { select(hand) { // ここに処理を書いていく console.log('selectされた!') }, }, } </script> <style> #app { font-family: Avenir, Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; margin-top: 60px; } </style>ここで出てきた
console.log
はブラウザのconsole画面にログを表示する命令です。
これで、グーを押したときの挙動を確認しましょう。
wh.imの画面に戻って、Chromeのデベロッパーコンソールを開きます。macの方は command + option + i で開きます。 そして、その状態でグーをクリックします。するとコンソール画面にselectされた!
と表示されるはずです。(以下の画像を参照)このように@clickでselectRock関数が実行されたのがわかると思います。
11. 選んだ手のstateの登録
次にデータベースに登録しましょう。selectメソッドを変更していきます。
データーベースはwh.im上ではstateと呼ばれます。また、このstateはデーターベースといいつつ、いわゆるRDBではなくJSON型で保存されます。(そしてこのデーターベースはこのアプリを立ち上げているブラウザ間で同期されます。)
stateを変更するときは、$whim.assignState
関数を使います。
今回はアクセスしてるユーザーがグーを出したことを保存したいので、stateに[this.$whim.accessUser.id]: "rock"
を書き込みます。src/App.vueselectRock() { // ここに処理を書いていく console.log('selectされた!') this.$whim.assignState({ [this.$whim.accessUser.id]: "rock" }) }動作確認をしてみましょう。グーをクリックしてみます。するとstateにグーが登録されます。wh.imの開発モードにはこのstateを確認する機能があります。右上のメニューボタンから、SHOW APP STATE を選んでください。
すると次にような画面が出てきます。
この
Object
がstateです。
今回はstateにoNyPTFZuMbOOZruCR4UMSsP9zRu2:"rock"
が登録されていることがわかりました。この、oNyPTFZuMbOOZruCR4UMSsP9zRu2
はユーザーidです。
App.vue全体
src/App.vue<template> <div id="app"> <div> <h2> 選択してください! </h2> <div> <img src="@/assets/rock.png" width="150" height="150" @click="selectRock" /> <img src="@/assets/scissors.png" width="150" height="150" /> <img src="@/assets/paper.png" width="150" height="150" /> </div> </div> </div> </template> <script> export default { name: 'App', methods: { selectRock() { // ここに処理を書いていく console.log('selectされた!') this.$whim.assignState({ [this.$whim.accessUser.id]: "rock" }) }, }, } </script> <style> #app { font-family: Avenir, Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; margin-top: 60px; } </style>12. チョキやパーを選択できるようにしよう!
今のところまだ、グーしか選択できません。チョキやパーも選択できるようにしましょう。これは今までの要領でいくと簡単です。
まずselect関数を2こ増やします。src/App.vue<script> export default { name: 'App', methods: { selectRock() { // ここに処理を書いていく console.log('selectされた!') this.$whim.assignState({ [this.$whim.accessUser.id]: "rock" }) }, selectScissors() { // ここに処理を書いていく console.log('selectされた!') this.$whim.assignState({ [this.$whim.accessUser.id]: "scissors" }) }, selectPaper() { // ここに処理を書いていく console.log('selectされた!') this.$whim.assignState({ [this.$whim.accessUser.id]: "paper" }) } } } </script>これで、関数が3つに増えました。これをそれぞれの画像のclickイベントに追加していきます。
src/App.vue<div> <img src="@/assets/rock.png" width="150" height="150" @click="selectRock" /> <img src="@/assets/scissors.png" width="150" height="150" @click="selectScissors" /> <img src="@/assets/paper.png" width="150" height="150" @click="selectPaper" /> </div>これで、3つともクリックしたらstateに反映されるようになりました!
(注意事項:動作確認は必ずSTATE確認画面を閉じてから行ってください。)
App.vue全体
src/App.vue<template> <div id="app"> <div> <h2> 選択してください! </h2> <div> <img src="@/assets/rock.png" width="150" height="150" @click="selectRock" /> <img src="@/assets/scissors.png" width="150" height="150" @click="selectScissors" /> <img src="@/assets/paper.png" width="150" height="150" @click="selectPaper" /> </div> </div> </div> </template> <script> export default { name: 'App', methods: { selectRock() { // ここに処理を書いていく console.log('selectされた!') this.$whim.assignState({ [this.$whim.accessUser.id]: "rock" }) }, selectScissors() { // ここに処理を書いていく console.log('selectされた!') this.$whim.assignState({ [this.$whim.accessUser.id]: "scissors" }) }, selectPaper() { // ここに処理を書いていく console.log('selectされた!') this.$whim.assignState({ [this.$whim.accessUser.id]: "paper" }) } }, } </script> <style> #app { font-family: Avenir, Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; margin-top: 60px; } </style>13. 関数を抽象化しよう!
先程のようにグー・チョキ・パーselect関数を3つ作ってもいいですが、これらの関数には似たような処理が多いです。こういうときは一つの関数にまとめます。これはプログラミングにおいて重要な抽象化の概念です。処理の内容を日本語化するとわかりやすいと思います。
まとめる前
- selectRock関数: グーをstateに登録する。
- selectScissors関数: チョキをstateに登録する。
- selectPaper関数: パーをstateに登録する。
まとめた後
- select関数: 出した手をstateに登録する。
グー、チョキ、パーが出した手に変わったこと以外は同じです。けどこのままだと、出した手ってなんやねん!ってなると思います。ここで関数の引数機能を使います。引数は
関数名(引数名)
みたいな感じで使います。
では早速コードを書いてみましょう。src/App.vue<script> export default { name: 'App', methods: { select(hand) { // ここに処理を書いていく console.log('selectされた!') this.$whim.assignState({ [this.$whim.accessUser.id]: hand }) } } } </script>そして、この関数の呼び出し側も変更します。
src/App.vue<div> <img src="@/assets/rock.png" width="150" height="150" @click="select('rock')" /> <img src="@/assets/scissors.png" width="150" height="150" @click="select('scissors')" /> <img src="@/assets/paper.png" width="150" height="150" @click="select('paper')" /> </div>これで、先程と同じ用に動くはずです。確認してみましょう。
お気づきの人もいるかも知れませんが、このステップでは機能としてなにも進んでいません。コードの書き方を変えただけです。この機能を変えずによりよいコードにすることをリファクタリングといいます。「機能が増えないなら時間の無駄やんw」という人もいるかも知れませんが、どんどん大きなプロジェクトになっていくと、それをメンテナンスするためにはコードの品質が重要になっていきます。そして、「良いコード」を書くのは一朝一夕でできることでもないので、普段から意識的に「良いコード」を書くことを意識することがおすすめです。
App.vue全体
src/App.vue<template> <div id="app"> <div> <h2> 選択してください! </h2> <div> <img src="@/assets/rock.png" width="150" height="150" @click="select('rock')" /> <img src="@/assets/scissors.png" width="150" height="150" @click="select('scissors')" /> <img src="@/assets/paper.png" width="150" height="150" @click="select('paper')" /> </div> </div> </div> </template> <script> export default { name: 'App', methods: { select(hand) { // ここに処理を書いていく console.log('selectされた!') this.$whim.assignState({ [this.$whim.accessUser.id]: hand }) }, }, } </script> <style> #app { font-family: Avenir, Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; margin-top: 60px; } </style>14. 選択後の画面を作ろう
今のままですと、何回もじゃんけんが選択できてしまいます。選択が終わったら、選択済みにかえましょう。
選択が終わったかどうかは、stateを見たらわかります。stateが空なら未選択ですし、stateに自分のaccessUserIdがあれば、選択済みです。これは$whim.state[$whim.accessUser.id]
で確認できます。また、vue.jsのテンプレートではv-if, v-else を使って条件分岐ができます。確認してみましょう。src/App.vue<template> <div id="app"> <div v-if="$whim.state[$whim.accessUser.id]"> <h2> {{ $whim.state[$whim.accessUser.id] }}を選択済みです。 </h2> </div> <div v-else> <h2> 選択してください! </h2> (以下略)
$whim.state
というのは以下のようなObject型です。(SHOW APP STATE で確認できると思います。){ jibunNoID: "paper" }ここでいう
jibunNoID
は$whim.accessUser.id
で取れる値です。(これはscriptの方で書いたthis.$whim.accessUser.id
と同じですが、vueのtemplateではthisは省略します。)
なので、すでにpaperを選んでいる場合は$whim.state[$whim.accessUser.id]
の値はpaperになりますし、何も選んでいない場合はundefined
になります。これを利用して、v-ifで条件分岐をしています。
App.vue全体
src/App.vue<template> <div id="app"> <div v-if="$whim.state[$whim.accessUser.id]"> <h2> {{ $whim.state[$whim.accessUser.id] }}を選択済みです。 </h2> </div> <div v-else> <h2> 選択してください! </h2> <div> <img src="@/assets/rock.png" width="150" height="150" @click="select('rock')" /> <img src="@/assets/scissors.png" width="150" height="150" @click="select('scissors')" /> <img src="@/assets/paper.png" width="150" height="150" @click="select('paper')" /> </div> </div> </div> </template> <script> export default { name: 'App', methods: { select(hand) { // ここに処理を書いていく console.log('selectされた!') this.$whim.assignState({ [this.$whim.accessUser.id]: hand }) }, }, } </script> <style> #app { font-family: Avenir, Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; margin-top: 60px; } </style>15. 結果の画面を作ろう
最後に結果の画面です。まずは、二人プレイでどのようにstateが登録されているか確認します。
一人目はpaperを選んでいて、二人目はrockを選んでいます。これを画面に表示します。
まずはじめに、全員がじゃんけんの手をすでに選んだかどうかを確認します。javascriptではfor文でループを表すことができます。usersを一人ずつ、stateに手が登録されているかを確認します。for文の詳しい説明はこちらなどを参考にしてください。
src/App.vuelet result = true for (let i = 0; i < this.$whim.users.length; i++ ) { if(!this.$whim.state[this.$whim.users[i].id]){ result = false } } return resultここで、
this.$whim.users
は今部屋にいるuser全員です。
まず、resultにtrueを設定しておきます。そして、users一人ずつ、手がセットしているかをthis.$whim.state[this.$whim.users[i].id]
で確認します。
ここでセットされていなかったら、resultをfalseに変更します。こうすることで、ひとりでもじゃんけんの手を選んでなかったらresultがfalseになります。これを関数として実装し、またtemplate側でv-ifを使います。(もともと、v-ifだったところはv-else-ifに変えました)この処理をisEveryoneSelectとして追加したコードは次のようになります。
src/App.vue<template> <div id="app"> <div v-if="isEveryoneSelect"> <h2> 全員が選択を終わりました。 </h2> </div> <div v-else-if="$whim.state[$whim.accessUser.id]"> <h2> {{ $whim.state[$whim.accessUser.id] }}を選択済みです。 </h2> </div> <div v-else> <h2> 選択してください! </h2> <div> <img src="@/assets/rock.png" width="150" height="150" @click="select('rock')" /> <img src="@/assets/scissors.png" width="150" height="150" @click="select('scissors')" /> <img src="@/assets/paper.png" width="150" height="150" @click="select('paper')" /> </div> </div> </div> </template> <script> export default { name: 'App', methods: { select(hand) { // ここに処理を書いていく console.log('selectされた!') this.$whim.assignState({ [this.$whim.accessUser.id]: hand }) }, }, computed: { isEveryoneSelect() { let result = true for (let i = 0; i < this.$whim.users.length; i++ ) { if(!this.$whim.state[this.$whim.users[i].id]){ result = false } } return result } } } </script> <style> #app { font-family: Avenir, Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; margin-top: 60px; } </style>ここで使用したcomputedとは、値を動的に算出するときに使うものです。今回ですと、usersが変わりうるので、methodsよりもcomputedのほうが適切です。詳しくはこの記事を読むといいでしょう。
では実際に確認しましょう。一人プレイにときはじゃんけんの手を選ぶと、すぐに全員が選択を終わりましたと出ると思いますが、2人プレイのときは、選択画面→グーを選択済みです。→全員が選択を終わりました。と順番に変更すると思います。16. 結果の画面に全員の選んだ手を表示してみよう!
全員の選択が終わったら結果を表示します。ここではVue.jsのv-forという機能を使います。v-forはtemplate内でループをするという機能です。今回の場合、全員分の結果を表示させないと行けないので、usersに対してv-forでループさせます。
src/App.vue<div v-if="isEveryoneSelect"> <h2 v-for="user in $whim.users" :key="user.id"> {{user.name}}の出した手は、{{$whim.state[user.id]}}です。 </h2> </div>
App.vue全体
src/App.vue<template> <div id="app"> <div v-if="isEveryoneSelect"> <h2 v-for="user in $whim.users" :key="user.id"> {{user.name}}の出した手は、{{$whim.state[user.id]}}です。 </h2> </div> <div v-else-if="$whim.state[$whim.accessUser.id]"> <h2> {{ $whim.state[$whim.accessUser.id] }}を選択済みです。 </h2> </div> <div v-else> <h2> 選択してください! </h2> <div> <img src="@/assets/rock.png" width="150" height="150" @click="select('rock')" /> <img src="@/assets/scissors.png" width="150" height="150" @click="select('scissors')" /> <img src="@/assets/paper.png" width="150" height="150" @click="select('paper')" /> </div> </div> </div> </template> <script> export default { name: 'App', methods: { select(hand) { // ここに処理を書いていく console.log('selectされた!') this.$whim.assignState({ [this.$whim.accessUser.id]: hand }) }, }, computed: { isEveryoneSelect() { let result = true for (let i = 0; i < this.$whim.users.length; i++ ) { if(!this.$whim.state[this.$whim.users[i].id]){ result = false } } return result } } } </script> <style> #app { font-family: Avenir, Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; margin-top: 60px; } </style>17. 最後に見た目を整えよう!
最後に画像やcssを使って、見た目を整えます。Vue.jsでは srcの前でコロンをつけることで、動的に値を設定します。
全体のコードはこちら
src/App.vue<template> <div id="app"> <!-- class="result"を追記します。 --> <div v-if="isEveryoneSelect" class="result"> <div v-for="user in $whim.users" :key="user.id"> <!-- じゃんけんの画像(rock.pngなど)を出した手に応じて表示します。 --> <img :src="require('@/assets/' + $whim.state[user.id] + '.png')" width="150" height="150" /> <h2>{{user.name}}</h2> </div> </div> <div v-else-if="$whim.state[$whim.accessUser.id]"> <h2> {{ $whim.state[$whim.accessUser.id] }}を選択済みです。 </h2> </div> <div v-else> <h2> 選択してください! </h2> <div> <img src="@/assets/rock.png" width="150" height="150" @click="select('rock')" /> <img src="@/assets/scissors.png" width="150" height="150" @click="select('scissors')" /> <img src="@/assets/paper.png" width="150" height="150" @click="select('paper')" /> </div> </div> </div> </template> <script> export default { name: 'App', methods: { select(hand) { // ここに処理を書いていく console.log('selectされた!') this.$whim.assignState({ [this.$whim.accessUser.id]: hand }) }, }, computed: { isEveryoneSelect() { let result = true for (let i = 0; i < this.$whim.users.length; i++ ) { if(!this.$whim.state[this.$whim.users[i].id]){ result = false } } return result } } } </script> <style> #app { font-family: Avenir, Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; margin-top: 60px; } /* resultクラスに対応するcssを追記します。 */ .result { display: flex; justify-content: center; } </style>画像は、この行の
$whim.state[user.id]
('rock', 'cissors', 'paper'のいずれか)が動的に表示されます。
:src="require('@/assets/' + $whim.state[user.id] + '.png')"
ここまでで、実装は終わりです。お疲れ様でした?
デプロイして公開する
18. githubにコードを上げる
ではここから、このゲームの公開方法に移ろうと思います。今のところ、このゲームはローカルで動いてるだけなので、なんらかのサーバにホスティングする必要があります。まず1ステップ目として、今作ったゲームをgithubに上げましょう。具体的は方法はここらへんの記事が参考になると思います。
その後、codeをgithubに上げます。
以下のようなコマンドになるともいます。$ git add . $ git commit -m "janken完成" $ git remote add origin git@github.com:username/janken.git $ git push -u origin master最終的にこのような感じで上げれたら完成です。
19. netlifyの設定をする
次にnetlifyの設定をします。こちらの記事が参考になりますが、この記事でも解説しようと思います。Netlifyは静的なサイトを無料でホスティングしてくれるサービスです。Netlifyのサイトよりアカウントを作成します。githubのアカウントに関連付けて作成します。
この画面に来たら、 New site from Git を選択します。
GitHubを選択します
自分のレポジトリ一覧が出てくるので、先程作ったレポジトリを選択します。
最後にデプロイ設定を以下のようにします。Build commandは
npm run build
Publish directoryはdist
に設定します。設定が終われば Deploy site を押しましょう!
この画面になれば成功です!
表示されているURLをコピーしておきましょう。
20. wh.imのdevelop画面でゲームの登録をする
wh.imの開発者用画面でアプリを登録します。
アクセスすると、googleでのログイン画面になると思います。(これは、gmailで開発者の認証のためです。)
ここで自分のgoogleアカウントで登録します。
すると次の画面に遷移します。これが開発者アプリ登録画面です。右上のボタンから、新規アプリを登録します。
その後、必要事項を記入していきます。
ゲームのhostのURLは先程コピーしたURLを記入します。
最後のチェックボックスは色々な人が遊べるように公開したい場合は、チェックをします。(個人的に遊ぶだけの場合はチェックを入れなくて大丈夫です。)saveを押すと公開完了です!
21. 遊んで見る!
早速遊んでみましょう。まず、IDをコピーします。そして、wh.imに移動します。(今回は開発モードにする必要はありません!)
アプリ選択画面を開き、コピーしたIDを入力しましょう。まとめ
長い記事になりましたが、これでVue.js未経験者の方でもwh.imを使ったゲーム公開までの流れは一通り理解できたかと思います。Vue.jsをもっと学んで行くと、ワードウルフといった複雑なゲームも作れるようになるので、是非挑戦して見てください!
最後に、これらの情報が体系的にまとめた(つもりの)、開発者用ドキュメントも用意しているので、参考にしてみてください。
- 投稿日:2020-06-03T08:51:51+09:00
jacaScript mapをfor文で処理する
環境:
GAS
注意点
for (let element of map){}
ではなく、
for (let element in map){}
とすることでelementはmapのkeyとなります。qiita arrayPractice.jsfunction Main() { const name_map = createMap(); for (let name in name_map){ Logger.log(name); Logger.log(name_map[name]); } } function createMap() { const this_spread = SpreadsheetApp.getActiveSpreadsheet(); const criteria_sheet = this_spread.getSheetByName('criteria'); let name_map = new Map(); for(let count = 1; count <= 3; count ++){ name_map[criteria_sheet.getRange(count,1).getValue()] = criteria_sheet.getRange(count,2).getValue(); } return name_map }
- 投稿日:2020-06-03T05:50:07+09:00
【DynamoDB】条件付き書き込み(ConditionExpression)を使ってupdateItemで新規項目を追加しないようにする
DynamoDBのupdate処理で、テーブルに存在しない項目を指定すると、新規項目としてテーブルに追加されてしまいます(UpdateItem)。
条件付き書き込み(ConditionExpression)を使用し、updateで存在しない項目を指定した時に、テーブルに追加されずに何も変化しない状態になるよう実装します。環境
- macOS
- AWS Cloud9
- Node.js 12
AWS.DynamoDB.DocumentClient
JavascriptでDynamoDBを操作するとき、AWS.DynamoDBを使う方法とAWS.DynamoDB.DocumentClientを使う方法の2パターンがあるのですが、AWS.DynamoDB.DocumentClientを使う方が、DBを操作するときにデータの型を指定せずにコードを書くことができて便利なので、今回はこちらを使用しています。
AWS.DynamoDBとの違いは、データ型の指定が不要な点と、メソッド名がやや異なるくらいで、メソッドの具体的な仕様は全く同じと考えて良さそうです。(AWS.DynamoDB.DocumentClient 公式ドキュメント)実行コード
const AWS = require('aws-sdk'); const DB = new AWS.DynamoDB.DocumentClient(); exports.handler = async(event, context) => { const dbParams = { TableName: "tableName", Key:{ timestamp: XXXXXX message: "message" }, ExpressionAttributeNames: { '#s': 'status' }, ExpressionAttributeValues: { ':status': true }, ReturnValues: 'ALL_NEW', UpdateExpression: 'SET #s = :status', ConditionExpression: 'attribute_exists(#s)' // ここで条件を指定 }; const data = await DB.update(dbParams).promise(); return data }ここで、
ConditionExpression: 'attribute_exists(#s)'によって、「
timestamp
属性の値がXXXXXX、message
属性の値が"message"である項目のうち、statusという属性を持つ項目が存在している場合」のみ、updateが実行されるようになります。
つまり、そもそも指定したtimestamp
やmessage
を持つ項目が存在しなければ、何も実行されずに済むということです。ConditionExpression
ConditionExpressionに使用できる演算子・関数は、
比較演算子: = | <> | < | > | <= | >= | BETWEEN | IN
論理演算子: AND | OR | NOT
関数: attribute_exists | attribute_not_exists | attribute_type | contains | begins_with | size
となっています。(参考:公式ドキュメント)各関数について簡単に説明しておくと、
- attribute_exists(path):キーで指定した項目にpathという属性が存在する場合に実行
- attribute_not_exists(path):キーで指定した項目にpathという属性が存在しない場合に実行
- attribute_type(path, type):pathという属性の値がtypeで指定したデータ型であれば実行
- begins_with(path, substr):pathという属性の値がsubstrで指定した文字列で始まる場合に実行
- contains(path, operand):pathという属性の値がoperandを含んでいれば実行
- size(path):pathで指定した属性のサイズを返す。例えば、
size(path) < 3
のようにして使うとなっております。
各演算子や関数に関する詳細は、こちらのリファレンスをご覧ください。実行に失敗したとき
指定したDBの操作がConditionExpressionの制約に反し、実行されなかった場合には、ConditionExpressionという例外が吐かれます。
例外時に何か処理を施したい場合は、try~catchでConditionExpressionをcatchしてあげると良さそうです。ご参考までに。
- 投稿日:2020-06-03T05:50:07+09:00
【DynamoDB】updateItemで新規項目を追加しないようにする 〜条件付き書き込み(ConditionExpression)を使って〜
DynamoDBのupdate処理で、テーブルに存在しない項目を指定すると、新規項目としてテーブルに追加されてしまいます(UpdateItem)。
条件付き書き込み(ConditionExpression)を使用し、updateで存在しない項目を指定した時に、テーブルに追加されずに何も変化しない状態になるよう実装します。環境
- macOS
- AWS Cloud9
- Node.js 12
AWS.DynamoDB.DocumentClient
JavascriptでDynamoDBを操作するとき、AWS.DynamoDBを使う方法とAWS.DynamoDB.DocumentClientを使う方法の2パターンがあるのですが、AWS.DynamoDB.DocumentClientを使う方が、DBを操作するときにデータの型を指定せずにコードを書くことができて便利なので、今回はこちらを使用しています。
AWS.DynamoDBとの違いは、データ型の指定が不要な点と、メソッド名がやや異なる(〇〇Itemが〇〇になる)くらいで、メソッドの具体的な仕様は全く同じと考えて良さそうです。(AWS.DynamoDB.DocumentClient 公式ドキュメント)実行コード
const AWS = require('aws-sdk'); const DB = new AWS.DynamoDB.DocumentClient(); exports.handler = async(event, context) => { const dbParams = { TableName: "tableName", Key:{ timestamp: XXXXXX message: "message" }, ExpressionAttributeNames: { '#s': 'status' }, ExpressionAttributeValues: { ':status': true }, ReturnValues: 'ALL_NEW', UpdateExpression: 'SET #s = :status', ConditionExpression: 'attribute_exists(#s)' // ここで条件を指定 }; const data = await DB.update(dbParams).promise(); return data }ここで、
ConditionExpression: 'attribute_exists(#s)'によって、「
timestamp
属性の値がXXXXXX、message
属性の値が"message"である項目のうち、statusという属性を持つ項目が存在している場合」のみ、updateが実行されるようになります。
つまり、そもそも指定したtimestamp
やmessage
を持つ項目が存在しなければ、何も実行されずに済むということです。ConditionExpression
ConditionExpressionに使用できる演算子・関数は、
比較演算子: = | <> | < | > | <= | >= | BETWEEN | IN
論理演算子: AND | OR | NOT
関数: attribute_exists | attribute_not_exists | attribute_type | contains | begins_with | size
となっています。(参考:公式ドキュメント)各関数について簡単に説明しておくと、
- attribute_exists(path):キーで指定した項目にpathという属性が存在する場合に実行
- attribute_not_exists(path):キーで指定した項目にpathという属性が存在しない場合に実行
- attribute_type(path, type):pathという属性の値がtypeで指定したデータ型であれば実行
- begins_with(path, substr):pathという属性の値がsubstrで指定した文字列で始まる場合に実行
- contains(path, operand):pathという属性の値がoperandを含んでいれば実行
- size(path):pathで指定した属性のサイズを返す。例えば、
size(path) < 3
のようにして使うとなっております。
各演算子や関数に関する詳細は、こちらのリファレンスをご覧ください。実行に失敗したとき
指定したDBの操作がConditionExpressionの制約に反し、実行されなかった場合には、ConditionExpressionという例外が吐かれます。
例外時に何か処理を施したい場合は、try~catchでConditionExpressionをcatchしてあげると良さそうです。ご参考までに。
- 投稿日:2020-06-03T01:03:58+09:00
Web ComponentsでTheme Providerを実装する
Web ComponentsでReactの
ThemeProvider
のようなものを実装したい。@ionic/coreのthemeの実装を読んだら感銘を受けたのでメモ
まとめ
- Element.closest(selector)でDOM treeを逆向きに検索する
- 実装に困ったら@ionic/coreを読む
実装
theme-provider
attribute変更の際にthemeの変更を適用したいという要求が無いなら、空のコンポーネントで良い
<template id="theme_provider"> <slot></slot> </template>customElements.define( "theme-provider", class extends HTMLElement { constructor() { super(); const template = document.getElementById("theme-provider"); this.template = template.content; const shadowRoot = this.attachShadow({ mode: "open" }).appendChild(template.content.cloneNode(true)); } } );子コンポーネント
子コンポーネントは何でもいいですが、各テーマのstylesを用意しておきます
<template id="heading_level1"> <style> h1.default { color: #333; } h1.christmas { color: green; } </style> <h1> <slot></slot> </h1> </template>customElements.define( "heading-level1", class extends HTMLElement { constructor() { super(); const template = document.getElementById("heading_level1"); this.template = template.content; const shadowRoot = this.attachShadow({ mode: "open" }).appendChild(template.content.cloneNode(true)); // theme-providerを探してthemeを登録する this.themeElement = this.closest('theme-provider'); const theme = this.themeElement ? themeElement.getAttribute('theme') : ''; this.shadowRoot.querySelector("h1").className = theme; } } );DOMツリーを親の方向に検索してThemeを取得する
DOMツリーの親方向に探索する場合は
Element.closest(selector)
const theme = this.closest('theme-provider').getAttribute('theme'); this.shadowRoot.querySelector("h1").className = theme;classNameをプログラマブルにしたい場合はclsxとかを使うと良いと思う
theme-provider
が変更された場合に反映したい動的にthemeを入れ替える必要がある場合は、その変更を子コンポーネントに通知したい。
theme-provider
側:変更を外に通知するためにCustomEvent
を使う必要がある。子コンポーネント側:初回のtheme取得と同じく、親Nodeを遡って
theme-provider
を見つけ、event listenerを登録しておく。というdemoはCodepenに置きました。