「JavaScript」の版間の差分
(Table Read) |
(elasticの例追加。) |
||
(同じ利用者による、間の13版が非表示) | |||
177行目: | 177行目: | ||
not ${2 * a + b}.`); | not ${2 * a + b}.`); | ||
変数を交えた複雑な文字列の生成時にすっきりとする。ただ、${}が必須なので、長くなることが多い。 | 変数を交えた複雑な文字列の生成時にすっきりとする。ただ、${}が必須なので、長くなることが多い。 | ||
===== trim ===== | |||
* [https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/String/trim String.prototype.trim() - JavaScript | MDN] | |||
* [https://stackoverflow.com/questions/8141718/how-to-trim-specific-characters-from-the-end-of-the-javascript-string How to trim specific characters from the end of the JavaScript string? - Stack Overflow] | |||
* https://chatgpt.com/c/67452c7c-1f60-800b-9ab4-2b2989de1f9e | |||
文字列の前後の不要な文字を削除したいことがある。 | |||
* String.prototype.trim(): 文字列前後のスペースを削除して、結果を返す。 | |||
* trimStart/trimEnd: trimの片側のみ。 | |||
スペースは標準関数がある。スペース以外の特定文字は、正規表現やsubstringでの切り出しが必要になる。少々面倒くさい。 | |||
function trimCustom(str, char) { | |||
<nowiki> </nowiki> const regex = new RegExp(`^${char}+|${char}+$`, 'g'); | |||
<nowiki> </nowiki> return str.replace(regex, <nowiki>''</nowiki>); | |||
} | |||
const result = trimCustom('***Hello World***', '\\*'); // 正規表現なので、エスケープが必要 | |||
console.log(result); // "Hello World" | |||
function trimCustom(str, char) { | |||
while (str.startsWith(char)) { | |||
str = str.slice(char.length); | |||
} | |||
while (str.endsWith(char)) { | |||
str = str.slice(0, -char.length); | |||
} | |||
return str; | |||
} | |||
const result = trimCustom('###Hello World###', '#'); | |||
console.log(result); // "Hello World" | |||
==== Array ==== | ==== Array ==== | ||
220行目: | 254行目: | ||
0.6000000238418579</nowiki> | 0.6000000238418579</nowiki> | ||
関数を使わない分、lengthのほうが速い。空配列の考慮が少々面倒だけど。 | 関数を使わない分、lengthのほうが速い。空配列の考慮が少々面倒だけど。 | ||
===== Remove ===== | |||
* Array.prototype.shift(): 先頭要素を取得・削除。 | |||
* Array.prototype.pop(): 末尾要素を取得・削除。 | |||
===== Find ===== | |||
[https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/find Array.prototype.find() - JavaScript | MDN] | |||
配列の検索。いくつか方法がある。 | |||
* find: 最初の要素を返す。関数テスト。 | |||
* findIndex: 最初の要素の添え字を返す。関数テスト。 | |||
* indexOf: 最初の要素の添え字を返す。値テスト。 | |||
* findLast | |||
* findLastIndex | |||
* lastIndexOf | |||
* includes: 値の有無。 | |||
* some: テスト関数の有無。 | |||
* every: テスト関数の全一致。 | |||
findが使いやすい。 | |||
===== 一括関数 ===== | ===== 一括関数 ===== | ||
255行目: | 311行目: | ||
console.log(obj); | console.log(obj); | ||
====== 配列から作成 ====== | |||
[https://yklog.net/js_array_to_object/ 【JS】配列→オブジェクトに変換する5つの方法 | YKlog] | |||
Object.assign | |||
スプレッド構文 | |||
forEach | |||
reduce | |||
Object.fromEntries | |||
Object.assignがシンプル。 | |||
Object.assign({}, ["aaa", "bbb", "ccc"]) | |||
// { 0: "aaa", 1: "bbb", 2: "ccc" } | |||
// 参)オブジェクトのコピー | |||
const target = { a: 1, b: 2 } | |||
const newObg = Object.assign(target, { b: 4, c: 5 }) | |||
console.log(target) // { a: 1, b: 4, c: 5 } | |||
console.log(newObg === target) // true | |||
// 参)オブジェクトの結合 | |||
const obj1 = { a: 1 } | |||
const obj2 = Object.assign(obj1, { b: 2, c: 3 }, { d: 4 }) | |||
console.log(obj1) // { a: 1, b: 2, c: 3, d: 4 } | |||
console.log(obj2) // { a: 1, b: 2, c: 3, d: 4 } | |||
===== Merge ===== | ===== Merge ===== | ||
477行目: | 563行目: | ||
=== function === | === function === | ||
* [https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Functions 関数 - JavaScript | MDN] | |||
* [https://jsprimer.net/basic/function-declaration/ 関数と宣言 · JavaScript Primer #jsprimer] | |||
==== IIFE/即時実行関数式 ==== | ==== IIFE/即時実行関数式 ==== | ||
494行目: | 583行目: | ||
})(); | })(); | ||
丸括弧の位置はどちらでもOK。ただ、後者の波括弧直後のほうがイメージとしてはあっている。関数オブジェクト・コールバックを作って、それに引数を渡して使う感じだから。 | 丸括弧の位置はどちらでもOK。ただ、後者の波括弧直後のほうがイメージとしてはあっている。関数オブジェクト・コールバックを作って、それに引数を渡して使う感じだから。 | ||
==== オブジェクトリテラル引数 ==== | |||
関数の引数の指定方法がいくつかある。その中に、オブジェクトリテラルがある。 | |||
https://chatgpt.com/c/6745132f-5cdc-800b-bd27-aa26feac5e30 | |||
// 通常の引数 | |||
function configure(true, false, 3) { /*...*/ } | |||
configure(true, false, 3); // 何を渡しているか分かりづらい | |||
// オブジェクトリテラル | |||
function configure({ darkMode, debugMode, retries }) { /*...*/ } | |||
configure({ darkMode: true, debugMode: false, retries: 3 }); | |||
関数の引数にオブジェクト1個を受け取るのとやっているのは同じ。 | |||
以下の利点がある。 | |||
# 引数の順序に独立・将来の拡張性。 | |||
# 可読性向上。 | |||
# デフォルト値を最後以外でも指定可能。 | |||
オブジェクトリテラルを使わないほうがいい場合。 | |||
# 引数1個のみ。 | |||
# 非常に高頻度の呼び出し関数。 | |||
# 引数2-3個のみのシンプルな関数。 | |||
特に以下の場合は、関数の引数をオブジェクトリテラルにしたほうがいい模様。 | |||
# 引数が多い場合。 | |||
# デフォルト値やオプションの引数を使う場合。 | |||
# 拡張性、可読性重視時。 | |||
できるだけ使うことにする。ただし、関数の引数名のプロパティー名が使うときも固定になるので、命名に注意する。 | |||
jsdocで文書化する場合。少しだけ注意が必要。 | |||
/** | |||
* ユーザーを作成します。 | |||
* | |||
* @param {Object} options - ユーザーの設定オプション。 | |||
* @param {string} options.name - ユーザーの名前。 | |||
* @param {number} options.age - ユーザーの年齢。 | |||
* @param {boolean} [options.isAdmin=false] - ユーザーが管理者であるかどうか(オプション)。 | |||
*/ | |||
function createUser({ name, age, isAdmin = false }) { | |||
console.log(`Name: ${name}, Age: ${age}, Admin: ${isAdmin}`); | |||
} | |||
// 使用例 | |||
createUser({ name: "Alice", age: 30 }); | |||
h | |||
引数全体をObjectとして記述しておいて、プロパティーを説明する。同じオブジェクトを他で流用するならparamではなく最初を@typedefにする。 | |||
=== class === | === class === | ||
507行目: | 647行目: | ||
=== Global Object === | === Global Object === | ||
[https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects 標準組み込みオブジェクト - JavaScript | MDN] | |||
グローバルオブジェクト (global objects) (標準組込オブジェクト) はthe global objectとは異なる。グローバルスコープにあるオブジェクトのこと。日本語だと標準組込オブジェクトで理解しておくとよい。 | |||
どこからでもアクセス可能なオブジェクト。非常に重要。 | |||
===== NaN ===== | |||
[https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/NaN NaN - JavaScript | MDN] | |||
標準組込オブジェクトのプロパティー。言い換えれば、グローバルスコープの変数。 | |||
扱いが特殊。 | |||
NaNは別のNaNを含むあらゆる数と不一致。NaNの判定には、Number.isNaN()かisNaN()を使うしかない。あるいは、自己比較。自己比較したら普通trueになるが、NaNだけfalseになる。 | |||
NaN === NaN; // false | |||
Number.NaN === NaN; // false | |||
isNaN(NaN); // true | |||
isNaN(Number.NaN); // true | |||
Number.isNaN(NaN); // true | |||
function valueIsNaN(v) { | |||
return v !== v; | |||
} | |||
valueIsNaN(1); // false | |||
valueIsNaN(NaN); // true | |||
valueIsNaN(Number.NaN); // true | |||
* isNaN=変換結果がNaNの場合もtrue。 | |||
* Number.isNaN=値そのものがNaNの場合だけtrue。 | |||
==== Number ==== | |||
[https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Number Number - JavaScript | MDN] | |||
===== NaN ===== | |||
[https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Number/NaN Number.NaN - JavaScript | MDN] | |||
==== Date ==== | ==== Date ==== | ||
514行目: | 692行目: | ||
JavaScriptの日付書式文字列は少々面倒くさい。関数を作ってそれでやるのがよい。 | JavaScriptの日付書式文字列は少々面倒くさい。関数を作ってそれでやるのがよい。 | ||
==== RegExp ==== | |||
* [https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/RegExp RegExp - JavaScript | MDN] | |||
* [https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Regular_expressions 正規表現 - JavaScript | MDN] | |||
* [https://developer.mozilla.org/ja/docs/Web/JavaScript/Guide/Regular_expressions 正規表現 - JavaScript | MDN] | |||
正規表現のオブジェクト。文字列内で文字列の照合に使用するパターン。 | |||
以下で使用する。 | |||
* RegExp: exec/test | |||
* String: match/matchAll/replace/replaceAll/search/split | |||
===== String.prototype.replace() ===== | |||
[https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/String/replace String.prototype.replace() - JavaScript | MDN] | |||
replace(pattern, replacement) | |||
非常によく使う。 | |||
* pattern=文字列かRegExp | |||
* replacement=文字列かコールバック。 | |||
関数の場合、以下が想定される。 | |||
function replacer(match, p1, p2, /* …, */ pN, offset, string, groups) { | |||
return replacement; | |||
} | |||
== Topic == | == Topic == | ||
578行目: | 782行目: | ||
基本はwindow.addEventListener('load', )で設定。これをhead要素内などの冒頭に記述するなどするといい。どこに記述しても問題ない書き方。 | 基本はwindow.addEventListener('load', )で設定。これをhead要素内などの冒頭に記述するなどするといい。どこに記述しても問題ない書き方。 | ||
=== Other === | === Other === | ||
783行目: | 906行目: | ||
基本はElement.removeで良いと思う。 | 基本はElement.removeで良いと思う。 | ||
===== 複製 ===== | |||
[https://gray-code.com/javascript/duplicate-html-element/ HTML要素を複製(コピー)する | GRAYCODE JavaScript] | |||
cloneNode。引数をtrueにすると、再帰的に。 | |||
返却値が複製後オブジェクト。これをafterなどに指定すると追加できる。追加前にidなどの編集を忘れずに。 | |||
===== 属性 ===== | ===== 属性 ===== | ||
918行目: | 1,048行目: | ||
outerHTMLがスマート。 | outerHTMLがスマート。 | ||
===== | ===== Elasstic ===== | ||
データの追加と削除ができるテーブルの実装例。 | データの追加と削除ができるテーブルの実装例。 | ||
[[ファイル:ElasticTable.png|サムネイル]] | [[ファイル:ElasticTable.png|サムネイル]] | ||
1,000行目: | 1,130行目: | ||
追加は末尾の行コピーでやったほうがシンプル。 | 追加は末尾の行コピーでやったほうがシンプル。 | ||
既存行コピーの実装例。<syntaxhighlight lang="php" line="1"> | |||
@php | |||
/** | |||
* table汎用コンポーネント。 | |||
* | |||
* [CSSのposition: stickyでテーブルのヘッダー行・列を固定する \#Sticky \- Qiita](https://qiita.com/orangain/items/6268b6528ab33b27f8f2) を参考に、行・列見出し固定実装。 | |||
* | |||
* ## Usage | |||
* ``` | |||
* // cS69/index.blade.php | |||
* // SearchService.php のgetListで取得できるデータを渡す想定。 | |||
* @component('components.table', ['columns' => $columns, 'data' => $data ?? null, 'rows' => $rows ?? []]) | |||
* @endcomponent | |||
* // c21200/form.blade.php | |||
* @component('components.table-dynamic', [ | |||
* 'elastic' => true, | |||
* 'prefix' => $prefix, | |||
* 'search' => $search, | |||
* 'columns' => [ | |||
* ['label' => '代表オーナーコード', 'view' => 'components.input.text.search', 'params' => [ | |||
* 'searchPath' => 'cS68', | |||
* 'searchCode' => '代表オーナーコード', | |||
* 'mapNameColumn' => $mapNameColumn, | |||
* 'prefix' => $prefix, | |||
* 'search' => $search, | |||
* 'path' => '楽楽販売_代表オーナーマスタ', | |||
* ]], | |||
* ['prefix' => ['楽楽販売_オーナーマスタ'], 'label' => 'オーナー名'], | |||
* '物件コード', | |||
* ]]) | |||
* @endcomponent | |||
* ``` | |||
* @var object $data ページネーションを含む取得データ一覧。ページネーション用に使う。 | |||
*/ | |||
/** @var bool $elastic 伸縮可能テーブルの場合true。trueだと左端に増減用の+/-の列が追加される。 */ | |||
$elastic = $elastic ?? false; | |||
/** @var array $columns = [column1, column2, column3] or [['label' => column1, 'key' => ''relation1.column1, class' => '', 'style' => '', 'params' => ['length' => 10, 'placeholder' => '🔍']], ...]。 列を絞り込みたかったらcontrollerで調整する。 */ | |||
/** label=ヘッダー表示用文字列。key=行の列名。指定しない場合labelと同一とみなす。class/style=td/thのスタイル。params=要素内のカスタムHTMLなどに渡すパラメーター。length/placeholderなど。 */ | |||
$columns = $columns ?? []; | |||
/** @var array $rows = [['column1' => 'value1', 'column2' => 'value2'], ...] 形式。空の場合、テンプレート行追加用に[[]]を設定する。 */ | |||
$rows = $rows ?? [[]]; | |||
/** @var string[] $prefix */ | |||
$prefix = $prefix ?? []; | |||
/** @var bool $search 検索画面の場合true。選択用のラジオボタンの列が追加される。 */ | |||
$search = $search ?? false; | |||
/** @var bool $disabled ボタンやフォームのdisabled属性。2回以上のincludeの場合、フォームとボタンを無効化。 */ | |||
$disabled = (count($prefix) > 1) ? 'disabled' : ''; | |||
/** @var array $columns_new $columns の要素が文字列だったり、キーの省略があった場合のデフォルト値設定 (views以外)。&で$columnをそのまま編集するとデータがなぜか重複するため別変数で処理。 */ | |||
$columns_new = []; | |||
foreach ($columns as $pos => $column) { | |||
/** 配列が値の場合、labelとみなす。 */ | |||
$columns_new[$pos]['label'] = !is_array($column) ? $column : ($column['label'] ?? ''); | |||
$columns_new[$pos]['key'] = $column['key'] ?? $columns_new[$pos]['label']; | |||
$columns_new[$pos]['prefix'] = $column['prefix'] ?? []; | |||
$columns_new[$pos]['class'] = $column['class'] ?? ''; | |||
$columns_new[$pos]['style'] = $column['style'] ?? ''; | |||
$columns_new[$pos]['view'] = $column['view'] ?? ''; | |||
$columns_new[$pos]['params'] = $column['params'] ?? []; | |||
} | |||
@endphp | |||
@component('components.row-col.base', ['rightClass' => 'col-md']) | |||
@slot('right') | |||
<label>該当件数 <span class="total_count"> {{ empty($data) ? 0 : $data->total() }} </span> 件<span></span></label> | |||
@endslot | |||
@endcomponent | |||
<div class="sticky-table-wrapper"> | |||
<table class="table table-sm table-bordered sticky-table"> | |||
<thead class="text-center"> | |||
<tr> | |||
@if (!empty($elastic)) | |||
<th class="bg-info text-center"><button type='button' onclick=" | |||
let prefix = {{json_encode($prefix)}}; | |||
let disabled = '{{$disabled}}'; | |||
let columns = {{json_encode($columns_new)}}; | |||
let tbody = event.target.parentElement.parentElement.parentElement.parentElement.tBodies[0]; | |||
let rowEnd = tbody.rows[tbody.rows.length-1]; | |||
let rowNew = rowEnd.cloneNode(true); | |||
rowEnd.before(rowNew); | |||
/** 番号増分 */ | |||
const columnNum = Number({{$elastic}})+Number({{$search}}); | |||
rowEnd.children[columnNum].textContent = +rowEnd.children[columnNum].textContent+1; | |||
for (let col = columnNum+1; col<rowEnd.children.length; ++col) { | |||
let name = rowEnd.children[col].firstChild.name; | |||
/** table[0][列名] -> table[1][列名] に増加*/ | |||
rowEnd.children[col].firstChild.name = name.replace(/(\[)(\d)(\])/, (match, p1, p2, p3)=>p1+(+p2+1)+p3); | |||
if (disabled) rowEnd.children[col].firstChild.disabled = disabled; | |||
} | |||
">+</button></th> | |||
@endif | |||
@if (!empty($search)) | |||
<th class="bg-info text-center">◎</th> | |||
@endif | |||
<th class="bg-info">#</th> | |||
@foreach ($columns_new as $column) | |||
<th class="bg-info {{ $column['class'] }}" style="{{ $column['style'] }}">{{ $column['label'] }}</th> | |||
@endforeach | |||
</tr> | |||
</thead> | |||
<tbody> | |||
@foreach ($rows as $row_key => $row_value) | |||
<tr> | |||
@if (!empty($elastic)) | |||
<th class="bg-info text-center"><button type='button' onclick='let tr = this.parentElement.parentNode; if (tr.parentNode.rows.length > 1) tr.remove();'>-</button></th> | |||
@endif | |||
@if (!empty($search)) | |||
<th class="bg-info text-center"><div style="width:100%;height:100%;"><input type="radio" name="choice" /></div></th> | |||
@endif | |||
@foreach ($columns_new as $column) | |||
@if ($loop->first) | |||
<th class="bg-info text-center"><label style="width:100%;">{{ $loop->parent->iteration }}</label></th> | |||
@endif | |||
<td class="{{ $column['class'] }}" style="{{ $column['style'] }}"> | |||
@php $name = GASHelper::getPrefixedName(array_merge(['table'], $prefix, [$row_key], $column['prefix']), $column['key']); @endphp | |||
@php $column_value = $row_value[$column['key']] ?? ''; @endphp | |||
@empty($column['view']) | |||
<output name="{{ $name }}">{{ $column_value }}</output> | |||
@else | |||
@include($column['view'], array_merge(['name' => $name, 'value' => $column_value], $column['params'])) | |||
@endisset | |||
</td> | |||
@endforeach | |||
</tr> | |||
@endforeach | |||
</tbody> | |||
</table> | |||
</div> | |||
{{ method_exists($data ?? null, 'links') ? $data->links() : '' }} | |||
</syntaxhighlight> | |||
===== テーブルの全チェックオン・オフ ===== | |||
Ref: [https://arakan-pgm-ai.hatenablog.com/entry/2023/02/20/000000 HTMLのテーブルでチェック行の情報だけ処理するサンプル/PHP・JavaScript - SE_BOKUのまとめノート的ブログ]. | |||
テーブルの1列目にチェックボックスを配置して、1行目のチェックボックスで全チェックオン・オフしたいことがある。<syntaxhighlight lang="html"> | |||
<script> | |||
function AllChanged(){ | |||
var check = document.getElementById('aaa'); | |||
var checkarr = document.getElementsByName('bbb[]'); | |||
for (var i=0; i<checkarr.length; i++){ | |||
if(check.checked === true){ | |||
checkarr[i].checked = true; | |||
}else{ | |||
checkarr[i].checked = false; | |||
} | |||
} | |||
} | |||
</script> | |||
<body> | |||
<body> | |||
<form action="" name="form" method="POST"> | |||
<table> | |||
<tr> | |||
<th><input type="checkbox" name="aaa" id="aaa" onClick="AllChanged();" /> </th> | |||
<th>KEY</th> | |||
<th>値</th> | |||
</tr> | |||
<tr> | |||
<td><input type="checkbox" name="bbb[]" value="AAAAAA,100000" onClick="OneChanged();" /></td> | |||
<td>AAAAAA</td> | |||
<td>100000</td> | |||
</tr> | |||
<tr> | |||
<td><input type="checkbox" name="bbb[]" value="BBBBBB,200000" onClick="OneChanged();" /></td> | |||
<td>BBBBBB</td> | |||
<td>200000</td> | |||
</tr> | |||
</table> | |||
<div> | |||
<button type="submit" name="b1" id="b1" disabled>ボタンサンプル</button> | |||
</div> | |||
</form> | |||
</body> | |||
</syntaxhighlight>こんな感じでJavaScriptでチェックボックスを抽出してやる。PHP送信用にvalueに値を入れておく。 | |||
===== 集計 ===== | |||
[https://qiita.com/nonbiri3/items/b8329c1e79311485cfb4 javascriptでtableタグのtd内の数値の合計を自動で計算 #JavaScript - Qiita] | |||
<nowiki><html></nowiki> | |||
<body> | |||
<nowiki><table></nowiki> | |||
<nowiki><tr></nowiki> | |||
<nowiki><td></nowiki>りんご<nowiki></td></nowiki> | |||
<nowiki><td></nowiki>300<nowiki></td></nowiki> | |||
<nowiki></tr></nowiki> | |||
<nowiki><tr></nowiki> | |||
<nowiki><td></nowiki>バナナ<nowiki></td></nowiki> | |||
<nowiki><td></nowiki>400<nowiki></td></nowiki> | |||
<nowiki></tr></nowiki> | |||
<nowiki><tr></nowiki> | |||
<nowiki><td></nowiki>いちご<nowiki></td></nowiki> | |||
<nowiki><td></nowiki>500<nowiki></td></nowiki> | |||
<nowiki></tr></nowiki> | |||
<nowiki><tr></nowiki> | |||
<nowiki><td></nowiki>合計<nowiki></td></nowiki> | |||
<nowiki><td id='goukei'></nowiki><nowiki></td></nowiki> | |||
<nowiki></tr></nowiki> | |||
<nowiki></table></nowiki> | |||
<script> | |||
goukei.textContent = [...document.querySelectorAll('td:not(#goukei)')] | |||
.map(e => isNaN(e.textContent) ? 0 : +e.textContent) | |||
.reduce((a, b) => a + b); | |||
</script> | |||
</body> | |||
<nowiki></html></nowiki> | |||
querySelectorAllでやるとスマート。 | |||
==== Topic ==== | ==== Topic ==== | ||
2024年11月28日 (木) 12:50時点における最新版
About
about
JavaScriptはプログラミング言語だ。元はHTMLに埋め込まれウェブブラウザー上で処理をするために作られた言語だが、作業の自動化・GUIの開発・ウェブサーバーの開発など様々な分野に活用されている。
ウェブブラウザーのJavaScriptで可能なことの例を挙げる。
- HTML文書の要素を追加/変更/削除する。
- ウェブサーバーに HTTP (HTTPS) で情報を送信する。
- マウスやキーボードの操作や時間経過に応じて処理を実行する。
- ウェブブラウザーに情報を記憶させたり読み出したりする (Cookieなど)。
多くの分散SNSのウェブブラウザー版はJavaScriptで実装されており、JavaScriptを実行できない環境 (ターミナル上で動作するテキストブラウザーなど) では利用できない。
GNU socialもQvitterプラグインを使用するならJavaScriptが必要だが、クラシックの場合はJavaScript無しでも閲覧や投稿が可能だ。
ECMAScript
JavaScriptの言語仕様はECMAScriptという名前で標準化されている。ECMAScriptには版数があり、仕様改訂を重ねるごとに言語仕様も拡張される。ECMAScriptの版数は古い環境をサポートする上で重要だ。たとえばあるOSの標準ブラウザーがECMAScript 6までの言語仕様しか実装していないなら、それより後に追加された言語仕様はコードに使用できない。
ECMAScript 5.1以前は不定期で仕様改訂されてきたが、2015年に公開されたECMAScript 6以降は年に1度改訂されている。そのため版数はECMAScript 2015のように年で表記されることが多い。
JavaScript tool
JSDoc
JavaScriptの文書化ツール。コメントの記述方法として悪くない。
Basic
Use JSDoc: Getting Started with JSDoc 3
PHPDoc同様/**ではじまる。
/** This is a description of the foo function. */ function foo() { }
/** * Represents a book. * @constructor */ function Book(title, author) { }
/** * Represents a book. * @constructor * @param {string} title - The title of the book. * @param {string} author - The author of the book. */ function Book(title, author) { }
Reference
Grammar and types
Basic
セミコロン
Automatic Semicolon Insertionというセミコロンを自動挿入解釈する仕組みがあり、セミコロンはなくても動作することが多い。
ただ、ASIの完全理解は難しいし、バグの元なのでやめたほうがいいと思う。
変数
未定義アクセス
How to check a not-defined variable in JavaScript - Stack Overflow
未定義変数にアクセスすると以下の例外になる。
VM289:1 Uncaught ReferenceError: undefinedVar is not defined at <anonymous>:1:1
以下のようにtypeofで判定する。
typeof undefinedVar === 'undefined'; var o = {}; typeof o.undefinedVar === 'undefined';
他にはtry-catchで確認。
なお、未定義プロパティーの場合は、例外にはならなくて、undefinedが返ってくる。ガードはしなくてもよさそう。
宣言
変数宣言の3種類がある。
- var: 変数宣言。
- let: ブロックスコープでローカル変数宣言。
- const: ブロックスコープで読み取り専用。
letはES2015で登場。
varはいくつか問題があった。
- 再宣言の許容: var xを何回もできた。let xは1回だけ。2回目はエラー。
- 巻き上げ (hoisting): varの宣言は、どこで宣言されていようが、スコープの最上部に巻き上げられる。宣言前に使ってもエラーにならなかった。JavaScriptでは宣言と代入が別扱いで、宣言だけ全体パースを先にする模様。letも同じく巻き上げは発生するが、登場までアクセス不能になっている。
- 関数スコープ: PHP同様に、varは関数スコープだけ持つ。波括弧のブロックではスコープを作らない。
基本はletを使ったらいい。
データ型
JavaScript のデータ型とデータ構造 - JavaScript | MDN
Primitive
JavaScript のデータ型とデータ構造 - JavaScript | MDN
null
オブジェクトの値が存在しないことを表す。Booleanではfalse扱い。
いくつか特殊な動きがある。
''+null // "null" typeof null // "object"
y
よくやりたいのが、nullなら空文字にするというの。
let var = null; var || '';
var || '';だと、0やfalseでも''になる。いろいろ困ることがある。
- Is there a standard function to check for null, undefined, or blank variables in JavaScript? - Stack Overflow
- Nullish value - MDN Web Docs 用語集: ウェブ関連用語の定義 | MDN
╭─ nullish ──────╮ ╭─ not nullish ─────────────────────────────────╮ ┌───────────┬──────┬───────┬───┬────┬─────┬──────┬───┬─────────┬─────┐ │ undefined │ null │ false │ 0 │ "" │ ... │ true │ 1 │ "hello" │ ... │ └───────────┴──────┴───────┴───┴────┴─────┴──────┴───┴─────────┴─────┘ ╰─ falsy ───────────────────────────────╯ ╰─ truthy ───────────────╯
nullとundefinedはJavaScriptではNullish value。以下のいずれかの式でnullishを判断できる。
if (value == null) { /* value is nullish */ } if (value === undefined || value === null) { /* value is nullish */ } if (value == undefined) { /* value is nullish */ } if ((value ?? null) === null) { /* value is nullish */ }
??はES2020。value == null/value == undefinedがシンプルだが、==は規約で禁止にすることもある。素直に、value === nullかorでちゃんと書くのがよいかも。
Boolean
Falsy (偽値) - MDN Web Docs 用語集: ウェブ関連用語の定義 | MDN
JavaScriptのBooleanへの変換。非常に重要。falseに変換されるケースのほうが少ないので、そのケースだけ覚えておいて、それ以外trueと思ったらいい。
- null
- undefined
- false
- NaN
- 0, 0.0, 0x0, -0, -0.0, -0x0, 0n, 0x0n
- "", '', ``
- document.all
例えば、配列は空でもtrueになるし、"false"や"0"も文字列だからtrueになる。
上記の型強制と同じ効果を、論理型コンテキスト以外でも発揮したい場合の方法が、以下の2種類ある。
- !! (二重否定)
- Boolean関数。
上記が非常に重要。!!は短い。Boolean()は引数が空だとfalseになる。PHPなどの値を使う場合など、変数がそもそも空になる場合を考慮するならBooleanがいい。
PHPのプレースホルダーを使う場合、'{{}}'
のように使う場所を引用符で囲んで文字列にするもあり。if文などは空文字は評価する。
String
template literal
テンプレートリテラル (テンプレート文字列) - JavaScript | MDN
バッククオート`で囲む。内部に${expression}形式のプレースホルダーを埋め込める。
let a = 5; let b = 10; console.log("Fifteen is " + (a + b) + " and\nnot " + (2 * a + b) + ".");
console.log(`Fifteen is ${a + b} and not ${2 * a + b}.`);
変数を交えた複雑な文字列の生成時にすっきりとする。ただ、${}が必須なので、長くなることが多い。
trim
- How to trim specific characters from the end of the JavaScript string? - Stack Overflow
- https://chatgpt.com/c/67452c7c-1f60-800b-9ab4-2b2989de1f9e
文字列の前後の不要な文字を削除したいことがある。
- String.prototype.trim(): 文字列前後のスペースを削除して、結果を返す。
- trimStart/trimEnd: trimの片側のみ。
スペースは標準関数がある。スペース以外の特定文字は、正規表現やsubstringでの切り出しが必要になる。少々面倒くさい。
function trimCustom(str, char) { const regex = new RegExp(`^${char}+|${char}+$`, 'g'); return str.replace(regex, ''); } const result = trimCustom('***Hello World***', '\\*'); // 正規表現なので、エスケープが必要 console.log(result); // "Hello World"
function trimCustom(str, char) { while (str.startsWith(char)) { str = str.slice(char.length); } while (str.endsWith(char)) { str = str.slice(0, -char.length); } return str; } const result = trimCustom('###Hello World###', '#'); console.log(result); // "Hello World"
Array
生成
連続値の作成。
https://yara-shimizu.com/programing/javascript/make-js-nums-array
https://mebee.info/2020/11/02/post-21479/
https://qiita.com/sakymark/items/710f0b9a632c375fbc31
Merge
いくつか方法がある。
- concat
- push: 本体変更。
- スプレッド構文
たぶんスプレッド構文が速い。
(f=>{p=performance;s=p.now();f();return p.now()-s})(()=>{for(let i=0;i<10000;++i){[].concat([0], [1])}}); 1.600000023841858 (f=>{p=performance;s=p.now();f();return p.now()-s})(()=>{for(let i=0;i<10000;++i){[...[0], ...[1]]}}); 2.899999976158142
concatのほうが速かった。
Read
最終要素
https://chatgpt.com/c/673d6472-b5b8-800b-89a9-fe7a0119ff49
- length
- スプレッド構文とpop
- slice
- at (ES2022以上)
let array = [...Array(10000).keys()]; (f=>{p=performance;s=p.now();f();return p.now()-s})(()=>{for(let i=0;i<10000;++i){array.length > 0 ? array[array.length - 1] : undefined;}}); 0.30000007152557373 (f=>{p=performance;s=p.now();f();return p.now()-s})(()=>{for(let i=0;i<10000;++i){array.slice(-1)[0] || undefined}}); 0.6000000238418579
関数を使わない分、lengthのほうが速い。空配列の考慮が少々面倒だけど。
Remove
- Array.prototype.shift(): 先頭要素を取得・削除。
- Array.prototype.pop(): 末尾要素を取得・削除。
Find
Array.prototype.find() - JavaScript | MDN
配列の検索。いくつか方法がある。
- find: 最初の要素を返す。関数テスト。
- findIndex: 最初の要素の添え字を返す。関数テスト。
- indexOf: 最初の要素の添え字を返す。値テスト。
- findLast
- findLastIndex
- lastIndexOf
- includes: 値の有無。
- some: テスト関数の有無。
- every: テスト関数の全一致。
findが使いやすい。
一括関数
配列要素に対して一括処理するメソッドがいくつかある。非常に重要。
- forEach: 適用結果を返さない。
- map: 適用結果を返す。
- find
- every: 全て満たす場合true。
- some: 1個でも満たす場合true。
Array.prototype.filter()
Array.prototype.filter() - JavaScript | MDN
callbackでtrueの要素の配列を返す。
Object
Object (オブジェクト) - MDN Web Docs 用語集: ウェブ関連用語の定義 | MDN
Create
https://chatgpt.com/c/673d626b-be64-800b-9258-903aa6ab89a4
いくつか方法がある。
- reduce
- Object.fromEntries
- キーと値を別の配列から生成
- forループ
- forEach
- reduceRight
let array1 = [111, 222, 333, 444, 555, 666]; let obj = Object.fromEntries(array1.map((value, index) => ['key' + index, value])) console.log(obj);
配列から作成
【JS】配列→オブジェクトに変換する5つの方法 | YKlog
Object.assign
スプレッド構文
forEach
reduce
Object.fromEntries
Object.assignがシンプル。
Object.assign({}, ["aaa", "bbb", "ccc"]) // { 0: "aaa", 1: "bbb", 2: "ccc" } // 参)オブジェクトのコピー const target = { a: 1, b: 2 } const newObg = Object.assign(target, { b: 4, c: 5 }) console.log(target) // { a: 1, b: 4, c: 5 } console.log(newObg === target) // true // 参)オブジェクトの結合 const obj1 = { a: 1 } const obj2 = Object.assign(obj1, { b: 2, c: 3 }, { d: 4 }) console.log(obj1) // { a: 1, b: 2, c: 3, d: 4 } console.log(obj2) // { a: 1, b: 2, c: 3, d: 4 }
Merge
Object.assignかスプレッド演算子...で展開・割り当てするとよい。
- Object.assign
- スプレッド構文 ({...obj1, obj2})
2のスプレッド構文のほうが速い。
(f=>{p=performance;s=p.now();f();return p.now()-s})(()=>{let obj1={}; let obj2={}; for (let i = 0; i<10000; ++i) var obj = {...obj1, ...obj2};}); 0.20000000298023224 (f=>{p=performance;s=p.now();f();return p.now()-s})(()=>{let obj1={}; let obj2={}; for (let i = 0; i<10000; ++i) var obj = Object.assign(obj1, obj2);}); 1
プロパティー存在判定
- jsでのプロパティの存在チェック方法をまとめてみる #JavaScript - Qiita
- Object.hasOwn() - JavaScript | MDN
- Object.prototype.hasOwnProperty() - JavaScript | MDN
いくつか方法がある。
- in演算子: 継承元もみる。
- Object.prototype.hasOwnProperty: 継承元は見ず、直属のプロパティーのみ見る。
- Object.hasOwn: hasOwnPropertyより推奨されている。
継承元を含めていいかどうかで、inとhasOwnを使い分ける。基本はinでいい。
in演算子の場合、検査対象がobjectであることを前提とする。スカラー値にin演算子を使うと例外になる。objectの例の判定がいる。
単純に、プロパティーでアクセスして、undefinedかどうかを見るのもありだと思う。
判定
プロパティーの存在判定する場合に、まず変数がオブジェクト化判定が必要。スカラー値にプロパティーアクセスすると例外になるから。
function isObjectA(x) { return (typeof x === "object" && x !== null) || typeof x === "function"; }
判定方法は上記。ポイント。
- typeof nullはobjectになるので条件追加。
- functionもobjectの一種だがtypeofだとfunctionになるので追加。
function isObjectB(x) { return Object(x) === x; }
なお、document.allはobjectだが、typeofだと誤認する。その場合にObjectへの変換が必要。こちらがシンプル。
スカラー値だと、違いはないが、nullやfunctionの判定時に1個目のほうが速い。
(f=>{p=performance;s=p.now();f();return p.now()-s})(()=>{for(var i = 0; i<100000; ++i)isObjectA(()=>{})}); 1.3999998569488525 (f=>{p=performance;s=p.now();f();return p.now()-s})(()=>{for(var i = 0; i<100000; ++i)isObjectB(()=>{})}); 2.8999998569488525
が、パフォーマンスが重要じゃないなら、Objectでもいいかも。
Loop
オブジェクトはforで反復可能。
var obj = {a: 0, b: 1}; for (var key in obj){console.log(key);}
ただし、配列ではないので、mapなどの配列関数は当然だが使用不能。
関数で一括処理したい場合、オブジェクトを配列に一度変換する。
- Array.from: Iterableなものを配列に変換する。
- Object.keys
- Object.values
- Object.entries
Array.fromを使うのが素直。だが、余計な変換をすると速度が劣る可能性がある。素直にforとかforの処理を関数にしたほうがいいかもしれない。
Conversion
JavaScriptで文字列を数値に変換する:Number(), parseInt(), parseFloat() | UX MILK
基本はNumberとかparseXXXなどだが、型変換でいくつか便利な暗黙の型変換がある。
- String→Number: +'0'
- Number→String: ''+0
Control_flow_and_error_handling
制御フローとエラー処理 - JavaScript | MDN
for...in
for (variable in object) statement
objectのキーに対する反復。
variableにプロパティー名を受け取る。
要素にアクセスするにはobject[variable]
順序には法則がある。
- 整数の昇順
- 文字列の作成順
通常の配列反復としても使える。ただし、配列添え字以外の文字列も反復するので注意する。
for...of
ES2015で標準化。
objectのバリューに対する反復。一般的なforeach。
順序保障。
Expression+Operator
this
JavaScriptのthisは文脈 (実行コンテキスト) に応じて対象が異なるので注意が必要。
- DOM イベントハンドラー: 配置要素。
- インラインイベントハンドラー: リスナーのDOM要素。
- グローバル: グローバルオブジェクト。Webブラウザーならwindow。
- 関数: call/applyで設定しない限り、グローバルオブジェクトかstrictモードならundefined。
- クラス: constroctor内はただのオブジェクト。他のstaticでないメソッドは、thisのプロトタイプに追加される。
- メソッド: 呼び出し元のインスタンス。
- constructor: コンストラクターとしてnewキーワードとセットで使われた場合、インスタンス。
typeof
typeof <operand> typeof <operand>
<operand> の型を示す文字列を返す。
型 | 結果 |
---|---|
Undefined | "undefined"
|
Null | "object" (下記参照)
|
論理値 | "boolean"
|
Number | "number"
|
BigInt (ECMAScript 2020 の新機能) | "bigint"
|
文字列 | "string"
|
シンボル (ECMAScript 2015 の新機能) | "symbol"
|
関数 オブジェクト (ECMA-262 の用語では [[Call]] を実装) | "function"
|
その他のオブジェクト | "object"
|
注意が必要なものがいくつかある。
- Null="object"
- NaN="number"
特にnullに注意が必要。nullの判定が必要なら、=== nullが必要と思われる。
関係演算子
in 演算子
指定プロパティーがオブジェクトにある場合にtrueを返す。プロパティーの有無チェックに使う。
'make' in {make: 'Honda', model: 'Accord'};
集合の有無判定はArray.includes()を使う。
[0, 1, 2].includes(3); // false
分割代入
配列とオブジェクトに対して、要素とプロパティーを部分的に取り出すことができる。
特に、オブジェクトはたくさんあるプロパティーから一部分の検索・取得ができて重宝しそう。
Statement
async function
async function - JavaScript | MDN
非同期関数を宣言し、内部でawwaitキーワードを使用可能にする。
async/awaitを使うことでPromiseベースの非同期処理をシンプルに記述できる。関数式の定義にも使える。
async function内に配置したawait式はPromiseオブジェクトの解決まで待機して、同期しているように動作させる。
awaitがないと、同期的に実行される。awaitを指定すると、指定した式が非同期待機になる。
awaitがあると、その上下で処理が分割されるとみなすとわかりやすい。awaitまで到達すると、Promiseが返ってくるまで、中断する。返ってきたら続行する。
非同期系の処理で、前後に何かをしたいということがある。
async function showLoading(cb) { dispLoading(); await cb(); removeLoading(); } // showLoading(async () => await ajaxGetNameFromIdAndCallBack());
非同期の処理の完了を待って、後続の処理をする必要があるので、asyncとawaitが必要。awaitはPromiseを返すところを連鎖させないと効果がない。そこに注意する。
function
IIFE/即時実行関数式
IIFE (即時実行関数式) - MDN Web Docs 用語集: ウェブ関連用語の定義 | MDN
JavaScriptで即時関数を使う理由 #JavaScript - Qiita
IIFE (Immediately Invoked Function Expression; 即時実行関数式)
(function () { //処理 console.log('処理1'); }()); (function () { //処理 console.log('処理2'); })();
丸括弧の位置はどちらでもOK。ただ、後者の波括弧直後のほうがイメージとしてはあっている。関数オブジェクト・コールバックを作って、それに引数を渡して使う感じだから。
オブジェクトリテラル引数
関数の引数の指定方法がいくつかある。その中に、オブジェクトリテラルがある。
https://chatgpt.com/c/6745132f-5cdc-800b-bd27-aa26feac5e30
// 通常の引数 function configure(true, false, 3) { /*...*/ } configure(true, false, 3); // 何を渡しているか分かりづらい // オブジェクトリテラル function configure({ darkMode, debugMode, retries }) { /*...*/ } configure({ darkMode: true, debugMode: false, retries: 3 });
関数の引数にオブジェクト1個を受け取るのとやっているのは同じ。
以下の利点がある。
- 引数の順序に独立・将来の拡張性。
- 可読性向上。
- デフォルト値を最後以外でも指定可能。
オブジェクトリテラルを使わないほうがいい場合。
- 引数1個のみ。
- 非常に高頻度の呼び出し関数。
- 引数2-3個のみのシンプルな関数。
特に以下の場合は、関数の引数をオブジェクトリテラルにしたほうがいい模様。
- 引数が多い場合。
- デフォルト値やオプションの引数を使う場合。
- 拡張性、可読性重視時。
できるだけ使うことにする。ただし、関数の引数名のプロパティー名が使うときも固定になるので、命名に注意する。
jsdocで文書化する場合。少しだけ注意が必要。
/** * ユーザーを作成します。 * * @param {Object} options - ユーザーの設定オプション。 * @param {string} options.name - ユーザーの名前。 * @param {number} options.age - ユーザーの年齢。 * @param {boolean} [options.isAdmin=false] - ユーザーが管理者であるかどうか(オプション)。 */ function createUser({ name, age, isAdmin = false }) { console.log(`Name: ${name}, Age: ${age}, Admin: ${isAdmin}`); } // 使用例 createUser({ name: "Alice", age: 30 });
h
引数全体をObjectとして記述しておいて、プロパティーを説明する。同じオブジェクトを他で流用するならparamではなく最初を@typedefにする。
class
JavaScriptのclassは実際には特別な関数。
constructor
constructorメソッドは、オブジェクト生成時の初期化用の特別なメソッド。1個しか定義できない。
コンストラクター内で、インスタンスプロパティーを設定する。newで呼び出す際の引数を受け取る。
Global Object
標準組み込みオブジェクト - JavaScript | MDN
グローバルオブジェクト (global objects) (標準組込オブジェクト) はthe global objectとは異なる。グローバルスコープにあるオブジェクトのこと。日本語だと標準組込オブジェクトで理解しておくとよい。
どこからでもアクセス可能なオブジェクト。非常に重要。
NaN
標準組込オブジェクトのプロパティー。言い換えれば、グローバルスコープの変数。
扱いが特殊。
NaNは別のNaNを含むあらゆる数と不一致。NaNの判定には、Number.isNaN()かisNaN()を使うしかない。あるいは、自己比較。自己比較したら普通trueになるが、NaNだけfalseになる。
NaN === NaN; // false Number.NaN === NaN; // false isNaN(NaN); // true isNaN(Number.NaN); // true Number.isNaN(NaN); // true function valueIsNaN(v) { return v !== v; } valueIsNaN(1); // false valueIsNaN(NaN); // true valueIsNaN(Number.NaN); // true
- isNaN=変換結果がNaNの場合もtrue。
- Number.isNaN=値そのものがNaNの場合だけtrue。
Number
NaN
Date
formatDate
JavaScript 日付を指定した書式の文字列にフォーマットする
JavaScriptの日付書式文字列は少々面倒くさい。関数を作ってそれでやるのがよい。
RegExp
正規表現のオブジェクト。文字列内で文字列の照合に使用するパターン。
以下で使用する。
- RegExp: exec/test
- String: match/matchAll/replace/replaceAll/search/split
String.prototype.replace()
String.prototype.replace() - JavaScript | MDN
replace(pattern, replacement)
非常によく使う。
- pattern=文字列かRegExp
- replacement=文字列かコールバック。
関数の場合、以下が想定される。
function replacer(match, p1, p2, /* …, */ pN, offset, string, groups) { return replacement; }
Topic
JSON
データ記述言語「JSON (JavaScript Object Notation)」は、JavaScriptの構文に多少の制限を加えてデータの記述に特化させたものだ。そのためJavaScriptのパーサーはJSONのコードをそのまま解釈できる。
RFC 8259, ECMA-404, ISO/IEC 21778:2017 で標準化されている。
特に重要な仕様がいくつかある。
- 終端カンマは不能。
- コメントなし。
- 文字列の囲みは二重引用符のみ使用可能。一重引用符はNG。おそらく、英文で頻出のアポストロフィーを考慮したと思われる。
- 文字列に含められるものは、二重引用符、バックスラッシュ、制御文字 (U+0000からU+001F) を除いた全Unicode文字 (RFC 8259=7. Strings The representation of strings is similar to conventions used in the C family of programming languages. A string begins and ends with quotation marks. All Unicode characters may be placed within the quotation marks, except for the characters that MUST be escaped: quotation mark, reverse solidus, and the control characters (U+0000 through U+001F).)。
- \エスケープで表現可能な特殊文字がいくつかある。" \ / b f n r t uXXXX。特に\nの改行が重要だろう。
Newline
JSONに改行文字はそのまま含められない。含めたいならば、エスケープした\nにする必要がある。
Double quote
How to escape double quotes in JSON - Stack Overflow
JSON string valueに二重引用符を含む場合。エスケープが少々ややこしい。
"を\"で表現するというのが基本。だが、実際はこう簡単ではない。\\\"にしないといけないケースが多い。
{time: '7 "o" clock'}
'{"time":"7 \\"o\\" clock"}'
JSON.parse('{"time":"7 \\"o\\" clock"}')
JSONとしては、\"でOK。だが、これをいろんな言語や状況でテキストで解釈後に、最終的に\"
にする必要がある。そのため、ここからさらにエスケープが必要になる。
例えば、JavaScriptの文脈だと、\"が"になってしまうので、\"の\を\\でエスケープして\\"にする。外側が二重引用符"の場合、それ用に"→\"が追加で必要になる。
JSON.parse("{\"time\":\"7 \\\"o\\\" clock\"}");
エスケープ、置換の考え方は以下の通り。
- "→\"=\": JSON値用の二重引用符のエスケープ。
- \→\\=\\": 言語の二重引用符の解釈回避用に、バックスラッシュのエスケープ。
- "→\"=\\\": 外側二重引用符用の二重引用符のエスケープ。
JavaScriptの読込場所
- [laravelMix jsjsファイルで気を付けること(初心者向け) #Laravel - Qiita]
- JavaScriptを書く場所や読み込む場所はどこがいいのか? – CodeAid-Lab(コードエイド・ラボ)
- JavaScript初心者脱却計画(addEventListenerについて)|Kuu
HTMLでのJavaScriptの記述場所が何か所かある。記述方法によって、読込タイミングなどが異なる。
- head要素内: body要素のDOM構築前なので、大半のDOMアクセス不能。
- body要素末尾: DOM構築後なので大半のDOMアクセス可能。
- window.onload=: 読込完了後。イベントハンドラーなので1個しか設定できず上書きで消える。
- window.addEventListener('load', ): 複数設定可能。
- document.addEventListener('DOMContentLoaded', ): loadより先。DOM構築後画像類の読込前。
基本はwindow.addEventListener('load', )で設定。これをhead要素内などの冒頭に記述するなどするといい。どこに記述しても問題ない書き方。
Other
Uncaught TypeError: Cannot set property 'checked' of null
javascript - Uncaught TypeError: Cannot set property 'checked' of null - Stack Overflow
取得したDOM要素がnullでcheckedのプロパティーにアクセスできていない。要素のnullチェックが必要。
Web API
Performance
JavaScriptの実行速度の計測に使える。昔はDate.nowの差分だった。
今はperformance.now()の差分。
// 関数の実行時間を計測する関数 // 実行にかかった時間をミリ秒で出力 function measure(name, func) { const start = performance.now(); func(); const end = performance.now(); const elapsed = (end - start); const elapsedStr = elapsed.toPrecision(3); console.log(`${name}: ${elapsedStr}`); }
(f=>{p=performance;s=p.now();f();return p.now()-s})(()=>{for(let i=0;i<10000;++i){}});
Console API
Webブラウザーのconsole画面を操作するAPI群。デバッグで役立つ。
consoleオブジェクト。グローバルオブジェクトでこれ経由で使用する。
- log: 引数を出力。
- assert(assertion, obj/msg): 引数がfalseのときだけ出力。
DOM
ドキュメントオブジェクトモデル (DOM) - Web API | MDN
Basic
Interface
ドキュメントオブジェクトモデル (DOM) - Web API | MDN
- NodeList: Node.childNodesか document.querySelectorAll()で取得。Arrayとは異なるが、forEachメソッドで反復処理可能。Array.fromや[...]でArrayに変換可能。
- Node: インターフェイス。抽象クラス。
- Document
- Element: いわゆる要素。
- DocumentFragment
- Node: インターフェイス。抽象クラス。
DOM/Node/Element
DOM、Node、Elementを理解する #JavaScript - Qiita
- DOM: WebブラウザーAPI。文書をJavaScriptなどで操作するためのAPI。
- Node: 要素やコメントなどを含む、DOMのObjectの単位。
- Element: Nodeの一種 (Nodeを継承)。いわゆる要素のオブジェクト。
生成
Documentオブジェクト (window.document) のメソッドでNodeを生成する。
- createAttribute
- createAttributeNS
- createCDATASection
- createComment
- createDocumentFragment
- createElement
- createElementNS
- createEvent
- createNodeIterator
- createProcessinInstruction
- createRange
- createTextNode
- createTouch
- createTouchList
- createTreeWalker
頻出で重要なのが以下。
- createAttribute
- createElement
- createTextNode
取得
いくつか方法がある。
- Document.getElementById(): id属性の値で取得。
- getElementById(): Elementを取得。
- getElementsByClassName()
- getElementsByName()
- getElementsByTagName()
- getElementsByTagNameNS()
- querySelector: Elementを取得。
- querySelectorAll: NodeListを返す。
querySelectorが使えるならこれを使うのがシンプル。だが、querySelectorより上記のほうが速い。
追加
Node: insertBefore() メソッド - Web API | MDN
要素を追加する方法がいくつかある。
- Element.append: Nodeか文字列をElementの子の末尾に挿入。文字列の場合、Textノード扱い。
- Element.prepend: Nodeか文字列をElementの子の先頭に挿入。
- Element.before: Elementの前に挿入。
- Element.after: Elementの後に挿入。
- Element.insertAdjacentElement: append/prepend/before/afterを引数で指定して実行。
- Node.appenChild: Nodeのみ。追加されたNodeを返却。1個だけ。
- Node.insertBefore: appendChildの逆。
削除
Node: removeChild() メソッド - Web API | MDN
- Node.removeChild
- Element.remove
基本はElement.removeで良いと思う。
複製
HTML要素を複製(コピー)する | GRAYCODE JavaScript
cloneNode。引数をtrueにすると、再帰的に。
返却値が複製後オブジェクト。これをafterなどに指定すると追加できる。追加前にidなどの編集を忘れずに。
属性
いくつかある。
- getAttribute
- setAttribute(name, value)
- removeAttribute
属性の一括設定
Setting multiple attributes for an element at once with JavaScript - Stack Overflow
基本はsetAttbituteで1個ずつ設定だが、いくつか方法がある。
- setAttbituteをforで反復。
- Object.assignでプロパティーを一括設定。
function setAttributes(el, attrs) { for(var key in attrs) { el.setAttribute(key, attrs[key]); } }
var elem = document.createElement('img') Object.assign(elem, { className: 'my-image-class', src: 'https://dummyimage.com/320x240/ccc/fff.jpg', height: 120, // pixels width: 160, // pixels onclick: function () { alert('Clicked!') } })
クラス
- Element: classList プロパティ - Web API | MDN
- 【JavaScript】classListの使い方まとめ(add.remove.contains.toggle) #JavaScript - Qiita
classも属性でもあるが、複数指定したり、よく使うので専用のclassList APIがある。
メソッド名 | 機能 |
---|---|
add | クラスを追加する |
remove | クラスを削除する |
contains | クラスが含まれているか確認する |
toggle | クラスが含まれていれば削除、
含まれていなければ追加する |
const div = document.createElement("div"); div.className = "foo"; // 最初の状態: <div class="foo"></div> console.log(div.outerHTML); // classList API を用いてクラスを除去、追加 div.classList.remove("foo"); div.classList.add("anotherclass"); // <div class="anotherclass"></div> console.log(div.outerHTML); // visible が設定されていれば除去し、なければ追加 div.classList.toggle("visible"); // i が 10 未満であるかどうかの条件によって visible を追加または除去 div.classList.toggle("visible", i < 10); // false console.log(div.classList.contains("foo")); // 複数のクラスを追加または除去 div.classList.add("foo", "bar", "baz"); div.classList.remove("foo", "bar", "baz"); // スプレッド構文を使用したクラスの追加または除去 const cls = ["foo", "bar"]; div.classList.add(...cls); div.classList.remove(...cls); // "foo" クラスを "bar" クラスで置き換え div.classList.replace("foo", "bar");
Table
List
以下のAPIがある。
- HTMLTableCaptionElement - Web API | MDN: caption要素相当?
- HTMLTableCellElement - Web API | MDN: td要素相当。
- HTMLTableColElement - Web API | MDN
- HTMLTableElement - Web API | MDN: table要素のAPI。HTMLElementに加えて、テーブル用の特殊なプロパティー、メソッドが追加されている。
- HTMLTableRowElement - Web API | MDN: tr要素相当。
- HTMLTableSectionElement - Web API | MDN
Read
- HTMLTableElement.tBodies
- HTMLTableElement.rows
Append
table要素に行(tr要素)を追加 | JavaScript逆引き | Webサイト制作支援 | ShanaBrian Website
// table要素を取得 var tableElem = document.getElementById('sample-table'); var tr = tableElem.insertRow(-1); // tbody要素にtr要素(行)を最後に追加 var trElem = tableElem.tBodies[0].insertRow(-1); // td要素を追加 var cellElem = trElem.insertCell(); // td要素にテキストを追加 cellElem.appendChild(document.createTextNode('セル'));
なお、th要素を挿入するメソッドなどはない。
- javascript - Is it possible to create a TH with TableRow.insertCell()? - Stack Overflow
- javascript - Insert th in thead - Stack Overflow
outerHTML/innerHTMLかcreateElement/appendChildでやるしかない。
var tr = document.getElementById('table').tHead.children[0]; tr.insertCell(1).outerHTML = "<th>Second</th>" // some browsers require the index parm -- 1
outerHTMLがスマート。
Elasstic
データの追加と削除ができるテーブルの実装例。
<!DOCTYPE html>
<html lang="ja">
<head>
<script>
window.addEventListener('load', () => {
let rowRemove = (event) => {
let tr = event.target.parentElement.parentElement;
tr.remove();
}
let nodes = document.querySelectorAll('tbody>tr>td>button');
nodes.forEach(e => e.addEventListener('click', rowRemove));
let rowNew = (event) => {
let table = event.target.parentElement.parentElement.parentElement.parentElement;
let tr = table.tBodies[0].insertRow(-1);
let row = table.tBodies[0].rows.length;
let cells = table.tHead.rows[0].cells;
for (let col = 0; col < cells.length; ++col) {
if (col === 0) {
let button = document.createElement('button');
button.onclick = rowRemove;
button.appendChild(document.createTextNode('-'));
tr.insertCell().appendChild(button);
} else if (col === 1) {
tr.insertCell().appendChild(document.createTextNode(row));
} else if (col === 2) {
let input = document.createElement('input');
input.placeholder = '🔍'+cells[col].textContent+row;
tr.insertCell().appendChild(input);
} else {
tr.insertCell().appendChild(document.createTextNode(''));
}
}
};
let node = document.querySelector('thead>tr>th>button');
node.addEventListener('click', rowNew);
})
</script>
</head>
<body>
<table>
<thead>
<tr>
<th><button>+</button></th>
<th>#</th>
<th>id</th>
<th>value</th>
</tr>
</thead>
<tbody>
<tr>
<td><button>-</button></td>
<td>1</td>
<td><input name="id[1][id]" placeholder="🔍id1" value="id1" /></td>
<td>value1</td>
</tr>
<tr>
<td><button>-</button></td>
<td>2</td>
<td><input name="id[2][id]" placeholder="🔍id2" value="id2" /></td>
<td>value2</td>
</tr>
<tr>
<td><button>-</button></td>
<td>3</td>
<td><input name="id[3][id]" placeholder="🔍id3" /></td>
<td></td>
</tr>
</tbody>
</table>
</body>
</html>
追加は末尾の行コピーでやったほうがシンプル。
既存行コピーの実装例。
@php
/**
* table汎用コンポーネント。
*
* [CSSのposition: stickyでテーブルのヘッダー行・列を固定する \#Sticky \- Qiita](https://qiita.com/orangain/items/6268b6528ab33b27f8f2) を参考に、行・列見出し固定実装。
*
* ## Usage
* ```
* // cS69/index.blade.php
* // SearchService.php のgetListで取得できるデータを渡す想定。
* @component('components.table', ['columns' => $columns, 'data' => $data ?? null, 'rows' => $rows ?? []])
* @endcomponent
* // c21200/form.blade.php
* @component('components.table-dynamic', [
* 'elastic' => true,
* 'prefix' => $prefix,
* 'search' => $search,
* 'columns' => [
* ['label' => '代表オーナーコード', 'view' => 'components.input.text.search', 'params' => [
* 'searchPath' => 'cS68',
* 'searchCode' => '代表オーナーコード',
* 'mapNameColumn' => $mapNameColumn,
* 'prefix' => $prefix,
* 'search' => $search,
* 'path' => '楽楽販売_代表オーナーマスタ',
* ]],
* ['prefix' => ['楽楽販売_オーナーマスタ'], 'label' => 'オーナー名'],
* '物件コード',
* ]])
* @endcomponent
* ```
* @var object $data ページネーションを含む取得データ一覧。ページネーション用に使う。
*/
/** @var bool $elastic 伸縮可能テーブルの場合true。trueだと左端に増減用の+/-の列が追加される。 */
$elastic = $elastic ?? false;
/** @var array $columns = [column1, column2, column3] or [['label' => column1, 'key' => ''relation1.column1, class' => '', 'style' => '', 'params' => ['length' => 10, 'placeholder' => '🔍']], ...]。 列を絞り込みたかったらcontrollerで調整する。 */
/** label=ヘッダー表示用文字列。key=行の列名。指定しない場合labelと同一とみなす。class/style=td/thのスタイル。params=要素内のカスタムHTMLなどに渡すパラメーター。length/placeholderなど。 */
$columns = $columns ?? [];
/** @var array $rows = [['column1' => 'value1', 'column2' => 'value2'], ...] 形式。空の場合、テンプレート行追加用に[[]]を設定する。 */
$rows = $rows ?? [[]];
/** @var string[] $prefix */
$prefix = $prefix ?? [];
/** @var bool $search 検索画面の場合true。選択用のラジオボタンの列が追加される。 */
$search = $search ?? false;
/** @var bool $disabled ボタンやフォームのdisabled属性。2回以上のincludeの場合、フォームとボタンを無効化。 */
$disabled = (count($prefix) > 1) ? 'disabled' : '';
/** @var array $columns_new $columns の要素が文字列だったり、キーの省略があった場合のデフォルト値設定 (views以外)。&で$columnをそのまま編集するとデータがなぜか重複するため別変数で処理。 */
$columns_new = [];
foreach ($columns as $pos => $column) {
/** 配列が値の場合、labelとみなす。 */
$columns_new[$pos]['label'] = !is_array($column) ? $column : ($column['label'] ?? '');
$columns_new[$pos]['key'] = $column['key'] ?? $columns_new[$pos]['label'];
$columns_new[$pos]['prefix'] = $column['prefix'] ?? [];
$columns_new[$pos]['class'] = $column['class'] ?? '';
$columns_new[$pos]['style'] = $column['style'] ?? '';
$columns_new[$pos]['view'] = $column['view'] ?? '';
$columns_new[$pos]['params'] = $column['params'] ?? [];
}
@endphp
@component('components.row-col.base', ['rightClass' => 'col-md'])
@slot('right')
<label>該当件数 <span class="total_count"> {{ empty($data) ? 0 : $data->total() }} </span> 件<span></span></label>
@endslot
@endcomponent
<div class="sticky-table-wrapper">
<table class="table table-sm table-bordered sticky-table">
<thead class="text-center">
<tr>
@if (!empty($elastic))
<th class="bg-info text-center"><button type='button' onclick="
let prefix = {{json_encode($prefix)}};
let disabled = '{{$disabled}}';
let columns = {{json_encode($columns_new)}};
let tbody = event.target.parentElement.parentElement.parentElement.parentElement.tBodies[0];
let rowEnd = tbody.rows[tbody.rows.length-1];
let rowNew = rowEnd.cloneNode(true);
rowEnd.before(rowNew);
/** 番号増分 */
const columnNum = Number({{$elastic}})+Number({{$search}});
rowEnd.children[columnNum].textContent = +rowEnd.children[columnNum].textContent+1;
for (let col = columnNum+1; col<rowEnd.children.length; ++col) {
let name = rowEnd.children[col].firstChild.name;
/** table[0][列名] -> table[1][列名] に増加*/
rowEnd.children[col].firstChild.name = name.replace(/(\[)(\d)(\])/, (match, p1, p2, p3)=>p1+(+p2+1)+p3);
if (disabled) rowEnd.children[col].firstChild.disabled = disabled;
}
">+</button></th>
@endif
@if (!empty($search))
<th class="bg-info text-center">◎</th>
@endif
<th class="bg-info">#</th>
@foreach ($columns_new as $column)
<th class="bg-info {{ $column['class'] }}" style="{{ $column['style'] }}">{{ $column['label'] }}</th>
@endforeach
</tr>
</thead>
<tbody>
@foreach ($rows as $row_key => $row_value)
<tr>
@if (!empty($elastic))
<th class="bg-info text-center"><button type='button' onclick='let tr = this.parentElement.parentNode; if (tr.parentNode.rows.length > 1) tr.remove();'>-</button></th>
@endif
@if (!empty($search))
<th class="bg-info text-center"><div style="width:100%;height:100%;"><input type="radio" name="choice" /></div></th>
@endif
@foreach ($columns_new as $column)
@if ($loop->first)
<th class="bg-info text-center"><label style="width:100%;">{{ $loop->parent->iteration }}</label></th>
@endif
<td class="{{ $column['class'] }}" style="{{ $column['style'] }}">
@php $name = GASHelper::getPrefixedName(array_merge(['table'], $prefix, [$row_key], $column['prefix']), $column['key']); @endphp
@php $column_value = $row_value[$column['key']] ?? ''; @endphp
@empty($column['view'])
<output name="{{ $name }}">{{ $column_value }}</output>
@else
@include($column['view'], array_merge(['name' => $name, 'value' => $column_value], $column['params']))
@endisset
</td>
@endforeach
</tr>
@endforeach
</tbody>
</table>
</div>
{{ method_exists($data ?? null, 'links') ? $data->links() : '' }}
テーブルの全チェックオン・オフ
Ref: HTMLのテーブルでチェック行の情報だけ処理するサンプル/PHP・JavaScript - SE_BOKUのまとめノート的ブログ.
テーブルの1列目にチェックボックスを配置して、1行目のチェックボックスで全チェックオン・オフしたいことがある。
<script>
function AllChanged(){
var check = document.getElementById('aaa');
var checkarr = document.getElementsByName('bbb[]');
for (var i=0; i<checkarr.length; i++){
if(check.checked === true){
checkarr[i].checked = true;
}else{
checkarr[i].checked = false;
}
}
}
</script>
<body>
<body>
<form action="" name="form" method="POST">
<table>
<tr>
<th><input type="checkbox" name="aaa" id="aaa" onClick="AllChanged();" /> </th>
<th>KEY</th>
<th>値</th>
</tr>
<tr>
<td><input type="checkbox" name="bbb[]" value="AAAAAA,100000" onClick="OneChanged();" /></td>
<td>AAAAAA</td>
<td>100000</td>
</tr>
<tr>
<td><input type="checkbox" name="bbb[]" value="BBBBBB,200000" onClick="OneChanged();" /></td>
<td>BBBBBB</td>
<td>200000</td>
</tr>
</table>
<div>
<button type="submit" name="b1" id="b1" disabled>ボタンサンプル</button>
</div>
</form>
</body>
こんな感じでJavaScriptでチェックボックスを抽出してやる。PHP送信用にvalueに値を入れておく。
集計
javascriptでtableタグのtd内の数値の合計を自動で計算 #JavaScript - Qiita
<html> <body> <table> <tr> <td>りんご</td> <td>300</td> </tr> <tr> <td>バナナ</td> <td>400</td> </tr> <tr> <td>いちご</td> <td>500</td> </tr> <tr> <td>合計</td> <td id='goukei'></td> </tr> </table> <script> goukei.textContent = [...document.querySelectorAll('td:not(#goukei)')] .map(e => isNaN(e.textContent) ? 0 : +e.textContent) .reduce((a, b) => a + b); </script> </body> </html>
querySelectorAllでやるとスマート。
Topic
DOM反復
Ref: javascript - For loop for HTMLCollection elements - Stack Overflow.
Web上のデータの取得でコンソール画面で簡単にやりたいことがある。
Inspectorで右クリック-セレクターのコピー。
https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/renderer_context_menu/link_to_text_menu_observer.cc;bpv=1
let dom = document.querySelector('#mat-tab-content-0-0 > div > history-panel > div > history-table > mat-table')
for (let i = 0; i < dom.children.length; ++i) {
let child = dom.children[i].children[1];
if (child && child.querySelector('a')) {
console.log(child.querySelector('a').getAttribute('href').replace(/^.*\//, ''));
}
}
なお、domの反復ではfor (const key in dom)は使えないので注意する。lengthで添字を見ないと、関係ないプロパティーもkeyに入ってくるため。
子要素の一括削除
- Remove all child elements of a DOM node in JavaScript - Stack Overflow
- 【JavaScript】すべての子要素を削除するときは、cloneNode(false) して replaceChild するのが多分いちばん速い。 - freefielder.jp
いくつか方法がある。
- innerHTML = "": 簡単だが遅い。
- textContent = "": HTMLのパースがない分innerHTMLより速い。
- lastChild: 速い。
- replaceChild/replaceChildren: 2022年以後はこれ。replaceChildren()で削除になる。
select
JavaScriptにおけるselectオブジェクトのtext値の取得方法について現役エンジニアが解説【初心者向け】 | TechAcademyマガジン
select要素の値処理。
select要素はHTMLでのリストボックス、ドロップダウンを実現する要素。実際には子要素に選択肢のoption要素がある。
select要素にいくつかプロパティーがある。
- value: 選択中のoption要素のvalueが入る。
- selectedIndex: 選択中のoption要素の添え字。
e.children[e.selectedIndex].textContentで選択中のテキストを取得できる。
Event
Handler
インラインイベントハンドラーの場合、デフォルトでevent変数が引き渡されている (How to pass event as argument to an inline event handler in JavaScript? - Stack Overflow)。
function name(/*args*/) {body}
上記のbodyが値 (value) になる。argsは通常eventの1個だが、onerrorのみevent/souce/lineno/colno/errorの5引数。
他のイベント処理は自分で指定した引数にEventオブジェクトが渡される。
そして、値部分でのthisはリスナーのDOM要素になる (this - JavaScript | MDN)。
List
HTMLのフォームと連携する場合に重要なのがイベント。イベントやHTML要素別に発動するイベントが決まっている。「HTML Standard」がその対応。
特に重要なものを抜粋する。
- form (HTMLFormElement - Web API | MDN): formdata=フォーム送信直前/reset/submit
addEventListener
EventTarget: addEventListener() メソッド - Web API | MDN
addEventListener(type, listener) addEventListener(type, listener, options) addEventListener(type, listener, useCapture)
一般的にはuseCaptureの書式。デフォルトでは第3引数はfalse。1番目の引数2個の書式で書いておけば基本はOK。
- useCapture: 論理値。イベント伝播の方式。通常はfalse。trueにするとたぶんイベントを捕捉して終わり。
- options
- capture
- once: 初期値=false。trueにすると1回で自動削除。
- passive
- signal
入力判定
フォームの入力後に処理を行いたいことがよくある。例えば、自動補完など。
いくつか使えるイベントがある。
- beforeinput: inputの前。
- input: valueの変更時。
- change: valueが変更され、ユーザーの確定時 (HTMLElement: change イベント - Web API | MDN)。変更されてフォーカスが外れた場合。input+blurのようなイメージ。
- blur: フォーカスが外れた場合。
- keyup:
例えば、ユーザーの入力完了後に、そのデータを使って何かを行うならchangeでやるのがいい気がする。
JavaScript操作のイベント
jquery - Activate onchange after changing content with javascript - Stack Overflow
JavaScriptでフォームの値を操作した場合、changeイベント類は発動しない。必要なら、操作後に自分でイベントを発動させる必要がある。
イベントの発動
- Creating and triggering events - Event reference | MDN
- How to trigger event in JavaScript? - Stack Overflow
- JavaScriptでjQueryを使わずにイベントをtriggerする #JavaScript - Qiita
function triggerEvent(element, event) { if (document.createEvent) { // IE以外 var evt = document.createEvent("HTMLEvents"); evt.initEvent(event, true, true ); // event type, bubbling, cancelable return element.dispatchEvent(evt); } else { // IE var evt = document.createEventObject(); return element.fireEvent("on"+event, evt) } }
こんな古臭い書き方じゃなくて、今は以下でOK。
element.dispatchEvent(new Event(event));
eventは 'change' などのイベント名。
発動順序
"要素Amousedownイベント" "要素Afocusイベント" "要素Amouseupイベント" "要素Aclickイベント" "要素Abeforeinputイベント" "要素Bmousedownイベント" "要素Achangeイベント" "要素Ablurイベント" "要素Bfocusイベント" "要素Bclickイベント"
複数要素ある場合の、イベントの順序。2個目のmousedownが発動してから、1個目のchange/blurが発動する。
localStorage
JavaScriptでのデータ保存。使い勝手がいい。
- ウェブストレージ API の使用 - Web API | MDN
- Window: localStorage プロパティ - Web API | MDN
- LocalStorage, sessionStorage
- 【ワレコのWEBプログラミング】checkbox、select、input textの状態を記憶復元する
基本はwindowのloadで復元させる。
localStorage.setItem("myCat", "Tom");
const cat = localStorage.getItem("myCat");
window.addEventListener('load', () => {
document.getElementById('MaMiShare.host').value =
localStorage.getItem('MaMiShare.host');
});
Fetch
Ref: フェッチ API - Web API | MDN.
JavaScriptでHTTP通信するための標準的なAPI。
async function logMovies() {
const response = await fetch("http://example.com/movies.json");
const movies = await response.json();
console.log(movies);
}
fetchは引数を2個とる。1個目はリクエストURL。2個目はリクエストパラメーター。
// POST メソッドの実装の例
async function postData(url = "", data = {}) {
// 既定のオプションには * が付いています
const response = await fetch(url, {
method: "POST", // *GET, POST, PUT, DELETE, etc.
mode: "cors", // no-cors, *cors, same-origin
cache: "no-cache", // *default, no-cache, reload, force-cache, only-if-cached
credentials: "same-origin", // include, *same-origin, omit
headers: {
"Content-Type": "application/json",
// 'Content-Type': 'application/x-www-form-urlencoded',
},
redirect: "follow", // manual, *follow, error
referrerPolicy: "no-referrer", // no-referrer, *no-referrer-when-downgrade, origin, origin-when-cross-origin, same-origin, strict-origin, strict-origin-when-cross-origin, unsafe-url
body: JSON.stringify(data), // 本体のデータ型は "Content-Type" ヘッダーと一致させる必要があります
});
return response.json(); // JSON のレスポンスをネイティブの JavaScript オブジェクトに解釈
}
postData("https://example.com/answer", { answer: 42 }).then((data) => {
console.log(data); // `data.json()` の呼び出しで解釈された JSON データ
});
Frame/Window
Cross Window
複数のウィンドウをまたいで、情報や処理を連携する方法がいくつかある。
- iframe
- postMessage
検索画面のように、新規タブを開いて、そこでの実行結果を受け取るような場合に使う。
windows.onmessageにコールバックを登録。
新規タブでは、window.opner.postMessageでデータを送信。これでいける。
window.addEventListener("message", function(event) { if (event.origin != 'http://javascript.info') { // 未知のドメインからの場合は無視しましょう return; } alert( "received: " + event.data ); });
<iframe src="http://example.com" name="example"> <script> let win = window.frames.example; win.postMessage("message", "*"); </script>
別ウィンドウには以下の2属性でアクセスできる。
- window.open: 開き先。
- window.opener: 開き元。
Form
HTMLと連動したForm関係の処理が入出力で非常に重要。よくあるパターンをいろいろメモしておく。
formの送信データ
Ref: JavaScript オンリーで動的に form タグを作ってデータを送信する方法.
フォームから送信時に、自分でデータを作ったりできる。form要素のformdataイベントがポイント。ここで送信直前に送信データを追加したりできる。送信データのデフォルト値や、JavaScriptでいろいろ収集したデータを自動設定できる。
validate
データ送信前のチェック。送信後エラーの表示の対応方針。
- validateでボタンの有効無効の変数を制御
- フォームごとのエラー文字を判定
- フォームのinputやchangeのイベントに指定
- API呼び出し毎にvalidateを実行