前提知識

INTER-Mediatorに関して、以下の内容をすでに知っている事を前提とします。

  • data-im属性への記述によりフィールドの内容が表示されること
  • TABLEタグを使った表の中で、複数のレコードが繰り返し表示されるようになっていること
  • 一定数ごとのレコードを表示する仕組みと、表示範囲を切り替えるページネーションの仕組みがあること
  • 定義ファイルのコンテキストには、検索条件が指定できること
  • JavaScriptによるページ機能の拡張

作成例について

利用するデータベースは、郵便番号と地名が含まれているもので、サンプルのデータベースにあるpostalcodeという名称のものです。レポジトリ内では、Samples/Practices/search_page2.htmlを開く事で、実際に稼働させることができます。Samples/index.htmlにあるサンプルの一覧では「Practices」にある「search」の「search(using JavaScript)」のリンクをクリックして表示できます。

まず、どのような動作になるのかを見て行くことにしましょう。最初にページを開くと、郵便番号と地名がともかく一覧されています。サンプルのデータベースには、ある時期の東京都のデータのみを収録しています。検索条件は何もなく、おそらく全てのデータがリストされていると想定されます。ここで、ページネーション(ページ移動)の「>」ボタンをクリックすると、次々と10件ずつレコードが表示されます。また、何件目のデータを現在表示しているのかという情報も見えています。

ここで、「検索条件」に適当な地名(ここでは「池袋」)を入力して「Search」ボタンをクリックします。すると、一覧表には、池袋を含む地名やビル名のものだけに絞り込まれました。全部で68件の地名があることが分かります。「>」ボタンでページ移動ができますが、ページ移動後にも、検索条件はそのまま見えていることにも注意を払いましょう。

検索条件に、ここでは「171」と入力しました。すると、郵便番号が171で始まる地名だけが絞り込まれています。また、ここで、「郵便番号」と書かれた右側の▼をクリックすると、郵便番号の逆順で表示されるようになりました。つまり、一覧表示が「郵便番号」の逆順にソートされて表示されるようになっています。

検索条件に、東京都に明らかになさそうな「大阪」という文字列を指定して検索してみます。もちろん、大阪を含む地名は存在しませんが、検索結果が0件のときに表示する行が見えています。

「表示件数」については、ここでは省略します。

定義ファイル

定義ファイルのSamples/Practices/search_def.phpの内容は以下の通りです。コンテキストは、postalcodeという名前のものが1つだけあり、これはテーブル名と同様なので、tableやviewキーの値は指定していません。1ページあたりのレコード数は10で、後からの変更をしても30より多くの数を一度にページに出す事はできません。そして、pagingキーがtrueなので、ページネーションのコントロールがページ上に表示されます。

データベースはMySQLを使うのでdb-classは「PDO」を指定します。それ以外の接続情報、アカウント、パスワードは、INTER-Mediator/params.phpファイルにあるものをそのまま使います。INTER-Mediatorのサンプルファイルをそのまま参照できる状態であれば、特に変更する必要はありません。

require_once(dirname(__FILE__) . '/../../INTER-Mediator.php');

IM_Entry(
array(
    array(
        'name' => 'postalcode',
        'records' => 10,
        'maxrecords' => 30,
        'paging' => true,
    ),
),
null,
array('db-class' => 'PDO'),
    false
);

ページファイル

ページファイルについては、ボディ部のみを示します。もちろん、ヘッダ部で、前述の定義ファイルSCRIPTタグで読み込んだ状態になっています。

<div>
    検索条件:
    <input type="text" id="condition" data-im="_@condition">
    表示件数:
    <input type="text" id="number" data-im="_@_im_pagedSize" size="3">
    <button id="search">search</button>
</div>
<div id="IM_NAVIGATOR">Navigation Controls by INTER-Mediator</div>
<table border="1" id="resultTable">
    <thead>
        <tr>
            <th>郵便番号
                <span id="sort1a">▲</span>
                <span id="sort1d">▼</span>
            </th>
            <th>都道府県</th>
            <th>市区町村</th>
            <th>町域名
                <span id="sort2a">▲</span>
                <span id="sort2d">▼</span>
            </th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td>
                <div data-im="postalcode@f3"></div>
            </td>
            <td>
                <div data-im="postalcode@f7"></div>
            </td>
            <td>
                <div data-im="postalcode@f8"></div>
            </td>
            <td>
                <div data-im="postalcode@f9"></div>
            </td>
        </tr>
        <tr data-im-control="noresult">
            <td colspan="4">検索結果はありません。あるいは検索前です。</td>
        </tr>
    </tbody>
</table>

それぞれの動作の実現

一覧表示の実現

postalcodeテーブルでは、f3フィールドに郵便番号、f7〜f9フィールドに都道府県、市区町村、町域名が収められています。一覧表に表示をしつつ、そこでの変更の必要がない場合には、INPUTタグ要素でフィールドの内容を表示する必要はありません。ページファイル内のTBODYタグ要素の中には2つのTR要素がありますが、その最初の方の要素の子要素にTDがいくつかあり、さらに各TDはDIV要素があります。そこに、f3、f7〜f9のそれぞれのフィールドを示すターゲット指定がdata-im属性に設定されています。

postalcodeコンテキストに対して検索して得られた郵便番号と地名の情報に対して、TBODYの最初のTR要素がレコードの数だけ繰り返されて、そしてそれぞれDIVタグ要素のテキストとしてフィールドの値が設定されて表示されます。初期状態では、定義ファイルのコンテキストにあるrecordsキーの値に応じて最大10レコードまでとなります。この仕組みによって、一覧表が作成されています。

検索結果が0のときの一覧表

TBODYの内容を解析して、このTBODYがエンクロージャーとして機能します。そして、postalcodeコンテキストに対して検索処理を行います。このとき、検索結果が0件の場合、data-im-control属性が「noresult」のものを残して、エンクロージャー内の他のリピーターは削除され、その結果を表示します。また、一方で、検索結果が0件でない場合、data-im-control属性が「noresult」のものを削除して残った結果をリピーターとして識別して、そのリピーターをレコードの数だけ複製してページを表示します。

ページネーションのコントロールの表示

postalcodeコンテキストでは、pagingキーの値がtrueなのでページネーションコントロールによるページ切り替えが可能です。そして、ページネーションのコントロールを表示するためには、id属性が「IM_NAVIGATOR」の要素を配置しておく必要があります。TABLEタグのすぐ上にその要素が見えており、この場所にページネーションコントロールを自動的に構築します。

ページの合成処理

実際にページを表示する処理は、window.onloadへの関数の代入で実現しています。Samples/Practices/search.jsにプログラムが置いてあります。そこにある以下の部分が、実際にページを合成します。window.onloadに代入した関数は、ページの要素を読み込んだ後、つまりページが表示された直後に呼び出され、INTERMediator.construct(true);によって実際にページの生成を行います。この関数のそれまでの部分については、この後に説明をします。

window.onload = function () {
        :
    INTERMediator.construct(true);
};

検索のユーザインタフェース

ページファイルの最初の方に、検索条件を指定するINPUTタグ要素があります。この要素はデータベースとは関係なく、ページ上で存在するものです。そのため、id属性(値はcondition)を設定していますが、加えて、data-im属性にローカルコンテキストを示す「_@condition」という記述があります。これにより、クライアント内部でのデータ記憶領域とバインドされます。アンダーラインはローカルコンテキストを示す記号で、「condition」がそのコンテキストでのキーとなります。このINPUT要素から値を取り出すのではなく、ローカルコンテキストより、このテキストフィールドに入力した値が取り出せるようになります。

実際の検索処理

検索処理は、Samples/Practices/search.jsにある以下の関数として、汎用的に記述しました。つまり、doSearch()の呼び出しで、検索条件を取り出してそれを設定した上で、ページの再合成を行うことで検索結果を反映させた一覧を取得します。INTERMediator.additionalConditionには、コンテキストとして定義ファイルに記述した検索条件に、さらに新たな検索条件を付加するプロパティです。

function doSearch() {
    INTERMediator.additionalCondition = {};
    var c1 = IMLibLocalContext.getValue("condition");
    if (c1 && c1.length > 0) {
        INTERMediator.additionalCondition = {"postalcode": [
            {field: 'f3', operator: 'LIKE', value: c1 + '%'},
            {field: 'f7', operator: 'LIKE', value: '%' + c1 + '%'},
            {field: 'f8', operator: 'LIKE', value: '%' + c1 + '%'},
            {field: 'f9', operator: 'LIKE', value: '%' + c1 + '%'},
            {field: '__operation__', operator: 'ex'}
        ]};
    }
    INTERMediator.startFrom = 0;
    IMLibLocalContext.archive();
    INTERMediator.construct(true);
}

最初に、付加条件をいったんクリアします。IMLibLocalContext.getValueメソッドにより、検索条件のテキストフィールドの値を取り出します。そのテキストフィールドは、直前に説明した通り、ローカルコンテキストのconditionキーの値とバインドしていて、テキストフィールドの値はキーの値を指定して取り出すことができます。つまり、変数c1には、テキストフィールドの値が取り出されます。

もし、検索条件が指定されていれば、INTERMediator.additionalConditionに付加検索条件を追加します。ここでは、f3が検索条件で始まり、f7〜g9については検索条件を含むという検索設定をMySQLの文法に従って記述しています。__operation__という記述により、それまでの並びをANDではなくOR条件で結びます。結果として、4つの条件がどれか1つでも満たすようなレコードが検索されて取り出されることになります。

INTERMediator.startFromは、検索結果の何番目から表示するかを示すプロパティで、ページネーションによりINTER-Mediatorによって自動的に更新されるものです。最初なので0にしておきます。IMLibLocalContext.archive()により、ローカルコンテキストを記憶しています。最後に、INTERMediator.construct(true);によってページの合成を行っています。

Searchボタンをクリックして検索する

ページファイルでは、BUTTONタグを使って「Search」ボタンを作成していますが、その要素の記述内では、特にプログラムの呼び出しなどを行っていません。onclick属性に記述する方法でももちろん動作しますが、別の方法も用意しています。INTERMediator.construct( ) でページ合成をする前に、IMLibMouseEventDispatch.setExecuteメソッドを利用して、イベントに対応する処理を記述できます。このメソッドの最初の引数は要素のid属性で、2番目にその要素をクリックしたときの処理を関数で記述します。Searchボタンのid属性値はsearchなので、以下のような記述を行えば、クリックしたときにdoSearch関数を実行します。なお、IMLibMouseEventDispatch.setExecuteは、実際にはdocumentオブジェクトでのclickコマンドを受け付けます。従って、オブジェクトに直接プログラムを記述するような場合や、オブジェクトにイベントリスナを設定する場合には、そのまま両方動くことになります。このサンプルプログラムでは、HTML側に一切の処理を書かないという方針でまとめています。

window.onload = function () {
        :
    IMLibMouseEventDispatch.setExecute('search', function () {
        doSearch();
    });
        :
    INTERMediator.construct(true);
};

Returnキーで検索をする

INPUTタグ要素は、原則としてReturnキーあるいはEnterキーによるサブミット処理は、FORMタグを使っている場合にしか適用されません。INTER-MediatorではFORMを使っていないので、簡単に言えばテキストフィールドでReturnキーを押しても「何も起こらない」ということになります。

そこで、ページ合成前に、IMLibKeyEventDispatch.setExecuteメソッドを利用すれば、第1引数の値をid属性に持つ要素に対して、2番目の引数の文字コードが入力されれば、3つ目の引数の関数を実行します。つまり、以下の記述により、テキストフィールドに入力中にEnterキーを押すと、doSearch関数が実行されて検索が行われるということになります。IMLibLocalContext.updateメソッドは、引数にローカルコンテキストのキーを指定し、現在の要素の値をローカルコンテキストへ反映させます。通常はこの作業は自動的に行われるのですが、要素のchangeイベントをもとにしており、キータイプだけでは同期処理が実行されません。そこで、このような記述で明示的に要素の値をコンテキストに反映させています。

window.onload = function () {
    IMLibKeyEventDispatch.setExecute('condition', 13, function () {
    IMLibLocalContext.update('condition');
        doSearch();
    });
        :
    INTERMediator.construct(true);
};

フィールド名の横の▲▼をクリックして、そのフィールドで並べ替える

例えば、郵便番号の▲ボタンは、spanタグ要素として記述して、id属性値は「sort1a」となっています。そして、window.onloadに代入している関数内で以下のようにIMLibMouseEventDispatch.setExecuteメソッドが記述されています。つまりは、▲ボタンをクリックすると、まず、INTERMediator.additionalSortKeyというプロパティに、追加のソート条件を設定します。右辺にあるように、postalcodeコンテキストに対してf3フィールドを昇順で並べ替えるという設定を追加します。このサンプルでは、フィールドに応じた並べ替えのクリックポイントが4カ所あるので、4つの要素に対してクリックイベントの実行時に処理を行うように記述をしています。

window.onload = function () {
        :
    IMLibMouseEventDispatch.setExecute('sort1a', function () {
        INTERMediator.additionalSortKey
            = {"postalcode": {field: 'f3', direction: 'ASC'}};
        doSearch();
    });
    IMLibMouseEventDispatch.setExecute('sort1d', function () {
        INTERMediator.additionalSortKey
            = {"postalcode": {field: 'f3', direction: 'DESC'}};
        doSearch();
    });
    IMLibMouseEventDispatch.setExecute('sort2a', function () {
    INTERMediator.additionalSortKey
            = {"postalcode": {field: 'f9', direction: 'ASC'}};
        doSearch();
    });
    IMLibMouseEventDispatch.setExecute('sort2d', function () {
        INTERMediator.additionalSortKey
        = {"postalcode": {field: 'f9', direction: 'DESC'}};
        doSearch();
    });
    INTERMediator.construct(true);
};