「JavaScript」の版間の差分

提供:senooken JP Wiki
(elasticの例追加。)
 
(同じ利用者による、間の21版が非表示)
54行目: 54行目:
[https://developer.mozilla.org/ja/docs/Web/javascript JavaScript | MDN]
[https://developer.mozilla.org/ja/docs/Web/javascript JavaScript | MDN]


=== Grammar and types ===
== Grammar and types ==
[https://developer.mozilla.org/ja/docs/Web/JavaScript/Guide/Grammar_and_types 文法とデータ型 - JavaScript | MDN]
[https://developer.mozilla.org/ja/docs/Web/JavaScript/Guide/Grammar_and_types 文法とデータ型 - JavaScript | MDN]


==== Basic ====
=== Basic ===


===== セミコロン =====
==== セミコロン ====


* [https://blog.tai2.net/automatic_semilocon_insertion.html JavaScriptの行末セミコロンは省略すべきか | blog.tai2.net]
* [https://blog.tai2.net/automatic_semilocon_insertion.html JavaScriptの行末セミコロンは省略すべきか | blog.tai2.net]
67行目: 67行目:
ただ、ASIの完全理解は難しいし、バグの元なのでやめたほうがいいと思う。
ただ、ASIの完全理解は難しいし、バグの元なのでやめたほうがいいと思う。


===== 変数 =====
==== 変数 ====


====== 未定義アクセス ======
===== 未定義アクセス =====
[https://stackoverflow.com/questions/858181/how-to-check-a-not-defined-variable-in-javascript How to check a not-defined variable in JavaScript - Stack Overflow]
[https://stackoverflow.com/questions/858181/how-to-check-a-not-defined-variable-in-javascript How to check a not-defined variable in JavaScript - Stack Overflow]


83行目: 83行目:
なお、未定義プロパティーの場合は、例外にはならなくて、undefinedが返ってくる。ガードはしなくてもよさそう。
なお、未定義プロパティーの場合は、例外にはならなくて、undefinedが返ってくる。ガードはしなくてもよさそう。


===== 宣言 =====
==== 宣言 ====
[https://zenn.dev/keiichiro/articles/07450b50e4b808 JavaScriptのvarとletの違いとは?]
[https://zenn.dev/keiichiro/articles/07450b50e4b808 JavaScriptのvarとletの違いとは?]


102行目: 102行目:
基本はletを使ったらいい。
基本はletを使ったらいい。


==== データ型 ====
=== データ型 ===
[https://developer.mozilla.org/ja/docs/Web/JavaScript/Data_structures JavaScript のデータ型とデータ構造 - JavaScript | MDN]
[https://developer.mozilla.org/ja/docs/Web/JavaScript/Data_structures JavaScript のデータ型とデータ構造 - JavaScript | MDN]


===== Primitive =====
==== Primitive ====
[https://developer.mozilla.org/ja/docs/Web/JavaScript/Data_structures JavaScript のデータ型とデータ構造 - JavaScript | MDN]
[https://developer.mozilla.org/ja/docs/Web/JavaScript/Data_structures JavaScript のデータ型とデータ構造 - JavaScript | MDN]


====== null ======
===== null =====
[https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/null null - JavaScript | MDN]
[https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/null null - JavaScript | MDN]


138行目: 138行目:
??はES2020。value == null/value == undefinedがシンプルだが、==は規約で禁止にすることもある。素直に、value === nullかorでちゃんと書くのがよいかも。
??はES2020。value == null/value == undefinedがシンプルだが、==は規約で禁止にすることもある。素直に、value === nullかorでちゃんと書くのがよいかも。


===== Boolean =====
==== Boolean ====
[https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Boolean Boolean - JavaScript | MDN]
[https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Boolean Boolean - JavaScript | MDN]


162行目: 162行目:
上記が非常に重要。!!は短い。Boolean()は引数が空だとfalseになる。PHPなどの値を使う場合など、変数がそもそも空になる場合を考慮するならBooleanがいい。
上記が非常に重要。!!は短い。Boolean()は引数が空だとfalseになる。PHPなどの値を使う場合など、変数がそもそも空になる場合を考慮するならBooleanがいい。


===== String =====
PHPのプレースホルダーを使う場合、<code><nowiki>'{{}}'</nowiki></code>のように使う場所を引用符で囲んで文字列にするもあり。if文などは空文字は評価する。


====== template literal ======
==== String ====
 
===== template literal =====
[https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Template_literals テンプレートリテラル (テンプレート文字列) - JavaScript | MDN]
[https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Template_literals テンプレートリテラル (テンプレート文字列) - JavaScript | MDN]


176行目: 178行目:
変数を交えた複雑な文字列の生成時にすっきりとする。ただ、${}が必須なので、長くなることが多い。
変数を交えた複雑な文字列の生成時にすっきりとする。ただ、${}が必須なので、長くなることが多い。


===== Array =====
===== 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 ====
[https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array Array - JavaScript | MDN]
[https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array Array - JavaScript | MDN]


====== 生成 ======
===== 生成 =====
連続値の作成。
連続値の作成。


188行目: 224行目:
<nowiki>https://qiita.com/sakymark/items/710f0b9a632c375fbc31</nowiki>
<nowiki>https://qiita.com/sakymark/items/710f0b9a632c375fbc31</nowiki>


====== 追加 ======
===== Merge =====
concat
いくつか方法がある。


====== 一括関数 ======
* concat
* push: 本体変更。
* スプレッド構文
 
たぶんスプレッド構文が速い。
<nowiki>(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</nowiki>
concatのほうが速かった。
 
===== Read =====
 
===== 最終要素 =====
https://chatgpt.com/c/673d6472-b5b8-800b-89a9-fe7a0119ff49
 
# length
# スプレッド構文とpop
# slice
# at (ES2022以上)
 
<nowiki>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</nowiki>
関数を使わない分、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が使いやすい。
 
===== 一括関数 =====
配列要素に対して一括処理するメソッドがいくつかある。非常に重要。
配列要素に対して一括処理するメソッドがいくつかある。非常に重要。


200行目: 286行目:
* some: 1個でも満たす場合true。
* some: 1個でも満たす場合true。


====== Array.prototype.filter() ======
===== Array.prototype.filter() =====
[https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/filter Array.prototype.filter() - JavaScript | MDN]
[https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/filter Array.prototype.filter() - JavaScript | MDN]


callbackでtrueの要素の配列を返す。
callbackでtrueの要素の配列を返す。


===== Object =====
==== Object ====
[https://developer.mozilla.org/ja/docs/Glossary/Object Object (オブジェクト) - MDN Web Docs 用語集: ウェブ関連用語の定義 | MDN]
[https://developer.mozilla.org/ja/docs/Glossary/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);
 
====== 配列から作成 ======
[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 =====
 
* [https://zenn.dev/kou_pg_0131/articles/js-merge-multiple-objects 【Javascript】複数のオブジェクトを結合する]
 
* [https://qiita.com/riversun/items/60307d58f9b2f461082a JavaScriptでオブジェクトをマージ(結合)する方法、JSONのマージをする方法 #Deepcopy - Qiita]
 
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
 
===== プロパティー存在判定 =====


* [https://qiita.com/rymiyamoto/items/be91b04f70de2b621bb3 jsでのプロパティの存在チェック方法をまとめてみる #JavaScript - Qiita]
* [https://qiita.com/rymiyamoto/items/be91b04f70de2b621bb3 jsでのプロパティの存在チェック方法をまとめてみる #JavaScript - Qiita]
226行目: 377行目:
単純に、プロパティーでアクセスして、undefinedかどうかを見るのもありだと思う。
単純に、プロパティーでアクセスして、undefinedかどうかを見るのもありだと思う。


====== プロパティーのマージ ======
===== 判定 =====
[https://qiita.com/riversun/items/60307d58f9b2f461082a JavaScriptでオブジェクトをマージ(結合)する方法、JSONのマージをする方法 #Deepcopy - Qiita]
 
Object.assignかスプレッド演算子...で展開・割り当てするとよい。
 
====== 判定 ======


* [https://zenn.dev/sosukesuzuki/articles/5abfd04a4ca7c8 JavaScript である値がオブジェクト型であることを判定する変なテク]
* [https://zenn.dev/sosukesuzuki/articles/5abfd04a4ca7c8 JavaScript である値がオブジェクト型であることを判定する変なテク]
257行目: 403行目:
が、パフォーマンスが重要じゃないなら、Objectでもいいかも。
が、パフォーマンスが重要じゃないなら、Objectでもいいかも。


====== Loop ======
===== Loop =====


* [https://zenn.dev/yuji6523/articles/fd63e3ec01271e 【JS】配列じゃなくオブジェクトをループさせたい!!!]
* [https://zenn.dev/yuji6523/articles/fd63e3ec01271e 【JS】配列じゃなくオブジェクトをループさせたい!!!]
276行目: 422行目:
Array.fromを使うのが素直。だが、余計な変換をすると速度が劣る可能性がある。素直にforとかforの処理を関数にしたほうがいいかもしれない。
Array.fromを使うのが素直。だが、余計な変換をすると速度が劣る可能性がある。素直にforとかforの処理を関数にしたほうがいいかもしれない。


==== Conversion ====
=== Conversion ===
[https://uxmilk.jp/11582 JavaScriptで文字列を数値に変換する:Number(), parseInt(), parseFloat() | UX MILK]
[https://uxmilk.jp/11582 JavaScriptで文字列を数値に変換する:Number(), parseInt(), parseFloat() | UX MILK]


417行目: 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/即時実行関数式 ====
434行目: 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 });
引数全体をObjectとして記述しておいて、プロパティーを説明する。同じオブジェクトを他で流用するならparamではなく最初を@typedefにする。


=== class ===
=== class ===
447行目: 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 ====
454行目: 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 ==
518行目: 782行目:


基本はwindow.addEventListener('load', )で設定。これをhead要素内などの冒頭に記述するなどするといい。どこに記述しても問題ない書き方。
基本はwindow.addEventListener('load', )で設定。これをhead要素内などの冒頭に記述するなどするといい。どこに記述しても問題ない書き方。
=== Table ===
==== テーブルの全チェックオン・オフ ====
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でやるとスマート。


=== Other ===
=== Other ===
723行目: 906行目:


基本はElement.removeで良いと思う。
基本はElement.removeで良いと思う。
===== 複製 =====
[https://gray-code.com/javascript/duplicate-html-element/ HTML要素を複製(コピー)する | GRAYCODE JavaScript]
cloneNode。引数をtrueにすると、再帰的に。
返却値が複製後オブジェクト。これをafterなどに指定すると追加できる。追加前にidなどの編集を忘れずに。


===== 属性 =====
===== 属性 =====
827行目: 1,017行目:
* [https://developer.mozilla.org/ja/docs/Web/API/HTMLTableRowElement HTMLTableRowElement - Web API | MDN]: tr要素相当。
* [https://developer.mozilla.org/ja/docs/Web/API/HTMLTableRowElement HTMLTableRowElement - Web API | MDN]: tr要素相当。
* [https://developer.mozilla.org/ja/docs/Web/API/HTMLTableSectionElement HTMLTableSectionElement - Web API | MDN]
* [https://developer.mozilla.org/ja/docs/Web/API/HTMLTableSectionElement HTMLTableSectionElement - Web API | MDN]
===== Read =====
* HTMLTableElement.tBodies
* HTMLTableElement.rows


===== Append =====
===== Append =====
853行目: 1,048行目:
outerHTMLがスマート。
outerHTMLがスマート。


===== Elsastic =====
===== Elasstic =====
データの追加と削除ができるテーブルの実装例。
データの追加と削除ができるテーブルの実装例。
[[ファイル:ElasticTable.png|サムネイル]]
[[ファイル:ElasticTable.png|サムネイル]]
934行目: 1,129行目:
</syntaxhighlight>
</syntaxhighlight>


追加は末尾の行コピーでやったほうがシンプル。
既存行コピーの実装例。<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>該当件数&emsp;<span class="total_count"> {{ empty($data) ? 0 : $data->total() }} </span>&nbsp;件<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

Use JSDoc: Index

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

JavaScript | MDN

Grammar and types

文法とデータ型 - JavaScript | MDN

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が返ってくる。ガードはしなくてもよさそう。

宣言

JavaScriptのvarとletの違いとは?

変数宣言の3種類がある。

  • var: 変数宣言。
  • let: ブロックスコープでローカル変数宣言。
  • const: ブロックスコープで読み取り専用。

letはES2015で登場。

varはいくつか問題があった。

  1. 再宣言の許容: var xを何回もできた。let xは1回だけ。2回目はエラー。
  2. 巻き上げ (hoisting): varの宣言は、どこで宣言されていようが、スコープの最上部に巻き上げられる。宣言前に使ってもエラーにならなかった。JavaScriptでは宣言と代入が別扱いで、宣言だけ全体パースを先にする模様。letも同じく巻き上げは発生するが、登場までアクセス不能になっている。
  3. 関数スコープ: PHP同様に、varは関数スコープだけ持つ。波括弧のブロックではスコープを作らない。

基本はletを使ったらいい。

データ型

JavaScript のデータ型とデータ構造 - JavaScript | MDN

Primitive

JavaScript のデータ型とデータ構造 - JavaScript | MDN

null

null - JavaScript | MDN

オブジェクトの値が存在しないことを表す。Booleanではfalse扱い。

いくつか特殊な動きがある。

''+null // "null"
typeof null // "object"

よくやりたいのが、nullなら空文字にするというの。

let var = null;
var || '';

var || '';だと、0やfalseでも''になる。いろいろ困ることがある。

 ╭─ 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

Boolean - JavaScript | MDN

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種類ある。

  1. !! (二重否定)
  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

文字列の前後の不要な文字を削除したいことがある。

  • 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

Array - JavaScript | MDN

生成

連続値の作成。

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

  1. length
  2. スプレッド構文とpop
  3. slice
  4. 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

いくつか方法がある。

  1. reduce
  2. Object.fromEntries
  3. キーと値を別の配列から生成
  4. forループ
  5. forEach
  6. 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かスプレッド演算子...で展開・割り当てするとよい。

  1. Object.assign
  2. スプレッド構文 ({...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
プロパティー存在判定

いくつか方法がある。

  • 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";
}

判定方法は上記。ポイント。

  1. typeof nullはobjectになるので条件追加。
  2. 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...in - JavaScript | MDN

for (variable in object)
  statement

objectのキーに対する反復。

variableにプロパティー名を受け取る。

要素にアクセスするにはobject[variable]

順序には法則がある。

  1. 整数の昇順
  2. 文字列の作成順

通常の配列反復としても使える。ただし、配列添え字以外の文字列も反復するので注意する。

for...of

for...of - JavaScript | MDN

ES2015で標準化。

objectのバリューに対する反復。一般的なforeach。

順序保障。

Expression+Operator

式と演算子 - JavaScript | MDN

this

JavaScriptのthisは文脈 (実行コンテキスト) に応じて対象が異なるので注意が必要。

  • DOM イベントハンドラー: 配置要素。
  • インラインイベントハンドラー: リスナーのDOM要素。
  • グローバル: グローバルオブジェクト。Webブラウザーならwindow。
  • 関数: call/applyで設定しない限り、グローバルオブジェクトかstrictモードならundefined。
  • クラス: constroctor内はただのオブジェクト。他のstaticでないメソッドは、thisのプロトタイプに追加される。
  • メソッド: 呼び出し元のインスタンス。
  • constructor: コンストラクターとしてnewキーワードとセットで使われた場合、インスタンス。

typeof

typeof - JavaScript | MDN

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 演算子

in 演算子 - JavaScript | MDN

指定プロパティーがオブジェクトにある場合にtrueを返す。プロパティーの有無チェックに使う。

'make' in {make: 'Honda', model: 'Accord'};

集合の有無判定はArray.includes()を使う。

[0, 1, 2].includes(3); // false

分割代入

分割代入 - JavaScript | MDN

配列とオブジェクトに対して、要素とプロパティーを部分的に取り出すことができる。

特に、オブジェクトはたくさんあるプロパティーから一部分の検索・取得ができて重宝しそう。

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. デフォルト値を最後以外でも指定可能。

オブジェクトリテラルを使わないほうがいい場合。

  1. 引数1個のみ。
  2. 非常に高頻度の呼び出し関数。
  3. 引数2-3個のみのシンプルな関数。

特に以下の場合は、関数の引数をオブジェクトリテラルにしたほうがいい模様。

  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 });

引数全体をObjectとして記述しておいて、プロパティーを説明する。同じオブジェクトを他で流用するならparamではなく最初を@typedefにする。

class

出典: クラス - JavaScript | MDN

JavaScriptのclassは実際には特別な関数。

constructor

constructorメソッドは、オブジェクト生成時の初期化用の特別なメソッド。1個しか定義できない。

コンストラクター内で、インスタンスプロパティーを設定する。newで呼び出す際の引数を受け取る。

Global Object

標準組み込みオブジェクト - JavaScript | MDN

グローバルオブジェクト (global objects) (標準組込オブジェクト) はthe global objectとは異なる。グローバルスコープにあるオブジェクトのこと。日本語だと標準組込オブジェクトで理解しておくとよい。

どこからでもアクセス可能なオブジェクト。非常に重要。

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

Number - JavaScript | MDN

NaN

Number.NaN - JavaScript | MDN


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\"}");

エスケープ、置換の考え方は以下の通り。

  1. "→\"=\": JSON値用の二重引用符のエスケープ。
  2. \→\\=\\": 言語の二重引用符の解釈回避用に、バックスラッシュのエスケープ。
  3. "→\"=\\\": 外側二重引用符用の二重引用符のエスケープ。

JavaScriptの読込場所

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

Performance - Web API | MDN

JavaScriptで任意の処理にかかる時間を計測する

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

コンソール API - Web API | MDN

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
DOM/Node/Element

DOM、Node、Elementを理解する #JavaScript - Qiita

  • DOM: WebブラウザーAPI。文書をJavaScriptなどで操作するためのAPI。
  • Node: 要素やコメントなどを含む、DOMのObjectの単位。
  • Element: Nodeの一種 (Nodeを継承)。いわゆる要素のオブジェクト。
生成

Document - Web API | MDN

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個ずつ設定だが、いくつか方法がある。

  1. setAttbituteをforで反復。
  2. 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!')
  }
})
クラス

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がある。

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要素を挿入するメソッドなどはない。

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

データの追加と削除ができるテーブルの実装例。

ElasticTable.png
<!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>該当件数&emsp;<span class="total_count"> {{ empty($data) ? 0 : $data->total() }} </span>&nbsp;<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に入ってくるため。

子要素の一括削除

いくつか方法がある。

  • 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

イベントリファレンス | MDN

HTMLのフォームと連携する場合に重要なのがイベント。イベントやHTML要素別に発動するイベントが決まっている。「HTML Standard」がその対応。

特に重要なものを抜粋する。

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

入力判定

フォームの入力後に処理を行いたいことがよくある。例えば、自動補完など。

いくつか使えるイベントがある。

HTMLElement - Web API | MDN

  • 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イベント類は発動しない。必要なら、操作後に自分でイベントを発動させる必要がある。

イベントの発動

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でのデータ保存。使い勝手がいい。

基本は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

データ送信前のチェック。送信後エラーの表示の対応方針。

  1. validateでボタンの有効無効の変数を制御
  2. フォームごとのエラー文字を判定
  3. フォームのinputやchangeのイベントに指定
  4. API呼び出し毎にvalidateを実行