4.3. エレメントのサンプル実装¶
この章では、添付のサンプル(sample.zip の implemented ディレクトリ)の郵便番号入力エレメント(MyZipCodeField.ts)の実装について、説明します。
郵便番号入力エレメントは、以下のようなエレメントです。
- 全体の親要素(<div>)と、その子要素である2つのテキストボックス(<input>)と1つのラベル(<label>)から構成されています。
 - 1つめのテキストボックスに3桁、2つめのテキストボックスに4桁まで値を入力できます。
 - 全体でvalueという固有プロパティを持っています。valueの値は、2つのテキストボックスに入力された値を連結したものです。既存の多くのコンポーネントと同様に、双方向バインディングで実装します。(双方向バインディングについては、「固有プロパティを追加する」を参照してください。)
 
 
4.3.1. 本体のクラスを実装する¶
まずは、 {VSCODE_HOME}/src/public/elements に、MyZipCodeField.ts を作成します。
MyZipCodeField クラスで IUIElementCore インタフェースを実装します。 // TODO の部分はこれから実装していきます。
export class MyZipCodeField implements IUIElementCore {
    // 繰り返しのないエレメントなので、SimpleUIElement を指定します。
    public get wrapperClass(): UIElementWrapperClass {
        return 'SimpleUIElement';
    }
    // エレメント名を返却します。
    // { エレメントのクラス名 } .name 形式で、エレメントのクラス名を返却します。
    public get elementTypeName(): string {
        return MyZipCodeField.name;
    }
    // エレメントの表示形式を返却します。
    public get displayType(): UIElementDisplayType {
        // TODO
    }
    // エレメントの配置制約を定義したファイルのクラス名を返却します。
    public get constraintClass(): UIComponentConstraintClass {
        // TODO
    }
    // エレメント固有プロパティの定義を返却します。
    // 固有プロパティがない場合は、空のオブジェクトを返却します。
    public get uniquePropertyDefinition(): IUniquePropertyDefinition {
        // TODO
    }
    // 共通プロパティの定義を返却します。
    // 固有プロパティがない場合は、空のオブジェクトを返却します。
    public get commonPropertyDefinition(): ICommonPropertyDefinition {
        return {};
    }
    // createChildren(), updateChildren(), cloneChildren() で
    // 子エレメントとして自身以外のエレメントを使用する場合に、そのクラス名を返却します。
    // このサンプルでは子エレメントを作成しませんので、空配列を返却します。
    public get dependElements(): UIElementCoreClass[] {
        return [];
    }
    // エレメント作成時に一度だけ呼び出されるメソッドです。
    // このメソッド内で、このエレメントで使用するルートとなる HTML エレメントを、HTMLElementBuilder を経由して返却します。
    // このサンプルでは、<div> タグやその子要素の <input> タグを作成する処理を記述していきます。
    public createElement(
        container: IUIContainer,
        properties: IUIElementPropertyAccessor<PropertyDefinition & ICommonPropertyDefinition>
    ): IHTMLElementBuilder {
        // TODO
    }
    // レンダリングが必要になった際に呼び出されるメソッドです。
    // プロパティで紐づけている変数の値が変更された際など、エレメント側を更新する必要がある場合は、このメソッドに実装します。
    // このサンプルでは、プロパティである value が変更された際に、子要素の <input> タグに value の値を反映させる処理を記述していきます。
    public updateElement(
        builder: IHTMLElementBuilder,
        container: IUIContainer,
        properties: IUIElementPropertyAccessor<PropertyDefinition & ICommonPropertyDefinition>
    ): void {
        // TODO
    }
    // エレメントが作成された際に一度だけ呼び出されるメソッドです。 createElement() の後に呼び出されます。
    // 自身のエレメント配下に子エレメントを作成する必要がある場合、このメソッドでエレメントを作成し、配列として返却します。
    // このサンプルでは子エレメントを作成しませんので、空配列を返却します。
    public createChildren(self: IUIElement, container: IUIContainer): IUIElement[] {
        return [];
    }
    // エレメントが複製された際に呼び出されるメソッドです。 createElement() の後に呼び出されます。
    // このサンプルでは子エレメントを作成しませんので、children 引数をそのまま返却します。
    public clonedChildren(
        children: IUIElement[],
        self: IUIElement,
        container: IUIContainer,
        properties: IUIElementPropertyAccessor<IUniquePropertyDefinition & ICommonPropertyDefinition>
    ): IUIElement[] {
        return children;
    }
}
4.3.2. 制約を実装する¶
制約クラス Constraint で IUIComponentConstraint を実装します。
ここでは、以下のように制約を定義します。
| 制約の内容 | 可否 | 対応するメソッド | 
| エレメントの移動 | 可能 | movable() | 
| エレメントの削除 | 可能 | removable() | 
| エレメントの複製 | 可能 | copyable() | 
| 子エレメントの配置 | 不可能 | extremity() 
acceptableChild() 
 | 
| 親エレメントの配置 | 可能 | acceptableParent() | 
この場合の実装は以下です。
class Constraint implements IUIComponentConstraint {
    // エレメントの移動可否を指定するメソッドです。
    // このサンプルでは、エレメントは移動可能にするため、true を返却します。
    public get movable(): boolean {
        return true;
    }
    // エレメントの削除可否を指定するメソッドです。
    // このサンプルでは、エレメントは削除可能にするため、true を返却します。
    public get removable(): boolean {
        return true;
    }
    // エレメントの複製可否を指定するメソッドです。
    // このサンプルでは、エレメントは複製可能にするため、true を返却します。
    public get copyable(): boolean {
        return true;
    }
    // エレメントが末端かどうかを指定するメソッドです。
    // このサンプルでは、子エレメントを作成しませんので、true を返却します。
    public get extremity(): boolean {
        return true;
    }
    // 親要素に対して、自身の配置可否を指定するメソッドです。
    // このサンプルでは、どの親エレメントに対しても配置可能としますので、常に true を返却します。
    public acceptableParent(parent: ParentUIElement): boolean {
        return true;
    }
    // 自身に対して、子エレメントの配置可否を指定するメソッドです。
    // このサンプルでは、自身より子階層にエレメントを移動させたくないため、常に false を返却します。
    public acceptableChild(child: IUIElement): boolean {
        return false;
    }
}
MyZipCodeField クラスの constraintClass メソッドで、制約クラスを返却します。
// 上で実装した制約クラスのクラス名 Constraint を返却します。
public get constraintClass(): UIComponentConstraintClass {
    return Constraint;
}
4.3.3. 表示形式を実装する¶
// エレメントの表示形式を返却します。
public get displayType(): UIElementDisplayType {
    return 'INPUT';
}
返却可能な文字列については、「エレメントの表示形式を実装する」を参照してください。
4.3.4. 固有プロパティを実装する¶
type PropertyDefinition = {
    value: UniquePropertyDefinitionType;
};
// エレメント固有カテゴリのプロパティは uniquePropertyDefinition で設定できます。
const uniquePropertyDefinition: IUniquePropertyDefinition & PropertyDefinition = {
    value: {
        displayName: 'value',
        definition: {
            // value プロパティには何らかの指定が必要である
            required: true,
            // value プロパティの値が変わった時に再レンダリングされる必要がある
            rerender: true
        },
        type: 'string'
    }
};
MyZipCodeField クラスの uniquePropertyDefinition メソッドで、value プロパティを定義した変数 uniquePropertyDefinition を返却します。
public get uniquePropertyDefinition(): IUniquePropertyDefinition {
    return uniquePropertyDefinition;
}
4.3.5. createElement メソッドを実装する¶
createElement メソッドを実装します。
以下の処理を記述します。
- <div> タグの下に <input> タグと <label> タグが配置されるようにする。
 - 子要素の <input> タグに入力があった際、親要素の value プロパティを更新する。
 - setAttribute メソッドを使用して、属性を付与する。
 - setCSS メソッドを使用して、CSS を指定する。
 
実装は以下の通りです。
export class MyZipCodeField implements IUIElementCore {
    // 子要素のビルダは updateElement メソッド内からもアクセスされるため、クラスの先頭で宣言しておきます。
    private _firstInputBuilder: IHTMLElementBuilder;
    private _secondInputBuilder: IHTMLElementBuilder;
// エレメント作成時に一度だけ呼び出されるメソッドです。
// このメソッド内でエレメントを作成して返却します。
// ここでは、子要素の作成、イベントリスナ、属性、スタイルの付与を行っています。
public createElement(
    container: IUIContainer,
    properties: IUIElementPropertyAccessor<PropertyDefinition & ICommonPropertyDefinition>
): IHTMLElementBuilder {
    // エレメントを作成します。
    const builder = window.imHichee.HTMLElementBuilder.createElement('div', HTMLDivElement);
    // 子タグのテキストボックスを作成します。
    const inputElement1 = document.createElement('input');
    const inputElement2 = document.createElement('input');
    // テキストボックスの input イベントが呼ばれると、valueプロパティを更新するようにリスナを付与します。
    inputElement1.addEventListener('input', (e) => {
        properties.setProperty('value', inputElement1.value + inputElement2.value, false);
    });
    inputElement2.addEventListener('input', (e) => {
        properties.setProperty('value', inputElement1.value + inputElement2.value, false);
    });
    this._firstInputBuilder = window.imHichee.HTMLElementBuilder.fromElement(inputElement1);
    this._secondInputBuilder = window.imHichee.HTMLElementBuilder.fromElement(inputElement2);
    const labelElement = window.imHichee.HTMLElementBuilder.createElement('label', HTMLLabelElement).setText('-');
    // setAttribute() を使用すると、作成したエレメントに属性を追加できます。
    builder.setAttribute('my-attribute', 'zip-code-input');
    // setCSS() を使用すると、作成したエレメントにstyleを追加できます。
    builder.setCSS('padding', '10px');
    // appendChild() を利用して、上で作成したエレメント(ビルダ)を子エレメントとして追加します。
    // ここで追加した子エレメントは、IM-BloomMakerのデザイナ上で1つ1つの要素を動かしたりプロパティを設定したりすることはできません。全体で1つのエレメントとして扱われます。
    builder
        .appendChild(this._firstInputBuilder)
        .appendChild(labelElement)
        .appendChild(this._secondInputBuilder);
    // 返却値には作成したビルダを指定します。
    return builder;
}
4.3.6. updateElement メソッドを実装する¶
以下の処理を記述します。
- value プロパティの値を取得し、先頭の3桁とそれ以降に分割する。
 - それぞれの値を子要素のテキストボックスにそれぞれセットする。
 
実装は以下の通りです。
// レンダリングが必要になった際に呼び出されるメソッドです。
// プロパティで紐づけている変数の値が変更された際に、エレメント側を更新する必要がある場合などにその処理を記述します。
public updateElement(
    builder: IHTMLElementBuilder,
    container: IUIContainer,
    properties: IUIElementPropertyAccessor<PropertyDefinition & ICommonPropertyDefinition>
): void {
    // プロパティの値を文字列で取得します。
    const value = properties.getProperty('value').toString();
    // 取得したプロパティの値を2つの入力エレメントに反映するため、前3桁と残りに分割します。
    const value1 = value.substr(0, 3);
    const value2 = value.substr(3);
    // 子ノードの<input>タグのvalue属性に値をセットします。
    // 子ノードの IHTMLElementBuilder は自動で build() が呼ばれない仕様であるため、独自で build() を実行する必要があります。
    this._firstInputBuilder.setValue(value1).build();
    this._secondInputBuilder.setValue(value2).build();
}
4.3.7. 実装したクラスの登録¶
{VSCODE_HOME}/src/index.ts で以下のように、カテゴリとエレメントを登録します。
ここでは、カテゴリIDは programming-sample としています。
import {MyZipCodeField} from './public/elements/MyZipCodeField';
// エレメントのリポジトリは以下のように取得します。
const elementRepository = window.imHichee.UIElementRepository;
// エレメントのカテゴリを登録します。
// 新たにカテゴリを追加したい場合には IUIElementRepository.registerCategory() を使用して、カテゴリを追加してください。
// id にはカテゴリの ID 、sortNumber にはカテゴリを表示させる際の優先順位を指定してください。
// カテゴリは sortNumber を基準に昇順で表示されます。
elementRepository.registerCategory('programming-sample', {sortNumber: 120});
// 作成したエレメントをカテゴリに登録します。
// categoryId には登録先のカテゴリ名、sortNumber はカテゴリ内でエレメントを表示させる際の優先順位を指定できます。
// sortNumber を基準に昇順で表示されます。
elementRepository.registerClass(MyZipCodeField, {
    categoryId: 'programming-sample',
    sortNumber: 0,
});
// メッセージプロパティを JSON 化したものが以下に置換され、メッセージとして登録されます。
// そのため、この記述は削除しないでください。
window.imHichee.MessageBuilder.register('6549e165-925d-4de2-8c52-ec3cda282ed2');
4.3.8. プロパティファイルの実装¶
{VSCODE_HOME}/src/public/messages に component_ja.properties を作成し、以下のようにメッセージプロパティを記述します。
# エレメント - カテゴリ
CAP.Z.IWP.HICHEE.COMPONENT.CATEGORY.NAME.programming-sample=プログラミングガイドサンプル
# エレメント - 名称
CAP.Z.IWP.HICHEE.COMPONENT.NAME.MyZipCodeField=郵便番号入力
# エレメント - ヘルプ
CAP.Z.IWP.HICHEE.COMPONENT.DESCRIPTION.MyZipCodeField=郵便番号を入力するためのサンプルエレメントです。