- 投稿日:2021-12-24T23:42:13+09:00
Amazonリンク短縮(クエリ削除)・タグ付与ブックマークレット
経緯 長大なAmazonリンクがLINEトークを埋めつくした 三種 Amazonの商品ページで押した場合に余分なクエリを削除(画面遷移) ①に加え、他サイトで押した場合にAmazonホームへ移動(画面遷移) ②に加え、アソシエイトタグ添付の確認をはさみ、ダイアログでタグ付URLを表示(OKの場合は非遷移) コード 1. 商品ページで押した場合に余分なクエリを削除 ブクマ用 javascript:(function(m){m=location.href.match(/\/(dp|hz\/intuition|gp\/(product|aw\/[a-z]))\/(\w{8,12})(\/|\?|$)/)??false;if(m){window.open('https://amazon.co.jp/dp/'+m[3],'_self');}else{alert('Not available on this page.');}})(); 全体 javascript:( function(m){ // 今のURLから「/dp/」「/gp/product/」「/gp/aw/d/」「/hz/intuition/」を探し、その後に続く商品ID(10文字)を取得( m[3] ) m=location.href.match(/\/(dp|hz\/intuition|gp\/(product|aw\/[a-z]))\/(\w{8,12})(\/|\?|$)/)??false; if(m){ // 商品IDがあった場合:最短URLに結合して画面遷移 window.open('https://amazon.co.jp/dp/'+m[3],'_self'); }else{ // 商品IDがなかった場合:「ここでは使えません」と通知 alert('Not available on this page.'); } } )(); 2. 他サイトで押した場合にAmazonトップへ移動 ブクマ用 javascript:(function(url,m1,m2){url=location.href;m1=url.match(/^(|w{3}\.|https:\/{2}(w{3}\.)?)amazon\.co\.jp(\/|$)/)??false;m2=url.match(/\/(dp|hz\/intuition|gp\/(product|aw\/[a-z]))\/(\w{8,12})(\/|\?|$)/)??false;if(m1===false){window.open('https://amazon.co.jp','_self');}else{if(m2){window.open('https://amazon.co.jp/dp/'+m2[3],'_self');}else{alert('Not available on this page.');}}})(); 全体 javascript:( function(url,m1,m2){ url=location.href; // m1: 現在のページがAmazonか否か m1=url.match(/^(|w{3}\.|https:\/{2}(w{3}\.)?)amazon\.co\.jp(\/|$)/)??false; if(m1===false){ window.open('https://amazon.co.jp','_self'); }else{ // m2: 商品IDを探索 m2=url.match(/\/(dp|hz\/intuition|gp\/(product|aw\/[a-z]))\/(\w{8,12})(\/|\?|$)/)??false; if(m2){ window.open('https://amazon.co.jp/dp/'+m2[3],'_self'); }else{ alert('Not available on this page.'); } } } )(); 3. タグ付与の確認をはさみ、OKならダイアログでタグ付URLを表示 ブクマ用 ※「hogemoge-22」(拙タグ) の部分を自分のトラッキングIDに修正して下さい javascript:(function(yourTag,url,m1,m2){yourTag='hogemoge-22';url=location.href;m1=url.match(/^(|w{3}\.|https:\/{2}(w{3}\.)?)amazon\.co\.jp(\/|$)/)??false;if(m1===false){window.open('https://amazon.co.jp','_self');}else{m2=url.match(/\/(dp|hz\/intuition|gp\/(product|aw\/[a-z]))\/(\w{8,12})(\/|\?|$)/)??false;if(m2){if(confirm('add your tag?')){prompt('The URL is below. Please be careful not to self-purchase.','https://amazon.co.jp/dp/'+m2[3]+'?tag='+yourTag,'_self');}else{window.open('https://amazon.co.jp/dp/'+m2[3],'_self');}}else{alert('Not available on this page.');}}})(); 全体 javascript:( function(yourTag,url,m1,m2){ yourTag='hogemoge-22'; url=location.href; m1=url.match(/^(|w{3}\.|https:\/{2}(w{3}\.)?)amazon\.co\.jp(\/|$)/)??false; if(m1===false){ window.open('https://amazon.co.jp','_self'); }else{ m2=url.match(/\/(dp|hz\/intuition|gp\/(product|aw\/[a-z]))\/(\w{8,12})(\/|\?|$)/)??false; if(m2){ if(confirm('add your tag?')){ prompt('The URL is below. Please be careful not to self-purchase.','https://amazon.co.jp/dp/'+m2[3]+'?tag='+yourTag,'_self'); }else{ window.open('https://amazon.co.jp/dp/'+m2[3],'_self'); } }else{ alert('Not available on this page.'); } } } )(); 表示されたURLが自分のIDにきちんと関連付けられているかは、Amazonアソシエイトのリンク動作確認ツールで確認してみて下さい(要ログイン)。 自分のタグを付与したAmazonURLにはアクセスしないようご注意下さい 参考:自己購入を回避する方法 ブックマークレットの利用方法 Android版Chromeはブックマークのタップでは動作せず、アドレスバーに「amatan」等の(自分がつけた)ブクマ名を入力し、候補として表示されたものをタップすると動くようです もし誤った点や、12文字以上のID、指定したパターン以外の商品ページ等がありましたら、お手数ですがご教示頂ければ幸いです。
- 投稿日:2021-12-24T23:35:35+09:00
youtubeの動画を再生しながら下で紙芝居を再生していくスクリプト
youtubeの動画を再生しながら下で紙芝居を再生していくスクリプト youtubeの動画を再生しながら、下で紙芝居を再生するスクリプトを作成しました。 githubURL: サンプルページURL: 使い方 まず下で紙芝居再生用ファイルを読み込ませてから、youtubeで一緒に再生する 動画のIDをセットボタンでセットします。それから上画面でyoutubeの動画を再生 することで秒ごとの指定でyoutubeの動画を再生しながら下で紙芝居を再生します。 紙芝居再生用ファイルの書式 紙芝居再生用ファイルの書式は以下のように作成します。 書式 表示時間1(秒単位),画像URL1 表示時間2(秒単位),画像URL2 ... youtubeの動画再生中に表示時間の2秒前になった時点で画像が切り替わるように なっています。
- 投稿日:2021-12-24T22:43:50+09:00
JavaScriptでjsonの中身を確認したら[object Object]と表示された場合の対処法
はじめに 最近、API開発をしていて外部API時のエラーオブジェクトの構造を知りたい時がありました。 しかし、対象オブジェクトをログ出力しても[object Object]と表示されてしまいます。 そこで、オブジェクトの中身を表示する方法を調べたので紹介します。 方法1 : JSON.stringifyを使う 以下のようにJSON.stringifyを使う方法です。 console.log(JSON.stringify(object)) 他の記事でもよく見かけます。 確かにこれでオブジェクトの中身が表示されるのですが、正直みづらいです。 そこで、次の方法が個人的におすすめです。 方法2 : console.dirを使う 以下のようにオブジェクトをconsole.dirで出力すると、良い感じに見やすく表示されます。 console.dir(object)
- 投稿日:2021-12-24T22:35:03+09:00
エンジニアに転向して1年で開発チームのリーダーになるまでに勉強したことをまとめる
これはなに? 自分は2020年8月ごろにプロダクトマネージャーからエンジニアに転向し、この1年半でバックエンド、フロントエンド、インフラなど色々やっているうちに気付いたらいちチームのリーダーを任されるまでになりました。なのでこの記事ではその間にどんなことを勉強したのかをまとめておこうと思います。 エンジニアになったばかりの人やこれからなる人の一つの参考になれば幸いです。 担当プロダクトの技術スタック バックエンド:Python, flask フロントエンド:JavaScript, Vue.js DB:MySQL インフラ:AWS 勉強したこと メインとしてバックエンドのapi開発をやっていたため、フロントエンドは薄めになっています。 とりあえず入門 Progate 受講コース:Git/Command Line/SQL/Python/HTML&CSS/JavaScript とりあえず入門するのに手軽でよかったと思います。 キタミ式イラストIT塾 ITパスポート とりあえず基礎中の基礎をさらうために勉強しました。資格自体にめっちゃ意味があるとは思ってないですが、資格勉強をすることで一旦広く知識が得られるのはとても有益だと思っています。 コンピュータの基礎 コンピュータはなぜ動くのか~知っておきたいハードウエア&ソフトウエアの基礎知識 プログラムはなぜ動くのか 第3版 知っておきたいプログラミングの基礎知識 この2つはプログラミングの勉強をするにあたって、まず最初に読みました。いきなり言語の勉強をし始めるより基礎をちゃんとさらったことは良かったと思っています。 Python 独学プログラマー Python言語の基本から仕事のやり方まで マジでこれから入ってよかった。オライリーから入ったら死んでたと思う Pythonチュートリアル 入門Python3 ガチ初学者には向いてないです。一回プログラミングを通った人がPythonを学ぶのには○ flaskアプリの自作 flaskに関しては恥ずかしながら体系的なインプットをする前に、実務で教えてもらいながら見様見真似で少しずつ理解していきました。(というか1ヶ月くらいで実務に放り込まれたので時間がなかった) 代わりと言ってはなんですが、少し時間が経った頃にこの記事や関連記事を参考に、自分でflaskを使ったslackアプリをつくって遊んでいました。結構勉強になったと思います。 SQL SQL 第2版 ゼロからはじめるデータベース操作 select文は死ぬほど書いたことがあったのですが、それ以外は全くだったので基本をさらうために。 HTML/CSS これからWebを始める人のHTML&CSS,JavaScriptのきほん この辺はある程度はわかっていたのですが、改めてちゃんとエンジニアになるにあたって基本は押さえておこうと思って一応。 JavaScript Udemy 【JS】ガチで学びたい人のためのJavaScriptメカニズム JS学ぶのにいい本ないですか?と聞いたら、これが一番わかりやすくておすすめ、と教えてもらったのがこちら。 結構基礎から非同期処理とかまで一通り扱ってくれて結構助かりました。 Vue.js Vue.js入門 基礎から実践アプリケーション開発まで とりあえず一番基本っぽいものをやりました。 プログラミング基礎 リーダブルコード ―より良いコードを書くためのシンプルで実践的なテクニック 括りかたわからなかったけど、言わずもがなの必読本。一番お世話になってる気がする。 Linux linux標準教科書 Linuxコマンドポケットリファレンス ポケットリファレンスは、とりあえず通しでパラパラっと読んで、辞書的に使ってます。 最初はMacOSとLinuxの違いが分かってなかったので、オプションの違いとかで結構戸惑いました。 Git GitHub実践入門 ~Pull Requestによる開発の変革 (WEB+DB PRESS plus) 実際にチームで開発する中での細かいルールとかは実務で教えてもらいながらにはなりますが、Gitに関する最低限の知識として絶対読むべきなやつ。 セキュリティ 体系的に学ぶ 安全なWebアプリケーションの作り方 第2版 脆弱性が生まれる原理と対策の実践 めちゃくちゃ分厚くて心折れかけましたが、セキュリティに関する最低限の知識としてがんばりました。 オブジェクト指向/デザインパターン オブジェクト指向でなぜつくるのか 第3版 知っておきたいOOP、設計、アジャイル開発の基礎知識 デザインパターンとともに学ぶオブジェクト指向のこころ オブジェクト指向型の言語を使うにあたって最低限理解しておく必要があるだろうということで。 デザインパターンは正直ある程度実装に慣れた頃に読んで初めて意味があるかな、という感じです。 アルゴリズム プログラマ脳を鍛える数学パズル シンプルで高速なコードが書けるようになる70問 珠玉のプログラミング 本質を見抜いたアルゴリズムとデータ構造 実務でめちゃくちゃアルゴリズムに気を使わないといけないようなことがあるか、と言われたらそんなにないんですが、一通りやっておくことで普段の実装でも計算量とかを意識できるようになると思います。 AWS (模擬問題付き)改訂新版 徹底攻略 AWS認定 ソリューションアーキテクト − アソシエイト教科書[SAA-C02]対応 Udemy これだけでOK! AWS 認定ソリューションアーキテクト – アソシエイト試験突破講座(SAA-C02試験対応版) Udemy AWS 認定ソリューションアーキテクト アソシエイト模擬試験問題集(6回分390問) 実務でちょいちょいawsに触れる機会があったのですが、一回体系的にインプットしたいな、と思ってとりあえず資格を目標に勉強しました。Udemyの講座が、試験対策だけでなく一通りハンズオン形式で触らせてくれたのでとてもありがたかったです。 一通り最低限の知識を入れたことで、その後の公式ドキュメントとかも読みやすくなった気がします。 ネットワーク 3分間ネットワーキング 全然3分じゃ無理ですが、とりあえずネットワークについて最低限の知識を抑えるのにはおすすめだと思います REST Webを支える技術 -HTTP、URI、HTML、そしてREST (WEB+DB PRESS plus) API設計で必要な考え方の基本が抑えられます。 テスト テスト駆動開発 この本読んでからテストを書かないと気持ち悪くなりました。テスタブルなコードを意識すると、自然とコードがきれいに分割されていく気がするので早いうちに読んでおいて正解だったなと思います。 開発プロセス LeanとDevOpsの科学[Accelerate] テクノロジーの戦略的活用が組織変革を加速する (impress top gear) アジャイルな見積りと計画づくり ~価値あるソフトウェアを育てる概念と技法 SCRUM BOOT CAMP THE BOOK【増補改訂版】 スクラムチームではじめるアジャイル開発 チーム開発をより効率的に進めるために、DevOpsやアジャイル開発に関するインプットもしました。 チーム作り エンジニアリング組織論への招待 ~不確実性に向き合う思考と組織のリファクタリング カイゼン・ジャーニー たった1人からはじめて、「越境」するチームをつくるまで How Google Works(ハウ・グーグル・ワークス) 私たちの働き方とマネジメント Team Geek ―Googleのギークたちはいかにしてチームを作るのか チームリーダーを任されるということで、改めて開発組織をどう作っていくか、というのを考えるために読みました。 プロダクトマネジメント プロダクトマネジメントのすべて PMやデザイナーも含め、チームで一つのプロダクトを作るために目線を揃えよう、ということでチームみんなで読みました。とてもよかった。 おわりに こうやって見てみると、改めてとりあえず広く基本を抑えた感じでやってきたなと思いました。 来年は求められる役回り的にも、もう少しDBの深いところやミドルウェア関連の知識とかもつけていきたいなと思っています。 またここにはわざわざ書いてないですが、各種公式ドキュメントには大変お世話になりました。 ↓の記事にも書きましたが、日々色々とググる中でちゃんと公式に立ち戻ってインプットしていたことがとても良かったなと思います。
- 投稿日:2021-12-24T21:40:29+09:00
JavaScript Fetch API の解説と使い方
JavaScript Fetch API について この記事は、非同期でリクエストを発行し、そのレスポンスを取得するJavaScriptのFetch APIについて解説した上で使い方を簡単に紹介します。 非同期処理とは Fetch APIの最大とも言える特徴は、その非同期性にあります。非同期処理を理解するためには、まず同期処理について理解しなければいけません。 同期(Synchronization)は、プログラミングの文脈において、何かを行う処理を時間的に一致させることを指します。さらに言えば、処理Aを行う際、別の処理Bの完了まで待つことを同期といいます。 同期処理では、待ち合わせする間、他の処理が行えないため、効率が悪くなりがちです。そこで、処理Bの完了を待たずに処理を行うこと、これが非同期処理です。 非同期処理では、ある処理の完了を待たずに(あるいは待っている間に)、他の処理を行えることから、プログラムの効率を高めることができます。 Fetch APIで見てみる非同期処理 以下は実際に Fetch API を利用して、気象庁ホームページ防災気象情報からjsonを取ってくる非同期処理です。 <script> // 東京(130000)の予報を取得 let url = "https://www.jma.go.jp/bosai/forecast/data/overview_forecast/130000.json"; fetch(url) .then(function(response) { return response.json(); }) .then(function(weather) { console.log('こちらの方が後に表示される') console.log(weather); }); console.log('こちらの方が先に表示される') </script> ここで特徴的なのは、then()という書き方です。これはJavaScriptのPromiseオブジェクトにおける特徴的な書き方で、 前の処理が正常に完了した時に、then()の中身を同期的に実行する。 それ以外の処理は非同期で(前の処理の完了を待たずに)よろしく実行しておく。 といった処理になります。then()内にコールバック処理を記述するようなイメージです。 実際に同期処理と実行時間を比較してみる 例えば、関東地方の各都府県の天気を取ってみるような処理を書いてみます。 まず同期処理ですが、これはXMLHttpRequest()メソッドを使用した逐次処理(として以下のように実装できます。 const startTime = performance.now(); // 開始時間を計測しておく let request = new XMLHttpRequest(); locations = [ '080000.json', // 茨城県 '090000.json', // 栃木県 '100000.json', // 群馬県 '110000.json', // 埼玉県 '120000.json', // 千葉県 '130000.json', // 東京都 '140000.json', // 神奈川県 ] for (var i=0; i<locations.length; ++i) { // 実行完了を毎回待ち合わせするため、各県ごとに逐次実行される var url = "https://www.jma.go.jp/bosai/forecast/data/overview_forecast/" + locations[i] request.open('GET', url, false); request.send(null); if (request.status == 200){ let data = request.responseText; console.log(JSON.parse(data).text); } } const endTime = performance.now(); // 終了時間を計測 console.log(endTime - startTime); // 284.5 ms </script> 284.5ミリ秒かかっていますね。 これを非同期処理にすると大幅に時間が短縮されます。 const startTime = performance.now(); // 開始時間を計測しておく let request = new XMLHttpRequest(); locations = [ '080000.json', // 茨城県 '090000.json', // 栃木県 '100000.json', // 群馬県 '110000.json', // 埼玉県 '120000.json', // 千葉県 '130000.json', // 東京都 '140000.json', // 神奈川県 ] for (var i=0; i<locations.length; ++i) { // 実行完了を毎回待ち合わせせず、各県ごとに非同期に実行される var url = "https://www.jma.go.jp/bosai/forecast/data/overview_forecast/" + locations[i] fetch(url) .then(function(response) { return response.json(); }) .then(function(weather) { console.log(weather.text); }); } const endTime = performance.now(); // 終了時間を計測 console.log(endTime - startTime); // 4.6 ms </script> 4.6ミリ秒で実行できました。...あれ、本当に ? 非同期処理のプログラムの方は、実は終了時間を計測している時点で、Fetchが全て完了している保証がありません。なので、これをプログラムの実行時間として計測するのはちょっとずるいですね。 Fetch APIが返すのはPromiseオブジェクトなので、こういった場合、Promise.allメソッドを使うとよいでしょう。Promise.allメソッドは、複数の非同期処理の全ての実行完了を保証してくれます。 const startTime = performance.now(); // 開始時間を計測しておく var locations = [ '080000.json', // 茨城県 '090000.json', // 栃木県 '100000.json', // 群馬県 '110000.json', // 埼玉県 '120000.json', // 千葉県 '130000.json', // 東京都 '140000.json', // 神奈川県 ] var promises = [] for (var i=0; i<locations.length; ++i) { var url = "https://www.jma.go.jp/bosai/forecast/data/overview_forecast/" + locations[i] promises.push( fetch(url) .then(function(response) { return response.json(); }) .then(function(weather) { console.log(weather.text); }) ); } // 全都府県の天気取得の処理を待ち合わせ(=同期)してから終了時間を計測 Promise.all(promises) .then((result) => { const endTime = performance.now(); // 終了時間を計測 console.log(endTime - startTime); // 167.5 ms }); </script> うん、167ミリ秒か、妥当ですね、非同期処理により少し効率が上がってます! まとめ Fetch APIは非同期のWebリクエストをPromiseオブジェクトを使って簡単に記述できる素敵なメソッド 以上、ご訪問ありがとうございました。
- 投稿日:2021-12-24T21:06:19+09:00
FileMaker の WEB ビューアで Lit を用いて Web Components
はじめに 前書き 本記事は FileMaker Advent Calendar 2021 24 日目の記事です。 対象読者 Claris FileMaker Pro 19 ユーザ FileMaker の WEB ビューアにおいて Web Components を触ってみたいという(奇特な)方 Lit を CDN で触ってみることに興味のある方 Lit に限らず FileMaker Pro における JavaScript コンポーネント管理について考えてみたい方 検証環境 Windows 10 Pro FileMaker Pro 19.4.1 lit-element 3.0.2 インターネット接続環境 Web Components とは? Web Components は、再利用可能なカスタム要素を作成し、ウェブアプリの中で利用するための、一連のテクノロジーです。コードの他の部分から独立した、カプセル化された機能を使って実現します。 これだけだとナンジャイって話だと思いますが、手っ取り早く言うと ブラウザにネイティブでサポートされているリッチな UI 実装のための技術 です。超大雑把ですが、間違っていない 今は WEB の UI 構築というと React, Vue, Angular, Svelte その他 JavaScript フレームワークで書くぞーというのが主流ですが、そのうち Web Components や Flutter Web が来るかもしれない(し、来ないかもしれない) 2021/12/24 時点だと、この記事が良い感じにまとまっていると思います あーありがち - 今さらWeb Components Lit とは? Web Components をそのまま使おうとするとツラミ感が高くなるのでライブラリを使うのが一般的 一番定番だったような気がする Polymer の後継となる Web Components 用のライブラリが Lit です Polymer から Lit へ、そして Lit 1.x 系から Lit 2.0 へ、ということについては、以下の記事がよくまとまっていると思います TypeScript で書くのが本道ですが、今回はトランスパイルなどできない FileMaker Pro の WEB ビューア環境により CDN を利用するため JavaScript で書くことになります この記事では何をする? FileMaker Pro の WEB ビューアで Lit を用いて以下の機能を実装してみます ToDo 管理 現在の時分秒のリアルタイムなカウント表示 マウスカーソルの位置取得 準備 FileMaker ファイル作成 ファイル名は何でも構いませんが、ここでは lit.fmp12 という名前で作成 FileMaker テーブルとフィールドの作成と定義 outputs メインとなるテーブル 計算フィールドの中身は後で解説予定 js 各 JavaScript コンポーネントを格納しておくためのテーブル includes 要は outputs と js の二つを繋ぐための中間テーブル outputs テーブルから js テーブルの中身を手軽に覗くために計算フィールドを作っておく もちろんテーブルオカレンスの定義をした後でないと計算フィールドは作れない todos ToDo の中身を保管しておくためのテーブル FileMaker テーブルオカレンスの定義 全体像 outputs ⇔ includes レコード作成許可にチェックを入れておく outputs ⇔ todos こちらも同じくレコード作成許可にチェック includes ⇔ js こちらは includes テーブルに計算フィールドを作るために FileMaker レイアウトの作成 outputs メインとなるレイアウト 完成形を貼っておきますが、WEB ビューアの中身やスクリプトトリガなどは後で解説します js 最低限のコードを弄るために code フィールドは大きくしておく includes, todos 基本的には覗くこともほぼないので、最低限の一行レイアウトに FileMaker スクリプトの作成 set_global_lit_element_js_path $$lit_element_js_path というグローバル変数の定義 "https://unpkg.com/lit-element/lit-element.js?module" open OnLayoutEnter スクリプトトリガで実行させる用として reload ウインドウ内容の再表示 outputs レイアウトへのスクリプトトリガ設定 先ほど作っておいた open スクリプトを OnLayoutEnter スクリプトトリガで設定 値一覧の作成 後で必要になるので、以下の通りに作成しておく さらなる準備 情報元 あらためて今回は公式サイトより、チュートリアルにおける ToDo リスト作成、時計表示、マウスカーソルの位置取得をベースとして少し改変したものを実装してみましょう g_for_webview フィールド 全レコードに共通となる html コードとして、以下の通り入力しておきます [[[js]]] や [[[components]]] など、他では使われ得ない記述の仕方をしておいて、後で WEB ビューア側で Substitute 関数により置換するようにします <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <script type="module"> [[[js]]] </script> </head> <body> [[[components]]] </body> </html> todos ポータル done フィールドには 次の場合にオブジェクトを隠す 設定 IsEmpty ( outputs⇔todos◎output_id::id ) ポータルオブジェクトには todos_portal という名前をつけておく includes ポータル 関連レコード移動用の ⇒ や c_js_name, c_js_code については 次の場合にオブジェクトを隠す 設定 IsEmpty ( outputs⇔includes◎output_id::id ) js_id は先ほど作成した値一覧をもとにドロップダウンリストに また js_id は入力が評価されたら OnObjectSave スクリプトトリガで reload を実行させるようにする ⇒ を押したら関連レコード移動させるために以下の通り from_outputs_to_js というスクリプトを作成、設定 孫リレーションまで作っておいて、スクリプトでなく単一ステップということも考えられるものの、孫リレーションの管理コストとの天秤で、今回はこのように c_components, c_list_items の定義 解説を後回しにしていた計算フィールドについて、それぞれ以下のように While 関数で実装します c_components インクルードするものとして選択された JavaScript の名前をベースにして、html 側で読み込むタグを生成します c_components While ( [ i = 0; items = List ( outputs⇔includes◎output_id::c_js_name ); max = ValueCount ( items ); result = "" ]; i < max; [ i = i + 1; result = If ( not IsEmpty ( result ); result & ¶ ) & "<" & GetValue ( items ; i ) & "></" & GetValue ( items ; i ) & ">" ]; result ) これによって、例えば以下のようなテキストが出力されます <clock-element></clock-element> <todo-list></todo-list> <mouse-position></mouse-position> c_list_items id, text, completed という三つの項目を持った JSON データを作ります todos テーブルにある関連レコードをもとに生成させます c_list_items While ( [ i = 0; item_names = List ( outputs⇔todos◎output_id::name ); item_status = List ( outputs⇔todos◎output_id::done ); max = ValueCount ( item_names ); result = "" ]; i < max; [ i = i + 1; result = If ( not IsEmpty ( result ); result & ¶ ) & "{id: " & i & ", text: '" & GetValue ( item_names ; i ) & "', completed: " & If ( GetValue ( item_status ; i ); "true"; "false" ) & "}," ]; result ) これによって、例えば以下のようなテキストが出力されます {id: 1, text: 'To Do リストを作る', completed: false}, {id: 2, text: 'CSS を宛てる', completed: false}, WEB ビューア 完成形としては以下のようなコードを指定 Let 関数内の変数処理でゴリゴリ Substitute していくというスタイル 要注意点としては、Substitute をかける順番で、js については先にやっておく必要がある 先にやらないと lit_element_js_path などは js 内に存在するため、正しく置換完了しない Let ( [ lit_element_js_path = $$lit_element_js_path; js = outputs::js & ¶ & List ( outputs⇔includes◎output_id::c_js_code ); css = outputs::css; list_items = outputs::c_list_items; components = outputs::c_components; result = outputs::g_for_webview; result = Substitute ( result ; "[[[js]]]" ; js ); result = Substitute ( result ; "[[[css]]]" ; css ); result = Substitute ( result ; "[[[lit_element_js_path]]]" ; lit_element_js_path ); result = Substitute ( result ; "[[[list_items]]]" ; list_items ); result = Substitute ( result ; "[[[components]]]" ; components ) ]; result ) css, js フィールド css 各レコード単位で制御できるように、あえて css フィールドを一つ切っていますが、全部まとめて管理させたいという場合はグローバルフィールドにするのもアリだと思います 中身はあくまで一例 .completed { color: #777; text-decoration-line: line-through; } .cursor-pointer { cursor: pointer; } .margin-bottom-3 { margin-bottom: 3rem; } .text-color-red { color: red; } js js フィールドも、一応、各レコード単位で制御できるように通常のテキストフィールドに設定している 以下の一行は必須なので、たとえば計算値自動入力のフィールド定義をしておいてもよい import { LitElement, html, css } from "[[[lit_element_js_path]]]" 計算値自動入力のフィールド定義をする場合の例 "import { LitElement, html, css } from \"[[[lit_element_js_path]]]\"" FileMaker.PerformScriptWithOption 実行用のスクリプト作成 後で必要となるので、以下二つのスクリプトを作成しておきます toggle_todos todos テーブルの done の状態を 0 / 1 に切り替えるためのスクリプト 受け取った引数が数字となっていて、その数字に応じた行のポータルレコードを弄る 中身は以下の通り add_todo_item WEB ビューア内で ToDo を「追加」された場合に todos テーブルへレコード作成するためのスクリプト 中身は以下の通り 実装 ToDo 管理 いよいよ実装に取りかかれます。ここまで準備に次ぐ準備ができたら、後は色んな機能を追加していくことが容易になっているはずです ということでまずは ToDo 管理から始めます 完成形のイメージ 以下のような感じです チュートリアルのソースコード あらためて以下にありますが、そのままは使えないので、色々改変します ソースコードの完成形 以下のような感じになるので、これを js テーブルに登録しましょう このコードの中身を解説して欲しい人って読者の中にいるのかどうかわからないので、割愛…… class ToDoList extends LitElement { static properties = { listItems: {attribute: false}, hideCompleted: {}, }; static styles = css` [[[css]]] `; constructor() { super(); this.listItems = [ [[[list_items]]] ]; this.hideCompleted = false; } render() { const items = this.hideCompleted ? this.listItems.filter ((item) => !item.completed) : this.listItems; const todos = html ` <ul> ${items.map ( (item) => html ` <li class=${item.completed ? 'completed cursor-pointer' : 'cursor-pointer'} @click=${() => this.toggleCompleted(item)}> ${item.text} </li>` )} </ul> `; const caughtUpMessage = html ` <p class="text-color-red">全て完了!</p> `; const todosOrMessage = items.length > 0 ? todos : caughtUpMessage; return html ` <div class="margin-bottom-3"> <h1>To Do</h1> <input id="newitem" aria-label="New item"> <button @click=${this.addToDo}>追加</button><br> <label class="cursor-pointer"> <input type="checkbox" @change=${this.setHideCompleted} ?checked=${this.hideCompleted}> 完了したものを非表示にする </label> ${todosOrMessage} </div> `; } toggleCompleted(item) { FileMaker.PerformScriptWithOption ("toggle_todos", item.id, 0); this.requestUpdate(); } setHideCompleted(e) { this.hideCompleted = e.target.checked; } get input() { return this.renderRoot?.querySelector('#newitem') ?? null; } addToDo() { if ( this.input.value.length > 0 ) { this.listItems.push({text: this.input.value, completed: false}); FileMaker.PerformScriptWithOption ("add_todo_item", this.input.value, 0); this.input.value = ''; this.requestUpdate(); } } } customElements.define('todo-list', ToDoList); ポイントは、以下の二行ですね。先ほど作成しておいたスクリプトを引数つきで実行させています FileMaker.PerformScriptWithOption ("toggle_todos", item.id, 0); FileMaker.PerformScriptWithOption ("add_todo_item", this.input.value, 0); name フィールドは以下の箇所で設定した todo-list というものを入力 customElements.define('todo-list', ToDoList); includes ポータルへのセット あとは以下のようにポータルから todo-list を呼び出すようにすれば完成 最初に見せたとおり、以下のようになります 動作確認 WEB ビューア内の To do リストを作る という行をクリックすると、取消線が引かれ、さらに todos ポータル内の値も変わります 逆に、 todos ポータル内の値を直接変更すると、それに応じて WEB ビューア内の表示も変わってくれます 完了したものを非表示にした状態 全て完了 WEB ビューア内のインプットフィールドに入力したものは、正しく FileMaker 側にもレコード作成される ということで ToDo 機能が実装できましたね 時間表示 時分秒をリアルタイムにカウント表示してくれる機能の実装へ 先ほどと同じことの繰り返しになってくるので、記述はざっくり省略します もうこの記事を書ける時間もあまり残されていないというのっぴきならない事情もある…… js テーブルへのレコード作成 こんな感じに作りましょう。name は clock-element 先ほどと比べるとあまりソースコードは弄っていません class ClockElement extends LitElement { static styles = css` [[[css]]] `; clock = new ClockController(this, 100); render() { const formattedTime = timeFormat.format(this.clock.value); return html ` <div class="margin-bottom-3"> <h1>現在時刻</h1> <p>${formattedTime}</p> </div> `; } } const timeFormat = new Intl.DateTimeFormat('en-US', { hour: 'numeric', minute: 'numeric', second: 'numeric', }); class ClockController { host; value = new Date(); timeout; _timerID; constructor(host, timeout = 1000) { (this.host = host).addController(this); this.timeout = timeout; } hostConnected() { this._timerID = setInterval(() => { this.value = new Date(); this.host.requestUpdate(); }, this.timeout); } hostDisconnected() { clearInterval(this._timerID); this._timerID = undefined; } } customElements.define('clock-element', ClockElement); インクルードして動作確認 どぞ! 参考情報 表示形式の変更については Intl.DateTimeFormat のドキュメントとして、以下参照 マウスの位置取得 サクサクといきましょう。下準備を念入りにしただけあって、サクサクといけますでしょう? js テーブルへのレコード作成 こんな感じに作りましょう。name は mouse-position ここのソースコードは元となっているものからほとんど弄っていません class MousePosition extends LitElement { mouse = new MouseController(this); render() { return html` <div class="margin-bottom-3"> <h1>The mouse is at:</h1> x: ${this.mouse.pos.x} y: ${this.mouse.pos.y} </div> `; } } class MouseController { host; pos = {x: 0, y: 0}; _onMouseMove = ({clientX, clientY}) => { this.pos = {x: clientX, y: clientY}; this.host.requestUpdate(); }; constructor(host) { this.host = host; host.addController(this); } hostConnected() { window.addEventListener('mousemove', this._onMouseMove); } hostDisconnected() { window.removeEventListener('mousemove', this._onMouseMove); } } customElements.define('mouse-position', MousePosition); インクルードして動作確認 どぞ! コンポーネントを組み合わせてインクルード ToDo + 時刻 たとえばこんな感じですね 全部載せ 当たり前ですが組み合わせは自由自在で、インクルードする順番によって表示順も変わります おわりに 次回 ……は、恐らくないです 将来的に、たとえばインクルードする js に対してレコード内容に応じた引数を渡してあげることができる、とかそんな機能をつけたりすることもアリだろうと思います *今回の実装上いたしかたないところですが、データベース側の情報変更にあわせて WEB ビューア全体を再レンダリングしているので、本来のリアクティブ性は全く無くなってしまっているので、そのあたりも課題感としては残っています。誰か後を継いでほしい 感想 ちょっと軽い気持ちで最近の Web Components ってどうなってるんだっけー、からの、FileMaker の WEB ビューアで実装できるからやってみたらどうなるかなー、からの、コンポーネント管理について真剣に考え始めるところまで入ってしまって、気づけば最初は単一テーブルだけで簡素に作っていた(非実用的な)だけのものから、わりかし実用的なものにまで膨らんでいってしまったなー、と…… 前日のアドカレ記事で Alpine.js について書かれていて、これはコンポーネント管理とかほとんど意識しなくてよいからとても相性は良いのだけれど、多少コンポーネント管理した方が規模感が大きくなると取り回しが効きやすいんじゃないかなーという気はしています とはいえ、何も Lit でやる必然性なんてどこにもなくて、たぶん Vue あたりで書いた方が、実用面では高く作れると思います まあ、遊び心満載な記事ということで! メリークリスマス!?
- 投稿日:2021-12-24T19:37:13+09:00
templateタグを使ってフォームを複製
はじめに 以前、「cloneNode()で入力フォームを複製してみた」という記事で入力フォームをcloneNode()を使って複製する記事を投稿した。 しかしその記事内のコードではフォーム内のテキストまで複製されてしまう。 ここではHTML5より追加されたtemplateタグを使ってフォームのテンプレートを作成し、それを複製することで、何も入力されていないフォームを追加していく。 要件 「追加」ボタンをクリックし、名前、年齢、性別を入力するフォームを複製したい。(最大8人) ⇓ templateタグとは Javascriptで操作するDOM要素をテンプレート化するHTML要素。 templateタグで囲まれた要素は、ページの読み込み時には描画されない。 templateタグの要素を複製する方法 template要素内の内容を複製し、ブラウザに表示するために必要な処理は以下の3つ。 ①template要素を取得 ②取得したtemplate要素の内容を複製 ③DOMに追加 使用例 // template要素を取得 const template = document.getElementById('template'); // template要素の内容を複製 const clone = template.content.cloneNode(true); // div(id="container")の中に追加 document.getElementById('container').appendChild(clone); 実際にフォームを複製してみる 今回は「追加」ボタンをクリックすることで、HTML内のtemplateタグ内の内容を複製し、親要素であるid="input-form"のdivタグ内に追加していく。 また、フォームで受け取った値を扱う場合を考慮して、inputタグのname属性にインデント番号を付与し、複製するタイミングで1ずつ足していく。 フォームのHTML index.html <!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <script type="text/javascript" src="addForm.js"></script> </head> <body> <!--親要素--> <div class="input-form" id="input-form"> <!--テンプレート作成--> <template id="form-template"> <div class="member" id="member"> <!--name属性にはフォーム追加時にインデント番号を付与--> <input type="text" size="20" name="" placeholder="名前"> <input type="text" size="3" name="" placeholder="年齢"> <label for="male"> <input type="radio" value="男性" id="male">男性 </label> <label for="female"> <input type="radio" value="女性" id="female">女性 </label> </div> </template> </div> <!--ボタンをクリックしたらJavascriptファイル内の関数addForm()を実行する--> <div class="bt_addForm"> <input type="button" value="追加" onclick="addForm()"> </div> </body> </html> Javascriptで実装 ページ読み込み時にaddFormを一度実行し、フォームを一人分生成しておく。 addForm.js //インデント番号を初期化 let i = 1 function addForm() { // 8人以上なら処理を終了する if (i > 8) { return; } else { // HTMLからtemplate要素を取得 const template = document.getElementById("form-template"); // templateの内容を複製 const new_form = template.content.cloneNode(true); // 子要素を指定しname属性の値を変更 const new_form_name = new_form.children[0].children[0]; new_form_name.name = 'name-'+i; const new_form_age = new_form.children[0].children[1]; new_form_age.name = 'age-'+i; //親要素を取得し 複製した要素を追加 const parent = document.getElementById("input-form"); parent.appendChild(new_form); //インデント番号を更新 i++; } } //ページ読み込み時に関数addForm()を実行 window.addEventListener('DOMContentLoaded', addForm); まとめ 複製したいコンポーネントをテンプレートとしてHTML内に用意しておけるので、初期化されたフォームを簡単に追加することができた。 記事の内容に間違いや改善点などございましたら、ぜひコメントでご指摘ください。
- 投稿日:2021-12-24T17:34:15+09:00
Mapbox GL JSのスワイプ機能を試してみた
概要 Mapbox GL JSのスワイプ機能を弊社のB2B webサービス(地域や時間などで検索し取得したSNS投稿データと地図をマッピングさせる)上で使えるかどうかを調査も兼ねて試してみました。 弊社のB2B webサービス CITY INSIGHTはフリープランもありますので、気になる方はチェックしてみてください。 ⚠️ 現状の調査段階としては期待通りの結果を得られませんでした。以下の内容はあくまで参考程度にお読みください。引き続き調査し、進捗があれば随時更新していく予定です。 MapBoxのスワイプ機能とは? スワイプ機能の作成方法 パッケージをインストール npm install mapbox-gl-compare インストールしたパッケージをimportする import MapboxCompare from 'mapbox-gl-compare' import 'mapbox-gl/dist/mapbox-gl.css' import 'mapbox-gl-compare/dist/mapbox-gl-compare.css' テンプレートを修正する スワイプできるマップを親要素にして、その子要素に実際に比較するの2つのマップを配置する。 この形にしなければならない。 Vue.js <template lang="pug"> #container div(id='before' class='map') div(id='after' class='map') </template> スワイプできるマップを作成 比較対象の2つのマップを作成する。これは通常通りそれぞれにMapboxインスタンスを作成する。 Vue.js const beforeMap = new mapboxgl.Map({ container: 'before', style: 'mapbox://styles/mapbox/light-v10', center: [0, 0], zoom: 0 }); const afterMap = new mapboxgl.Map({ container: 'after', style: 'mapbox://styles/mapbox/dark-v10', center: [0, 0], zoom: 0 }); 上でimportしたMapboxCompareを使う。第1、第2引数に比較するマップを指定し、第3引数にターゲットとするHTML要素を指定する。 Vue.js const container = '#container' this.compareMap = new MapboxCompare(beforeMap, afterMap, container, { // 以下オプション mousemove: true, orientation: 'vertical' }) これでスワイプできるマップができるはず。 検索結果を比較する 前提 別々に検索をかけてそれぞれの投稿データをマッピングさせた2種類のマップを比較するためには、以下のことが必要と考えられます。 比較対象の2つのマップに同じレイヤーを重ねる 2つのマップに別々に命令を出し、別々の結果を出力させる 比較対象の2つのマップに同じ複数のレイヤーを重ねる これは単純に、マップ初期化の際に2つのマップに同じ処理を施してあげればよいです(ループで回す)。 2つのマップに別々に命令を出し、別々の結果を出力させる Vue.js @Watch('tweets') onUpdateFilteredTweets(): void { this.setLayerTweets(this.tweets, this.compareMap._mapA) } 親要素であるスワイプできるマップは、以下のプロパティを持っている。 _mapAと_mapBが比較対象のマップにあたる。 結果 今回取り組んだ方法では、検索結果はマップ上に反映できませんでした。 結果から考えられること このスワイプ機能を使ってレイヤーを重ねて2つの結果を比較する例は確認しました(改めて探したけど見つかりませんでした)。 なので、レイヤーを重ねることに問題はなさそうです。 私がうまくいかなかった理由は、比較対象のマップを結合した後に、(検索結果を出力する)命令を出したからかもしれません(確認した例ではユーザーが期間などを指定して検索することはできず、1つの決まった結果のみが比較できるものでした)。 なので、以下の手順を踏めば可能かもしれません。 ①ユーザーの比較したい2種類の検索内容をあらかじめ取得する ②マップを2つ用意しその2種類の検索結果をそれぞれのマップに格納する ③その2種類のマップを結合し、そこで初めてスワイプできるマップを表示させる。 最後に 上でも書きましたが、現状の調査段階としては期待通りの結果を得られていません。 引き続き調査し、進捗があれば随時更新していく予定です。
- 投稿日:2021-12-24T17:28:45+09:00
JavaScriptの基本「プロトタイプ」
プロトタイプとは 実はJavaScriptの関数オブジェクトは、標準でprototypeプロパティを持っています。 試しに関数を作ってみます。 オブジェクトの中に何も記述しておりませんが、コンソールはしっかり表示されます。 function F() { } console.log(F.prototype) デフォルトでは空のオブジェクトを参照しています。 この関数F()のprototypeプロパティがが参照しているオブジェクトをプロトタイプオブジェクトといいます。 ではこのプロトタイプオブジェクトにpropというプロパティを作り、そこに値を入れてみましょう //プロトタイプオブジェクトのプロパティ F.prototype.prop = "プロトタイプ" //インスタンス化 let obj = new F(); //propはobjオブジェクトのプロパティではないがそのように使える console.log(obj.prop) prototypeオブジェクトのプロパティはコンストラクタによって生成されたインスタンスから、あたかもそのインスタンスのプロパティであるかのように参照できます。 ※あくまで参照しているだけなので、 obj.prop = "代入" 代入はできないです。 プロトタイプを使用するのメリット index1.js //コンストラクタ function User(email,name) { this.email = email this.name = name } //プロトタイプ User.prototype.login = function () { this.online = true console.log(this.email , 'has logged in') } //インスタンス化 let japanese = new User("japan@gmail.com", "まさき") japanese.login(); //コンソール japan@gmail.com has logged in プロトタイプを使って、login()メソッドを作りました。 でもこれは、オブジェクトの中にメソッド作れば同じじゃないか?と思うかもしれません index2.js //コンストラクター function User(email,name) { this.email = email this.name = name this.online = true this.login = function () { console.log(this.email , 'has logged in') } } //インスタンス化 let japanese = new User("japan@gmail.com", "まさき") japanese.login(); //コンソール japan@gmail.com has logged in コンソール自体は同じになります。 何が違うのか オブジェクトの中にプロパティ(メソッド)を書いてしまうとインスタンス化するごとに新しいプロパティが生成されます。 当然といえば当然なのですが、たくさんのインスタンスを生成するとその分メモリを消費します。 一方protptypeはプロトタイプオブジェクトを参照しているので、インスタンスを何個作ってもメモリは増えません。 クロージャー 余談ですが、ここでなぜ、プロトタイプオブジェクトでthisが使えるのだろうと思った方いるかもしれません。 試しにコンソールでthisを見てみましょう。 //コンストラクタ function User(email,name) { this.email = email this.name = name } //プロトタイプ User.prototype.login = function () { this.online = true console.log(this) } //インスタンス化 let japanese = new User("japan@gmail.com", "まさき") japanese.login(); //コンソール User {email: 'japan@gmail.com', name: 'まさき', online: true}email: "japan@gmail.com"name: "まさき"online: true[[Prototype]]: Object コンソールにUserが出てきましたね。プロトタイプオブジェクトの中でもthisはUserを表しています。 javaScriptでは「自分を定義した関数を親とする」のでプロトタイプオブジェクトの親は「User = this」になるわけです。 クロージャーについての記事 何か間違えなどあれば、指摘していただけると助かります。 追記予定 継承
- 投稿日:2021-12-24T15:45:16+09:00
Vue.js2で作ったサイトがIE11で表示されなくなった調査の軌跡
前提 pakcage.jsonにbrowserslistは指定してあって、ある時点まではIE11でも動いていた。 エラーの原因を特定 こちらの記事と似たような事象だと思ったが。 キャプチャのようにエラーがバンドルされて1つになったJS内で起きていたためどこに原因があるのか全く分からなかった。 そこでまずはこちらの記事を参考に読み込んでいるモジュール単位にビルド後のファイルを分割するようにした。 vue.config.js configureWebpack: { optimization: { runtimeChunk: 'single', splitChunks: { chunks: 'all', maxInitialRequests: Infinity, minSize: 0, cacheGroups: { vendor: { test: /[\\/]node_modules[\\/]/, name(module) { // get the name. E.g. node_modules/packageName/not/this/part.js // or node_modules/packageName const packageName = module.context.match(/[\\/]node_modules[\\/](.*?)([\\/]|$)/)[1]; // npm package names are URL-safe, but some servers don't like @ symbols return `npm.${packageName.replace('@', '')}`; } } } } } } 自作のVue.jsコンポーネントの構文エラー 上記の対応を行うと、Minifyされていないソースコード位置にジャンプできるようになったので読んでみると、自作のチェックボックスのコンポーネントを読み込むところでエラーになっていた。 こちらの記事を参考にstrictモードを切ってみようとするもうまくいかず。 こちらの投稿にチェックボックスでv-modelとchecked、valueを一緒に使ってるとエラーになると書かれていたので試しに消してみるとエラー箇所が変わった。 IE11非対応のライブラリのトランスパイル コンポーネントのエラーが解消したところで別のエラーが出るようになった。 node_modules配下にインストールされているライブラリはデフォルトではトランスパイルの対象とならないため、IE11非対応のライブラリの場合エラーになるとのこと。 バンドルを抑制したことでライブラリごとのjsファイルが生成されるようになり、どのライブラリでエラーが出ているのか確認できたので、これを指定。 今回はpackage.jsonに書かれているライブラリとpackage-lock.jsonに書かれている依存ライブラリにエラーがあった。 vue.config.js }, transpileDependencies: ['library1', 'library2'], configureWebpack: { この2つの対処をしたところ無事表示されるようになった(早くIEなくなってくれ…)。
- 投稿日:2021-12-24T15:08:50+09:00
JSでの日付フォーマット
実装 dateFormat.js /** * 日付フォーマット * @param {Date} date date * @param {string} fmt format * @returns {string} StringDate */ export const dateFormat = (date, fmt = 'YYYY/mm/dd') => { let ret; const opt = { 'Y+': date.getFullYear().toString(), // 年 'm+': (date.getMonth() + 1).toString(), // 月 'd+': date.getDate().toString(), // 日 'H+': date.getHours().toString(), // 時 'M+': date.getMinutes().toString(), // 分 'S+': date.getSeconds().toString() // 秒 }; for (let k in opt) { ret = new RegExp('(' + k + ')').exec(fmt); if (ret) { fmt = fmt.replace(ret[1], (ret[1].length == 1) ? (opt[k]) : (opt[k].padStart(ret[1].length, '0'))) }; }; return fmt; }
- 投稿日:2021-12-24T15:06:16+09:00
JSでのURLパラメータ取得
実装 getParam.js /** * パラーメタの取得 * @param {string} name パラメータ名 * @param {string} url URL * @returns {string} 取得パラメータ */ export const getParam = (name, url) => { if (!url) url = window.location.href; name = name.replace(/[\[\]]/g, '\\$&'); let regex = new RegExp('[?&]' + name + '(=([^&#]*)|&|#|$)'), results = regex.exec(url); if (!results) return null; if (!results[2]) return ''; return decodeURIComponent(results[2].replace(/\+/g, ' ')); } 参考文献
- 投稿日:2021-12-24T15:04:24+09:00
JSでのファイルサイズ変換関数
実装 fileSizeUnit.js /** * ファイルサイズ変換 * @param {*} size バイト * @returns 変換後のサイズ */ export const fileSizeUnit = (size) => { // 1 KB = 1024 Byte const kb = 1024 const mb = Math.pow(kb, 2) const gb = Math.pow(kb, 3) const tb = Math.pow(kb, 4) const pb = Math.pow(kb, 5) const round = (size, unit) => { return Math.round(size / unit * 100.0) / 100.0 } if (size >= pb) { return round(size, pb) + 'PB'; } else if (size >= tb) { return round(size, tb) + 'TB'; } else if (size >= gb) { return round(size, gb) + 'GB'; } else if (size >= mb) { return round(size, mb) + 'MB'; } else if (size >= kb) { return round(size, kb) + 'KB'; } return size + 'バイト'; }
- 投稿日:2021-12-24T14:21:11+09:00
Webページの配色をユーザーが選択するJavaScript
過去にWebシステムの管理画面を作った際に、管理画面としては無難な配色でリリースしたのですが、 一部の利用者からページの色合いがイマイチ気に入らない、といった意見をいただきました。 一部の意見で配色を変更したとしても、別の一部から同様の意見が出ないとも限らないため、 だったら各自で好みの配色を選択できるようにしてしまおう! ということで、JavaScriptでそんな機能を作ってみました。 HTML まずはHTMLです。 青、緑、赤の各ボタン押下をすることで、 <ul>のメニュー部分と<h1>の見出しの配色を変更することとします。 <button>のvalue値に配色コードを持たせています。 sample.html <!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <title>sample</title> <link rel="stylesheet" type="text/css" href="default.css"> <script src="sample.js"></script> </head> <body> <ul> <li>メニューA</li> <li>メニューB</li> <li>メニューC</li> </ul> <h1>見出しサンプル</h1> <p>下のボタンをクリックで配色を変更します</p> <button type="button" class="colorChangeButton" value="1">青</button> <button type="button" class="colorChangeButton" value="2">緑</button> <button type="button" class="colorChangeButton" value="3">赤</button> </body> </html> CSS 以下が基本となるCSSです。 default.css body { background: #eee; color: #111; } li { display: inline-block; padding: 2px 5px; list-style: none; } 以下は3パターンの配色用CSSファイルです。 color1.css ul { background: #006; } li { background: #669; } h1 { color:#006; } color2.css ul { background: #060; } li { background: #696; } h1 { color:#060; } color3.css ul { background: #600; } li { background: #966; } h1 { color:#600; } JavaScript localStorageにユーザーが選択した配色コードを保存することで、 次回以降にページを開いても選択した配色で表示されます。 append()で配色用cssファイルを読み込ませるlinkタグを追加します。 各ボタンのonclickイベントにchange()を登録し、 ボタンクリックすることでlocalStorageに配色コード保存と配色CSSファイルの差し替えを行います。 sample.js document.addEventListener('DOMContentLoaded', () => { const cssId = 'colorCss'; const selector = '.colorChangeButton'; const keyName = 'colorType'; append(localStorage.getItem(keyName) || 1); document.querySelectorAll(selector).forEach(function (button) { button.addEventListener('click', {value: `${button.value}`, handleEvent: change}); }); function append(colorType) { const link = document.createElement('link'); link.id = cssId; link.setAttribute('rel', 'stylesheet'); link.setAttribute('type', 'text/css'); link.setAttribute('href', 'color' + colorType + '.css'); document.getElementsByTagName('head')[0].appendChild(link); } function change() { if (this.value === localStorage.getItem(keyName)) { return; } localStorage.setItem(keyName, this.value); document.getElementById(cssId).remove(); append(this.value); } }); さいごに 簡単なHTMLとCSSの例でしたが、これを応用することで複雑な配色やレイアウトの切り替えなど、使える場面はいろいろありそうな気がします。 何かの参考になれば幸いです。
- 投稿日:2021-12-24T14:03:00+09:00
TypeScriptはJavaScriptの上位互換ではない
一般に「TypeScriptはJavaScriptの上位互換であって、任意のJavaScriptコードはTypeScriptとしても動く!」とか言われていますがいや、動かないコード割とたくさんないか?というお話。 確かに機能単位で見ると98%以上の部分がそのまま動きますが、残りの2%がコードのどこかしらに入っていることが多いので、コードベース全体で見るとそのまま動くことは稀な気がするんですが。 互換でない部分 JavaScriptコードがTypeScriptとしてそのまま動かない部分2つを挙げます。 オブジェクトのプロパティを後から追加する 例えば以下のJavaScript、 const obj = { a: 0, b: 1 }; obj.c = 2; これはTypeScriptだとエラーになります。 tsconfig.jsonでエラー扱いしないようなオプションがあれば良いのですが、私が調べた限りない。 上記のコードだと現実味がないですが、例えば function createPerson(type) { const obj = { type, firstName: '', lastName: '' }; if(type === 'student') { obj.grade = ''; } return obj; } みたいなコードを書いてる場所が割とたくさんあるので困る。 ES2015のクラス構文でのフィールド ES2015からはJavaScript側でもクラス構文が導入されており、今では広く使われています。しかしES2015でのクラスがTypeScriptでそのまま動くことはほぼ無いと言っていいと思います。 なぜならTypeScriptではフィールドの明示的な宣言が必要だからです。例えば以下のJavaScript、 class AddNode extends Node { constructor(lhs, rhs) { super(); this.lhs = lhs; this.rhs = rhs; } execute(ctx) { return this.lhs.execute(ctx) + this.rhs.execute(ctx); } } これはTypeScriptではエラーになります。TypeScriptでは以下のようにフィールドの宣言が必須です。 class AddNode extends Node { lhs: Node; rhs: Node; constructor(lhs, rhs) { super(); this.lhs = lhs; this.rhs = rhs; } execute(ctx) { return this.lhs.execute(ctx) + this.rhs.execute(ctx); } } これも私の知る限り、tsconfig.jsonでエラー扱いしないようにするオプションはないです。 クラスなんて今時普通に使うし、これで上位互換というのは嘘でしょうと思いましたね。 私がTypeScriptに求めていたこと 私がTypeScriptに最も求めていたことは端的に言えば関数の引数と戻り値の型のアノテーションをすること、そしてその関数を使う側がそれをドキュメントとして利用しまたエディタの補完機能によって快適にコーディングができるようにすることだったんですよね。ローカル変数やクラスのプライベートフィールドにまで正確な型付けをして完璧に型安全なコードにしようとは思っていなかった。型のない言語にはコードの記述量が少なく考えのままにサクサク書ける、仕様変更に柔軟に対応しやすい等それはそれで価値があると思っています。 TypeScriptはJavaScriptの上位互換で次第に移行しながら使えるという触れ込みだったので、じゃあドキュメント的に関数等の外向きの型のアノテーションのみに使うか、と思って導入したら互換性のない箇所が結構あったので、なんだよと思ったという話でした。
- 投稿日:2021-12-24T10:02:28+09:00
TableauをWebに埋め込む技術 続
この記事はちゅらデータアドベントカレンダーの24日目の記事になります。 思い出すこと1年前、私はTableauをWebに埋め込む技術という記事を公開しました。 https://qiita.com/thanyu/items/3561c1e4e634b1e74697 実際私はこの技術をとある案件の中で使用していたのですが、1画面上に40枚程度のグラフを埋め込んでいるという制約上、描画速度が非常に遅く実運用は耐えられないようなものとなってしまっていました。 そのため今年は大量の Tableauを Webに組み込んで、なおかつパフォーマンス改善を図るという対応を行ったため、その過程で得られた知見についてご紹介いたします。 あまりにも限られた方向けの記事となってしまいますが、Tableauを Webに組み込む場合には様々な制約があるんだなとお思っていただければ幸いです。 そもそもなぜ遅いのか 我々の環境では、Tableau Serverと Webサーバーをそれぞれ AWS EC2上で起動し、Tableau Serverで用意した Tableauグラフを Web側に組み込むというような構成としていました。そして Tableauはまた別に用意した Snowflakeのデータベースを参照するようにしていました。 そして組み込んだ Tableauに対して、Web側から Tableau Javascript APIを用いてフィルターを適用することで、ユーザーが見たいデータを可視化するというような仕組みを開発していました。 とくに描画に時間がかかってしまっていたのが、Webに大量のグラフを組み込み、さらに読み込んだグラフに対して追加でフィルターを適用するような処理でした。 1画面に大量のグラフを描画している場合、このフィルターの適用こそが描画速度の遅くなってしまう原因となってしまいます。 Webに組み込んだ Tableauに対して Tableau Javascript APIを用いて、フィルターを適用する場合、「グラフ数 × 適用するフィルター数」分のリクエストを Tableau Serverに送付する必要があります。 我々が開発していたツールでは、最大15項目程度のフィルターをすべてのグラフ(40個程度)に対して適用する必要があったため、一度の画面描画処理に必要なリクエスト数が「(グラフ数 40個) * (フィルター数 15個) = 600 リクエスト」となり、そのような大量のリクエストの処理をさばくのに Tableau Serverが耐えられないために起こってしまっていた事象となります。 解決策 上記の大量のリクエストが発生してしまうために生じる課題に対して、GETリクエストのパラメータにフィルター条件を書くことで上記の課題の解決を試みました。 本来の Tableau Javascript APIのチュートリアルで紹介されているようなフィルターは POSTリクエストとなり、異なるフィルター項目ごとに1件1件送信されているのですが、GETパラメータにフィルター条件を含めることにより複数のフィルターリクエストを同時に送信することができるようになります。 これによりリクエスト数の削減ができるようになります。 この機能は Tableau Javascript APIでは以下リンクの「Apply Filters Before Loading the Visualization」で紹介されています。 https://help.tableau.com/current/api/js_api/en-us/JavaScriptAPI/js_api_concepts_filtering.htm 以下の例では、"Container"というカラムに対して、"Boxes"という値でフィルターを適用するという命令です。 var containerDiv = document.getElementById("vizContainer"), url = "http://YOUR-SERVER/views/YOUR-VIEW", options = { "Container": "Boxes" }; viz = new tableau.Viz(containerDiv, url, options); ただし、十分なドキュメントが整備されているとは言い難く、実装過程で躓いてしまった点がいくつかありましたので、紹介したいと思います。 URLが長すぎると404エラーとなってしまう 非常に多くのフィルタを適用してしまうと、その適用したフィルタはすべて GETリクエストのパラメータとなるため、発行される URL長が長くなってしまいます。 Tableau Serverが受け取る URL長の制限は特にないはずなのですが、5000文字程度を超えるようなURLが発行されてしまった場合には404エラーが帰ってきてしまうことがあります。 それだけの長さの URLが発行されるケースというのは稀だとは思うのですが、多くのフィルターをURLに含めている場合で404エラーが帰ってくるような事象が発生した場合には URL長を疑ってみることをおすすめします。 「,」が含まれているような値は、フィルターの値に組み込むことができない 上の例で実際に発行されるURLは以下のドキュメントに準拠しています。 https://help.tableau.com/current/pro/desktop/en-gb/embed_structure.htm 一つのカラムに対して複数値でフィルターする場合には、以下のようなURLが発行されます。 http://<servername>/#/views/<workbook>/sheet?param1=value1,value2 こちらの仕様と競合してしまうため、値に「,」が入ってしまっている場合は正しくフィルターの適用が行うことができなくなります。 セカンダリデータソースのフィルターはURLに埋め込むことができない セカンダリデータソースに対するフィルターは、URLに埋め込んで実装することができないため、下記のような applyFilterAsync文を書いてPOSTリクエストで対応する必要があります。 workSheet.applyFilterAsync('[Data Source].[field name]', value, tableau.FilterUpdateType.REPLACE); 存在しないフィルターを適用しようとしてもエラーは発生しない URL にフィルターを埋め込むため、たとえ存在しないフィルター項目に対するフィルターを行ってしまった場合でもエラーを検知することができません。 存在しないフィルター項目をURLに埋め込んでも、特にエラーとならずそのフィルター項目は無視されてしまいます。 (非推奨)パラメータ埋め込み機能 しかし公式のサポート外ではありますが、以下の optionsの中に 定義したTableau Parameterの名称と値を埋め込む事が一応可能となります。 以下のようにすることで可能となります。 var containerDiv = document.getElementById("vizContainer"), url = "http://YOUR-SERVER/views/YOUR-VIEW", options = { "TableauParameterYear": "2020" }; viz = new tableau.Viz(containerDiv, url, options); ただし、こちらの機能はTableau社に直接問い合わせを行ったところ、公式サポートは行っていないとの回答でしたので使用しないのが無難な機能となります。 終わりに いかがでしたでしょうか。 非常に限られた方向けの記事となってしまいましたが、もしお役に立てれば幸いです。
- 投稿日:2021-12-24T07:08:56+09:00
ガチJavascript初心者がhtml動かしてみた
こんにちは、Javascript初心者として、初めて触った記録を残します。 ちなみにQiita初投稿です。 ちなみにちなみにブログ投稿も初めてです。 課題^^ KONICHIWA JAPAN と表示させてみる <!DOCTYPE html> <html> <head> <meta charaset="utf-8" /> <title> KONICHIWA JAPAN </title> </head> <script> document.write("KONICHIWA!") </script> </html> これを実行すると以下のようになる。 作成手順 1,Visual Studio Code を起動する 2,新しいファイルを作成する、名前を 〇〇.html とする 3,上記のプログラムを書く 4,保存する 5,ファイル名の上で右クリック→”エクスプローラーで表示する”をクリック 6,結果がブラウザ上で保存される 語句の意味 Javascript初心者なので、一つ一つのプログラムの意味を知ろうと思い、調べてみた <!DOCTYPE html>: Document Type Definitionの略 HTMLの仕様をブラウザに認識させるための宣言 C言語での#include < stdio.h > だと思った <html>:HTMLの文書を書くために基底となる要素 <!DOCTYPE html>以外の要素は全てこのタグの内側に入れる必要がある <head>~</head>:機械可読な情報を入れる、題名など <meta charaset="utf-8" />:文字コードを指定し文字化けを防ぐ meta要素により情報を指定する。charaset属性でHTMLファイルの文字コードを指定する <script>~</script>:Javascriptをドキュメントに埋め込む、実行するコードをここに入れる この中だけJavaScriptで残りはHTMLかな? ボタンで画像を表示させてみる 色んなサイトを見て、5枚の画像を1枚ずつ表示させる。 ボタンを2個作成し、前からも後ろからでも見れるようにした。 注釈のつけ方がわかったので、よくわからない部分についてはコードの中にコメントを書いた。 <!DOCTYPE html> <html> <head> <meta charaset="utf-8" /> <title> USAGI </title> </head> <body> <!--width=ボタン幅、height=ボタン高さ、font-size=ボタンの文字の大きさ onclickはクリック時の処理-->> <button style = "width:500px; height:100px; font-size:80px " onclick="foward()">次</button> <button style = "width:500px; height:100px; font-size:80px " onclick="back()">戻る</button> <br><!--改行要素--> <img id="image_place" src="image1.jpg"> <script> //指定したidに合ったドキュメント要素を取得する let img = document.getElementById("image_place") //画像番号 let idx = 1; //画像切り替える関数 function foward(){ idx++; if(idx > 5){ idx = 1; } //scrに画像ファイル名を設定 img.src = "image" + idx + ".jpg"; src.height = 100; } function back(){ idx--; if(idx < 1){ idx = 5; } img.src = "image" + idx + ".jpg"; src.height = 100; } </script> </body> </html> 表示させた画像は以下の通り image1 image2 image3 image4 image5 感想 ボタンの大きさや文字の大きさの変更はできたが、画像自体の比率や大きさが変更できなかった。ボタン自体のデザインを変更するのにはCSSがいると様々なサイトに載っていたので、それも少し見てみたいと思いました。 Javascriptも全然わかっていない状況だし、Qiitaの投稿自体も特殊でめちゃくちゃ難しい。でもたのしいからOKです^^ 参考文献 【HTML】意外と重要なDOCTYPE宣言についてわかりやすく解説。 < html >: HTML 文書 / ルート要素 ELEMENT meta 要素 ページに関する情報を表す要素 JavaScriptで画像を表示する方法を現役エンジニアが解説【初心者向け】
- 投稿日:2021-12-24T07:08:56+09:00
課題1_27
課題1_27-樋掛来夢 KONICHIWA JAPAN と表示させてみる <!DOCTYPE html> <html> <head> <meta charaset="utf-8" /> <title> KONICHIWA JAPAN </title> </head> <script> document.write("KONICHIWA!") </script> </html> これを実行すると以下のようになる。 作成手順 1,Visual Studio Code を起動する 2,新しいファイルを作成する、名前を 〇〇.html とする 3,上記のプログラムを書く 4,保存する 5,ファイル名の上で右クリック→”エクスプローラーで表示する”をクリック 6,結果がブラウザ上で保存される 語句の意味 Javascript初心者なので、一つ一つのプログラムの意味を知ろうと思い、調べてみた <!DOCTYPE html>: Document Type Definitionの略 HTMLの仕様をブラウザに認識させるための宣言 C言語での#include < stdio.h > だと思った <html>:HTMLの文書を書くために基底となる要素 <!DOCTYPE html>以外の要素は全てこのタグの内側に入れる必要がある <head>~</head>:機械可読な情報を入れる、題名など <meta charaset="utf-8" />:文字コードを指定し文字化けを防ぐ meta要素により情報を指定する。charaset属性でHTMLファイルの文字コードを指定する <script>~</script>:Javascriptをドキュメントに埋め込む、実行するコードをここに入れる この中だけJavaScriptで残りはHTMLかな? ボタンで画像を表示させてみる 色んなサイトを見て、5枚の画像を1枚ずつ表示させる。 ボタンを2個作成し、前からも後ろからでも見れるようにした。 注釈のつけ方がわかったので、よくわからない部分についてはコードの中にコメントを書いた。 <!DOCTYPE html> <html> <head> <meta charaset="utf-8" /> <title> USAGI </title> </head> <body> <!--width=ボタン幅、height=ボタン高さ、font-size=ボタンの文字の大きさ onclickはクリック時の処理-->> <button style = "width:500px; height:100px; font-size:80px " onclick="foward()">次</button> <button style = "width:500px; height:100px; font-size:80px " onclick="back()">戻る</button> <br><!--改行要素--> <img id="image_place" src="image1.jpg"> <script> //指定したidに合ったドキュメント要素を取得する let img = document.getElementById("image_place") //画像番号 let idx = 1; //画像切り替える関数 function foward(){ idx++; if(idx > 5){ idx = 1; } //scrに画像ファイル名を設定 img.src = "image" + idx + ".jpg"; src.height = 100; } function back(){ idx--; if(idx < 1){ idx = 5; } img.src = "image" + idx + ".jpg"; src.height = 100; } </script> </body> </html> 表示させた画像は以下の通り image1 image2 image3 image4 image5 感想 ボタンの大きさや文字の大きさの変更はできたが、画像自体の比率や大きさが変更できなかった。ボタン自体のデザインを変更するのにはCSSがいると様々なサイトに載っていたので、それも少し見てみたいと思った。 Javascriptも全然わかっていない状況だし、Qiitaの投稿自体も特殊でめちゃくちゃ難しい。でもたのしいからOK^^ 参考文献 【HTML】意外と重要なDOCTYPE宣言についてわかりやすく解説。 < html >: HTML 文書 / ルート要素 ELEMENT meta 要素 ページに関する情報を表す要素 JavaScriptで画像を表示する方法を現役エンジニアが解説【初心者向け】
- 投稿日:2021-12-24T03:11:43+09:00
開発フレームワーク「Goqoo on kintone」を正式リリースしました!
はじめに kintoneプログラマーの赤座です! クリスマスイブにアドベントカレンダー書かせていただきます 思い起こせば3年前、2018年11月にkintone hack NIGHTに登壇して、kintoneカスタマイズ用の開発フレームワーク「Goqoo on kintone」を紹介したのでした。 順位はブービーでしたが、アスキーさんの記事でじっくり触れていただきました。 スライドはコチラ。 この時点でGoqooはまだまだ荒削りの超ベータ版で、プレゼンも「自分語り」が中心でした。 どんどん洗練させていく予定でいたのですが、翌年に三人目の娘が生まれてから、とにかく本業と子育てだけで必死で、Goqooの開発はずっと止まってしまっておりました。 その間に時は流れ、今やTypeScriptが当たり前の世界となっております。 三女が二歳になった今年、ようやく時間的にも余裕が戻ってきて、夏頃からコツコツとGoqooの改修を進めてきました。最後の一歩、自分を追い込むためにkintoneアドベントカレンダーに登録し、何とか間に合いました。 本日ここに、Goqoo on kintone v1.0、正式版をリリースさせていただきます!!! Goqoo on kintoneとは? kintoneを自由自在に乗りこなすためのツール、Goqoo(悟空)であります。 JS/TSプログラミング、コマンドライン/Node.js/Gitなどに精通している人のための開発フレームワークで、プログラミング初心者の利用は想定していません。 とにかく「自分がトコトン使いやすいように」というオレオレ視点でチューニングしてありますw Getting Started プロジェクト作成 まずはコマンド一発、これだけ叩いてみてください。 $ npx goqoo new プロジェクト名 プロンプトが出るので、Project name、descriptionを任意で設定します。 カスタマイズビューを作りたいときは、Frontend FrameworkにReact/Vueのどちらかを選ぶこともできます。 ? Project name › プロジェクト名 ? Project description › ? Frontend Framework … ❯ (None) React Vue 自動的にフォルダが作成され、テンプレートのコピーと各種ライブラリのインストールが走ります。 ちなみに内部ではSAOというScaffoldライブラリを使っています。(0.x系で使っていたYeomanはやめました) 初回コミット 作成されたディレクトリに移動します。git initは済んでいるので、一度git commitをしておきます。 (一度もコミットをしていない状態ではGoqooは正しく動きません) $ cd my-project $ git add . $ git commit -m 'Initial commit' アプリのエントリ作成 $ npx goqoo generate app my-app これでsrc/apps/my-appのようにアプリ名フォルダが作成され、ビルド対象となります。 開発ビルド(webpack-dev-server) Goqooによる開発は、webpack-dev-serverをローカルで立ち上げて、localhostでホスティングされたjsファイルをkintoneに読み込ませるのが基本になります。 $ npx goqoo start { bundlerType: 'default', nodeEnv: 'development' } { mode: 'development' } { env: { WEBPACK_SERVE: true } } { 'my-app': 'https://localhost:59000/my-app.js' } コンソールに表示されたURL(ここではhttps://localhost:59000/my-app.js)を、kintoneアプリのJavaScriptカスタマイズURLとして設定します。 「アプリを更新」すると、いかがでしょう・・・? 「オッス、オラ悟空!」と挨拶してくれますね! これで初回のデプロイは成功です これで、プログラムを修正するたびにkintoneが自動リロードして、サクサクと開発が進みます。 kintone側のJSを入れ替える必要はありません。 localhostなので、他のPCでkintoneにアクセスした場合は何も起きません。 逆に言うと、複数人でプログラムの共同開発はしやすくなります。 開発ビルド(ファイル) $ npx goqoo build developmentモードでビルドしてdist配下にファイルが作成されます。 一応機能としては用意していますが、そんなに使わないかもしれません。 本番ビルド $ npx goqoo release productionモードでビルドしてdist配下にファイルが作成されます。 圧縮などされるので、本番リリース時にはこの方法でビルドしてください。 ビルド済みファイルをkintoneにアップロードすれば、デプロイ完了です。 ちなみに、Goqoo 0.x系ではkintoneに自動的にJS URLをデプロイする機能がありましたが、事故を招きやすい仕様なので一旦廃止しました。(既存のJSを全部消しちゃってたのです・・・) 安全かつ使いやすい仕組みが考えられたらまた復活するかもしれません。 サイボウズさん公式のkintone-customize-uploader は、使いにくいので採用してません。やるなら自分で仕組み作る予定です。 もう少し詳しく フォルダ構成 こんな感じ。 . ├── .env.example ├── .eslintrc.js ├── .git/ ├── .gitignore ├── .prettierignore ├── .prettierrc.yaml ├── README.md ├── babel.config.js ├── dts/ ├── goqoo.config.js ├── goqoo.config.types.ts ├── jest.config.js ├── node_modules/ ├── package.json ├── src │ ├── apps/ │ │ ├── *** │ │ │ ├── ***.ts │ │ │ └── index.ts │ │ └── *** │ │ ├── ***.ts │ │ └── index.ts │ ├── context.ts │ ├── example.ts │ └── types.ts ├── test │ ├── ***.test.ts │ ├── ***.test.ts │ ├── example.test.ts │ └── jest.ignore.js └── tsconfig.json 一番メインはsrc配下。ここにkintoneカスタマイズ用のコードを書いていきます。 goqoo generate appするとsrc/appsに自動的にフォルダが作られましたが、 src/apps/***/index.[ts/js]が見つかったら全部ビルド対象になるCoCな仕様です。 ジェネレータを使わずに、手動でフォルダ・ファイルを作っても動きます。 index.tsについて goqoo generate app直後はこうなってます。 index.ts import { goqoo } from 'goqoo' goqoo('app', () => { require('./hello') }) goqoo()という関数のコールバック内で、別モジュールをrequire()しています。 こんな感じでhello.tsと同列に色んなモジュールを作って、 すべてindex.ts内でrequire()するように書いてください。1 goqoo()関数を挟む理由ですが、以下の2つの機能を提供するためです。 関数の内部でゴニョゴニョっとやっているわけですね。 dev-server向けのlocalhost URLと、ビルドしたファイルを両方kintoneにアップしても動くようにする dev-serverが止まっていればファイルの方を使う window.__devinfo__というオブジェクトに開発情報を入れて、デバッグコンソールから参照可能にする アプリ別に分けてデプロイすることもあるので、コミットが特定できるとメンテも安心! どうです?超嬉しくないすか??? この嬉しさが分かるなら、kintoneプログラマーとして本物だと言えるでしょうw 便利アラート関数 ジェネレータで生成されたモジュール内で、 helloGoqooという関数をimportしています。 hello.ts import { helloGoqoo } from 'goqoo' import type { IndexEvent } from 'types' kintone.events.on('app.record.index.show', async (event: IndexEvent<any /* kintone.types.SavedXxxxFields */>) => { await helloGoqoo() return event }) コレ自体はどうでも良いサンプル関数なのですが、 他にも便利アラート関数がいくつかあるので紹介しておきます。 import { confirmDialog } from 'goqoo' 「よろしいですか?」のような確認ダイアログを表示 OKで確定すると、ダイアログが消えずにそのままSpinnerになる 完了後に新しいアラートを出すと消える import { successDialog } from 'goqoo' 「成功しました!」みたいなダイアログ confirmDialog() → successDialog()の流れで使う import { errorDialog } from 'goqoo' エラー表示用のダイアログ confirmDialog() → errorDialog()の流れで使っても、他の場面で使ってもOK エラーの中身をクリップボードにコピーする機能もある 引数や戻り地などは、TypeScriptで型定義を見てもらうのが早いと思います。 すべてSweetAlertをラップしたものです。2 設定ファイル goqoo.config.jsという設定ファイルがプロジェクトルートにあり、 goqoo.config.types.tsという型定義専用のTSファイルとセットになっています。 まずは型定義ファイルの編集から。 goqoo.config.types.ts import type { Config as _Config } from 'goqoo' export type Env = 'development' // | 'staging' | 'production' export type AppId = { project: number // 案件管理 customer: number // 顧客管理 sales_activity: number // 活動履歴 } export type Context = { env: Env host: string appId: AppId } export type Config = _Config<Env, Context> Env: 文字列Union型で、開発環境・ステージング環境・本番環境などの各種環境名を列挙 AppId: アプリ名をキーにしたオブジェクト型を定義 直接JSカスタマイズを仕込まないアプリでも、API経由でリクエストを投げる可能性があれば列挙しておく デフォルトでは、サンプルとしてkintoneアプリストアの「営業支援パック」を想定した3種類のアプリ名を登録済み。 これは削除して、自分なりのアプリ名をつける。 Context: 型定義は特に触らなくて良い Config: 型定義は特に触らなくて良い これを踏まえて、goqoo.config.jsを編集します。 goqoo.config.js // @ts-check /** * @type {import('./goqoo.config.types').Config} */ const config = { bundlerType: 'default', dtsGen: { env: 'development', // skip: ['customer'], }, environments: [ { env: 'development', host: 'example.cybozu.com', appId: { project: 0, customer: 0, sales_activity: 0, }, }, // { // env: 'staging', // host: '...', // appId: { ... }, // }, // { // env: 'production', // host: '...', // appId: { ... }, // }, ], } module.exports = config 冒頭の@type {import('./goqoo.config.types').Config}がポイント。 この設定ファイルは色んなところから読み込ませやすいように.jsにしてるんですが、 JSDocでゴリゴリ型定義を書くのは苦行でしか無いので、 隣に「型定義専用の.tsファイル」を置いて、importだけしてやるのです。 このパターンは意外といろんなところで使えるのでオススメ! goqoo.config.jsを設定しておくと、大きく2つの恩恵があります。 開発・本番環境で、APIで扱うアプリIDを自動切り替えできる dtsファイルを自動生成できる APIで扱うアプリID 以前僕が書いたこの記事 kintoneの本番環境・開発環境で異なるアプリIDを自動判定する方法 https://qiita.com/the_red/items/b2832b5cd8325b97a989 この機能を、フレームワーク側に取り込みました。 goqoo.config.jsで環境別にアプリIDを定義した上で、 src/context.tsというモジュールをインポートして使います。 ちなみにsrc直下を直接インポートできるようにtsconfig.jsonを設定済みです。 import { KintoneRestAPIClient } from '@kintone/rest-api-client' import { context } from 'context' const client = new KintoneRestAPIClient() kintone.events.on('app.record.create.submit.success', async (event) => { const 顧客名 = event.record.顧客名.value client.record.addRecord({ app: context.appId.customer, // ❗❗❗注目❗❗❗ record: { 顧客名: { value: 顧客名 }, }, }) return event }) context.tsの中身は割愛しますが、この中で開発・本番環境の判別をしてくれるので、 使う側はcontext.appId.xxxのように書くだけで良いのです。 そしてそして、appId.までエディタで打ち込んだら、 goqoo.config.jsに定義済みのアプリ名を入力補完してくれちゃいます。素晴らしい。 dtsファイルの自動生成 @kintone/dts-genという神ツールがあります。 これはまじで素晴らしいんですが、複数アプリの型定義ファイルを一括で作る機能がないんですよね。 なので、Goqooでラップしました! 使えるようにするために、いくつかステップがあります。 goqoo.config.js 先程のgoqoo.config.jsの解説追加。 アプリIDは定義済みの前提で、さらにdtsGenプロパティに注目。 const config = { bundlerType: 'default', dtsGen: { env: 'development', // skip: ['customer'], }, envプロパティで、「どの環境のアプリをdtsファイル化するか」を1つだけ選びます。 ほとんどの場合はデフォルトのdevelopment環境で良いでしょう。 skipプロパティは、「アプリID定義はしたけど、dtsファイル化したくない」ものがあれば列挙します。 デフォルトはコメントアウトしてあるので、そのままでOK .env kintoneのログイン情報を.envに書いておきます。3 .envはGitコミットされないように設定済み。 サンプルファイルがあるので、それをコピーして編集が便利です。 $ cp .env.example .env # .envを編集、GOQOO_USERNAMEとGOQOO_PASSWORDを設定 生成 設定ファイルを書いてしまえばあとは簡単! $ npx goqoo generate dts これで、dtsフォルダ配下にずらーっとmy-app-fields.d.tsのようなファイルが出来ていきます。 使い方 src/types.tsというファイル内に、便利な型関数を用意してあります。 hello.ts内で既に使われているので、そこを書き換えてみましょう。 import { helloGoqoo } from 'goqoo' import type { IndexEvent } from 'types' -kintone.events.on('app.record.index.show', async (event: IndexEvent<any /* kintone.types.SavedXxxxFields */>) => { +kintone.events.on('app.record.index.show', async (event: IndexEvent<kintone.types.SavedMyAppFields>) => { await helloGoqoo() return event }) ここまでやっておくと、event.record(s)オブジェクトの中身に実際のアプリの型が反映されるので、 フィールドコードのtypoチェックとか、入力補完とか、いろんな恩恵が受けられます。 開発途中でkintone側のフィールド追加・フィールドコードを変更などしたら、 その都度goqoo generate dtsを叩いて更新しましょう。 フィールドコードが変わっていたら、影響がある場所はエラー出してくれて、 kintoneアプリ側のリファクタも安心して行うことができます。 ほかにも・・・ Jestでテスト書くための準備も整ってるよ(example.test.ts) goqoo generateはgoqoo gと省略できるよ goqoo startはgoqoo sと省略できるよ npm run generate yarn generateみたいなscriptsも使えるよ .envにAWSのアクセスキーを書くと、goqoo build:s3でS3にJSを自動アップできるよ ESLintの設定めんどくさいのでeslint-config-goqoo内に丸っとまとめてるよ 至れり尽くせり、個人的にとっても便利な機能満載でございます! だって、自分のために自分で作ったんだからね!!! おわりに 公式ドキュメント書くには英語とかもふくめて相当気を使うのですが、 Qiita記事であれば、これくらいの書きなぐり口語体でも許されるかなと思いまして、 とにかく勢いに任せてガーッと雑に書きました! 今回の記事は、記事そのものではなくて、 あくまで「Goqoo on kintone」というフレームワーク自体に価値があると思っております。 アドベントカレンダーに合わせてリリースするモチベーションにはなりましたが、 アドベントカレンダーのために作ったのではありません。 「自分が便利に使い倒している開発体験を、みんなで使ってよ!」という想いを詰め込んでおります! 感想、Issue、プルリク、「ガッツリ一緒に開発したいよ」のラブコールなど、 ドシドシお待ちしてますので、よろしくおねがいしまーす! それでは皆さん、Goqooと共にメリークリスマス Dynamic Importでなくてrequireなのは、苦肉の策です。。。Dynamic Importを使っちゃうと、本番ビルド時にファイルが複数に分かれちゃうので、kintoneにデプロイする場合には不向きなんですよね。 ↩ SweetAlert2ではなく、元祖SweetAlertです。Swal2の方はEasyだけどカスタマイズ性が悪いので好きではありません。 ↩ 本当は、プレーンテキストの.envにクレデンシャル情報を書きたくない。OAuth対応そのうちやります。 ↩
- 投稿日:2021-12-24T02:43:01+09:00
iframeの中のaタグをクリックした際に、iframeの高さを遷移したページの高さ分に変更させる
iframe内のaタグをクリック iframe内のページ遷移 ページの高さによって、元ページでの高さを変更 のような挙動を実装した時にiframeについて理解することができたので、その時に行ったことを記載します。 注意!! クロスドメイン(親ページと子ページのドメインが異なる)の場合、この方法は適用できない可能性があります。 参考 iframeタグとは <IFRAME>はInline Frameの略です。<IFRAME>を使用すると、 <FRAMESET>のようにウィンドウを水平・垂直に分割する形式だけではなく、 ウィンドウの中に独立して表示される形式のインラインのフレームが作成できます。 <IFRAME>タグは<BODY>~</BODY>内で使用します。 src属性でフレーム内に表示する内容を指定します。name属性でフレームに名前を付けることで、 そのインラインフレームをリンクの表示先として指定することができます。 制作したもの 検証したこと iframe内のスタイルとtop内のスタイルの!importantのどちらが優先されるのか検証 iframe内のリンクを押すと、iframe内のページ遷移をしiframeのbodyの高さ分要素の高さを変更 制作したコードについて HTML iframe外のhtml <!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <link rel="stylesheet" href="./css/top.css"> <script src="./js/top.js" defer></script> <title>iframe - sample</title> </head> <body> <p>トップ</p> <div> <iframe class="js-iframe" src="./move/page1.html" frameborder="0"></iframe> </div> <p>テキスト</p> </body> </html> iframe内のhtml(page1.html) <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <link rel="stylesheet" href="../css/page1.css"> </head> <body> <p>page1</p> </body> </html> iframe内のhtml(page2.html) <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <link rel="stylesheet" href="../css/page2.css"> </head> <body> <p>page2</p> </body> </html> CSS iframe内のcss p { color: #333 !important; } iframe外のcss(page1.css) * { margin: 0; box-sizing: border-box; } .border { border: 1px solid #f0f } a { color: inherit; } p { color: #f00; } iframe外のcss(page2.css) * { margin: 0; box-sizing: border-box; } .border { height: 100px; border: 1px solid #f00 } a { color: inherit; } p { color: #00f; } JavaScript // iframeのコンテンツの要素取得 function getIframeElem() { const elemIframe = document.querySelector('.js-iframe') return { elemIframe } } // iframeの要素の中にあるbodyの高さを取得 function getIframeHeight(elemIframe) { const iframeBodyHeight = elemIframe.contentWindow.document.body.clientHeight return { iframeBodyHeight } } // iframeの中にあるリンクタグの要素一覧を取得 function getElemIframeLink(elemIframe) { const elemIframeLinks = elemIframe.contentWindow.document.querySelectorAll('.js-link') return { elemIframeLinks } } // iframe内の要素をクリックした時の処理 function clickEvent(elemIframe) { elemIframe.addEventListener('load', loadEvent) } // ページが読み込まれた時の処理 function loadEvent() { const { elemIframe } = getIframeElem() const { iframeBodyHeight } = getIframeHeight(elemIframe) const { elemIframeLinks } = getElemIframeLink(elemIframe) elemIframe.height = iframeBodyHeight + 2 + 'px' elemIframeLinks.forEach(function(link) { link.addEventListener('click', clickEvent(elemIframe)) }) } window.addEventListener('load', function() { loadEvent() }, false) 行ったこと 1. iframe内のスタイルとtop内のスタイルの!importantのどちらが優先されるのか検証 /* ページ全体 */ p { color: #333 !important; } /* iframe内 */ p { color: #f00; } 結果 結果として、iframe内のスタイルはページ全体のスタイルと競合しない iframe外のpタグ iframe内のpタグ 2. iframe内のリンクを押すと、iframe内のページ遷移をしiframeのbodyの高さ分要素の高さを変更 行いたいことは表題の通りだが、jsでの実行の手順については以下になります。 2-1. iframeの要素を取得 後々管理しやすいようにhtml側にjs-iframeのclass名を付与、js側で要素の取得 <iframe class="js-iframe" src="./move/page1.html" frameborder="0"></iframe> // iframeのコンテンツの要素取得 function getIframeElem() { const elemIframe = document.querySelector('.js-iframe') return { elemIframe } /** * 以下の結果になる * <iframe class="js-iframe" src="./move/page1.html" frameborder="0"></iframe> */ } ここまでで以下のような要素が取得されます。 2-2. iframe内にあるbodyタグ・aタグの一覧の要素を取得 /** * elemIframeについては`2-1`で取得したiframeの要素 */ // iframeの要素の中にあるbodyの高さを取得 function getIframeHeight(elemIframe) { const iframeBodyHeight = elemIframe.contentWindow.document.body.clientHeight return { iframeBodyHeight } /** * 以下の結果になる * 24 */ } // iframeの中にあるリンクタグの要素一覧を取得 function getElemIframeLink(elemIframe) { const elemIframeLinks = elemIframe.contentWindow.document.querySelectorAll('.js-link') return { elemIframeLinks } /** * 以下の結果になる * NodeList [a.js-link] */ } HTMLIFrameElement.contentWindowを使用することで、iframe内のwindowオブジェクトにアクセスすることができます。 2-3. iframeの高さを変更 2-2で取得したiframeBodyHeightをiframeの高さに設定 // 今回の場合はiframe内のページ内のborder分もプラスしたいため、2pxの追加も行っている。 elemIframe.height = iframeBodyHeight + 2 + 'px' 2-4. 2-2で取得したaタグにeventを追加 イベントを付与 elemIframeLinks.forEach(function(link) { link.addEventListener('click', clickEvent(elemIframe)) }) 2-5. iframeのload時に処理を行う // iframe内の要素をクリックした時の処理 function clickEvent(elemIframe) { elemIframe.addEventListener('load', loadEvent) } 詰まったところ 1. addEventListenerを意識しないと読み込み回数がおかしくなる addEventListenerの第2引数を無名関数にすると複数回実行した時に処理が無駄に走ってしまう。 // NG elemIframe.addEventListener('load', function() { loadEvent() }) // OK elemIframe.addEventListener('load', loadEvent) NGの動き OKの動き
- 投稿日:2021-12-24T00:03:51+09:00
【PHP】buttonタグのsubmitした値がPOSTされなかった話
クリックしたボタンによって処理を分ける formでpostする際において複数のボタンを設置している場合、クリックしたボタンの値を受け取って処理を分岐できる。 例えばメールフォームなどで、確認画面から入力画面に戻るか、送信するかという場合、以下のような処理で実現できる。 <form action="post.php" method="post"> <button type="submit" name="action" value="back">戻る</button> <button type="submit" name="action" value="send">送信</button> </form> $action = (string)filter_input(INPUT_POST, 'action'); if ($action === 'back') { // 入力画面に戻る処理 // リダイレクトとか } // 送信処理 なんてことない、よくある実装だ。 buttonの値が送信されない 上記のようなフォームを実装して、実際に動かしてみたところどちらのボタンをクリックしても、送信処理だけが実行されてていた。$actionのチェックがすり抜けているようだ。 $_POSTをダンプすると、actionの値そのものが送信されていない。 原因 しばらく原因がわからず、さまざまな検証をしたが、JavaScriptでこのようなコードを記述していた。 $(function($) { $('form').submit(function() { $('.submit', this).prop('disabled', true); }); }); disabled…? このコードを削除すると、buttonタグの値が送信された。 これなに? いわゆる多重送信を防ぐために、サブミット後にボタンを無効化する処理。 さいきょうの二重サブミット対策 - Qiita disabledの要素の値が送信されないことは承知だが、submit後に変更してもダメなのか? と、この記事を書いている途中に以下の記事を見つけた。 Chromeの場合、submitボタン押下時にボタンをdisabledにすると、フォームデータが送信されない。 - Qiita なるほど…、Chrome特有の問題なのか? しかし、改善後の内容と現在の記述は(私はjQueryだが)同じものと思われる。 結論(未解決) 記述を変更したが、二重送信防止のためのdisabed処理を実装している場合、Chromeでは値が送信されなかった。 二重送信については、これにおいては直接の問題にはならないので、該当コードを削除して対応した。 ググって出てきた参考リンク(忘備録) Google Chromeでsubmitボタンの多重送信防止対策に対する対策【JavaScript】 JQueryでsubmitボタン連打禁止 送信ボタンの二重クリック防止を目的とするボタン無効化の推奨される対応方法について