出典:ArcGIS Maps SDK for JavaScript - Widget development
ウィジェットは再利用可能な UI コンポーネントで、リッチな UX を提供する鍵となります。ArcGIS Maps SDK for JavaScript では、完全にカスタム化されたウィジェットを HTML 要素として View に含めることができます。これには、完全に新しい機能を持つ独自のビューを開発する必要があり、ウィジェットの ViewModel を拡張することとは異なります。
カスタムウィジェットを作成する場合、@arcgis/core ES モジュールをお好みのフロントエンド JavaScript フレームワークと共に使用することをお勧めします。React、Angular、Vue.js などのフレームワークは必須ではありませんが、最大の拡張性を提供します。SDK と疎結合で、フレームワークのよく知られた UI/UX パターンとコンポーネントのライフサイクルに準拠した複数のウィジェットを作成できます。独自のカスタムウィジェットの作成方法については、jsapi-custom-widget のサンプルを参照してください。
非推奨のお知らせ
バージョン 4.27 以降、完全なカスタム ウィジェットを構築するための基礎として、/esri/widgets/Widget をサブクラス化または拡張することは推奨されなくなりました。このパターンでは、SDK 内部への依存関係が作成され、文書化されていない変更が頻繁に発生する可能性があります。以下のガイド内容は、レガシー パターンに従ったものであり、既存のウィジェットを保守する場合にのみ使用してください。
このフレームワークはすべての Dijit を直接差し替えることを意図していません。例えば、dgrid の利用には Dijit が必要です。
トピック
独自のカスタムウィジェットを作成する前に、自分のマシンでローカルにアプリケーションをビルドできるなど、最低限の要件を満たしていることを確認してください。このプロセスは、Web サーバー上でホストされ、ブラウザー上で実行される標準的な JavaScript アプリケーションを作成する場合に比べ、いくつかのステップが増えます。
TypeScript を使うことをお勧めします。その利点のひとつは、デコレーターを使って機能を強化できることです。TypeScript のセットアップ ガイド ページでは、ArcGIS Maps SDK for JavaScript を使用して TypeScript 開発環境をセットアップするための基本的な手順を説明しています。また、TypeScriptとは何か、なぜ使われるのか、どのように使うのかについて詳しく説明したオンライン リソースもたくさんあります。これらの基本に慣れることで、ウィジェット開発プロセスがより簡単になります
JSX は、JavaScriptの拡張構文で、HTMLと同様にウィジェットのユーザーインターフェイスを記述することができます。一般的には React と関連していますが、他の実装においても使うことができます。JavaScript とインラインで使えるという点では、HTML に似ています。カスタム ウィジェットは、TypeScript と JSX を組み合わせた拡張子 .tsx を使ってビルドされ、JavaScript に直接コンパイルされます。
Accessor は 4.x の主要な特徴のひとつであり、ウィジェットを含むすべてのクラスの基盤です。既存の API クラスを拡張してサブクラス、つまり子クラスを作成するため、カスタムウィジェットもこれに含まれます。カスタムウィジェットは、super() メソッドを呼び出すことで、親クラスからメソッドとプロパティを継承します。その他の詳細や使用パターンについては、Implementing Accessor のトピックを参照してください。
@arcgis/core のESモジュールは、AMD モジュールと同じ API 機能を持ちます。 ES モジュールは専用の AMD ローダーを必要としないため、ほぼすべての主要な JavaScript フレームワークやビルドツールでシームレスに動作し、ブラウザや最新のビルドツールでネイティブに消費できるため、カスタムウィジェットの構築には @arcgis/core を使用することをお勧めします。モジュールは npm を使って開発マシンのローカルにインストールします。
開発をはじめる前に、ウィジェット ライフサイクルを理解している必要があります。ウィジェットの種類にかかわらず、ライフサイクル特有の一般的な概念は同じです。
ウィジェット開発では TypeScript のデコレーターが活用され、API ではデコレーターがクラスの作成に使用される基礎的なグルーとして使用されます。これにより、既存のプロパティ、メソッド、コンストラクタに共通する動作を設計時に拡張することができます。ウィジェット デコレーターの最も一般的なタイプについて、以下で説明します。
API デコレーターを使用する場合は、後方互換性のために useDefineForClassFields
フラグを false
に設定する必要があります。詳細は TSConfig を参照してください。
@subclass
(declared
と共に使われます)@subclass() デコレーターは、API クラスを拡張するために使用します。また、コンストラクタで declaredClass
プロパティを文字列で指定することもできます。これは、declaredClass が読み取り専用である既存のクラスと、作成しようとしているカスタム クラスを区別するのに役立ちます。
以下のスニペットは、ベースとなる Widget
クラスをインポートして継承し、render
メソッドで UI を定義しています。JSX は UI を定義するために使用されます。この単純なシナリオでは、div
要素が作成され、そのコンテンツとして John Smith
という文字列が定義されます。
import Widget from "@arcgis/core/widgets/Widget.js";
@subclass("esri.widgets.HelloWorld")
class HelloWorld extends declared(Widget) {
render() {
return (
<div>John Smith</div>
);
}
}
@property()
Accessor プロパティを定義するには @property() デコレーターを使用します。このデコレーターで定義されたプロパティはすべて、get
と set
ができます。さらに、プロパティの変更を watch することもできます。
@property()
name: string;
@aliasOf()
@aliasOf() デコレーターは、デコレートするプロパティとそのメンバーの内部プロパティとの間に双方向バインディングを作成します。
@aliasOf("initialCenter.extent")
extent: Extent;
以下のステップは、独自のカスタム ウィジェットを実装する際に必要なステップの簡単な概要を提供します。
ベースとなる Widget クラスを拡張し、@subclass() コンストラクタで宣言されたクラスを指定することから始めます。
// 拡張に使用する Widget クラスのインポート
import Widget from "@arcgis/core/widgets/Widget.js";
@subclass("esri.widgets.HelloWorld")
class HelloWorld extends Widget {
}
次に、ウィジェット特有のプロパティとメソッドを実装します。このコードは、プロパティ作成において、デコレーターをどのように利用するのかを示しています。
// 'name' プロパティの作成
@property()
name: string = "John Smith";
// private _onNameUpdateメソッドの作成
private _onNameUpdate(): string { return '${this.name}';}
デフォルトでは、要素の中で参照される関数は、実際の要素を参照する this
を持ちます。必要に応じて、this
の参照先を変更するために bind
属性を使用します。以下では、name
プロパティを更新するときに呼ばれる _onNameUpdate
コールバック関数を結び付けています。これは次の postInitialize メソッドに表示されます。
class HelloWorld extends Widget {
constructor(params?: any) {
super(params);
this._onNameUpdate = this._onNameUpdate.bind(this);
}
}
postInitialize メソッドは、ウィジェットのプロパティは用意されているが、描画される前に呼ばれます。以下のコードは name
プロパティを監視しています。プロパティが更新されると、_onNameUpdate
コールバック関数を呼びます。watchUtils.init()
は WatchHandle オブジェクトを返し、own()
に渡されます。これは、ウィジェットを削除した時点で、リソースを解放するメソッドです。
import * as watchUtils from "@arcgis/core/core/watchUtils.js";
postInitialize() {
const handle = watchUtils.init(this, "name", this._onNameUpdate);
// Helper used for cleaning up resources once the widget is destroyed
this.own(handle);
}
プロパティが実装された後、ウィジェットの UI は JSX を使用して描画されます。これはウィジェットの renderer メソッドに処理されます。これは、ウィジェットの実装に必要な唯一のメソッドです。
JSX 要素として作成されたウィジェットは、まだサポートされていないことに注意してください。例えば、以下のスニペットは動作しません。
const search = <Search view={view} />;
// UI の描画
render() {
return (
<div>
{this._onNameUpdate()}
</div>
);
}
最後に、destroy
を呼びます。このメソッドはウィジェットを削除し、postInitialize()
内で参照されている own()
メソッドに登録された、すべてのリソースを解放します。
postInitialize() {
const handle = watchUtils.init(this, "name", this._onNameUpdate);
// ウィジェットが削除された時点でリソースを解放するメソッド
this.own(handle);
}
コードページの一番最後に、モジュールをエクスポートする行を追加します。エクスポートは、ウィジェットの機能を import
文によって利用可能にします。
export default HelloWorld;
jsapi-custom-widget
サンプルは、widget.tsx
ファイル全体を示します。TypeScript ファイルは、クラスが JSX を使用していることを示すために次のような拡張子を使用しています。例:.ts + .jsx = .tsx
以下のプロパティは、ウィジェットのレンダリングに使用できます。
// Widget のクラス ヘルパー メソッドで動作する新しいメソッド
render() {
const dynamicClass = {
[CSS.bold]: this.isBold,
[CSS.italic]: this.isItalic
}
return {
<div class={this.classes(CSS.base, dynamicClass)}>Hello World!</div>
};
}
render() {
const dynamicStyles = {
background-color: this.__hasBackgroundColor ? "chartreuse" : ""
};
return (
<div styles={dynamicStyles}>Hello World!</div>
);
}
private _doSomethingWithRootNode(element: Element){
console.log(element);
}
private _doSomethingWithChildNode(element: Element){
console.log(element);
}
// Access real DOM node within render()
render() {
return (
<div afterCreate={this._doSomethingWithRootNode}>Hello World!</div>
);
}
// Can also be used per element
render() {
return (
<div afterCreate={this._doSomethingWithRootNode}>
<span afterCreate={this._doSomethingWithChildNode}>Hello World!<span>
</div>
);
}
private _afterUpdate(element: Element){
console.log(element);
}
render() {
return (
<div afterUpdate={this._afterUpdate}>Hello world!</div>
);
}
private _whatIsThis(): void {
console.log('this === widget: ${this}');
}
render() {
return (
<div bind={this} onclick={this._whatIsThis}>'this' is the widget instance</div>
);
}
// キーは、以下のサンプルでは文字列で指定されていますが、数値やオブジェクトを指定することもできます。
render() {
const top = this.hasTop ? <li class={CSS.item} key="top">Top</header> : null;
const middle = this.hasMiddle ? <li class={CSS.item} key="middle">Middle</section> : null;
const bottom = this.hasBottom ? <li class={CSS.item} key="bottom">Bottom</footer> : null;
return (
<ol>
{top}
{middle}
{bottom}
</ol>
);
}
上記のメソッドに加えて、storeNode という便利なメソッドもあります。HTMLElement の DOM ノード リファレンスを変数に代入するときに使います。これは、カスタム データ属性 data-node-ref
を使用して、要素の DOM ノードへの参照を格納します。これを正しく動作させるには、以下のスニペットに示すように、bind={this}
のように、ウィジェット インスタンスにもバインドする必要があります。
// data-node-ref 属性を DOM ノード値に割り当てます。
//これは `bind` プロパティと組み合わせて使用する必要があり、
// storeNode コンビニエンス メソッドを使用するときに使用します。
rootNode: HTMLElement = null;
render() {
return (
<div afterCreate={storeNode} bind={this} data-node-ref="rootNode" />
);
}