...
A typical use case involves including advanced charting capabilities in the Nexaweb Client. Although the client comes with some built in charting ability, it may not be sufficient to display certain advanced charts like those found in financial applications. Using the plug-in architecture, developers can integrate a 3rd-party chart component of their choice and define their own XML tags and format to display that chart. That chart can then be imbedded inside of standard NXML.
Example:
Nexaweb Client でプラグインがよく使用されるケースとして、高度なグラフ作成機能が必要な場合などがあります。Nexaweb Client にはもともとグラフ機能が備わっていますが、財務アプリケーションで見られるような、より高度なグラフを表示するには不十分な場合もあります。開発者は、 プラグインアーキテクチャを使用して、選択したサードパーティ製のグラフコンポーネントを統合し、そのグラフを表示するための独自の XML タグやフォーマットを定義できます。その後、このグラフを標準の NXML 内に埋め込むことができます。
例:
Code Block |
---|
<window> <borderLayout/> <label text="A custom chart" borderPosition="north" /> <!-- a specialized chart created via the plug-in architecture --> <financialChart title="Dow Jones by year" borderPosition="center" onSelect="myPage.jsp> <xAxis start="1950" end="2005"/> <yAxis start="0" y="5000"/> <pointData points="1900,2000,2020 ... "/> </financialChart> </window> |
Technical Overview
...
最 も基本的なレベルでは、プラグインはcom.nexaweb.client.tags.AbstractTagImpl
...
...
...
AbstractTagImpl はすべてのプラグインの基底クラスです。また、特定タイプのプラグインの作成を容易にするために、多くのサブクラスがあります。たとえば、 ContainerBridge または SwingBridge をサブクラスとして使用し、java.awt.Component
...
...
...
In the example above, the developer has some 3rd party charting components that serve as the peers. The handling classes that extend AbstractTagImpl are responsible for manipulating these peers based on changes in the XML, as well as responding to changes in the peer classes.
AbstractTagImpl and its subclasses automatically listen for StructureChangeEvents and AttributeChangeEvents their associated XML elements. It is the responsibility of the individual subclasses to perform some meaningful operation when attributes change or child tags are added and removed. Similarly, subclasses can listen for changes to a peer object and fire events or update the UI Document Object Model (DOM) based on those changes. The bridge class is a two way bridge in that is listens for XML changes and passes them on to the peer, and listens for peer changes and fires events or updates the DOM based on those changes.
Care should be taken to ensure that plug-ins will run in the desired JVMs. A UI component plug-in based on Swing will only run on Swing-compatible (1.2+) JVMs.
Developing a JButton Plug-in
This section develops a simple plug-in that will map the <jButton> tag to a JButton component. The <jButton> tag will understand basic attributes such as bgColor. In addition the <jButton> tag will implement an attribute named "armed" as a boolean, and fire onCommand events when activated.
...
上記の例では、開発者は、ピアとして機能するサードパーティ製のグラフ作成コンポーネントを使用します。AbstractTagImpl を拡張する処理クラスには、ピアクラスへの変更に応答すると共に、XML への変更に基づいてこれらのピアを操作する役割があります。
AbstractTagImpl と そのサブクラスは、StructureChangeEvents と AttributeChangeEvents、およびそれらに関連付けられている XML 要素を自動的に待機します。各サブクラスは、属性が変更されたり、子タグが追加および削除されたときに、意味のある操作を行います。同様に、サブクラスは ピアオブジェクトへの変更を待機して、イベントを発生させるか、このような変更に基づいて UI DOM (Document Object Model) を更新できます。ブリッジクラスは双方向のブリッジで、XML への変更を待機してその変更をピアに渡します。また、ピアへの変更を待機して、イベントを発生させるか、その変更に基づいて DOM を更新します。
プラグインが適切な JVM で実行されるよう注意する必要があります。Swing に基づく UI コンポーネントのプラグインは、Swing 対応の JVM (1.2 以上) でのみ実行されます。
JButton プラグインの作成
こ こでは、<jButton> タグを JButton コンポーネントにマップする、シンプルなプラグインを作成します。<jButton> タグは bgColor などの基本属性を認識します。また、<jButton> タグは、"armed" (作動準備完了) という属性をブール値として実装し、アクティブになったときに、onCommand イベントを発生させます。
<jButton> タグを作成する最初の手順では、<jButton> タグにマップするタグ ハンドラを作成します。JButton は Swing ベースのコンポーネントであるため、使用するクラスによって SwingBridge が拡張されます。<jButton> タグが最初に検出されると、パーサーは JButtonBridge のインスタンスをインスタンス化して、それに対する初期化メソッドを呼び出します。このようなメソッドの 1 つであるinit() は、順に createUiComponent() を呼び出します。次に示すように、このメソッドをオーバーライドして必要な UI コンポーネントを作成する必要があります。
Code Block | ||||
---|---|---|---|---|
| ||||
public class JButtonBridge extends SwingBridge { public void createUiComponent(){ setUiComponent(new JButton()); } } |
At this point, our bridge class will create a JButton component, and will automatically respond to basic attributes like bgColor. We would also like this tag to respond to the "armed" attribute. We can do this by overriding onAttributeSet() to handle the new attribute. こ の時点で、ブリッジクラスは JButton コンポーネントを作成して、bgColor などの基本属性に自動的に応答します。また、このタグを "armed" 属性に応答させる必要があります。そのためには、新しい属性を処理するために onAttributeSet() をオーバーライドします。
Code Block | ||||
---|---|---|---|---|
| ||||
public void attributeSet(AttributeChangeEvent e) throws AttributeConversionException { String attributeName = e.getName(); String attributeValue = e.getNewValue(); //if the attribute is "armed" set it to true or false if ("armed".equals(attributeName) ){ ( (JButton)getUiComponent() ).getModel(). setArmed(AttributeConverter.toBoolean(attributeValue)); } //otherwise just fall through else super.attributeSet(e); } |
AttributeConverter is a utility class that provides methods that convert Strings to booleans, Color, enumerations and other data types. Overriding attributeSet and using AttributeConverter makes any errors in the XML report in the usual manner.Now that our <jButton> can respond to an attribute change, we would also like the <jButton> to report "onCommand" events when the JButton ActionListener is called. We do that by implementing ActionListener and adding ourselves as a listener to the JButton. Then when actionPerformed() is called we simply call a method from AbstractTagImpl that handles the firing of the event. The following is the final code for JButtonBridge with the most recent changes highlighted.は、文字列をブール型、Color、列挙型、その他のデータ型に変換するメソッドを提供するユーティリティクラスです。attributeSet をオーバーライドしてAttributeConverter を使用すると、XML でのすべてのエラーが通常の方法で報告されます。
<jButton> が属性の変更に応答できるようになったので、今度はJButton ActionListener が呼び出されたときに、<jButton> が "onCommand" イベントを報告する必要があります。そのためには、ActionListener を実装して、それ自体をリスナとして JButton に追加します。その後で、actionPerformed() が呼び出されたら、イベントの発生を処理する AbstractTagImpl からメソッドを呼び出すだけです。次に、JButtonBridge の最終的なコードを示します。最近行われた変更内容がハイライトされています。
Code Block | ||||
---|---|---|---|---|
| ||||
public class JButtonBridge extends SwingBridge implements ActionListener{ public void createUiComponent(){ JButton b = new JButton(); setUiComponent(b); //listen for action events so we can report them b.addActionListener(this); } public void actionPerformed(ActionEvent e){ //call the method from AbstractTagImpl that reports the event //fill in a ClientEvent object and calls the appropriate //MCO/JSP/event handler fireEvent("onCommand",null); } public void attributeSet(AttributeChangeEvent e) throws AttributeConversionException{ String attributeName = e.getName(); String attributeValue = e.getNewValue(); //if the attribute is "armed" set it to true or false if ("armed".equals(attributeName) ){ ( (JButton)getUiComponent() ).getModel(). setArmed(AttributeConverter.toBoolean(attributeValue) ); } //otherwise just fall through else super.attributeSet(e); } } |
Now that our JButtonBridge is complete we must add a mapping so that our <jButton> tag is defined and handled by the JButtonBridge class. To do this we must create a tag mapping file with the appropriate entry, then add that file to our list of tag mapping files in the client configuration. The tag mapping file itself is simply a list of tag names with the name of the handling class as the text. We will call this file customTagMappings.xml:JButtonBridge が完了したので、今度は<jButton> タグがJButtonBridge クラスによって定義および処理されるようにマッピングを追加する必要があります。そのためには、適切なエントリを使用してタグマッピングファイルを作成す る必要があります。次に、クライアント構成内のタグマッピングファイルのリストにそのファイルを追加します。タグマッピングファイル自体は、テキスト形式 の処理クラス名を持つタグ名のリストに過ぎません。このファイルを customTagMappings.xml と呼びます。
Code Block |
---|
<tag-mappings> <jButton>JButtonBridge</jButton> </tag-mappings> |
Once this file is created we must add an entry to このファイルが作成されたら、エントリを nexaweb-client.xml :に追加する必要があります。
Code Block |
---|
<client-tag-mapping> <tag-map-file>customTagMappings.xml</tag-map-file> </client-tag-mapping> |
...
プラグインアーキテクチャクラスの概要
AbstractTagImpl -...
...
...
...
...
Plug-in Class Lifecycle
When a new NXML block is appended to the existing UI DOM:
- The name of the class associated with the root element is looked up.
- The class is loaded if not loaded already.
- A new instance of the handler class is created.
- setSession() is called on the handler with the current ClientSession.
- The handler is added to the NXML element as a StructureChangeListener and AttributeChangeListener.
- setElement() is called on the handler with the NXML element.
- init() is called on the handler.
- The "onCreate" event is fired on the handler.
This process is performed only once for the entire NXML block. The root element of that block may have children and/or initial attributes set on it. AbstractTagImpl provides two methods which can be called in init() to handle these situations. The "parseInitialChildren()" method will repeat this parsing process for all children of the initial element. The "parseInitialAttributes()" method will feed the attributes on the initial element through onAttributeSet() as if they were being set after the element was created. In addition, ContainerBridge will call these methods by default in its init() method.
When an NXML block is removed from the UI DOM:
- unload() is called on the handler for the root element.
- The handler is removed from the NXML element as a StructureChangeListener and AttributeChangeListener
- The handler calls afterChildRemoved(), which recursively repeats the process for each child element of the root Element being removed.
If a handler class override onChildRemoved(), it must be sure to call afterChildRemoved() to make sure the recursive clean-up takes place.
Packaging Plug-Ins
Plug-ins consists of three sets of resources: tag-mapping files, the tag-handler classes and the peer classes. In our example the JButton peer class is included in any Swing-compatible JVM, but often peer classes will come in separate JAR files.
...
プラグインクラスのライフサイクル
新しい NXML ブロックが既存の UI DOM に追加されると、次の処理が実行されます。
- ルート要素に関連付けられているクラスの名前が検索されます。
- ロードされていないクラスがロードされます。
- ハンドラ クラスの新しいインスタンスが作成されます。
- setSession() が現在の ClientSession を使用して、ハンドラで呼び出されます。
- ハンドラが StructureChangeListener と AttributeChangeListener として NXML 要素に追加されます。
- setElement() が NXML 要素を使用して、ハンドラで呼び出されます。
- init() がハンドラで呼び出されます。
- "onCreate" イベントがハンドラで発生します。
NXML block はUI DOMから削除されます
:- unload() はroot 要素に対しハンドラに呼び出されます。
- ハンドラは、StructureChangeListener と AttributeChangeListener として NXML要素から削除されます。
- ハンドラが、再帰的にroot Element のそれぞれの子要素が削除されるプロセスを繰り返す afterChildRemoved() を呼び出します。
ハンドラクラスが onChildRemoved() より優先になる場合、 afterChildRemoved() を呼び出し、再帰的クリーンアップが確実に行われるようにします。
プラグインのパッケージ化
プラグインは次のリソース3セットから構成されています。タグマッピングファイル, タグハンドラクラス、ピア クラス。この例では JButton ピアクラスは全ての Swing-compatible JVM に含まれていますが、ピアクラスが別の JAR ファイルに含まれる場合もあります。
タグハンドラクラスとピアクラスは MCO と同様の開発ルールに従っており、tag-タグマッピングファイルは標準ファイルとして考慮されます。タグマッピングファイルとクラスを一緒に JAR ファイルに入れるのが簡単だと感じる開発者もいるでしょう。例えば、JButtonBridge クラスと カスタムTagMappings.xm は "jButtonPlugin.jar." と呼ばれるJAR ファイルに入る可能性があります。その場合 nexweb-client.xml クラスパスを変更し、その jar を含み、tag-mapping location を変更し"classpath://" specifier を使用しなければなりません。詳細は、クライアントクラスパスの調整に関しての詳細は nexaweb-client.xml のドキュメントご確認下さい。
Code Block |
---|
<!-- classpath changed to include our plug-in jar file --> <client-classpath> <pre-loaded-in-applet-def> <archive name="jButtonPlugin.jar" path="/WEB-INF/client/lib/jButtonPlugin.jar"/> </pre-loaded-in-applet-def> </client-classpath> <!-- load the tag mapping file from the classpath --> <client-tag-mapping> <tag-map-file>classpath://customTagMappings.xml</tag-map-file> </client-tag-mapping> |
One final packaging option is to make plug-in classes load on startup, rather than when the tag corresponding to that plug-in is first encountered. Classes loaded at startup will load during the Nexaweb loading animation, whereas large plug-ins loaded on the fly can cause the Nexaweb application to freeze for a few seconds. If an application makes consistent and frequent use of a large plug-in loading it at startup is often the better choice. This can be done by modifying the contents of the tag-mapping file as below:パッケージングの最終的なオプションは、プラグインに対応しているタグが最初にエンカウンタする時より、起動時に プラグインクラスをロードさせることです。起動時にロードされたクラスは Nexaweb がアニメーションをローディングしている間ロードしますが、一方でオンザフライでロードされた大きなプラグインが原因で、Nexaweb アプリケーションが数秒フリーズする可能性があります。アプリケーションが常に大きいプラグインローディングを使用する場合、起動時にローディングするのが良い選択でしょう。これは、次のようにタグマッピングファイルのコンテンツを修正することによって行う事ができます :
Code Block |
---|
<tag-mappings> <jButton loadPolicy="onStartup">JButtonBridge</jButton> </tag-mappings> |
Namespaces can be defined for a set of tags. An example is the プラグイン名前空間の定義
名前空間はタグセットに対し定義されます。例は Nexaweb Data Framework , which was implemented using the plugin architecture. The XML that defines this plugin isで、プラグインアーキテクチャを使用し実装しています。このプラグインを定義する XML は以下になります :
Code Block |
---|
<tag-mappings namespace="http://nexaweb.com/data" document="bindings"> <mapping class="com.nexaweb.plugin.data.bridge.BindingBridge" name="binding"/> </tag-mappings> |
This declares the tag "binding" in the nexawebこれはnexaweb.com/data namespace.
Overriding Built-in Components
Plugins can be used to replace the behavior of built in NXML tags. For instance if a plugin was defined as <button> it would replace the built in behavior of button.namespaceでタグ “binding” を宣言します。
ビルトインコンポーネントをオーバーライド
プラグインはビルトイン NXML タグの振る舞いを置換えるために使用されます。例えばプラグインが <button> として定義される場合、ボタンのビルトインビヘイビアを置き換えます。