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 {};
    }
    // 依存させたいエレメントのクラスを返却します。
    // ここで指定されたエレメントは、当該エレメントとの間に依存関係が定義され、自動的に定義が読み込まれます。
    // 子エレメントを実装する必要がない場合は、空の配列を返却します。
    // 返却値は子エレメントのクラス名です。
    public get dependElements(): UIElementCoreClass[] {
        return [];
    }
    // エレメント作成時に一度だけ呼び出されるメソッドです。
    // このメソッド内でエレメントのビルダーを作成して返却します。
    // このサンプルでは、<div>タグやその子要素の<input>タグを作成する処理を記述していきます。
    public createElement(
        container: IUIContainer,
        properties: IUIElementPropertyAccessor<PropertyDefinition & ICommonPropertyDefinition>
    ): IHTMLElementBuilder {
        // TODO
    }
    // レンダリングが必要になった際に呼び出されるメソッドです。
    // 例えば、rerender: trueのプロパティの値が変更されたときなど、エレメント側を更新する必要がある場合にその処理を記述します。
    // このサンプルでは、プロパティであるvalueが変更された際に、子要素の<input>タグにvalueの値を反映させる処理を記述していきます。
    public updateElement(
        builder: IHTMLElementBuilder,
        container: IUIContainer,
        properties: IUIElementPropertyAccessor<PropertyDefinition & ICommonPropertyDefinition>
    ): void {
        // TODO
    }
    // デザイナ上でエレメントが削除された際などに呼び出されるメソッドです。
    // 基本的には特に何の処理も記述しません。
    public destroyElement(
        builder: IHTMLElementBuilder,
        container: IUIContainer,
        properties: IUIElementPropertyAccessor<IUniquePropertyDefinition & ICommonPropertyDefinition>
    ): void {
        return;
    }
    // このメソッドでエレメントに子要素を追加できます。
    // このエレメントが作成された際に自動的に用意する子エレメントのインスタンスを返却してください。
    // 子エレメントは配列の格納順に配置されます。
    // 子要素がない場合は空の配列を返却します。
    // appendChild() で子要素を追加する場合と異なり、デザイナ上でコンテナに配置した際に、子要素はそれぞれ別のエレメントとして扱われます。
    public createChildren(self: IUIElement, container: IUIContainer): IUIElement[] {
        return [];
    }
    // レンダリングが必要になった際に、都度呼び出されるメソッドです。
    // 引数で渡された子エレメントの配列を変更する必要がある場合、変更後の配列を返却してください。
    // 変更の必要がない場合は、引数の children をそのまま返却してください。
    public updateChildren(
        children: IUIElement[],
        self: IUIElement,
        container: IUIContainer,
        properties: IUIElementPropertyAccessor<IUniquePropertyDefinition & ICommonPropertyDefinition>
    ): IUIElement[] {
        return children;
    }
    // 仮想エレメントがクローンによって作成された際に呼び出されるメソッドです。
    // エレメントが複製されたタイミングで特殊な採番処理等を行いたい場合などに、その処理を記述します。
    // 引数で渡された子エレメントの配列を変更する必要がある場合、変更後の配列を返却してください。
    // 変更の必要がない場合は、引数の 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 か false を返却値に指定します。true を返却する場合、デザイナ画面上に配置したエレメントを移動できます。
    public get movable(): boolean {
        return true;
    }
    // エレメントの削除可否を指定するメソッドです。
    // true か false を返却値に指定します。true を返却する場合、デザイナ画面上に配置したエレメントを削除できます。
    public get removable(): boolean {
        return true;
    }
    // エレメントの複製可否を指定するメソッドです。
    // true か false を返却値に指定します。true を返却する場合、デザイナ画面上に配置したエレメントを複製できます。
    public get copyable(): boolean {
        return true;
    }
    // エレメントが末端かどうか(配下への子エレメントの配置可否)を指定するメソッドです。
    // true か false を指定します。
    // true を返却する場合、デザイナ画面上に配置したエレメントの配下に子エレメントは配置できません。
    // このメソッドの返却値を true に指定した場合は acceptableChild() の返却値を false 、逆に false を指定した場合は acceptableChild() の返却値を true に指定するようにしてください。
    public get extremity(): boolean {
        return true;
    }
    // エレメントにおける親エレメントの配置可否を指定するメソッドです。
    // true か false を指定します。true を返却した場合、デザイナ画面上に配置したエレメントに親エレメントを配置できます。
    public acceptableParent(parent: ParentUIElement): boolean {
        return true;
    }
    // 子エレメントの配置可否を指定するメソッドです。
    // true か false を指定します。true を返却した場合、デザイナ画面上に配置したエレメントに対し子エレメントを配置できます。
    // このメソッドの返却値を true に指定した場合には extremity() の返却値を false 、逆に false を指定した場合には extremity() の返却値を true に指定するようにしてください。
    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: {
        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=郵便番号を入力するためのサンプルエレメントです。