らばーいもっきんぐ

プログラミング等々についての覚え書き

【JavaScript】inputフォームのmaxlengthで全角文字による入力超過を制御する方法

はじめに

HTMLのinputフォームでmaxlengthを指定した場合に、半角英数字の場合は上限値を超えて入力はできないが、全角文字の場合は上限値を超えて入力できるように見えるため、これをJavaScriptで制御する方法について検討しました。

事象

Edit fiddle - JSFiddle - Code Playground

<form>
    <label>MaxLengthSample</label>
    <input type="text" maxlength=3 />
</form>

上記のようなフォームがあった場合、maxlengthのとおり入力できる文字数は3文字になる。
半角英数字の場合は3文字まで入力でき、それ以上入力してもフォームの値は変更されない。
しかし、全角で入力した場合は3文字以上の入力が可能なように見える。
全角で3文字以上入力した場合も、入力値を確定した時点で上限値を超えた部分は切り捨てられる。
また、Chromeを使用した場合はブラウザ側の機能として、何文字超過しているかの表示がされる。

Chromeの機能により何文字超過しているのかが表示された画面キャプチャ
Chromeの機能により何文字超過しているのか表示される

全角入力時でも超過部分は切り捨てられるため機能的には問題無いが、半角入力時との動作の違いが気になったり、確定を押したタイミングで超過部分が切り捨てられるのは使用感がよろしくなさそう。
そこで、全角入力時も半角入力時と同じ挙動にできないか検証してみた。

結論

結果的には半角入力時とまったく同じようには実装できなかった。
全角の場合でも上限値を超えて入力できないようにすることはできるが、表示や挙動を完璧に一致させることができなかった。
おとなしくブラウザの標準機能かライブラリなどを使用するのが吉かも。

検討案

案1(sliceメソッドで超過分を削除)

https://jsfiddle.net/em8taw0q/1/

inputタグにaddEventListenerでinputイベントを付与し、入力された文字数を取得する。
maxLengthの値を超えて入力された場合はsliceメソッドで超過分を削除してvalueに入れ直す。

<form>
    <label>MaxLengthSample</label>
    <input id="hoge" type="text" maxlength=3 />
</form>
const inputForm = document.getElementById("hoge");
const maxLength = parseInt(inputForm.getAttribute("maxLength"), 10);

inputForm.addEventListener('input', function(e) {
    const inputValue = e.target.value;
    if (inputValue.length > maxLength) {
        e.target.value = inputValue.slice(0, maxLength);
    }
});

メリット

  • 実装がシンプル

デメリット

  • 最大まで入力した状態でも、途中に文字を挿入できてしまう
  • 最大値を超えてに入力はできないが、変換候補が一瞬表示されてしまう
  • 漢字を入力する場合に、変換前の文字数でカウントされるため、文字数によっては漢字での入力ができなくなる

案2(保存した入力値で上書き)

https://jsfiddle.net/em8taw0q/

入力された文字を一時的に保存しておき、上限値を超えて入力された場合は最後に保存された文字で上書きする方法。

<form>
    <label>MaxLengthSample</label>
    <input id="hoge" type="text" maxlength=3 />
</form>
const inputForm = document.getElementById("hoge");
const maxLength = parseInt(inputForm.getAttribute("maxLength"), 10);
let prevValue = '';

inputForm.addEventListener('input', function(e) {
    const inputValue = e.target.value;
    if (inputValue.length > maxLength) {
        e.target.value = prevValue;
    } else {
        prevValue = inputValue;
    }
});

メリット

  • 最大まで入力した状態でも、途中に文字を挿入できない(半角入力時と同じ挙動)

デメリット

  • フォームが複数ある場合は、一次保存用変数の状態管理が煩雑になる
  • 最大値を超えてに入力はできないが、変換候補が一瞬表示されてしまう
  • 漢字を入力する場合に、変換前の文字数でカウントされるため、文字数によっては漢字での入力ができなくなる

まとめ

変換候補の非表示や、漢字変換時の字数制限は対応できなかった。(何かしら手がありそうだけど。。。)
基本的にはブラウザの標準機能で対応が良さそう。
そもそも、文字数超過した場合に切り捨ててしまうのはユーザーに誤解を与える可能性が高い(入力できたと思ってしまう)ので、よくある超過した場合はフォームを赤反転してメッセージを表示するとかが最善な気がします。

【資格】HTML5プロフェッショナル 認定試験 レベル2(Ver2.5)

はじめに

先日、HTML5プロフェッショナル 認定試験 レベル2(Ver2.5)に無事合格できたため、学習方法などについてメモしておきます。

試験概要

html5exam.jp

HTML5プロフェッショナル 認定試験にはレベル1とレベル2が存在し、レベル1はHTMLとCSS、レベル2はJavaScriptがメインになっています。

レベル2の認定条件は

HTML5 レベル2試験に合格し、かつ有意なHTML5レベル1認定を保有していること。

となっているため、レベル1も取得する必要があります。

試験は全国にあるピアソンVUEの試験会場か自宅や職場から受験ができます。

試験日は試験会場に空きがあれば平日、休日関係なく受験することができます。

自分はピアソンVUEから申し込みをして試験会場で受験したのですが、この試験の良いところは一度申し込みをした後でも「予約した受験日時の24時間前」までであれば受験日時の変更が可能な点です。

試験日直前にもかかわらず、全く勉強していないという状況はよくある事と思いますが、そんな場合でも試験日を後ろ倒しにしてしまえば何の問題もありません。

しかもこの受験日時の変更は何度でも可能です。(おそらく)

自分も受験日時を延ばしに延ばし、実際受験したのは最初の受験予定日から4ヶ月後でした。

受験料も16,500円とお高めのため、なるべく一発合格を目指したいところ。試験対策を万全にしてから臨むことをおすすめします。(自分のようにダラダラと先延ばしにするのは問題ですが...)

学習方法

下記の参考書と公式サイトの例題を使用して学習しました。

html5exam.jp

問題集についてはレベル1の学習時にもお世話になったスピードマスター問題集を使用しました。

一問ごとに解説が入るため隙間時間での学習がしやすく、解説も丁寧なためおすすめです。

ただし、Kindle版については解説に誤りがあり、試験直前まで誤った内容を覚えていました。正誤表を確認して誤りに気づいたのですが、その他にも結構な数の誤記載が未修正のため、購入された際はすぐに正誤表を確認した方が良いです。

最終的には3周くらい回しました。

公式サイトの例題もおすすめです。 問題集よりも難易度が高めで詳細な知識が求められますが、その分勉強になります。 あまり質の良くない問題も含まれているので、完璧にやるよりはある程度勉強が進んだところでサラッと確認するくらいで良いと思います。

試験は基本的に選択式ですが、稀にキーボードを使った記述式の問題も出るとのことで、メソッド名やプロパティ名など細かい部分まで学習しました。

また、可能であれば実際にコードを書いて実行するのが良いと思います。特にアニメーション系のAPIはあまり使用経験がなかったため、実際に書いてみることで動きのイメージがしやすくなりました。

Ver2.5

2021年4月に、HTML5 プロフェッショナル認定試験レベル 2 の出題範囲が改訂されました。

以前はVer2.0とVer2.5のどちらかを選択して受験できたようですが、2022年6月時点ではVer2.5のみ受験可能となっています。

改訂内容としてはES6(ECMAScript2015)への対応です。(詳細はこちらをご確認ください)

具体的には以下の内容が追加されています。

  • ローカル変数と定数
  • アロー関数
  • Promise と非同期関数
  • 可変長引数/残余引数
  • クラス
  • テンプレートリテラル (テンプレート文字列)

普段からJavaScriptを使っている方には聞き慣れた項目も多くあると思いますが、初学者の方や最新のJavaScriptに触れていない方には結構ボリュームのある内容だと感じました。

特に問題なのは、改訂されたVer2.5に対応した参考書が存在していないことです。(2022年6月時点、自分調べ)

自分が使用した問題集もVer2.0対応のものしかありませんでした。

人気の資格試験であれば出題範囲の改訂には即対応すると思いますが、試験自体の人気がないのか対応版が販売される予定もないため、上記内容については自分で調べていくしかありませんでした。

その際に参考にさせて頂いたサイトのリンクを貼っておきます。

qiita.com ES6で導入された各機能についてわかりやすく説明されています。

developer.mozilla.org ES6だけを取り上げたページはなさそうですが、詳細な知識を得るために最適です。

jsprimer.net MDNのサイトよりもわかりやすく解説されていると思います。特にPromiseの項目については詳細まで記載されていて勉強になります。

受けてみて

せっかくレベル1に合格したのでレベル2もという気持ちと、今までなんとなく使っていたJavaScriptの知識をきちんと学びたいと思い受験しました。

感想としてはレベル1よりも実務で役立つ知識が多いと感じました。 レベル1のHTMLやCSSについては、「このタグ一生使わなさそう」という知識が多く、正直あまり役立つ感じがしませんでした。(すでにほとんど忘れてしまいました)

レベル2はJavaScriptがメインのため実務での使用頻度も高く、レベル1よりはモチベーションを高めに保てました。 DOM操作についても普段はReactやVueにおまかせの部分が多いですが、裏側がどのような仕組みで動いているのかといった基本に繋がる部分を学ぶことができたので満足しています。

あまり人気のある資格ではなさそうですが、自分のようにJavaScriptの基本を学習したいという場合はおすすめの資格ですのでぜひチャレンジしてみてください。

【シェル】bashで改行区切りの複数行を標準入力する方法

はじめに

bashスクリプトを書く際に、改行区切りの複数行をキーボードから入力し、配列として扱う方法を調べたのでメモ。

やりたい事

2021-01-01 10:00:00  
2021-01-02 10:00:00  
2021-01-03 10:00:00  

のように、スペースを含む日時を複数行入力した際に、改行区切りで繰り返し処理を実行したい。

やり方

#!/bin/bash
IFS=$'\n'
input=$(</dev/stdin)

for v in ${input[@]}
do
  echo $v
done

解説

IFSとは

IFS(Internal Field Separator)は文字の区切りを設定するための環境変数bashではデフォルトで「スペース」「タブ」「改行」が設定されている。
今回の場合は入力文字列にスペースが含まれているため、スペースで区切られてしまうのを防ぐためにIFS=$'\n'を指定することで改行区切りに設定している。

/dev/stdinとは

/dev/stdinは標準入力(通常ではキーボード)を表しており、input=$(</dev/stdin)とする事でキーボードから入力された値を変数inputに代入する。

【Flutter】ReorderableListViewの並び替え時の背景色を変更する方法

はじめに

Flutter の ReorderableListView を使用して項目の並べ替えをする際に、デフォルトでは選択した項目の背景色が白色になっていますが、この背景色を変更する方法について調べました。

並び替え時に背景が白になる挙動
選択した項目の背景色が白のため違和感がある

結論

こちらの Issue にある通り、Theme ウィジェットでラップすることで解決することができます。

以下解説。

ReorderableListView とは

www.youtube.com

api.flutter.dev

ReorderableListViewはリスト表示された項目をドラッグアンドドロップで簡単に並び替えることができるウィジェットです。

並び替え可能なリストを簡単に実装できる便利なウィジェットですが、並び替える項目の背景色はデフオルトでは白色になっています。

そのため背景色が白色でない場合は、並び替えをする時だけ部分的に背景が白色になってしまい、全体のデザインを損なう可能性があります。

並べ替え時の背景色を変更する方法

return Theme(
    data: ThemeData(
        canvasColor: Colors.transparent
    ),
    child: ReorderableListView(...)
)

並べ替え時の背景色を変更するには、上記のように ReorderableListView ウィジェットを Theme ウィジェットでラップします。

さらに、ThemeDataのcanvasColorプロパティを使い、背景色を透過することで本来の背景色が表示されるようにします。

上書きする際に指定する色は Colors.transparent を指定しています。

これは背景を透過する指定のため、デフォルトでは白色になっているものを透過させ、本来の背景色を表示しています。

背景色を変更した後の挙動
背景色を変更することで自然な見た目になりました

Theme ウィジェットとは

flutter.dev

Theme は、テーマカラーやフォントスタイルを指定する事でアプリ全体のデザインを統一するために利用されます。 今回は Theme ウィジェットを使って部分的に上書きをする事で背景色を変更しています。

まとめ

Flutter の Theme はトップレベルで一度設定するだけと思っていましたが、部分的にスタイルを上書きする場合にも使える事が分かりました。 ただ、もっとスマートなやり方は無いのかなという気もします。(ReorderableListView のプロパティで設定できるとありがたい)

参考

https://flutter.dev/docs/cookbook/design/themes https://itome.team/blog/2019/12/flutter-advent-calendar-day12/

【JavaScript】Object.assignでオブジェクトのコピーを作る

はじめに

参照型のデータを扱う際に、Object.assignメソッドを使ってオブジェクトのコピーを作る方法を学んだのでその記録です。

Object.assignとは

MDNの定義は以下。

Object.assign() メソッドは、すべての列挙可能なプロパティの値を、1つ以上のコピー元オブジェクトからコピー先オブジェクトにコピーするために使用されます。戻り値としてコピー先オブジェクトを返します。

使用例はこちら。

let a = { x: 1, y: 2, z: 3 };
let b = { x: 5, y: 6, };
let c = { x: 7 };

//戻り値としてコピー先のオブジェクト(a)を返す
Object.assign(a, b, c); //=> { x: 7, y: 6, z: 3 }

//コピー先のオブジェクト(a)は変更される
//コピー元のオブジェクト(bとc)は変更されない
console.log(a); //=> { x: 7, y: 6, z: 3 };
console.log(b); //=> { x: 5, y: 6, };
console.log(c); //=> { x: 7 };

Object.assignは第一引数の値(a)がコピー先になり、その後に続く値(b,c)がコピー元になる。

コピー先の値とコピー元の値がマージされる。

マージする際に、コピー先とコピー元に同じプロパティが存在する場合は、コピー元の値が優先され、コピー先の値は上書きされる。

コピー元の値が複数ある場合(上記例のbとc)は後のものが優先される(bよりcが優先される)

上記の例では、変数aのyの値は変数bのyで上書きされ6になる。変数aのxの値は変数bのxの値で上書きされ、さらに変数cのxの値で上書きされるため、結果的に7になる。

Object.assingでコピーを作る

例えば以下のような場合。

let a = { x: 1, y: 2, z: 3 };
let b = a;

console.log(b); //=> { x: 1, y: 2, z: 3 };

a["x"] = 10;

console.log(a); //=> { x: 10, y: 2, z: 3 };
console.log(b); //=> { x: 10, y: 2, z: 3 };

JavaScriptでは配列やオブジェクトは参照型のため、aの値が変更されるとbの値も変更されてしまう。

そこで、aのコピーを作り、それを利用すればbに影響を与えずに済む。 コピーを作るには以下のようにする。

let a = { x: 1, y: 2, z: 3 };
let b = a;
// aのコピーを作り、変数cに代入する
let c = Object.assign( {}, a );

// 変数cにはaの値がコピーされている
console.log(c) //=> { x: 1, y: 2, z: 3 };

// 変数cの値を変更する
c["x"] = 10;

console.log(a) //=> { x: 1, y: 2, z: 3 };
console.log(b) //=> { x: 1, y: 2, z: 3 };
console.log(c) //=> { x: 10, y: 2, z: 3 };

Object.assignの第一引数(コピー先)に空のオブジェクトを用意し、コピー元の要素を第二引数以降に指定する。

aとcは別のオブジェクトになるため、cの値をいくら変更してもコピー元のaには影響せず、また、aを参照するbにも当然ながら影響を与えない。

まとめ

参照型の挙動を意識していないと、思わぬところに影響を与える可能性があるので注意しましょう。

【JavaScript】Promiseについて学んでみる

はじめに

JavaScriptのasync/awaitについて調べたら、その前にPromiseを理解する必要がありそうだったので、まずPromiseについて調べてみた。

Promiseとは

MDNでPromiseについて調べると、以下のように書かれている。

Promise は非同期処理の最終的な完了もしくは失敗を表すオブジェクトです。

雑にまとめると以下のような感じ。

  • PromiseはES2015で導入された機能で、非同期処理と一緒に使われることが多い。

  • Promiseを使うと非同期処理の実行結果が成功した場合の処理と、失敗した場合の処理を簡潔に記述することができる。

Promiseオブジェクトを定義する

まずはPromiseの定義方法から。 Promiseは以下のように定義することができる。

new Promise(function(resolve, reject){ 
  //非同期の処理 
})

Promiseオブジェクトをインスタンス化する際には引数に関数を指定する(上記のfunctionの部分)。

この関数には最初から2つのパラメーターresolverejectが渡されている。 resolveとrejectは、どちらもそれ自体が関数で、その後に記述する非同期処理の成功と失敗を通知するために用いられる。

非同期の処理が成功した場合は、resolve関数に成功時に通知するメッセージや値などを渡す。

失敗した場合についてはreject関数に、失敗時に通知するメッセージや値などを渡す。

な...何を言っているかわからねーと思うので以下に例を示す。

function hoge(value) {
  return new Promise((resolve, reject) => {
    if(value) {
        resolve("ふるえるぞハート! 燃えつきるほどヒ――――――ト!!")
    } else {
        reject("貧弱! 貧弱ゥ!")
    }
  })
}

まずはPromiseオブジェクトについて確認するため、上記の例では肝心の非同期処理は一切実装されていない。

hoge関数はreturn new Promiseとあるように、最終的にはPromiseオブジェクトを返すようになっている。そしてPromiseの中ではhoge関数の引数valueがtrueかfalseかでresolveとrejectに成功時と、失敗時のメッセージを与えている。

今の状態でconsole.log(hoge(true))hoge関数の結果を表示してみると以下のようになる。

f:id:oaths3:20200302225327p:plain

hoge関数からはPromiseオブジェクトが返されていることが分かる。

また、PromiseStatusがresolvedとなっている。これはhoge関数から返されたPromiseオブジェクトがresolved状態であることを示している。

console.log(hoge(false))とすればhoge関数のvalueは偽になり、PromiseStatusはrejectedとなる。

resolvedやrejectedはPromiseオブジェクトの状態を示すもの。

Promiseの状態

Promiseには以下3つの状態というものがある。 以下はMDNから引用。

  • pending: 初期状態。成功も失敗もしていません。

  • fulfilled(resolved): 処理が成功して完了したことを意味します。

  • rejected: 処理が失敗したことを意味します。

Promiseの中で定義した処理が成功したか失敗したかにより、pending状態だったPromiseオブジェクトがresolvedかrejectedのどちらかの状態に変化する。

Promiseオブジェクトを利用する

Promiseオブジェクトの定義方法を説明したが、今のままではhoge関数はPromiseオブジェクトを返すだけなので、成功時や失敗時の値を取得することができない。 そこでPromiseオブジェクトを利用するためのメソッドがthenメソッドやcatchメソッドになる。

thenメソッドについて

thenメソッドを使うとPromiseオブジェクトの結果を受け取ることができる。

promise.then(成功時処理、失敗時処理)

thenメソッドの第一引数には成功時に実行したい処理を記述し、第2引数には失敗時に実行したい処理を記述する事ができる。

hoge(true).then(
  // 第一引数に成功時の処理を書く
  response => {
    console.log(response);
  },
 // 第二引数に失敗時の処理を書く
  error => {
    console.log(error);
  }
);
// => ふるえるぞハート! 燃えつきるほどヒ――――――ト!!

上記の例ではhoge関数にtrueを渡して実行しているので、hoge関数はresolved状態のPromiseオブジェクトを返す。

返ってきたPromiseオブジェクトに対しthenメソッドを呼び出すことで、その結果を受け取ることができる。thenメソッドの引数には2つのコールバック関数が定義されている。どちらも単純に受け取った結果をコンソールに表示するようになっている。

Promiseオブジェクトがresolved状態なら、thenメソッドの第一引数に指定したコールバック関数が実行される。Promiseオブジェクトの結果をresponseとして受け取り、それをコンソールに表示している。

同様に、hoge関数にfalseを渡して実行すれば、hoge関数はrejected状態のPromiseオブジェクトを返し、thenメソッドの第二引数で指定したコールバック関数が、Promiseの結果をerrorとして受け取り、それをコンソールに表示する。

ちなみにresponseやerrorは任意の名前を付けて構わない。

catchメソッドについて

先程はPromiseオブジェクトの結果をthenメソッドで受け取り、thenメソッドの中で成功時と失敗時の処理を記述していたが、thenメソッドの中には成功時の処理だけを記述し、失敗時の処理をthenメソッドの中から切り離すことができる。

そのためにはcatchメソッドを利用する。

hoge(false)
  .then(
    response => {
      console.log(response)
    }
  )
  .catch(
    error => {
      console.log(error)
    }
  );
// => 貧弱! 貧弱ゥ!

上記のようにthenメソッドの後にcatchメソッドを呼び出し、catchメソッドの中で失敗時の処理を記述する。

hoge関数にfalseを渡して実行しているので、hoge関数はrejected状態のPromiseオブジェクトを返す。この場合thenメソッドは無視されcatchメソッドだけが実行される。

catchメソッドで失敗時の処理を切り分けることで、可読性が向上したり、処理を一箇所にまとめることができる。

Promiseチェーンについて

thenメソッドは何度でも繋げることができる。以下はMDNで使われている例。

doSomething()
.then(result => doSomethingElse(result))
.then(newResult => doThirdThing(newResult))
.then(finalResult => {
  console.log(`Got the final result: ${finalResult}`);
})
.catch(failureCallback);

上記の例ではthenが3回使われている。

doSomething関数はPromiseオブジェクトを返し、その結果が1つ目のthenに渡される。1つ目のthenの結果は2つ目のthenに渡され、2つ目のthenの結果は3つ目のthenに渡される。そして、いずれかの処理で失敗した場合には最後のcatchが呼ばれることになる。

ちなみにPromiseを使わないで上記のような処理を書くと以下のようになる。

doSomething(function(result) {
  doSomethingElse(result, function(newResult) {
    doThirdThing(newResult, function(finalResult) {
      console.log('Got the final result: ' + finalResult);
    }, failureCallback);
  }, failureCallback);
}, failureCallback);

これがいわゆるコールバック地獄というやつで非常に分かりづらい。

Promiseを使うと簡潔に書けてとっても嬉しいことが分かる。

まとめ

Promiseについてなんとなく理解することができた。例として記述したコードに非同期処理の部分が無いためイマイチメリットが伝わらないかもしれない。axiosなど使ってみるとナルホドとなることがあるかもしれません。

【Vue.js】コンポーネントのdata()について

はじめに

Vue.jsのコンポーネントでdataを定義する際に、公式サイトでは以下のような書き方が使われています。

Vue.component('button-counter', {
  data: function () {
    return {
      count: 0
    }
  },
  template: '<button v-on:click="count++">You clicked me {{ count }} times.</button>'
})

data: function() {}と書いていくのが通常なのかと思っていたところ、以下のような書き方もできると知りました。

Vue.component('button-counter', {
  data() {
    return {
      count: 0
    }
  },
  template: '<button v-on:click="count++">You clicked me {{ count }} times.</button>'
})
変わったのはdata: function() {}data() {}となっているところ。 しかし、なぜこの書き方ができるのか分からなかったので調べてみました。

結論

先に結論を書いてしまうと、ECMAScript 2015から省略記法が利用できるようになり、functionを書かずにメソッド(プロパティに関数を格納しているもの)を定義できるようになったため。 以下のリンク先の「メソッドの定義」部分に省略記法についての記載があります。 developer.mozilla.org

JavaScriptのプロパティに関数を格納する書き方

省略記法を用いない場合、オブジェクトのプロパティに関数を格納するには以下のように書きます。

obj = { hoge: function() { console.log("hogehoge") } };
obj.hoge();
//=> hogehoge

これが省略記法を用いると以下のように書けるようになりました。

obj = { hoge() { console.log("hogehoge") } };
obj.hoge();
//=> hogehoge
省略記法を使う場合には:(コロン)とfunctionを書く必要が無くなります。

ここで、もう一度Vueのコンポーネントの記述を見てみると、Vue.componentの第二引数にオブジェクトが渡されており、オブジェクトのプロパティとしてdataが定義されています。 したがって、data(){ }という書き方は省略記法を使ってdataプロパティに関数を格納していると分かりました。

// 省略記法を使ってdataプロパティに関数を格納している
Vue.component('button-counter', {
  data() {  },
})

まとめ

分かってしまうと単に省略記法で書いていただけという結論でしたが、調べる過程でJSの理解が少し深まった気がします。 VueやReactを使うにしても、やはりJSの基本を理解しておくことは大事だと感じました。

参考サイト

developer.mozilla.org

jp.vuejs.org

jp.vuejs.org