本記事はSimon Codrington、Mallory van Achterbergが査読を担当しています。最高のコンテンツに仕上げるために尽力してくれたSitePointの査読担当者の皆さんに感謝します。
Webデザイナーの仕事をしていると、Webフォームの作成はまあまあ発生する作業ですね。でも、一生懸命作ってもそんなにほめられるものでもないですし、特に複数のステップを踏むような複雑なフォームの場合、ただ手間がかかるだけだったりします。そんなときは、UIフレームワークを使いましょう。悩まずに済みますし、構築のスピードも上がって効率がよくなります。
この記事では、Webixフレームワークを使った、極力面倒くさなくて、いろいろなタイプに対応できるフォームを制作するためのテクニックを説明します。
Webixとは?
Webixは、モバイルおよびデスクトップ用Webアプリを作りやすくする、HTML5コンポーネントのJavaScript UIライブラリーです。シンプルなボタンから、Excelのようなアプリの開発に便利なスプレッドシートウィジェットなど、幅広いコンポーネントを提供します。UIコンポーネントのコレクションのほかにも、イベント処理のメカニズムやオフラインモードのサポート、たくさんの開発ツールなどがあります。また、スキンビルダーを使って独自のスキンを作成したり、ビジュアルデザイナーを使ってドラッグ&ドロップでUIを構築したりして、オンラインでソースコードを試せる場もあります。また、詳細なドキュメントも備えられています。
このフレームワークの基本的な使い方や主な機能について書いた紹介記事もありますので、こちらもぜひ参考にしてください。
Webixの読み込み
必要となるJavaScriptやCSSファイルをプロジェクト内に読み込む方法はいくつかあります。ライブラリーのパッケージをダウンロードすると、これらのファイルはすでにcodebaseフォルダー内に入っています。これらは次のように読み込みます。
<link rel="stylesheet" href="./codebase/webix.css">
<script src="./codebase/webix.js"></script>
選択肢として、CDNを使うこともできます:
<link rel="stylesheet" href="http://cdn.webix.com/edge/webix.css">
<script src="http://cdn.webix.com/edge/webix.js"></script>
NuGetを使っても可能です:
nuget install Webix
Microsoft Visual Studioを使っている場合は、以下をパッケージマネージャーコンソールで実行します。
install-package Webix
またはBowerを使います。
bower install webix
シンプルなフォームの作成
ライブラリーの設置ができたら、Webix Form Widgetへ進みましょう。
webix.ui({
view: "form",
id: "myForm",
container: "areaA",
width: 350,
elements: [
{ // first form component },
{ // second form component},
{ // n-th form component */}
]
});
まず、webixオブジェクトのuiメソッドを呼び出すところから始め、パラメーターを通してアウトプットを設定します。
- viewプロパティは、作成されるエレメントの種類を決定します(記事ではフォームの作成をしますが、メニューや表の作成もできます)。
- idプロパティは、あとで参照できるように、フォームにIDを付与します。
- containerプロパティは、フォームがレンダリングされるHTMLエレメントのIDを特定します。
- widthプロパティは、フォームの横幅の設定をします。Webixで使う単位はピクセルなので、正しい数を設定します。
- elementsプロパティは、フォームが含む一連のコンポーネント群です。テキストフィールド、ラジオボタン、チェックボックス、ボタンなどの中から、フォームに適したコンポーネントを使えます。
では、簡単なログインフォームを作ってみましょう。ユーザーネーム用とパスワード用に、2つのテキストフィールド、チェックボックス、そして送信ボタンが必要ですね。
webix.ui({
...
elements: [
{ view: "text", label: "Username", name: "username" },
{ view: "text", label: "Password", name: "password", type: "password" },
{ view: "checkbox", labelRight: "I accept the terms of use", name: "accept" },
{ view: "button", value: "Submit", width: 150, align: "center", click: submit }
]
});
ここで、入力される文字列をマスクするために、フォームのエレメントにname属性、パスワードフィールドにはtype: "password"を指定しています。labelプロパティを設定し、ラベルを定義し、clickプロパティを使ってフォームが送信された時に呼ぶイベントハンドラーの定義を設定します。すべての入力要素が正しいかチェックするのも良いですが、クライアント側でのバリデーションは、サーバー側でのバリデーションの補足でしかないということを忘れないでください。
デモを試す前に、イベントハンドラーを定義する必要があります。以下は、Webix Message Boxを使った入力に対してのユーザーフィードバックです。
function submit(){
webix.message(JSON.stringify($$("myForm").getValues(), null, 2));
}
上記のコードはWebixのgetValuesメソッドを使っていて、フォームに入力されたデータからmyFormのIDを導き出し、JSON.stringify()を使ってJSON stringの文字列に変換しています。
すべて揃った結果は、次のようになります。
データを入力し、送信ボタンを押すと、メッセージが表示されます。
サンプルはここにあります。
すべて正常に機能しています。ここから、いろいろな機能を追加していきましょう。
複数選択とサジェスチョン
フォームのコントロールには、複数選択や項目の提案ができるものがあります。個人的に一番おもしろいと思うのはMulticomboです。シンプルかつ直感的なインターフェイスで、入力フィールドで複数の値が選択できるようにするコントロールです。
メモ:最新のリリース(2016年4月26日)ではMulticomboのコントロールが変更され、現状Webix Pro版(有料)でのみ提供されている。
たとえば、デベロッパーが履歴書を作るためのページを作りたいとすると、下のようなフィールドが必要になってきます。
ユーザーが使えるプログラミング言語は複数だと想定されるので、言語の種類のリストを作成し、Multicomboコンポーネントを使って選択肢を表示します。
var languages = [
{id: 1, lang: "JavaScript"},
{id: 2, lang: "PHP"},
{id: 3, lang: "Ruby"}
...
]
webix.ui({
...
elements: [
{
view: "multicombo", name: "skills", label: "Skills", button: true,
suggest: {
body: {
data: languages,
template: webix.template("#lang#")
}
},
}
]
});
おなじみのプロパティに加えて、buttonプロパティとsuggestプロパティを使っています。buttonプロパティは選択したものを確定させるためのボタンの作成、suggestプロパティはMulticomboの中で表示する項目のソースを定義付けています。例では、dataプロパティを使って一連の名前を特定すると同時にtemplateプロパティで表示する値を指定しています。ここでファイルへのパスも設定できますが(例:suggest: "path/to/file/data.js")、大きなソースから一連の異なるデータを抽出したい場合は、上の方がよりよい方法でしょう。
では実際にやってみましょう。テキストフィールドをクリックすると、ドロップダウンのリストが表示されます。
スクロールして希望のアイテムを選択するか、文字を入力してリストを絞れます。
このフォームの例は、選択したアイテムに合致したIDを返します。
サンプルはここにあります。
multicombo以外の選択肢として、GridsuggestやDataview Suggestもおすすめです。
フォームエレメントとしてツリーウィジェットを使う
Webixのエレメントは、従来からのテキストフィールドやボタン、チェックボックスなどに限らず、フォーム内に置きたいウィジェットをなんでも置けます。たとえば、ツリーウィジェットです。元々はフォームのコントロール用にデザインされたものではなかったため、setValueやgetValueのメソッドがこのエレメントにはありませんが、コンポーネントに値を返したり設定したりしたい場合はこれらのメソッドが必要となります。では、どうしたら良いのでしょう? 幸い、現在のビューをベースに新規ビューの作成ができる、protoUIメソッドが役に立ちます。
ではやってみましょう。
webix.protoUI({
name: "formTree",
setValue: function(id){ this.select(id); },
getValue: function(){ return this.getChecked(); }
}, webix.ui.tree);
上のコードでは、formTreeと呼ばれる新しいビューを作成したのが分かります。idの値の設定と受け取りができるように、2つのメソッドを定義しました。
何かデータを入れてみましょう。
var treedata = [
{ id: "1", value: "Web", data: [
{ id: "1.1", value: "HTML" },
{ id: "1.2", value: "CSS" },
...
]},
{ id:"2", value:"Scripting", data: [
{ id: "2.1", value: "Shell" },
...
]},
...
];
いつものとおり、ここでフォームに新しいエレメントを追加できます。
webix.ui({
...
elements: [
{
view: "formTree",
name: "skills",
data: treedata,
height: 150,
threeState: true,
template: "{common.icon()} { common.checkbox() } #value#"
},
...
]
});
ここで、また新しいエレメントがでてきました。templateプロパティはツリーノードにチェックボックスを追加し、threeStateプロパティは3状態チェックボックスを有効にします。これらのチェックボックスは下記のように動きます。
- ユーザーが親ノードをチェック/アンチェックした場合、親ノードとその階層下の子ノードもすべてチェック/アンチェックされます。
- ユーザーが子ノードをチェック/アンチェックした場合、選択した個別の子ノードのみチェック/アンチェックされます。
もし3状態チェックボックスを使う場合、1つだけ特に気をつけなければいけないことがあります。チェックボックスを選択したとき、Webixは再レンダリングします。キーボードを使ってフォームに入力するようにした場合、Spaceキーで選択するチェックボックスを切り替えていったとき(ChromeのようなWebKitベースのブラウザーの場合)、フォーカスが切れてフォームの先頭からTabキーで移動しなければならなくなる可能性があります。
幸い、これには対策があります。onプロパティを使って、新規のハンドラーをツリーに設置できます。特定のツリーのアイテムを選択した時に発生するonItemCheckイベントと一緒に使います。追加でいくつか他のメソッドも合わせて使うことで、フォーカスを外すことはありません。
on: {
onItemCheck: function(id){
var node = this.getItemNode(id);
if(node){
checkbox = node.getElementsByTagName("input")[0];
checkbox.focus();
}
}
}
これでもう大丈夫なはずです。しかし、新たな問題があります。WebKitはタブを使っている間はチェックボックスを選択しません。CSSを使ってアウトラインを追加するか、フォーカスしているチェックボックスにボックスシャドーを追加することで回避できます。
input[type=checkbox]:focus { outline: -webkit-focus-ring-color auto 5px; }
すべての配置が終わったら送信ボタンをクリックし、作成したメソッドがきちんと動いているか確認してみましょう。
IDは正常に送信されていますね。
サンプルはここにあります。
マルチタブとマルチステップフォーム
もしユーザーから大量のデータを集めようとしている場合、フォームを小さく分割できます。ここでマルチタブのフォームと、ステップごとにデータの入力ができるフォーム、2つのフォームを説明していきましょう。
Tabviewコンポーネント
Tabviewコンポーネントは、タブごとに別れたエレメントの集合体を作ります。ユーザーはタブ間の切り替えができ、1つのエレメントをTabviewコンテンツとして使うか、欲しいエレメントを含む行と列の組み合わせを定義します。
たとえば、次のようになります。
webix.ui({
...
elements: [{
view:"tabview",
cells: [
{
header:"First tab label",
body: {
// first tab content
}
},
{
header:"Second tab label",
body: {
rows:[
{ // first row content },
{ // second row content }
]
}
},
...
]
}]
...
});
このアプローチは、ユーザーが扱いやすいように、フォームを区分化することが主な目的です。フォーム全体に関連しているコンポーネント(たとえば送信ボタンや「同意します」のチェックボックスなど)はTabviewコンポーネントの外に設置することを忘れないようにしてください。
例を挙げると、次のようになります。
webix.ui({
...
elements: [{
view:"tabview",
cells: [
{
// 1st tab header and content
},
{
// 2nd tab header and content
},
{
view:"button",
value: "Submit",
width: 150,
align:"center",
click:submit
}
]
}]
...
});
タブ化されたフォームを作るのに必要なのはこれだけです。結果を確認してみましょう。
このアプローチのメリットは、パーツを1つにするための、追加のコーディングが必要ないということです。フォーム内にTabviewコンポーネントを入れ、各フィールドにnameプロパティを追加すると、入力された値を全部取り込めます。送信ボタンをクリックして確定すると次のようになります。
見た目は悪いですが、データが取れました。
キーボードアクセシビリティ
多数のタブを使って、Tabキーでタブを切り替えたいとき、タブバーの中のボタンを使えます。headerプロパティを変更するだけで、タブのナビゲーションバーにタブを追加できる方法です。
header: "<button class='webixbutton'>Personal Info</button>"
CSSでコードを追加して、これらのボタンがネイティブで表示されるようにします。
.webix_item_tab .webixbutton{
background-color:transparent;
}
.webix_item_tab.webix_selected .webixbutton:focus{
background-color:#268fd5;
}
.webix_item_tab .webixbutton:focus{
background-color:#98abb7;
}
次のサンプルは、日付ピッカーも使っています。ユーザーはキーボードで操作できない(現状)状態ですが、Returnキーを押すと表示されるので視覚的には分かりやすくなっています。
1つの方法は、hotkeyプロパティを使うことです。しかし、ここで気付くべき点があります。このプロパティはページのエレメントを1つだけキーとして結びつけた場合、何の問題もなく動作しますが、例のフォームには2つの日付ピッカーがあります。ということは、addHotKeyメソッドを使い、すべての日付ピッカーに適用可能な新しいハンドラーを作成しなければなりません。
webix.UIManager.addHotKey("enter", function(view){
view.getPopup().show(view.getInputNode());
}, "datepicker");
これらがお互いどう連携しているか、サンプルで確かめてください。
その他の選択肢としては、Accordionも使えます。
マルチビューコンポーネント
マルチビューコンポーネントは、1つ1つ連続して見られるエレメントを作成できます。タブを使ってマルチビューのエリアを切り替えられますが、マルチステップのフォームについての解説ですので、フォームの各ステップごとにボタンを追加して、ユーザーを誘導していきましょう。
次へと戻るボタンが動くように、2つの機能を作成します。
function next(){
var parentCell = this.getParentView().getParentView();
var index = $$("formContent").index(parentCell);
var next = $$("formContent").getChildViews()[index+1]
if(next){
next.show();
}
}
function back(){
$$("formCells").back();
}
nextのファンクションは、WebixのgetParentViewメソッドを使って、クリックされたボタンのリファレンス先のセル(現在表示されているセル)を取得します。そしてマルチビューコンポーネント(formContent)のidの値を使い、(あれば)どのセルが次に来るかを計算します。もし次のセルがあれば、showメソッドを使ってビューに移行します。backファンクションはメソッドを使ってマルチビューの前回アクティブだったビューにスイッチします。
前に触れたタブニューエレメントと同様に、マルチビューエレメントも定義できます。ただし今回は、すべてのセルの最下層に行を1つ余分に増やしておきましょう。なぜなら、この行にはコントロールボタンが含まれるからです。もしボタンが1つだけしか表示されない(最初のセルのように)場合は、ここには空っぽのオブジェクトを入れておきます。
次のようになります。
webix.ui({
...
elements: [{
view: "multiview",
id: "formContent",
cells: [{
// First step
rows: [{
...
cols: [
// Buttons
{},
{ view: "button", value: "Next", click: next }
]
}]
},
{
// Second step
rows: [{
...
cols: [
// Buttons
{ view: "button", value: "Back", click: back },
{ view: "button", value: "Next", click: next }
]
}]
},
{
// Final step
rows: [{
...
cols: [
// Buttons
{ view: "button", value: "Back", click: back },
{ view: "button", value: "Submit", click: submit }
]
}]
}]
...
}]
});
今までのものは次のように反映されています。
次へボタンをクリックすると、次のフォームが表示されます。
ではすべて予想通りに動作するか、確認してみましょう。
すばらしい! 最後のサンプルはこちらです。
まとめ
今回の記事では、Webixがいかに簡単に、複雑かつスタイリッシュでアクセシブルなフォームを作成することができるかを解説しました。このフレームワークは、多数の強力なウィジェットを指先1つで簡単に配置できます。また、フォームのコンポーネント以外も、protoUIメソッドを使って動作を簡単に再定義もできるのです。
(原文:Creating Forms with the Webix Framework — 4 Practical Examples)
[翻訳:Eri Noda]
[編集:Livit]