20210909のJavaScriptに関する記事は15件です。

iOSとAndroidを区別するのに便利なJavaScript置いておくね

var isAndroid = (UA && UA.indexOf('android') > 0) || (weexPlatform === 'android'); var isIOS = (UA && /iphone|ipad|ipod|ios/.test(UA)) || (weexPlatform === 'ios'); これは基本的にユーザーエージェント(UA)を見て正規表現でandroidかiOSかを判別しています。UA &&の部分はnullチェックですね。UAの値を取得できなかった時にエラーで落ちないようにしています。 日本のスマホのシェアはAndroidとiOSが99%を占めているので上記以外はその他で例外処理をしてしまって良いでしょう。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

tableのtrをリンクにしつつ、その中に通常のリンクを置く (Vanilla.js)

data-hrefの中にリンクを置きたいとき、 もしくはボタン、チェックボックスを置きたいときの備忘として残します。 <table> <thead> <tr> <th>リンク先</th> <th>通常リンク</th> </tr> </thead> <tbody> <tr data-href="https://qiita.com/" tabindex="0" role="link"> <th>https://qiita.com/</th> <td class="object"> <a href="#aaa">編集する</a> </td> </tr> </tbody> </table> tabindex="0"でタブ操作を有効にしてます var td = document.querySelectorAll('tr[data-href] > *:not(.object)'); Array.prototype.forEach.call(td, function (e) { e.addEventListener('click', function() { window.location = e.parentNode.getAttribute('data-href'); }); }); デモはこちら
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

tableのtrをリンクにしつつ、その中に通常のリンクを置く (Pure.js)

data-hrefの中にリンクを置きたいとき、 もしくはボタン、チェックボックスを置きたいときの備忘として残します。 <table> <thead> <tr> <th>リンク先</th> <th>通常リンク</th> </tr> </thead> <tbody> <tr data-href="https://qiita.com/" tabindex="0" role="link"> <th>https://qiita.com/</th> <td class="object"> <a href="#aaa">編集する</a> </td> </tr> </tbody> </table> tabindex="0"でタブ操作を有効にしてます var td = document.querySelectorAll('tr[data-href] > *:not(.object)'); Array.prototype.forEach.call(td, function (e) { e.addEventListener('click', function() { window.location = e.parentNode.getAttribute('data-href'); }); }); デモはこちら
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JSやCSSにクエリストリングを与えてファイル更新時にブラウザ側のキャッシュクリアを行わせる

はじめに タイトルの内容で記事を書いた理由は、「キャッシュクリアの為にファイル名の後ろに数字をつけるアレ」と曖昧な覚え方をしていたからです。 曖昧な覚え方だとググることすらもできなかったので、備忘録として記事にしました。 Cache Busting Javascript、CSS、画像等のファイルの更新に合わせてクエリストリングを変更することで、ブラウザ側でキャッシュクリアをしなくてもいい状態にすることです。 ブラウザ側がサーバ側のファイルを読み込まずキャッシュを読み込むことでJavascript、CSS、画像の修正が反映されないことを防ぎます。 後ろにつけるアレの正式名称を覚えたい クエリ クエリストリング クエリ文字 GETパラメータ パラーメータ 特に固定された呼び方はないのでしょうか。 個人的にクエリストリングがしっくりきたのでクエリストリングと呼びつつ他にこういう言い回しもあるという程度に覚えておきます。 具体例 Javascript、CSS、画像ファイルの後ろに?クエリストリングと記述します。 CSSファイルの読み込みを例として、クエリストリングを書いてない場合は <link rel="stylesheet" type="text/css" href="/css/default.css" /> クエリストリングを書いている場合は <link rel="stylesheet" type="text/css" href="/css/default.css?20210909" /> と記述します。 CakePHPでヘルパーを利用してタグを作成した場合にクエリストリングを付与 環境 ソフトウェアのバージョン PHP 7.3.15 CakePHP 2.10.20 HtmlHelper利用時にクエリストリング付与 パラメータ無しでクエリストリングを付与 パラメータの$urlにクエリストリングを付与したファイル名を記述します。 index.ctp <?php $this->Html->css('test.css?20210909', array('inline' => false)); ?> 出力結果は以下の通りです。 <link rel="stylesheet" type="text/css" href="/css/test.css?20210909"/> パラメータありでクエリストリングを付与 パラメータの$urlにクエリストリングを付与したファイル名を記述する際にパラメータを記述します。 index.ctp <?php $this->Html->css('test.css?param=20210909', array('inline' => false)); ?> 出力結果は以下の通りです。 <link rel="stylesheet" type="text/css" href="/css/test.css?param=20210909"/> Configureを利用してクエリストリングを付与 クエリストリングが複数ある場合に同時に修正したかったので、Configureを利用してみました。 Configディレクトリに設定用のファイルを作成し、設定したいクエリストリングを記述します。 testConfig.php <?php $config['reloadQuery'] = '20210909'; Configディレクトリのbootstrap.phpに設定ファイル読み込みの記述をします。 bootstrap.php Configure::load("testConfig.php"); Controllerでクエリストリングを受け取り、テンプレートに渡します。 テンプレートが複数ある場合に共通化した処理になると考えたため、AppController.phpにbeforeFilter()を記述してクエリストリングを受け取っています。 AppController.php class AppController extends Controller { public function beforeFilter() { $reloadQuery = Configure::read('reloadQuery'); $this->set(compact('reloadQuery')); } } Controllerから受け取った$reloadQueryをテンプレートで使用します。 index.ctp <?php $this->Html->css('test.css?' . $reloadQuery, array('inline' => false)); ?> 出力結果は以下の通りです。test.css?の後ろにtestConfig.phpで設定した値が表示されます。 <link rel="stylesheet" type="text/css" href="/css/test.css?20210909"/> index.ctpよりも外側のテンプレート(例:default.ctp)があるとして、そこで直接タグを書いている場合は以下のように記述します。 default.ctp <?php <link rel="stylesheet" type="text/css" href="/css/default.css?<?php echo $reloadQuery; ?>" /> ?> 出力結果は以下の通りです。 <link rel="stylesheet" type="text/css" href="/css/default.css?20210909" /> そもそもCakePHPだったらコメントアウトを外すだけ? そもそもCakePHP2.xだとcore.phpに記述されている以下のコメントアウトを外すだけでキャッシュクリアを行ってくれるようでした。 core.php Configure::write('Asset.timestamp', 'force'); コメントが英語だったのでしっかりと理解できていませんでしたが、おそらくブラウザを開くたびに毎回キャッシュクリアをする設定だと思います。 今回の目的はファイル更新後に1度だけキャッシュを読み直して欲しかったので上記の設定は使わなかったです。 参考にさせてもらったサイト 以上です。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【TypeORM】多対多リレーションの中間テーブルをEntityにて明記する。select join(結果の型はEntity準拠)の実装例

背景 TypeORMで最も時間がかかった多対多テーブルの実装方法について、一通りの実装例を以下に記載する。 こういう人向け 筆者自身。こういう風にコーディングしましたと自他に後で見せるため。 上記公式ドキュメントを見たりしてそのまま実装しようとしたら「えっ…中間テーブルの消し方…どうなってんの!?」と思った方 TypeORMにて多対多テーブルをどんな風に他の人が組んでるのか知りたい方 前提 TypeORMのEntityをテーブルの設計書代わりとしたい(≒Entityファイルを見れば、どういったデータベース定義か自明となるようにしたい) 上記より、フレームワーク側が勝手に定義・作成するテーブルは許容しづらい。ヒトの定義でコントロールしたい。 その上で、typescriptとtypeormが生み出す「静的型付け」「Entityにもとづくテーブル自動生成」を行いたい DBはmysql、typeormバージョンは^0.2.37 実装例 この方の記事が分かりやすかったので、上記の「ユーザ」多対多「コース」を題材とする。 user import { Entity, PrimaryGeneratedColumn, Column, BaseEntity, JoinColumn } from "typeorm"; import { date_dictionary } from "./datadirectory" import { user_course } from "./user_course"; // ユーザテーブル。 @Entity({ name: "t_user",synchronize:true }) export class t_user extends BaseEntity { // primary keyやtype、サイズ、コメントも設定できる。 @PrimaryGeneratedColumn({ name: "id", type: "bigint", comment: '自動採番ID' }) id!: number; @Column({ nullable: false, type: "varchar", length: 12, comment: "ユーザ名" }) userName!: string; // 共通カラム使用。デフォルトだとカラム名が連結される(dateTypeUpdatorみたいになる)ので、prefix:falseにしている @Column(() => date_dictionary, { prefix: false }) dateType!: date_dictionary // 中間テーブルを対象に、一対多でタイプを宣言する。 @JoinColumn() user_course!: user_course[] } course import { Entity, PrimaryGeneratedColumn, Column, BaseEntity, JoinColumn } from "typeorm"; import { date_dictionary } from "./datadirectory" import { user_course } from "./user_course"; // コードマスタ定義。 @Entity({ name: "t_course",synchronize:true }) export class t_course extends BaseEntity { // primary keyやtype、サイズ、コメントも設定できる。 @PrimaryGeneratedColumn({ name: "id", type: "bigint", comment: '自動採番ID' }) id!: number; @Column({ nullable: false, type: "varchar", length: 12, comment: "コース名" }) courseName!: string; // 共通カラム使用。デフォルトだとカラム名が連結される(dateTypeUpdatorみたいになる)ので、prefix:falseにしている @Column(() => date_dictionary, { prefix: false }) dateType!: date_dictionary @JoinColumn() user_course!: user_course[] } 中間テーブルuser_course import { Entity, Column, BaseEntity, JoinColumn, ManyToOne, PrimaryColumn } from "typeorm"; import { date_dictionary } from "./datadirectory" import { t_user } from "./t_user"; import { t_course } from "./t_course"; // 多対多テーブルのユーザ・コーステーブルの中間テーブル。 @Entity({ name: "user_course",synchronize:true }) export class user_course extends BaseEntity { // Foreign keyのための設定。 @ManyToOne(()=> t_user, user => user.id,{}) // JoinColumnでnameをしておかないとuserIdIdみたいな新規カラムを生み出してくる。 @JoinColumn({ name: "userId" }) // primary keyやtype、サイズ、コメントも設定できる。 @PrimaryColumn({ nullable: false, type: "bigint", comment: "ユーザID" }) userId!: number; @ManyToOne(()=> t_course, course => course.id) @JoinColumn({ name: "courseId" }) @PrimaryColumn({ nullable: false, type: "bigint", comment: "コースID" }) courseId!: number; @Column({ nullable: false, type: "int", comment: "コース進捗度" }) progressNum!: number; // 共通カラム使用。デフォルトだとカラム名が連結される(dateTypeUpdatorみたいになる)ので、prefix:falseにしている @Column(() => date_dictionary, { prefix: false }) dateType!: date_dictionary // ユーザ・コースを、このテーブル視点から多対一で結ぶ。 @JoinColumn() user!:t_user @JoinColumn() course!:t_course } 上記Entity+「synchronize: true」によって自動生成される中間テーブルuser_course CREATE TABLE `user_course` ( `userId` bigint(20) NOT NULL COMMENT 'ユーザID', `courseId` bigint(20) NOT NULL COMMENT 'コースID', `creator` varchar(12) DEFAULT NULL COMMENT '作成者', `createdAt` datetime(6) DEFAULT current_timestamp(6) COMMENT '作成日時', `updator` varchar(12) DEFAULT NULL COMMENT '更新者', `updatedAt` datetime(6) DEFAULT current_timestamp(6) ON UPDATE current_timestamp(6) COMMENT '更新日時', `progressNum` int(11) NOT NULL COMMENT 'コース進捗度', PRIMARY KEY (`userId`,`courseId`), KEY `FK_67a940b1d7b3cc2f0e99ab6d23b` (`courseId`), CONSTRAINT `FK_63b2ec4f34c89d4b1219f85a806` FOREIGN KEY (`userId`) REFERENCES `t_user` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION, CONSTRAINT `FK_67a940b1d7b3cc2f0e99ab6d23b` FOREIGN KEY (`courseId`) REFERENCES `t_course` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION ) ENGINE=InnoDB DEFAULT CHARSET=utf8 ManyToOneによって外部キーもしっかり宣言されている。 あまり関係ない共通部品 import { Column, UpdateDateColumn, CreateDateColumn } from "typeorm"; // @Entityが無い、テーブルとしては登録されないクラス。共通カラムを別々の場所で使う時に使える。 export class date_dictionary { @Column({ nullable: true, type: "varchar", length: 12, comment: "作成者" }) creator!: string; @CreateDateColumn({ nullable: true, type: "datetime", comment: "作成日時" }) createdAt!: Date; @Column({ nullable: true, type: "varchar", length: 12, comment: "更新者" }) updator!: string; @UpdateDateColumn({ nullable: true, type: "datetime", comment: "更新日時" }) updatedAt!: Date; } select join join系のメソッドを色々試したが、「innerJoinAndMapMany」系が一番使いやすかった。 // ユーサ~中間テーブル~コースをjoinして取得する let manymanyResult = await con.createQueryBuilder(t_user, "user") .innerJoinAndMapMany("user.user_course",user_course,"user_course", "user.id = user_course.userId ") .leftJoinAndMapMany("user_course.course",t_course,"course", "course.id = user_course.courseId AND user.id = user_course.userId") .getMany(); console.dir(manymanyResult,{depth:null}) INNER JOINs entity's table, SELECTs the data returned by a join and MAPs all that data to some entity's property. This is extremely useful when you want to select some data and map it to some virtual property. It will assume that there are multiple rows of selecting data, and mapped result will be an array. You also need to specify an alias of the joined data. Optionally, you can add condition and parameters used in condition. ↓deepl翻訳 エンティティのテーブルをINNER JOINし、結合によって返されたデータをSELECTし、そのすべてのデータをあるエンティティのプロパティにマッピングします。これは、あるデータを選択し、それをある仮想プロパティにマッピングしたい場合に非常に便利です。複数行のデータが選択されていることを想定し、マッピングされた結果は配列になります。また、結合したデータのエイリアスを指定する必要があります。オプションとして、条件や条件に使用するパラメータを追加することができます。 JoinAndMapMany系:("エイリアス.joinするエイリアス", joinするEntity,"joinするエイリアス", "join条件",{param]) 一応上記で下記のように上手くいった。 manymanyResult取得結果 [ t_user { id: '1', userName: 'user1', dateType: date_dictionary { creator: null, createdAt: 2021-09-09T00:20:25.507Z, updator: null, updatedAt: 2021-09-09T00:20:25.507Z }, user_course: [ user_course { userId: '1', courseId: '1', progressNum: 1, dateType: date_dictionary { creator: null, createdAt: 2021-09-09T00:20:48.112Z, updator: null, updatedAt: 2021-09-09T01:02:27.733Z }, course: [ t_course { id: '1', courseName: 'Aコース', dateType: date_dictionary { creator: null, createdAt: 2021-09-09T00:19:55.973Z, updator: null, updatedAt: 2021-09-09T00:43:39.479Z } } ] }, user_course { userId: '1', courseId: '2', progressNum: 2, dateType: date_dictionary { creator: null, createdAt: 2021-09-09T00:21:00.955Z, updator: null, updatedAt: 2021-09-09T01:02:28.717Z }, course: [ t_course { id: '2', courseName: 'Bコース', dateType: date_dictionary { creator: null, createdAt: 2021-09-09T00:20:06.046Z, updator: null, updatedAt: 2021-09-09T00:43:43.822Z } } ] }, user_course { userId: '1', courseId: '3', progressNum: 0, dateType: date_dictionary { creator: null, createdAt: 2021-09-09T00:44:07.213Z, updator: 'test', updatedAt: 2021-09-09T05:32:08.983Z }, course: [ t_course { id: '3', courseName: 'Cコース', dateType: date_dictionary { creator: null, createdAt: 2021-09-09T00:43:34.510Z, updator: null, updatedAt: 2021-09-09T00:43:34.510Z } } ] } ] }, t_user { id: '2', userName: 'user2', dateType: date_dictionary { creator: null, createdAt: 2021-09-09T00:20:33.497Z, updator: null, updatedAt: 2021-09-09T00:20:33.497Z }, user_course: [ user_course { userId: '2', courseId: '2', progressNum: 1, dateType: date_dictionary { creator: null, createdAt: 2021-09-09T00:21:11.953Z, updator: null, updatedAt: 2021-09-09T01:02:29.373Z }, course: [ t_course { id: '2', courseName: 'Bコース', dateType: date_dictionary { creator: null, createdAt: 2021-09-09T00:20:06.046Z, updator: null, updatedAt: 2021-09-09T00:43:43.822Z } } ] }, user_course { userId: '2', courseId: '3', progressNum: 1, dateType: date_dictionary { creator: '', createdAt: 2021-09-09T00:43:58.108Z, updator: '', updatedAt: 2021-09-09T01:02:29.877Z }, course: [ t_course { id: '3', courseName: 'Cコース', dateType: date_dictionary { creator: null, createdAt: 2021-09-09T00:43:34.510Z, updator: null, updatedAt: 2021-09-09T00:43:34.510Z } } ] } ] } ] ユーザが2人いて、それぞれ受けているコースが異なる。これらを中間テーブルに入れたコース進捗度(progressNum)含めて取得することができる。 t_user[]として取得できるため、見た目以上に入力補完が容易。 中間テーブルのEntityを既に用意しているため、insert・update・deleteなりも好きにできる。 終わりに TypeORMは公式ドキュメントに書かれていないオプションやメソッド、実装の仕方が意外とやりやすかったりする。 公式ドキュメントのようなシンプルな書き方で上手く行けばいいが、内部でやっていることが多すぎてエラーややれないこと、一目見て分からないことが増えていくため、結局ヒトが裏側に気をつけることが必要になってしまう。 ただ静的型付けとEntityにもとづくテーブル自動生成は良いと考える。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【詰まったログ】JS reduce( )でエラー

やりたいこと 各配列の数値をそれぞれ合計したい data:[ [] [300000, 600000] [] [100007, 99999] ] エラー内容 下記でConsole.log(total)するとエラーになる let total = data.map((val) => val.reduce((sum, i) => sum + i) ); 原因 初期値(initialValue)を設定していなかったので、配列がカラ(合計するものがない)ときにエラーが出たようです。 参照:Array.prototype.reduce() オブジェクトの配列に含まれた値の合計値を出すには、すべての項目を関数内で取得できるようにするために initialValue を指定する必要があります。 解決策 let total = beneficiariesData.map((val) => val.reduce((sum, i) => sum + i, 0) ); 結果 [] [900000] [] [110,006] 末尾に初期値として0を設定すると合計できました
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

@@species に未来はあるのか

いや、ない(反語) ES2015 から導入された @@species について ES2015 からクラス構文が導入され、ビルトインクラスを継承したクラスを簡単に作れるようになりました。 class MyArray extends Array {} ES2015 を策定するにあたって議論となったのがビルトインクラスそのものを返すメソッドの存在です。例えばこの MyArray に対して Array#map を実行したときにその返り値は MyArray であるべきでしょうか Array であるべきでしょうか。そしてそれをどうやって Array#map に伝えればいいでしょうか。 これを制御できるようにするのが @@species という Well-known Symbol です。普通は派生クラスを返しますが、任意のクラスを返すように変更することも可能になります。 class MyArray extends Array { // 明示的に @@species を指定しない場合は自身(派生クラス)を返す // static get [Symbol.species]() { return this; } } const mapped = new MyArray(1, 2, 3).map((val) => val * 2); console.log(mapped); // => MyArray(3) [2, 4, 6] class MyArray extends Array { // @@species を上書きする static get [Symbol.species]() { return Array; } } const mapped = new MyArray(1, 2, 3).map((val) => val * 2); console.log(mapped); // => Array(3) [2, 4, 6] 需要があるのかどうかはおいておいて、とりあえず ES2015 以降の言語仕様にはこの機能があります。 Array で具体的に列挙すると以下のメソッドたちが @@species の影響を受けます1。 Array#concat Array#filter Array#map Array#slice Array#splice Array#flat(ES2019) Array#flatMap(ES2019) Stage 1 Restricting subclassing support in built-in methods 2021年5月の TC39 meeting において、ビルトインクラスのメソッドから派生クラスを返すのを辞める提案が出されました。 モチベーションとしては 仕様が複雑になってしまいメンテナンス性にかける パフォーマンスが悪くなる セキュリティバグを引き起こす原因になってしまっている ということです。 もしこれが承認された場合、派生クラスから Array#map を呼んだとしても Array 以外を返さなくなります。 class MyArray extends Array {} const mapped = new MyArray(1, 2, 3).map((val) => val * 2); console.log(mapped); // => Array(3) [2, 4, 6] Web 互換性の問題 もちろんこの変更は Web 互換性を破壊します。ただし機能が機能なだけに影響は小さいだろうと見積もられています。Web 互換性は大切ですが、影響が小さければたびたび破壊的変更を取り入れることがあります2。 具体的には Array#flat (ES2019) によって Highcharts.js のラベルが表示されないというものは影響が小さかったため無視されました。 Array#at (ES2022) によってサイトが壊れる例が見つかりましたが、報告されたのがこの一例だけだったため無視されました。 この変更には core-js の作者である zloirock 氏が強く反対しています3。私も個人的にはこの変更はやりすぎなのではないかと思っています。 しかし今回も統計的に問題がないと判断されれば取り入れられることでしょう。 新しい提案への影響 現在 Stage 2 Change Array by Copy という提案があります。 この提案は Array#reverse や Array#sort などといった既存のメソッドと対応して、非破壊的に新しい Array を作るものとなっています。 const arr = [1, 2, 3]; const rev = arr.withReversed(); // メソッド名についてはまだ議論中です console.log(arr); // => Array(3) [1, 2, 3] console.log(rev); // => Array(3) [3, 2, 1] 先程の Stage 1 Restricting subclassing support in built-in methods の議論を受けて、現状この提案で追加されるメソッドでは意図的に @@species を無視してビルトインクラスを返すようになっています。 もし互換性の問題により Stage 1 Restricting subclassing support in built-in methods が却下されたとしても、今後の提案についてはビルトインクラスのメソッドから派生クラスを生成する仕組みは取り入れられないことになりそうです。 結び @@species は仕様から消えるか、それとも仮に生き残ったとしても新しいメソッドでは適用されないか。どちらにせよ未来はなさそうです。 JavaScript の言語仕様には一貫性がないことがよくあります。ES5 からある Array#indexOf は NaN を検知できませんが ES2015 Array#includes では検知できるなどは結構有名なのではないでしょうか。 const arr = [1, 2, 3, NaN]; console.log(arr.indexOf(NaN)); // => -1 console.log(arr.includes(NaN)); // => true こういった話は JavaScript の面白いところでもあり嫌われるところでもあるのかなと思います。ECMAScript の提案を追うのは割と楽しいので皆さんもよかったらどうぞ。 これらのメソッドは仕様の中で ArraySpeciesCreate を使っています。他のビルトインクラスでは SpeciesConstructor を使っているものを探すと見つけられるでしょう。 ↩ 各提案について Chrome チームによって既存サイトへの影響の大きさを統計で出して、それを元に取り入れるかどうか判断されています。影響があっても少数なら無視されることが多いです。 ↩ 私は彼以上に互換性に熱心な方を知りません。caniuse や compat-table では後から実装に埋め込められてしまったバグをトラッキングできないからと独自で core-js に互換性テーブルを導入したときにはそこまでやるのかと度肝を抜かれました。 ↩
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Symbol.species に未来はあるのか

いや、ない(反語) ES2015 から導入された @@species について ES2015 からクラス構文が導入され、ビルトインクラスを継承したクラスを簡単に作れるようになりました。 class MyArray extends Array {} ES2015 を策定するにあたって議論となったのがビルトインクラスそのものを返すメソッドの存在です。例えばこの MyArray のインスタンスで Array#map を実行したときにその返り値は MyArray であるべきでしょうか Array であるべきでしょうか。そしてそれをどうやって Array#map に伝えればいいでしょうか。 これを制御できるようにするのが @@species という Well-known Symbol です。普通は派生クラスを返しますが、任意のクラスを返すように変更することも可能になります。 class MyArray extends Array { // 明示的に @@species を指定しない場合は自身(派生クラス)を返す // static get [Symbol.species]() { return this; } } const mapped = new MyArray(1, 2, 3).map((val) => val * 2); console.log(mapped); // => MyArray(3) [2, 4, 6] class MyArray extends Array { // @@species を上書きする static get [Symbol.species]() { return Array; } } const mapped = new MyArray(1, 2, 3).map((val) => val * 2); console.log(mapped); // => Array(3) [2, 4, 6] 需要があるのかどうかはおいておいて、とりあえず ES2015 以降の言語仕様にはこの機能があります。 Array で具体的に列挙すると以下のメソッドたちが @@species の影響を受けます1。 Array#concat Array#filter Array#map Array#slice Array#splice Array#flat(ES2019) Array#flatMap(ES2019) Stage 1 Restricting subclassing support in built-in methods 2020年6月の TC39 meeting において、ビルトインクラスのメソッドから派生クラスを返すのを辞める提案が出されました。 モチベーションとしては 仕様が複雑になってしまいメンテナンス性にかける パフォーマンスが悪くなる セキュリティバグを引き起こす原因になってしまっている2 ということです。 もしこれが承認された場合、派生クラスから Array#map を呼んだとしても Array 以外を返さなくなります。 class MyArray extends Array {} const mapped = new MyArray(1, 2, 3).map((val) => val * 2); console.log(mapped); // => Array(3) [2, 4, 6] Web 互換性の問題 もちろんこの変更は Web 互換性を破壊します。ただし機能が機能なだけに影響は小さいだろうと見積もられています。Web 互換性は大切ですが、影響が小さければ破壊的変更を取り入れることがあります3。 具体的には Array#flat (ES2019) によって Highcharts.js のラベルが表示されないというものは影響が小さかったため無視されました。 Array#at (ES2022) によってサイトが壊れる例が見つかりましたが、報告されたのがこの一例だけだったため無視されました。 この変更には core-js の作者である zloirock 氏が強く反対しています4。私も個人的にはこの変更はやりすぎなのではないかと思っています。 しかし今回も統計的に問題がないと判断されれば取り入れられることでしょう。 新しい提案への影響 現在 Stage 2 Change Array by Copy という提案があります。 この提案は Array#reverse や Array#sort などといった既存のメソッドと対応して、非破壊的に新しい Array を作るものとなっています。 const arr = [1, 2, 3]; const rev = arr.withReversed(); // メソッド名についてはまだ議論中です console.log(arr); // => Array(3) [1, 2, 3] console.log(rev); // => Array(3) [3, 2, 1] 先程の Stage 1 Restricting subclassing support in built-in methods の議論を受けて、現状この提案で追加されるメソッドでは意図的に @@species を無視してビルトインクラスを返すようになっています。 もし互換性の問題により Stage 1 Restricting subclassing support in built-in methods が却下されたとしても、今後の提案についてはビルトインクラスのメソッドから派生クラスを生成する仕組みは取り入れられないことになりそうです。 結び @@species は仕様から消えるか、それとも仮に生き残ったとしても新しいメソッドでは適用されないか。どちらにせよ未来はなさそうです。 JavaScript の言語仕様には一貫性がないことがよくあります。ES5 からある Array#indexOf は NaN を検知できませんが ES2015 Array#includes では検知できるなどは結構有名なのではないでしょうか。 const arr = [1, 2, 3, NaN]; console.log(arr.indexOf(NaN)); // => -1 console.log(arr.includes(NaN)); // => true こういった話は JavaScript の面白いところでもあり嫌われるところでもあるのかなと思います。ECMAScript の提案を追うのは割と楽しいので皆さんもよかったらどうぞ。 これらのメソッドは仕様の中で ArraySpeciesCreate を使っています。他のビルトインクラスでは SpeciesConstructor を使っているものを探すと見つけられるでしょう。 ↩ https://docs.google.com/presentation/d/11fkQeEisoszNGF8SrautVT1ltSnsQBWRxJ4usoc-g_o/edit ↩ 各提案について Chrome チームによって既存サイトへの影響の大きさを統計で出して、それを元に取り入れるかどうか判断されています。影響があっても少数なら無視されることが多いです。 ↩ 私は彼以上に互換性に熱心な方を知りません。compat-table では後から実装に埋め込められてしまったバグをトラッキングできないなどの理由から独自で core-js に互換性テーブルを導入したときにはそこまでやるのかと度肝を抜かれました。 ↩
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

GASを活用した業務効率化レシピの紹介

はじめに Axrossの広報を担当している松田です。 Axross とは、エンジニアの"教育"と"実務"のギャップに着目し、「学んだが活用できない人を減らしたい」という想いのもと、ソフトバンクと社内起業制度にて立ち上げたサービスです。 現役エンジニアによる実践ノウハウを"レシピ"として教材化し、実際にプログラミングで実装を追体験しながら学ぶことができます。AI/機械学習をテーマにした、様々な業務領域やビジネスの課題解決に応用できる実践的な学習教材を180以上揃えています。(2021年9月時点) Axross:https://axross-recipe.com 公式Twitter:https://twitter.com/Axross_SBiv 最近は受講者の皆様から『AI・Pythonの実践的な教材だけでなく、プログラミング初学者向けの内容も増やして欲しい』、『身近な業務に活用できるようなレシピが欲しい』といった声を多くいただいており、初学者向けのレシピも拡充しています。 今回は、Axrossで学べる初心者におすすめレシピの中から、GASを活用した業務効率化レシピを一部ご紹介します。 GASとは GAS(Google Apps Script)は、Google Workspace の統合、自動化、拡張のためのビジネス ソリューションをすばやく簡単に構築するための唯一のローコードプラットフォームです。Google Apps Script を使えば、ビジネスユーザーは本格的なソフトウェア開発の経験がなくても、Google Workspace 上にカスタム ソリューションを構築できます。Apps Script は、Gmail アカウントをお持ちであればどなたでもご利用いただけます。(Google HPより引用) Google Apps Script を使えば、本格的なソフトウェア開発リソースは必要なく、日常的な課題を解決するカスタム ソリューションをすぐに作成できます。単純作業の自動化をはじめ、日常的に利用するアプリケーションとの統合機能の作成や、必要に応じて機能やカスタマイズの追加が可能となっています。 開発言語はHTML、CSS、JavaScript なため、独自のフレームワークの知識を新たに身に付ける必要はありません。また、Google Workspace API や、YouTube、Google アナリティクス、BigQuery など 100 以上の事前統合されたGoogleサービスをフル活用できます。 Gmail とスプレッドシートを使ってメールを統合 スプレッドシートのデータを Gmail で取り込み、視覚的にわかりやすいメールを作成して送信できます。 イベント セッションへの申し込み スプレッドシート、ドキュメント、フォーム、Gmail を連携させることで、イベント セッションの申し込みを受け付けた後、各参加者の日程表の作成とメール送信を自動で行えます。 自由回答式フィードバックの感情分析 スプレッドシートで直接 Cloud Natural Language を使用してエンティティ感情分析を実施することで、自由回答式フィードバックのようなテキストデータの大規模分析を行えます。 公式ホームページ: https://workspace.google.co.jp/intl/ja/products/apps-script/ GASを活用したレシピの紹介 Googleカレンダーのスケジュール調整を自動化するレシピ 投稿者:@su2umaruさん GASを使用して、会社や学校といった組織、また個人でも多くの方々が利用されているGoogleカレンダーでスケジュール調整の自動化を学べます。 ミーティングに参加するメンバーとミーティングに要する時間を指定すると全員が参加できる日程でのミーティングの作成とゲストの招待まで自動化します。 Google Apps Scriptを活用し、Googleフォーム回答後にメールを自動送信するレシピ 投稿者:@uehara7さん GASを使ってユーザーがGoogleフォーム入力した後に、回答者に対して自動で定型メールを送信するプログラムの作成を学べます。 イベント申し込み完了時や、アンケート回答後に自動で返信メールを送付できるようになったり、お問い合わせフォーム入力完了後に、問い合わせ完了メールを自動送信できるようになります。 Google App Scriptによる環境構築ほぼ不要の書籍管理システム 投稿者:@Kipさん JavascriptをベースとしたGASの基本的処理を用いて書籍管理のWebアプリケーションを学べます。 GASを利用して本に記載されているISBNコード(書籍の国際規格)をバーコードで読み込みスプレッドシートに書籍の情報を記載します。データベースにGoogleSpreadSheetを利用しますので、簡易にWEBアプリケーションとして作成できます。読み取り開始画面のみピュアなHTMLファイルを利用しますので、そのHTMLファイルを公開するサーバーさえあれば環境構築など必要なしに誰でも作成可能です。そのほかの部分については、Googleがホストしてくれます。 Googleフォーム回答者を予定のゲストに追加して会議通知を送るレシピ 投稿者:@su2umaruさん GASを使ってイベントを開催するとき、申込者に自動で会議通知を送る方法を学べます。 イベントを開催するとき、申込者にGoogleフォームで申し込んでもらうことが多いです。 その後、Googleフォームの回答をスプレッドシートに集計して、各メールアドレス宛にイベントへ会議通知を送ります。 しかしこのフローだと申込から会議通知が送られるまで時差があり、申込者はイベントに申し込んだことを自身で覚える、もしくは自身でカレンダーに予定を作成しなければいけません。 一方で開催側は手動で会議通知や参加確定のメールを送らなければならず、双方に手間がかかります。 この手間を省くため、Googleフォーム回答者を予定のゲストに追加して会議通知を送るように自動化します。 この自動化ができるとすぐに会議通知を送れるためイベントの離脱率が下がる、そして双方の手間を省くことができるといったメリットがあります。 Gmailで変数を使って宛先ごとにメッセージを変えるレシピ 投稿者:@su2umaruさん GASを使ってスプレッドシートの内容を取得し、GASでメールを送る方法を学べます。 個々のメッセージのやり取りから登録ユーザへのキャンペーン告知まで、メールは様々なシーンで使われています。 特にGoogleが提供しているフリーメールサービスのGmailは多くの個人、組織が活用しています。Gmailで複数人にメッセージを送るとき、宛先ごとにメッセージを変えたいときがあります。 例を挙げるとそれぞれのユーザの状態により告知するキャンペーンの内容を変える、ABテストを行うといった具合です。さらに冒頭の宛名を「○○様」と表示したい場合、1人1人のメッセージを手作業で作成しなければいけません。 そうした面倒な作業を行わずに済むよう、本レシピではGmailで変数を使って宛先ごとにメッセージを変える方法を解説します。 プログラミングの基礎しか取り組んだことがない方でも学習できるよう丁寧に解説しているのでぜひこの機会に1つアウトプットを行ってみましょう。 Googleフォームの回答をSlackに通知するレシピ 投稿者:@su2umaruさん GASを使ってGoogleフォームの回答を取得する方法やSlackにGoogleフォームの回答を通知する方法を学べます。 企業や個人からの問い合わせ、イベントの申込などでGoogleフォームを使われている方は多いと思います。 そのような用途でGoogleフォームを使っていると、Googleフォームの回答があったときに通知が来ると便利です。特にメールではなくチャットベースのコミュニケーションツールであるSlackに通知を送れると便利ですよね。 今回は「Googleフォームの回答をSlackに通知する」機能を実装する方法を解説します。 この機能を実現できると問い合わせや申込に対応するスピードが飛躍的に向上し、かつわざわざ回答状況を確認する手間が省けます。 最後に 今回は、AxrossのGASを活用した業務効率化レシピの紹介 をご紹介しました。 Axrossのレシピを通して、プログラムの意味を考えながら写経(コードを実際に書き写す行為)し、実際に動くものをつくりながら学ぶことで、新たな知識の習得やスキルアップの一助になれれば幸いです。 また、Axrossでは自身のナレッジを学習教材"レシピ"として寄稿いただけるエンジニアの方も募集しています! 見習いエンジニアから募った学びたい内容をウィッシュリストとして掲載しています。募集中のテーマからご自身で作成いただけるようでしたら、レシピ作成にご協力お願いいたします。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【JavaScript】関数の引数について

はじめに こんにちは。 JavaScriptの引数の定義についてアウトプットしていきます! 引数とは 引数とは、関数やメソッド、サブルーチンを呼び出す際に渡す値のことです。プログラム言語では、この「渡された値に対して処理を行い、結果を返す」ということを行っています。 参照:https://job-support.ne.jp/blog/javascript/basic-argument JavaScript //function 関数名(引数1, 引数2,~) function fn(a,b){ return a * b } //関数名(引数1, 引数2,~) fn(3,4); //12 関数fnの第1引数にaを、第2引数にbを記述する。関数を呼び出す際、引数に値や式を記述したものが関数内で実行されるので、aとbの積が出力される。 JavaScript function fn(name,jobs){ console.log(`${name}は、${jobs}になる。`) } JavaScript fn('佐藤','エンジニア'); //佐藤は、エンジニアになる。 fn('エラー'); //エラーは、undefinedになる。 fn('山田','マーケター',20); //山田は、マーケターになる。 関数を呼び出すときの引数が少ない場合、第2引数を参照するとundefinedとなる。それとは反対に、呼び出す時の引数が多い場合は、第3引数(20)が使用されないだけで、表示は可能となる。 最後に ここまで引数についてまとめました。 関数とセットでなんとなく使用していたのですが、新しい発見があって以前よりも理解が深まりました。 これからもアウトプットしながら学習を進めていきます!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【CSS】比率で配置できるFlexboxのflexプロパティ

はじめに 比率で配置できるFlexboxのflexプロパティについてのまとめです。 flex:数値 比率で配置する アイテムをどのような比率で配置するか 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" /> <title>Document</title> <link rel="stylesheet" href="style.css" /> </head> <body style="margin: 0"> <div class="container"> <div class="item item1">Item 1</div> <div class="item item2">Item 2</div> </div> </body> </html> 下記のコードの場合、1対3の比率で配置できる css .container{ display: flex; } .item1 { background-color: pink; flex: 1; } .item2 { background-color: blue; flex: 3; }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

サンプル

<!DOCTYPE html> <html> <head> <title>MP TEST PAGE</title> <script> document.addEventListener("recMsg", function(event) { alert("sample.html..." + event.detail); },false); var requestEctensionMinVersion = "1"; function launch(){ if(checkVersion()) { var event; var message='["late"]'; try{ event = new CustomEvent("launchApp",{"bubbles":true,"detail":message}); } catch(e) { event = document.createEvent("CustomEvent"); event.initCustomEvent("launchApp",true,false,message); } document.dispatchEvent(event); } else { document.dispatchEvent(event); } } function checkVersion() { var result = false; if(document.getElementById("extension-is-installed") != null) { if(document.getElementById("extension-is-installed").value >= requestEctensionMinVersion) { result = true; } else { alert("ブラウザ拡張のバージョンが古すぎます。") } } else { alert("ブラウザ拡張のインストールが必要です。") } return true; } </script> </head> <body> <button type="button" onclick="launch()">アプリ起動</button> </body> </html>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

HTML要素を新たに追加するinsertBeforeの使い方~備忘録~

子要素がHTML要素を持つ場合 以下のような構造のHTMLがあるとします。 <div id='text'> <p id='p1'>テキスト1</p> <p id='p2'>テキスト2</p> <p id='p3'>テキスト3</p> </div> テキスト1の後に、新しい要素 <p id='new'>新しいテキスト</p> を追加したいとき。 const textElement = document.getElementById('text') // 親要素の取得 const p1Element = document.getElementById('p1') // 基準とする要素の取得 const newElement = document.createElement('p') // 追加要素 newElement.id = 'new' // id属性 newElement.textContent = "新しいテキスト" //テキスト textElement.insertBefore(newElement, p1Element) 子要素がHTML要素を持たない場合 こんな状況あるのかな、、、 例えば、contenteditableな要素を操作する時に使えるのではないでしょうか。 以下のような構造のHTMLがあるとします。 <div contenteditable='true' id='text'> insertBeforeの使い方 </div> Enterキーを押した時に、 <span style='color: red;'>Enterキーを押さないでください</span> という要素を入力したテキストの後に追加したいとき。 const textElement = document.getElementById('text') //親要素の取得 const newAlertElement = document.createElement('span') // 新しい追加したい要素 newAlertElement.style.color = 'red' // 文字色を赤にする newAlertElement.textContent = "Enterキーを押さないでください" // テキスト textElement.insertBefore(newAlertElement, textElement.childNodes[0]) textElement.childNodes[0]というのは親要素の下にある"insertBeforeの使い方"を指します。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ホームページに雪や桜を降らせる「particles.js」に機能追加した話

ホームページに桜を降らそう どーも,だるだるし です 仕事をください(定型文) まずは答え CodePenで ここ 見て頂いたらどんなものかはわかると思います. 他のサンプルとして こんなの もあります 要はパーティクルというもので,HPに雪を降らせたり火の粉を散らせたりという効果を付けるものです. コードはGitHubで ここ に置いてます. 「particles.js」とは 「particles.js」は多分どこかで見たことのある画面上のパーティクル効果を簡単に作成するライブラリ?です. 「particles.js」の使い方は参照先からGitHubに飛べると思いますのでそちらを参照してください. 何をしたか で私が何をしたのかですが,「particles.js」は大変軽量で便利なのですが,「回転」機能と,「画像の透明度変更」が出来ません. *丸や四角形の透明度は変更できます そこで,回転と,画像の透明度の変更ができるように改造したものが これ です 使い方 基本的には大本の「particles.js」に則ってますので同じように使用できますが,一応変更部分の使用方法を書いておきます. 「particlesJS()」関数の第二引数にパラメータを渡しますが,回転させる場合はその中に以下のように「rotate」を記載します. 場所は「shape」や「color」と同格です. "rotate": { "value": 360, "random": true, "anim": { "enable": true, "speed": -10 } }, 「value」が回転角度 「random」が「true」なら各パーティクル毎に「value」以下でランダムな角度になります 「anim」の「enable」が「true」なら回転アニメーションになります 「anim」の「speed」の絶対値が大きいほど回転速度は速くなります 「speed」がプラスなら右回転,マイナスなら左回転です おわりに ちなみに こんなの もあります.これは非常に高機能ですが,正直重くてHP向けには実用的ではないと思いました.実用的なレベルでは今回作成したものくらいで上々と思ってます. ずっと記事にしようと思っていたのがようやく果たせました. npmなどは用意していないのでjsで読み込むことになりますが,よろしければご利用ください.
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Ajax]コメント機能を非同期通信にした

はじめに 本記事では、コメント機能を非同期通信にした方法を記述しています。 過去に、いいね機能とフォロー機能を非同期通信にしている記事も書いておりますので、 参照ください。 前提 コメント機能は同期通信にはできる状態です。 該当箇所のみの記載です。 コード コントローラー food(投稿した食べ物)の詳細画面(show)にてコメントできる仕様です。 show内で完結するので、newメソッドもあります。 createメソッドでもコメントできましたが、 コメントはcommentテーブルに保存するので、newの方が良いと思っています。 foods_controller.rb 省略 def show @food = Food.find(params[:id]) @like = Like.new @comment = Comment.new @comments = @food.comments.order("created_at DESC") end 省略 ここも特に同期通信と変わりません。 merge(user_id: current_user.id ,food_id: params[:food_id]) ここ忘れないように。 comments_controller.rb class CommentsController < ApplicationController def create @food = Food.find(params[:food_id]) @comment = Comment.create(comment_params) @comments = @food.comments.order("created_at DESC") end private def comment_params params.require(:comment).permit(:text).merge(user_id: current_user.id ,food_id: params[:food_id]) end end モデル 同期通信と変わりません。 belongs_toとかhas_manyなどですので、省略します。 ビュー 記事を書いててミスを見つけました。 localsがlacalsになっていました、、、 あっぶね〜? ・・・ <div class="food-comments-contents-<%= @food.id %>"> クラス名に-<%= @food.id %>を入れないと何の投稿に対するコメントなのか わかりませんので、ここが抜けているとリロードしないとコメントが反映されないと思います。 コードが長いので、部分テンプレートにしています。 foods/show.html.erb 省略 <h2 class="show-food-comments-title">Comments !</h2> <div class="food-comments-contents-<%= @food.id %>"> <%= render partial:"comments/comment", locals: {food: @food} %> </div> 省略 comments/_comment.html.erb 3行目にremote: trueを入れます。 これにより非同期通信が可能になり、リクエストがjs形式になります。 <div class="comments-comment-contents"> <% if user_signed_in? %> <%= form_with(model: [@food, @comments],url: food_comments_path(@food,@comment), remote: true) do |f| %> <%= f.text_area :text, placeholder: "コメントする", rows: "2", class: "food-comment-textbox" %> <%= f.submit "SEND" ,class: "food-comment-textbox-send" %> <% end %> <ul class="show-food-comments"> <% @comments.each do |comment| %> <li class="show-food-comment"> <div class="comment-food-post-users"> <div> <%= link_to user_path(comment.user_id) do %> <% if comment.user.icon.present? %> <%= image_tag comment.user.icon, class: "icon-img" %> <% else %> <i class="fas fa-user-circle"></i> <% end %> <% end %> </div> <div> <%= link_to user_path(comment.user_id),class: "comment-food-post-user" do %> <%= comment.user.nickname %> <% end %> <%= comment.text %> </div> </div> </li> <% end %> </ul> <% else %> <p class="please-login">コメント閲覧・投稿する際は<br/>「新規登録」「ログイン」をお願いします</p> <% end %> </div> comments/create.js.erb リクエストがjs形式のため、 ファイル名はcreate.js.erbになります。 先ほどfoods/show.html.erbにて作った クラスfood-comments-contents-<%= @food.id %>が また部分テンプレートのcomments/commentに行きます。 これでcreate=コメントしたということが反映されます。 $(".food-comments-contents-<%= @food.id %>").html("<%= j(render partial: 'comments/comment', locals: {food: @food}) %>") 以上です。 いいねやフォローに比べればまだ簡単なように思えます。 終わりに 私が作成したアプリはコメント機能を非同期化しています。 昨今のアプリでコメント機能が非同期化していないアプリはありませんね。 必ず導入するべきだと思います。 また、いいね機能やフォロー機能を非同期化していれば、 少し考えればコメント機能の非同期化は容易ですので、 いいね機能とフォロー機能の非同期化もマスターしたいところです。 以下参考サイトです。 [Rails]Ajaxを用いて非同期でコメント機能の実装 Railsアプリに非同期通信のコメント機能を実装 明日も頑張ります!!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む