変奏現実

パソコンやMMORPGのことなどを思いつくまま・・・記載されている会社名・製品名・システム名などは、各社の商標、または登録商標です。

この画面は、簡易表示です

「javascript]謎な不安定さ

contenteditableのカスタムエレメント版 の修正も大方終わった。

気がするけど、見る度に手直ししている。(大笑

‘”.*?”‘では、リテラル中のバッククォートを処理できない。なので、JDOCやコメント部はリテラルとと一緒に処理し、その他は字句解析し直すことにした。※convertJavascriptToHTML1~2

javascriptでは、'”(\\\\.|.)*??”‘がエラるが、'”(\\\\.|.)*?”‘なら通るので、matchの戻り値[0]のテキストを使い括弧で括ってないパターンの部分も取れる。※convertJavascriptToHTML3

多分これで大丈夫。(でも心配だから、1~2も残しておく。

JDOCも変になっていた。最後の字句解析で行ごとにspanで括る様にしたので再帰したりすると変更漏れ

ちょっと修正すると動作がガタガタになりやっと安定すると行番号の幅が変に狭いので見てみるとVisualStudioCode??が謎に}を追加しててcssが効かなくなってたりするのは比較的解りやすいけど…

`.code>.row2 {
    margin-left: 1.5rem;
} ☚❓
}\n`,
`.code>.row3 {
    margin-left: 2rem;
} ☚❓
}\n`,

そのガタガタ動作を追っていった最中に気が付いたコトをメモっておく。

staticな初期設定あるいはthisの話

class Sample {
  styles={};
  constructor(){
    this.styles['one'] = 1;
    this.styles['two'] = 2;
    this.styles['three'] = 3;
  }
}

は上手く動作するけど、staticにすると

class Sample {
  static _ = Sample.setup();
  static styles={};
  Sample.setup(){
    Sample.styles['one'] = 1;
    Sample.styles['two'] = 2;
    Sample.styles['three'] = 3;
  }
}

と初期設定をした後に

static styles={};

が動き出す場合があるので、

class Sample {
  static _ = Sample.setup();
  static styles;
  Sample.setup(){
    if(!Sample.styles) { Sample.styles={}; }
    Sample.styles['one'] = 1;
    Sample.styles['two'] = 2;
    Sample.styles['three'] = 3;
  }
}

の様に各static要素の初期設定順に依存しないようにした方がいい。

また、

class Sample {
  static _ = Sample.setup();
  static setup() {
       Sample.field1 = 10;
  }
  static field1;

とクラス名のトコにthisに変えてもstatic同士なら支障は無い。

class Sample {
  static _ = this.setup();
  static setup() {
       this.field1 = 10;
  }
  static field1;

がインスタンスを得た時点でthis経由でstaticなものは見えないので動きが変になりやすい。

更にサブクラスからSample.setup()の様に呼び出されると

class Sample {
  static _ = this.setup();
  static setup() {
       this.field1 = 10;
  }
  static field1;

class SubSample extends Sample{
  static _ = Sample.setup();

サブクラス側にstaticなfield1が出来てしまう様だ。

コレクション型フィールドのgetterの戻り値を使いまわす場合

一番悩ましいのはコレ。

class Sample {
  styles={};
  constructor(){
    this.styles['one'] = 1;
    this.styles['two'] = 2;
    this.styles['three'] = 3;
  }
  getStyles() { return this.styles; }
}

・・・

class SubSample extends Sample {
    ・・・
    const styles = this.getStyles();
    {
    styles['four'] = 4;
      {
      styles['five'] = 5;
        {
          styles['six'] = 6;
             ・・・
               {
               styles['ten'] = 10; 
  ※もうこのあたりまでくるとthis.getStyles();との距離がかなり離れてしまい
  this.stylesに更新した内容(=4のあたりから=10まで)が反映されない時がある。

単にソースが長くなってくると起きることがあるので、

class Sample {
  styles={};
  constructor(){
    this.styles['one'] = 1;
    this.styles['two'] = 2;
    this.styles['three'] = 3;
  }
  getStyles() { return this.styles; }
}
・・・
class SubSample extends Sample {
・・・
    const list = {
    'four': 4,
    'five': 5,
    'six': 6,
・・・
    'hundred': 100,
    };

な感じで

    const styles = this.getStyles();
    Object.keys(list).forEach((key) => styles[key] = list[key]);

とできるダケ「this.getStyles()」を近づけて逃げるよりも

    Object.keys(list).forEach((key) => this.styles[key] = list[key]);

this.stylesに直接入れるか、処理の戻り値でthis.stylesを上書きしてしまった方が良い。

class Sample {
  styles={};
  constructor(){
    this.styles['one'] = 1;
    this.styles['two'] = 2;
    this.styles['three'] = 3;
  }
  setStyles(v) { this.styles = v; }
  getStyles() { return this.styles; }
  ・・・・
  method() {
    ・・・・
    const styles = this.getStyles();
    ・・・・
  this.setStyles(styles);
  }
}


[javascript]クラスの名前を調べる

ログウインドウの右端にファイル名が付くけど、クラス名も書き出したい。

class xxxx{
  static className = this.name;
  ...
}

は安定してクラス名が取得できるが、これは、staticなメソッドのみ参照可能で、インスタンス依存のメソッド用に

class xxxx{
  className;
  constructor() {
    this.className = this.name;
    ...
  }
  ...
}

class xxxx{
  className;
  constructor() {
    this.className = this.constructor.name;
    ...
  }
  ...
}

のいづれかが必要だった。どっちが常に正しいという訳では無いので、

static className = this.name;
className;
constructor() {
  this.className = this.name ?? this.constructor.name;
  ...
}

~なぁあたりが良さそうだ。

??はあまり使わないけど、Null 合体演算子で、

「とりあえず【まともな値】を返す」場合に便利な演算子。

新しいパターンが増えたら

static className = this.name;
className;
constructor() {
  this.className = this.name ?? this.constructor.name ?? {新しいパターン};
  ...
}

にすればいいだろう。

これで各メソッドに

    メソッド名 (...) {
        const methodName = '{メソッド名}';
        console.debug(`${this.className}.${methodName}(...)`);

と書ける。

例外処理を発生してスタックを調べる事もできたけど、イマイチ感がある。

const getCurrentLineInfo = () => {
    try {
        throw new Error('getCurrentLineInfo Error');
    } catch (ex) {
        let stack = ex.stack.split('\n');
        let target = stack[2];
        // パターン1:at xxClass.yyMethod (https://{domain}/{path}/{filename}:{line}:{column})
        // パターン2:at xxClass.yyMethod (file:///{drive}/{path}/{filename}:{line}:{column})
        let match = target.match(/\s*at\s*([_a-zA-Z]+)[.]([_a-zA-Z]+)\s*([(])(.+?)([)])/);
        // /\s*at\s*([_a-zA-Z]+)[.]([<>_a-zA-Z]+)\s*(?<=[(])(.+?)(?=[)])/ にすると失敗?
        let className = match[1];
        let methodName = match[2];
        let url = match[4];
        url = url.split(':');
        let type = url.shift();
        switch (type) {
            case 'http':
            case 'https':
                var domain_fullpath = url.shift();
                var [_, _, domain, ...fullPathName] = domain_fullpath.split('/');
                fullPathName = '/' + fullPathName.join('/');
                var row = url.shift();
                var column = url.shift();
                break;
            case 'file':
                var drive = url.shift();
                var fullPathName = url.shift();
                var row = url.shift();
                var column = url.shift();
                break;
        }
        let fileName = fullPathName.split('/').slice(-1)[0];
        let fullPath = fullPathName.substring(0, fullPathName.length - fileName.length - 1);
        var rc = {
            type: type,
        }
        if (type == 'file') {
            Object.assign(rc, {
                drive: drive,
            });
        } else {
            Object.assign(rc, {
                domain: domain,
            });
        }
        Object.assign(rc, {
            fullPath: fullPath,
            fileName: fileName,
            className: className,
            methodName: methodName,
            row: row,
            column: column,
        });
    }
    return rc;
}
  • 名前を調べる目的の割に長すぎる
  • 関数を呼ぶ度に例外処理を起こすのは実行時間に響きそう
  • chromeのスタック情報に依存したコードなので他ではエラりそう

サンプルでボタンを押すと表示する。

getCurrentLineInfo.jsのSampleClassクラスのexec()を呼び出した場合、
{
 "type": "https",
 "domain": "ssiscirine.iobb.net",
 "fullPath": "/sample/stack",
 "fileName": "getCurrentLineInfo.js",
 "className": "SampleClass",
 "methodName": "exec",
 "row": "5",
 "column": "12"
}
getCurrentLineInfo.jsのgetCurrentLineInfo()を直接呼び出した場合、
{
 "type": "https",
 "domain": "ssiscirine.iobb.net",
 "fullPath": "/sample/stack",
 "fileName": "stack.html",
 "className": "HTMLButtonElement",
 "methodName": "<anonymous>",
 "row": "14",
 "column": "43"
}

FireFoxではクラス名は無いけどメソッド名(関数名?)まで取れるのでサンプルの方は対応している。

HTMLのscriptやonclickからgetCurrentLineInfo()を直接呼び出した場合は行列位置は正しいので使えるけど、他は当てできないね。

それにしても

out.innerText = JSON.stringify(getCurrentLineInfo(),null," ");  ※" "は全角1文字

と、インデントを指定すると、改行もしてくれるので読みやすい。※”\t”も良いらしい。



[javascript]spread-sheetカスタムエレメント

spread-sheetカスタムエレメントってありそうで無いっぽいので作ってみた。

セルに文字入力できるダケのしろもの。

  • IMEがONになると動きがおかしい
  • 範囲指定ができない
  • 計算できない
    • 簡単な計算は可、セルアドレスでセル値参照も可
    • 関数が何も無い
      • sum,count,average,min,max
    • 値の変動に計算式を連動
      • 範囲指定時の連動が未実装
  • 入力したデータの保存ができない
    • indexedDBにセル値を保持
      • indexedDBのスコープがドメイン単位なので別ページと被ってしまいがち
        • databasse属性にurlから置換する%domain%,%path%,%filename%を追加
          • 例:databasename=”%domain%%path%%filename%_スプレッドシート1”
            • 結果はDevToolsのアプリケーションのindexedDBを参照
  • セル幅や高さが変えられない
  • 文字のスタイルを変えられない
  • javascriptからシートのデータを参照できない
  • ステータスバーが無い
  • 数式入力バーが無い
  • セルを移動し画面外に出ても画面がスクロールしない
    • 行列タイトルがスクロールロックしてない
      • スクロールバーが無反応
  • マクロが無い
  • データのインポート、エクスポートが無い
  • グラフ、絵、矢印とか貼れない
  • セルに名前が付けられない
  • シートが1枚ダケ
  • 列・行の追加や削除ができない
  • カットアンドペーストができない

等々いっぱい未実装。

1つのクラスでは長すぎるので分割してみた。

  • SpreadSheetCustomElement   全体(this)
    • this.customElement     カスタムエレメント登録Callback等
    • this.event         イベント処理
    • this.command       コマンドっぽい処理
    • this.layout         レイアウト処理

各処理からはthis.spreadsheet経由で別処理を呼び出す必要がある。

function OpenEntry () {
  let rect = this.spreadsheet.layout.cellEditing.getBoundingClientRect(); //編集するセルの位置を求め
  this.spreadsheet.layout.divTextarea.top = rect.top; //テキストエリアを内包するdivをソコに移動させ
  this.spreadsheet.layout.divTextarea.left = rect.left;
  this.spreadsheet.layout.textarea.value = event.key; //テキストエリアに入力した文字を入れ
  this.spreadsheet.layout.divTextarea.style.display = 'block'; //テキストエリアを内包するdivを表示
}

this.xxxx.yyyy.zzzzと長々と詠唱するのは読みにくいので

function OpenEntry () {
  const spreadsheet = this.spreadsheet;
  const layout = spreadsheet.layout;
  let rect = layout.cellEditing.getBoundingClientRect(); //編集するセルの位置を求め
  layout.divTextarea.top = rect.top; //テキストエリアを内包するdivをソコに移動させ
  layout.divTextarea.left = rect.left;
  layout.textarea.value = event.key; //テキストエリアに入力した文字を入れ
  layout.divTextarea.style.display = 'block'; //テキストエリアを内包するdivを表示
}

constを使い短縮化した。

後で割り当てを変更する時には直しやすいかもしれない。(気がする。

this.spreadsheet.layout.divTextarea.top 
// divTextareaをlayoutからshadowへ移動
=>this.spreadsheet.layout.spreadsheet.shadow.divTextarea.top 
// divTextareaをshadowからlayoutへ戻す
=>this.spreadsheet.layout.spreadsheet.shadow.spreadsheet.layout.divTextarea.top 
// は?

となるよりは

layout.divTextarea.top 
// divTextareaをlayoutからshadowへ移動
=>shadow.divTextarea.top 
// divTextareaをshadowからlayoutへ戻す
=>layout.divTextarea.top 
// 良し!

の方がマシ。

さて!いざ、やってみるとVScodeのリファクタリングが不十分でサクラエディタで(ガシガシ

になるのはいつものこと。

カスタムエレメントの宣言をクラスの出す事は可能だけど、クラスのstatic getterがconstructorに作られるので差し替え不可、無名のクラス文でラップするとコードがとても長くなるので取止め。

ps.

行列タイトルロックできたけど、タイトルロックの状態の表と元のサイズの表の差をスクロール量としてスクロールバーのスライダー位置を調整しているので、まだスライダーを触ってもタイトルロックの状態は変動しない。

ps

spread-sheetカスタムエレメント3 セルの範囲をMS-Office365のExcelまで拡大してみたところ、Ctrl+↓↑で移動すると1,048,576行もあるせいで移動完了まで「しばらくお待ちください」状態。カーソルをあちこちと移動するとテーブルが右端から壊れていく。

ps.

多少良くなったので、spread-sheetカスタムエレメント3を更新、データもindexedDBに保持したので、ブラウザが重くなるかもしれない。また時々DB構成を変えるかもしれないので、devToolsのアプリケーションでストレージからIndexDBに▶が付いていたらDB削除してから見た方が良いかも。まだカーソルジャンプ中に帰ってこない時がある。

ps.

indexedDB周りをdatabase.jsに分離。データベースも’R0000001’とか’C00002’など固定文字列も止めてinteger化したが今までどおりVersionアップ処理はしていない。

ps.

A1セルから右端まで移動し一番下の行へジャンプするとセルが隠れた。同様に一番下の行のA列から右端の列までジャンプし一番上の行へジャンプすると右側の隙間がひろがっていた。この修正中に列を追加する処理を呼び出す側がawaitしていなかったせいで画面が壊れることがあった。

カスタムエレメントの属性にdatabasenameを追加し、スプレッドシート毎にindexedDBのデータベースを分離可能に。

ページ移動(PageUp/Down)で同期を取っていたら、キーリピートで並行に処理が走ってしまい画面構成がクズれるので、全般的にキー操作はsetTimeoutで後回しにすることで並行に処理が走らない様にした。

ps.2024/3/18

indexDBの構成を変更!パーサーコンビーネータを組み込んで数値やセルアドレスで四則演算程度の数式を計算できた。但し、indexedDBが非同期なので、パーサーコンビーネータでArray.reduceを使った箇所の同期ズレ(Promiseレーシングや無限ループ)を対処できずfor文に戻した。

セル値の変動に連動して再計算させてみたが、inndedDBのトランザクションは階層化できない様で、トランザクションを流しながらセルデータをinndedDBで読み書きした後に、カーソルを移動させると、トランザクションが終了な済みエラーになる。また、データベースに複数キーのインデックスがあると、単キーのインデックスでもカーソルの範囲(上下限)は配列で指定しなければいけない。bound([1],[1])でデータが取れるけど、bound(1,1)ではスカっと終了する。

そう云えば、数式とかデータに空白が混入するとパーサのパターンにマッチしない。パーサの処理の先頭に空白の読飛ばし処理を追加した。でも数式は先頭文字が=の場合だけに限定したいので、読飛ばしをON・OFFするようにした。seqとchiceにパラメータに混入させたのでソコはチョットごまかし。

#初期設定
this.fSpaceSkip = false;
・・・
#数式の構文のルート木
const exprCell = this.map(this.seq(this.token('='), () => { this.fSpaceSkip = true }, expr), async (parsed) => {
・・・
#値の構文のルート木
const cell = this.choice(exprCell, () => { this.fSpaceSkip = false }, valueCell);
・・・
#各パーサの処理
async (target, position) => {
  if (this.fSpaceSkip) { [target, position] = this.skipSpace(target, position); }
・・・
}
#seqまたはchoiceのforループ
for (var i = 0; i < parsers.length; i++) {
  var parsed = await parsers[i](target, position);
  if (parsed === undefined) {
    continue;
  }
  ・・・
}

TextAreaを表示中に他のセルをクリックするとTextAreaを閉じそのセルをフォーカスっぽい表示にするけど、その際にクリックイベントで渡されたevent.targetの中身が書換わってスクリプトが暴走状態になってたので即保持しておく。※Promiseを使い出してからはよく起きる現象

ps.2024/3/19

sum関数がちょっと動き出した。なのでindexedDBの構成がまた変わった。min.max,averageもセルの範囲指定でも計算可。※結果が正しいかもしれない。

ps.2024/3/20

indexedDBのセルデータをJSON風に変更で文字列型が修正漏れ。sum関数等でほぼ同じコードが続くのでコールバック式に変更。Deleteキーでセルデータを空白に。=A1などアドレスや範囲が結果になる場合にそれっぽく表示。1+2は計算するけど1+2+3は面倒そうなので後回しにしたら、関数のパラメータも3個あるとダメだったし、アドレスに小文字を使うと変で、=A1:C7と範囲値の場合も中途半端で、セルを空欄にするとundefined(ある意味正しいけど)になってたので、見直し。確認のためにindexedDBを見ると、画面からデータを削除するとレコードの値が空になってたダケだったのでレコードごと消える様に修正。indexedDBのデータが同じドメインなら共有だがdatabasename属性に置換文字列を追加し名前で切り分け可能にした。

ps.2024/3/21

セルデータをJSON風化のmin.max関数の修正漏れ。(v)?v:”でnullやundefinedが表示されない様にしたら0も表示しなくなっていた。date型で日付と時刻の入力でDate型のformatを実装、boolean型の入力、now(), today()を実装。論理式を実装しないとif関数が作れない。round()とvlookup()はコードしてみたダケ。

ps.2024/3/24

a1形式のaddress1をaddressに。関数をグループ管理に変更中。後、細かいバグ。セル上にマウスを移動するとデバッグ用ツールチップを表示。

ps.2024/3/25

データ型をstringをtextに変更。F2で入力すると直前のセルで入力した内容がチラっと見えていた。

パーサーコンビネータに論理式等を追加したのでexpr(計算式の根っこ)の呼び出しが激増しasync、 awaitも多く連動計算の待ち時間が長くなってきてsetTimeoutで連動計算を呼び出していることもああり、僅かなミスでスタックオーバーフローやメモリ不足に陥りやすくなっている。

ps.2024/3/26

計算式のパース(parse)と計算(calc)を分離、indexedDBの式はパース結果(トークン風)。これでA1とR1C1の切り替えができそう。少し寄り道してmapメソッドをBNFテキストからパーサーコンビネータの文法部分を作るようにしてしまいたい。今は this.map(this.seq( のようにthis と括弧が多いから。

ps.2024/3/27

演算子*と/が+ーな計算をしていた。関数をexprメンバーに文字で格納していた名残で式のトークンを配列で格納していたがname,parametersに分けた。式を再入力するとexprがname,parametersに変わる様に暫くは式の文字列化と計算でexprも併用させる。アドレス型のaddressを配列に変更したのでそのうちアドレス範囲型を無くす予定。



[javascript]スクロールバーのサイズ

気になると夜しか眠れないスクロールバーのサイズ

Windowsでは画面の解像度などで異なるし、古いXP等では「画面のデザイン」でも変更可能だった。

なので状況に応じて調べるしかない。

HTMLElementにスクロールバーを付けて

div.sample {
    overflow-x: scroll;
    overflow-y: scroll;
}

スクロールバーのサイズ = スクロールバーを含む領域 ー スクロールバーを含まない領域

から求められる。

具体的には

let div = document.querySelector('div.sample');
let rectDiv = div.getBoundingClientRect();
let scrollbarSize = {
    width: rectDiv.width - div.clientWidth,
    height: rectDiv.height - div.clientHeight
};

注意点としては

いつでも正しい数値が得られる訳では無いので

scrollbarSizeのwidthかheightのいづれかが0の場合は再トライが必要。

                //  タイミングによってはwidthが0になるため再処理
                if (scrollbarSize.width === 0 || scrollbarSize.height == 0) {
                    return this.resizeDivTable(0);
                }

ブラウザのDevTools上で【F5】を押すと無限ループしやすいので再トライ数を制限した方がいい。



[javascript]childrenを全削除したい

古くからある問題に列挙されたリストを全削除したい場合・・・

for (let index = 0; index < shadow.children.length; index++) {
    shadow.children[index].remove(); あるいは shadow.removeChildren(shadow.children[index]);
}
console.log('remain object:' + shadow.children.length;
>> remain object:1.

と残ってしまいがち。

リストを先頭から順に削除しているのでイテレータの思いとのすれ違いが起きてしまう。

while (0 < shadow.children.length) {
    shadow.children[0].remove(); あるいは shadow.removeChildren(shadow.children[0]);
};
console.log('remain object:' + shadow.children.length + ".");
>> remain object:0.

この様にイテレータを使わないのが正解だけど、[0]のマジックナンバーは~とか云われそう。

while (0 < shadow.children.length) {
    shadow.firstChild.remove();
};
console.log('remain object:' + shadow.children.length + ".");
>> remain object:0.

の方がいいかな?

for (let ch in shadow.children) {
    ch.remove();
>> Uncaught TypeError TypeError: ch.remove is not a function
};
console.log('remain object:' + shadow.children.length + ".");

とすると、最後の方でchに’length’が割り当てられて、TypeErrorが起きてしまうのはお約束。

for (let ch of shadow.children) {
    ch.remove();
};
console.log('remain object:' + shadow.children.length + ".");
>> remain object:1.

で、エラーは起きないけど、やはりイテレータとのすれ違いで消し残りが出てしまう。

今なら

Array.from(shadow.children).forEach(ch => ch.remove());
console.log('remain object:' + shadow.children.length + ".");

が良さそうかな?fromの名前がちょっとアレだけど。(笑

shadow.innerHTML= '';

やっぱりコレか!

でもいっぱいエレメントが入っていると遅いらしい。

var clone = shadow.cloneNode( false );
>> Uncaught DOMException DOMException: Failed to execute 'cloneNode' on 'Node': ShadowRoot nodes are not clonable.
shadow.parentNode.replaceChild( clone , shadow );

親エレメントの方ですげ替えした方が速そうだけど・・・

shadowでは無理



[javascript]QuerySelectorAllの妙な動き

<html>
・・・
<body>
  <br>
  <br/>
  <br>
  <br/>
</body>

</html>

これを

document.querySelectorAll('br')

すると

Array.from(document.querySelectorAll('br')).map((br)=>br.outerHTML)
0:"<br>"
1:"<br>"
2:"<br>"
3:"<br>"
length: 4

となる。

しかし自作タグ(カスタムなタグ)の場合は、

<html>
・・・
<body>
    <sample-element></sample-element>
    <sample-element />
    <sample-element></sample-element>
    <sample-element />
</body>

</html>
Array.from(document.querySelectorAll('sample-element')).map((sample)=>sample.outerHTML)
0:"<sample-element></sample-element>"
1:"<sample-element>\n    <sample-element></sample-element>\n    <sample-element>\n\n\n</sample-element></sample-element>"
2:"<sample-element></sample-element>"
3:"<sample-element>\n\n\n</sample-element>"
length: 4

ブラウザのDevToolsも同様なので、HTMLファイルの読み込み時点で【そのように解釈】されているので仕方が無いんだけどね。(笑

このため、ちゃんと「Autonomous custom element(自律カスタム要素)」として書いても結果は変わらない。(ハズ



[javascript]returnした後に

return  (fValue)?[nCount[name]= 1:nCount[name];
nCount[name]++;

以前はreturn文は戻り値をスタックするだけで、後ろに処理があれば実行してくれた。(時期もあった

でも、今はそんなことは無いので・・・

return (fValue)?[nCount[name]= 1, nCount[name]++][0]: nCount[name]++;

と変な書き方をすることがマレにあるけど

return (fValue)?nCount[name]= 1: ++nCount[name];

の方がマシかな



[javascript]関数の途中のパラメータを省略したい

例えば・・・

let result=function('text',2);

2番目のパラメータを省略するには

let result=function('text');

と書けるけど、1番目のパラメータを省略すると

let result=function(2);

2番目のパラメータのつもりが1番目のパラメータとして渡ってしまう。

そこで、

let result=function(,2);

BASICでは省略したい時は書かないことで通じるので

arg0: SyntaxError: Unexpected token ',' {stack: 'SyntaxError: Unexpected token ','', message: 'Unexpected token ',''}

とエラってしまう。

javascriptでは関数の引数は指定しなければ undefined になるので、

let result=function(undefined,2);

とundefinedを引き渡せば、実質的に1番目のパラメータを省略された様に関数側で処理してくれる。



[javascript]contenteditable

contenteditable=”yes”属性付きのdivタグはブラウザ上で直接編集できる。

しかし、子エレメントの構造が複雑化すると改行位置などが把握しにくいので自動的に改行させないほうがいいと云う記事を見つけたので、試しに作ってみたカスタムエレメント版も。

確かにメンドクサイw

コードしなくてもcontenteditable=”yes”設定で、太文字(Ctrl+B)、アンダースコア(Ctrl+B)、イタリック(Ctrl+I)でテキストを自動的に修正してくれるし、勿論HTMLの中身もどんどん変わっていく。

とりあえず、

  1. divタグからpreタグに変更。※半角空白を表示できるから
  2. inputやkeydownイベントのログ表示
  3. キャロット移動の行背景色変更や「m行m列」※範囲指定時も含む
  4. cssで行番号表示
  5. javascriptソースコードの着色※javadoc部は別処理
  6. ctrl+/でそれっぽく行コメントのトグル(ON/OFF)処理
  7. HTMLソースコードの着色も追加。※画面下の言語で切替
  8. 着色した結果をHTMLファイルでダウンロード。
  9. 言語選択状態に応じたサンプルソースを貼れるようにした。
  10. ソースを分割。
  11. バグ修正
    • 2024/2/17
      • 【彩色】押すと空行が消えてる。
        • CRクラスのspanのmarge属性が未設定だった。
      • ソースが空または1トークン分のテキストしかない場合にエラっていた。
        • 気にしてなかった。
      • 右端に縦にスクロールバーが2つ。
        • 何度調整しても不意に再現する。(謎
      • 01234と数字を入力すると先頭の0が数値扱いにならない
        • 空白のパターン(\x20+)のつもりが(x20+)になっていたせい。
          • ミスってるのに空白判定ができていたのは謎

相変わらず強制的にキャッシュされるのでソースを更新してもちゃんと反映されない。

対処方法1:ブラウザのDevToolのネットワークでキャッシュを無効化。

それでもiframe内のファイルはキャッシュされたままなので

対処方法2:そのiframe.htmlのソースの空っぽな画面だったりコンソールにエラーが表示されていたら・・・【F5】でソースを表示。

大体これで最新になる。

未解決や重複のエラーが出ていたら上の方法でキャッシュをクリアできる。(ハズ

それでもダメな時はブラウザの履歴をごっそり消すしかない様です。

それにしても、予想外の動きをするので大変だわ。(笑

ps.2024/3/21

カスタムエレメント版のHTMLのサンプルが文字化けしていたので修正。



[javascript]サンプルを実行

jsファイルにコードを書かないと簡単なサンプルも実行できない。

勿論、そんなものはオンラインにあるけど、そういうところにソースをパリッと貼り付けるのはご法度なケースも多いので・・・

パパッとjavascriptのサンプルをローカルなHTMLで実行するサンプルを作ってみた。

但し、new Functionからはグローバルな変数が見えてしまう。

それだけなら特に危なくないけど

某MS操縦士のセリフ:俺を踏み台にしたぁ~~~

なんてコードを書かれても不思議ではない碌でもない世の中なので、

ソースだけ公開。

<DOCTYPE html>
  <html lang="ja">

  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>javascriptを実行させるサンプル</title>
    <link href="jstest.css" media="all" rel="stylesheet" type="text/css" />
    <script src="createelement.js"></script>
    <script src="download.js"></script>
    <script src="jstest.js"></script>
    <link href="loader.css" media="all" rel="stylesheet" type="text/css" />
    <script src="loader.js"></script>
  </head>

  <body>
    <!-- loader begin -->
    <div id="js-loader" class="loader">
      <p>***</p>
      <p id="js-loader-text"></p>
      <p>***</p>
    </div>
    <!-- loader end -->
    <textarea class="jsText" id="jsText1" scrolling="auto">
// sample code
return new Promise((resolve, reject) => {
  let result = new Array(10);
  for (let index = 0; index < 10; index++) {
    result[index]=index + 1;
  }
  console.log(result); console.log(10);
  console.log('aaaaaaaaaaaaaa');
  console.log({log: 'data=> result=data' });
  resolve();
});
</textarea><br />
    <button type="button" id="executeJsText">実行</button><br />
    <div class="status" id="status"></div><br />
    <textarea class="jsResult" id="jsResult1" scrolling="auto"></textarea><br />
    <button type="button" id="downloadJsText">javaScriptダウンロード</button><br />
    <button type="button" id="downloadResult">結果ダウンロード</button><br />
  </body>

  </html>
@charset "UTF-8";

textarea.jsText {
    width: 95%;
    /* 親要素の幅と合わせる */
    height: 200px;
    resize: both;
    border: 2mm ridge rgba(211, 220, 50, .6);
}

textarea.jsResult {
    width: 95%;
    /* 親要素の幅と合わせる */
    height: 100px;
    resize: both;
    border: 2mm ridge rgba(211, 220, 50, .6);
}
let CRLF = '\r\n';
window.addEventListener('load', () => init());
/**
 * 初期化処理
 */
const init = () => {
    // ボタンクリックイベントの登録
    document.querySelector('#downloadJsText').addEventListener('click', () => downloadText('jsText1'));
    document.querySelector('#downloadResult').addEventListener('click', () => downloadText('jsResult1'));
    document.querySelector('#executeJsText').addEventListener('click', () => dexecuteJsText('jsText1', 'jsResult1'));
    loaderClose();
};
/**
 * サンプルをエンコーディングしたファイルをダウンロードする
 */
const downloadText = async (target) => {
    try {
        loaderOpen();
        try {
            let dateBegin = Date.now();
            displayStatus(`${target}ダウンロード処理中`);
            let jsText = document.querySelector(`#${target}`).value;
            download(jsText, `${target}.js`, "text/plan");
            displayStatus(`${target}ダウンロード完了。処理時間:${dateDateBetween(dateBegin, Date.now())}`);
        } catch (ex) {
            alert(`${target}ダウンロード異常終了${CRLF}${ex}`);
        }
        loaderClose();
    } catch (ex) {
        alert(ex);
    }
};
/**
 * 実行する
 * @param {*} jsText 
 * @param {*} jsResult 
 */
const dexecuteJsText = (jsText, jsResult) => {
    let jsTextElmText = document.querySelector(`#${jsText}`).value;
    let jsResultElm = document.querySelector(`#${jsResult} `);
    let dateBegin = Date.now();
    try {
        loaderOpen();
        try {
            displayStatus(`${jsText} 実行処理中`);
            let func = new Function('console', jsTextElmText);
            let result = [];
            let radix = 10;
            let rc = func({
                log: (data) => {
                    if (data === undefined) {
                        data = 'undefined';
                    }
                    if (data === null) {
                        data = 'null';
                    }
                    switch (typeof data) {
                        case 'object':
                            data = JSON.stringify(data);
                            break;
                        case 'string':
                            break;
                        default:
                            data = data.toString(radix);
                            break;
                    }
                    result.push(data);
                }
            });
            new Promise((resolve, reject) => {
                if (rc instanceof Promise) {
                    displayStatus(`${jsText} 実行中 Promise.then待ち`);
                    rc.then(() => {
                        resolve(result);
                    });
                } else {
                    resolve(result);
                }
            }).then((result) => {
                jsResultElm.innerHTML = result.join(CRLF);
                displayStatus(`${jsText} 実行完了。処理時間:${dateDateBetween(dateBegin, Date.now())} `);
            });
        } catch (ex) {
            displayStatus(`${jsText} 実行異常終了。処理時間:${dateDateBetween(dateBegin, Date.now())}${CRLF}${ex} `);
        }
        loaderClose();
    } catch (ex) {
        alert(ex);
    }
};
/**
 * ステータスを表示する
 * @param {string} text 
 */
const displayStatus = (text) => {
    let divStatus = document.querySelector('#status');
    divStatus.innerHTML = text;
    let divLoading = document.querySelector('#js-loader-text');
    divLoading.innerHTML = text;
};
/**
 * 時刻差のテキストを作成する
 * @param {Date} dateBegin 
 * @param {Date} dateEnd 
 * @returns 
 */
const dateDateBetween = (dateBegin, dateEnd) => {
    return `${(dateEnd - dateBegin) / 1000} 秒`
};

createelement.js
download.js
loader.css
loader.js

は先の記事

デスクトップに貼ったJSからSJISのCSVをダウンロードさせてみる【その後】サンプル

と同じなので割愛。

ミソはjsソース中に

console.log(result);

と書かれたらどうやって取り出すか?

である。

今回は

let result = "";
let func = new Function( 'console', jsテキスト);☚jsテキストの処理に対してパラメータ'console'を宣言
func({log: (data) => ☚{...}内がconsoleオブジェクトとして扱われ、result変数にセットされる。

これでjsテキストの中でconsole.logメソッドがそれっぽく動き出す。

jsコードがwindowやdocumentを直接参照するのが嫌なら

let func = new Function( 'console', 'window', 'document', ..., jsテキスト);
func(
{log: (data) => result.push(data);},
{console: (data) => xxxx;},
{body: (data) => xxxx;},
);

とパラメータを追加し、見せたい情報を渡してサンドバック風にできるだろう。




top