gologiusの巣

プログラミングなどの技術メモです。誰かの役に立てるとうれしいです。

恐怖!Edgeで全角入力時に重複して文字入力されてしまう問題(IME,Edge,Chrome)

問題

IMEで全角文字入力→確定せずに別の場所にフォーカスを移すと、文字が重複して入力される。

文字面だとわかりづらいので、下記のGIFを見てもらったほうが早い。

「あああ」 の入力が二倍になって「ああああああ」になっている。

この問題は、下記テストページで体験できるようにしてある。

この恐怖を体感してみてほしい。

IMEテスト

発生する条件

どうも下記の状態をすべて満たすと発生する模様

  • ブラウザが Edge Chrome (=つまりChroniumのブラウザ。Firefoxだと発生しない)
  • IMEが Microsoft IME (Google IMEだと発生しない。)
  • 入力欄(input type=textとか)で、フォーカス時に入力欄を全選択している。 jsとHTMLだと下記のような感じ。
  • 全角入力の変換確定前に、別の入力欄にフォーカスを移動させる
  • フォーカスを全選択していても、一度全選択を解除した後だと発生しない。

なぜ発生するのか

Microsoft IMEのバグ?(相性が悪い?)の模様。

フォーカス時に全選択する、というUIがあまりないことが原因で、あまりネットにも情報がない。

解決方法

  1. フォーカス時の全選択をやめてしまう。これで100点取れる。

  2. 該当項目が半角入力のみを想定されているのであれば、 inputmode を指定して、フォーカス時に半角入力モードになるようにしてあげれば、 発生確率は下げられる。

※ご参考URLの 全選択解除→時間をおいて再度全選択、の方法だと解消されなかった。

ほかの解決方法があるなら、教えてください

ご参考

input入力中に変換未確定の状態でフォーカス移動すると文字が二重入力される

Chrome(Windows版)でinput要素への入力中に変換未確定の状態でフォーカス移動すると文字列が二重入力される件について|さとう

余談(GIF動画)

GIFを撮影する方法ですが、

Win11 の Snipping Tool で動画撮影 → AdobeのサイトでGIF化 して作っています。

MP4をGIFに高速変換。フリー、高画質、アプリ不要|Adobe Express

ExcelからINSERT,UPDATEのSQLつくるの面倒だからWEBツールつくった

結論

これつくった

INSERT・UPDATE SQL作成くん

使い方

ExcelからINSERT・UPDATEしたいデータ(表)を

Ctrl+A → Ctrl+C で表をコピー

このツールの「TSVテキスト」というところに張り付ける

SQLができる

暇なら後日使い方GIFでもつけておこう。

習うより慣れてくれ。もともと個人で使う用として作ったんや。

ポイント

セキュリティ意識高いから、JavaScript(フロントエンド)だけで動く。

サーバーへの通信は発生しないから、機密情報もガンガン入力して問題ないはず。

エッチな広告とかも出ない。

それでも心配なら、GitHubからソースDLして、各自のローカルで動かしてね。

背景

Excelでメンテ用データつくるやん?

これを数式をコネコネしてSQLつくろうとするやん?

めっちゃしんどい数式ができる+汎用性ないやん?

作った。

レガシー環境のJavaで戦うための、SQL→Java変換器

背景

Javaで、複数行のSQLを、ソースに埋め込みたい場合があると思う。

Java15 からは テキストブロックという機能で、 複数行で文字列定数を定義するのが簡単にできるので、 あまり苦労しない(はず)

[Java]テキストブロックってかなり便利だね #java17 - Qiita

これができない、 古いJava環境で開発せざるをえない場合に使える、 JavaScriptで稼働するツールを作った。

つくったもの

Java⇔SQL変換

情報漏洩にうるさい方々のために、HTML+CSSJavaScriptのみで動作している。

使い方

select 
 id
from hogehoge
where id='123';

を張り付けると

StringBuffer sb = new StringBuffer();
sb.append("select ");
sb.append(" id ");
sb.append("from hogehoge ");
sb.append("where id='123'; ");

が出力される。

逆も用意している。 変数名はデフォルトsbにしているが、好きに変えられる。

技術詳細

今のJavaはどうか知らないが、昔のJavaは 文字列連結を

String hoge  = "a" + "b" + "c";

のように、+を使って連結すると、すごく遅いと言われていた。

解決方法としては、 StringBufferを使えと言われていたので、 それを踏襲している。

PHP+SQLiteで、UPDATE文を実行すると、エラーを吐かずになぜか0 がセットされる

総括

SQLiteにおいて、SQLの書き方によっては文法エラーにならずに、 意図しない値がDBにセットされる模様。

※見解が間違っていたらごめんなさいm( "m)

背景

下記のようなPHP関数を実行する。

実行結果としては、特定IDのレコードのカラム値がUPDATEされてほしい。

function update($id, $manual_label1, $manual_label2)
{
    var_dump($id, $manual_label1, $manual_label2);

    $pdo = new PDO(DB_DSN);
    $pdo->beginTransaction();

    $sql = "
        update TRAINING set 
                MANUAL_LABEL1 = :manual_label1 
            and MANUAL_LABEL2 = :manual_label2
        where ID = :id
    ";
    $stmt = $pdo->prepare($sql);
    $stmt->execute(
        array(
            ":manual_label1" => $manual_label1,
            ":manual_label2" => $manual_label2,
            ":id" => $id,
        )
    );

    $pdo->commit();
    return;
}

問題

  • 処理は成功する(エラーは出力されない)
  • 意図した値でUPDATEされない。 MANUAL_LABEL1 に 0 が入る。

原因

聡明な方ならお気づきだろうが、SQLが間違っている。

andではなく , でしたと。

typoなんだ許してくれ

 update TRAINING set 
                MANUAL_LABEL1 = :manual_label1 
            and MANUAL_LABEL2 = :manual_label2
        where ID = :id

 update TRAINING set 
                MANUAL_LABEL1 = :manual_label1 
               ,MANUAL_LABEL2 = :manual_label2
        where ID = :id

というよりちゃんとロールバックまでやるべきだが、 どうも例外吐いてすらいなさそう

    try {
        $pdo = new PDO(DB_DSN);
        $pdo->beginTransaction();

        $sql = "
            update TRAINING set 
                    MANUAL_LABEL1 = :manual_label1 
                   ,MANUAL_LABEL2 = :manual_label2
            where ID = :id
        ";
        $stmt = $pdo->prepare($sql);
        $stmt->execute(
            array(
                ":manual_label1" => $manual_label1,
                ":manual_label2" => $manual_label2,
                ":id" => $id,
            )
        );

        $pdo->commit();
    } catch (PDOException $e) {
        $pdo->rollBack();
    }

感想

SQL文法エラーで落ちてくれないですかね・・・

proxy環境下において、Python requests がグローバルIP制限に引っかったときのメモ

結論を先に書くと、「プロキシ用pacファイルまでちゃんと意識してコード書いてる?」

問題

プロキシ環境化において、Python requestsパッケージを用いて、APIを叩きたい

前提として、

  • API、管理画面はグローバルIP制限を施している
  • APIからレスポンスは返ってくるが、「許可されていないIPです」と出る
  • ブラウザ側から管理画面には普通にアクセスできる(=インフラ的な設定・申請は問題ないはず)
  • IP制限を外すと、PythonからAPIは叩ける

コード例

import requests

response = requests.get('APIのURL', proxies={
    'http' : 'http://example.com'
    'https' : 'http://example.com'
})

GIP制限外すとつながるので、コードにも問題はなさそう。

原因

OS側でpacファイルが指定されていた。 そのpacファイル内にて、アクセス先のサイトに応じて 使用するプロキシサーバーを振り分けていることが判明。

私のコードでは、通常利用するプロキシサーバーを指定しており、 そのプロキシサーバーのIPが可変だったため、 グローバルIPの制限にひっかかっていた。

対策

方法1 PACファイル内部を見て、アクセスしようとしているAPIのURLが どのプロキシサーバーを使うかを確認し、そのプロキシサーバーを記載する

方法2 pypacというパッケージがあるので、 プロキシサーバーではなく、 PACファイルを指定→pacファイル側でプロキシサーバーを指定するようにする

使い方は下記参照

PyPAC: Proxy auto-config for Python — PyPAC 0.16.3 documentation

PACファイル確認方法の一例

win11の例

コントロールパネル→インターネットオプションとかからでも確認できると思いますよ。

総括

プロキシと言われたときには、プロキシサーバーだけではなく、 その手前の.pacファイルまで意識しましょう。

終わり

WYSIWYGエディタをWEBページに埋め込めるライブラリ比較

背景

WEBページの管理画面でHTMLを直接書かせたくないので、 WYSIWYGエディタを埋め込みたいと思った。

WYSIWYGエディタの例(はてなブログの記事改廃画面)↓

javaScriptでいい感じのあるだろうと思ってググると案の定存在した。

調査比較

前提:

  • 無料
  • HTML+CSS+JSで埋め込める

三つほど見つかったので、触ってみた特徴を記載していく

一応簡単に実装もしたので、下記に配置しておく。

作成した比較サイト

WYSIWYGエディタライブラリ比較

ソース

gologius.github.io/test/test_WYSIWYG at master · gologius/gologius.github.io · GitHub

以下個人の感想。上記の比較サイトなども使って、自分にベストなものを選んでほしい。

trix.js

  • ○ 埋め込むだけなら一番楽。 jsも書かずに済むし、input への反映も楽。すでにinputタグがあるなら、既存コードにすぐ埋め込める
  • ○ undo redo ボタンがある
  • × 文字サイズとか色が設定できない
  • × 設定APIなどがないので、カスタマイズができない?

quill

  • ○ 機能豊富。文字の色なども変えられる。画像もbase64形式で埋め込める
  • ○ JSの設定変数で、表示するボタンなどを制御できる
  • × カスタマイズ方法が独特。 文字サイズを任意指定できないので改造しようと思ったら、CSSの改造まで必要。

POSTでHTMLを飛ばそうとすると、下記のように少し工夫が必要になる。 詳細は上記URLの quill_test.js 参照。

    var editor_input = document.getElementById(input_id);
    var editor_output = document.getElementById(output_id);
    
    var quill = new Quill(editor_input, baseOption);
    
    //inputにセットされている生HTMLを、変換してエディタに反映させる
    let initialContent = quill.clipboard.convert(editor_output.value);
    quill.setContents(initialContent);
    quill.root.innerHTML =  editor_output.value;

    //エディタ入力時に、input に生HTMLをセットする 
    quill.on('text-change', function(delta, oldDelta, source) {
        var editorHtml = editor_input.querySelector('.ql-editor').innerHTML;
        editor_output.value = editorHtml;
    });

Editor.js

  • ○ 表が挿入できる
  • × ブロックスタイル?と呼ばれる最新の見た目なので、使うのに慣れが必要かもしれない。通常のHTMLエディタを想定していると、現場の人に「なんじゃこれ」と言われるかも。
  • × ツールバー?を固定できない
  • × 行(ブロック)単位でしか、スタイル変更できない

感想

個人的なおすすめは quill

が、ベストだとは思わない*1ので、ほかにいいのがあれば教えて。

*1:quillは文字の大きさ変更がうまくいかない。HTMLも独自クラスで生成されるので、実際に反映しようとするとCSSの修正も必要になる。

stackoverflow.com

バニラJavaScriptで頑張ってファイルアップロード処理を書く話

JSのライブラリを利用せずに、モダンなファイルアップロード処理を書こうとすると 割と色々コーディングしないといけないということが分かったのでメモ。

ネットに転がっているのは、

  • D&DでUPする場合と、ファイルダイアログからUPする場合を共存させる考慮
  • ファイルを追加でUPする場合の考慮
  • 特定のファイルを削除する場合の考慮
  • UPしたファイルを一覧で見たい場合の考慮

などが漏れているサンプルが多かったので、参考になれば幸い。

前提

  • JQueryは使わない
  • ライブラリは使わない(いわゆるバニラJS)Vueとか使ったほうが本当はきれいなんだけどね。
  • IEなんて考慮しない
  • 以下では、ある程度JavaScriptを分かっている人向けの適当な解説しかしない(メモなので)

サンプルページ

ファイルUPすると、リストでファイルが表示される。

https://gologius.github.io/test/file_upload/upload.html

ソース

下記参照。

gologius.github.io/test/file_upload at master · gologius/gologius.github.io · GitHub

ポイント1

input タグを二つ用意する。 片方がファイルを仮UPする用で、チェックが通ったものを、もう一方のinputにセットする。

HTML(抜粋

 <form action="./upload.php" method="post" enctype="multipart/form-data">
        <div id="dd_area" class="drop_area_off">
            <div>
                ここにファイルをドラッグ&ドロップ
                or
                <input type="button" value="手動でファイル追加" onclick="clickUpload();" >
            </div>

            <input type="file" id="upload_files" name="upload_files[]">
            <input type="file" id="tmp_files" name="tmp_files" multiple>
        </div>

        <div>アップロードしたファイル↓</div>
        <table class="file_list">
            <tbody id="upload_files_list" >
            <!--実際の要素はJS側から挿入されるで-->
            </tbody>
        </table>    
    </form>

Javascript(抜粋

function upload_files(new_files) {
    
    //変数準備
    let sum_size = 0;
    let file_count = 0;
    let file_name_list = [];
    let work_transfer = new DataTransfer(); //通常の配列ではなく、datatransferでファイル管理する必要あり。

    //現時点でUPされているファイルを走査する → 配列に格納
    let now_files = document.getElementById("upload_files").files;
    for (var i = 0; i < now_files.length; i++) {
        var file = now_files[i];
        work_transfer.items.add(file);
        
        sum_size += file.size;
        file_count += 1;
        file_name_list.push(file.name);
    }

    //アップロード対象のファイル群を走査 → 配列に格納
    for (var i = 0; i < new_files.length; i++) {
        var file = new_files[i];
        work_transfer.items.add(file);
        
        sum_size += file.size;
        file_count += 1;
        file_name_list.push(file.name);
    }

    //対象ファイルの走査が完了して用済みのため、tmp_filesはリセットする
    let elem = document.getElementById("tmp_files");
    elem.files = new DataTransfer().files;

    //UPしてよいか判定
    let errflg = false;
    if (sum_size > MAX_FILE_SIZE_MB * 1048576){
        alert(`ファイルサイズの合計値は ${MAX_FILE_SIZE_MB}MB です`);
        errflg = true;
    }
    if (file_count > MAX_FILE_COUNT){
        alert(`ファイルをUPできる最大個数は ${MAX_FILE_COUNT}個 です`);
        errflg = true;
    }
    //重複を削除したSetを生成。ファイル名が重複していると、Setの要素数がファイル数よりも減るので、それを利用する
    let uniqueSet = new Set(file_name_list); 
    if (file_count != uniqueSet.size) {
        alert("同じファイル名が既に存在します");
        errflg = true;
    }
    
    //UPしても問題ないと判断できれば、実際にファイルアップロードする
    if (errflg == false) {
        document.getElementById("upload_files").files = work_transfer.files; 
    }

    //表示更新
    update_file_list_html();

    return;
}

なんでこんなことをしているのかというと、 inputタグからファイルダイアログを開くと、そこにセットされていたファイルが上書きされてしまう。 上書きされてしまうので、追加でのファイルUPができないのである。

仮UPすることで、ファイル全数のサイズや個数のチェックもできるので、 仮UPしたほうがいろいろ都合もよい。

ポイント2

inputタグが持つfiles要素は、インデックスを指定して参照はできるが、 「直接の加工はできない」

できない例

 let now_files = document.getElementById("upload_files").files;
 for (var i = 0; i < now_files.length; i++) {
        now_files[i]  =  tmp_file;// ←これはできない
 }

よって、datatransferを丸ごとfiles にセットしてあげる必要がある。

できる例

 let now_files = document.getElementById("upload_files").files;
 let work_transfer = new DataTransfer();
 for (var i = 0; i < now_files.length; i++) {
       work_transfer.items.add(tmp_file);
 }

document.getElementById("upload_files").files = work_transfer;

特定のファイルを削除する場合には、「特定のファイルを除いた」datatransferをセットすれば、実現できる。

補足

もっとよい方法があれば教えて。