<html><head><style type='text/css'>p { margin: 0; }</style></head><body><div style='font-family: times new roman,new york,times,serif; font-size: 12pt; color: #000000'><div style="color:#000;font-weight:normal;font-style:normal;text-decoration:none;font-family:Helvetica,Arial,sans-serif;font-size:12pt;"><div style="font-family: times new roman,new york,times,serif; font-size: 12pt; color: #000000">Hello everyone,<br><br>as a follow-up to my last email on improving plugin API, here comes the latest revision of UI Plugins proof-of-concept patch (please find it attached).<br><br>This patch is focused on improving JavaScript plugin API, along with important changes and improvements made to plugin infrastructure (<em>PluginManager</em>). Let's walk through the changes step by step.<br><br><hr style="width: 100%; height: 2px;"><br><strong>Improved plugin API, taking some inspiration from jQuery</strong><br style="font-weight: bold;"><br>Following is a sample plugin code that uses new plugin API:<br><br><span style="font-family: courier new,courier,monaco,monospace,sans-serif;">var myPlugin = pluginApi('myPlugin'); // Obtain plugin API instance for 'myPlugin'</span><br style="font-family: courier new,courier,monaco,monospace,sans-serif;"><span style="font-family: courier new,courier,monaco,monospace,sans-serif;">var myPluginConfig = myPlugin.configObject(); // Obtain plugin-specific configuration</span><br style="font-family: courier new,courier,monaco,monospace,sans-serif;"><br style="font-family: courier new,courier,monaco,monospace,sans-serif;"><span style="font-family: courier new,courier,monaco,monospace,sans-serif;">// Register event handler functions to be invoked by WebAdmin</span><br style="font-family: courier new,courier,monaco,monospace,sans-serif;"><span style="font-family: courier new,courier,monaco,monospace,sans-serif;">// Note: all functions are optional, the plugin only defines functions for events it wants to handle</span><br style="font-family: courier new,courier,monaco,monospace,sans-serif;"><span style="font-family: courier new,courier,monaco,monospace,sans-serif;">myPlugin.register({</span><br style="font-family: courier new,courier,monaco,monospace,sans-serif;"><span style="font-family: courier new,courier,monaco,monospace,sans-serif;">&nbsp; UiInit: function() {</span><br style="font-family: courier new,courier,monaco,monospace,sans-serif;"><span style="font-family: courier new,courier,monaco,monospace,sans-serif;">&nbsp;&nbsp;&nbsp; var testUrl = 'http://www.example.com/' + myPluginConfig.foo; // Assume plugin configuration has 'foo' attribute</span><br style="font-family: courier new,courier,monaco,monospace,sans-serif;"><span style="font-family: courier new,courier,monaco,monospace,sans-serif;">&nbsp;&nbsp;&nbsp; myPlugin.ui.addMainTab('Custom Tab', 'custom-tab', testUrl); // Invoke some operation using plugin API</span><br style="font-family: courier new,courier,monaco,monospace,sans-serif;"><span style="font-family: courier new,courier,monaco,monospace,sans-serif;">&nbsp; }</span><br style="font-family: courier new,courier,monaco,monospace,sans-serif;"><span style="font-family: courier new,courier,monaco,monospace,sans-serif;">});</span><br style="font-family: courier new,courier,monaco,monospace,sans-serif;"><br style="font-family: courier new,courier,monaco,monospace,sans-serif;"><span style="font-family: courier new,courier,monaco,monospace,sans-serif;">myPlugin.ready(); // Event handler functions are registered, we are now ready to get initialized (UiInit)</span><br style="font-family: courier new,courier,monaco,monospace,sans-serif;"><br><hr style="width: 100%; height: 2px;"><br><strong>UI plugin life-cycle, enforced by plugin infrastructure</strong><br style="font-weight: bold;"><br>The <em>PluginState</em> enumeration lists possible states of a plugin during its runtime:<br><ul><li><span style="font-weight: bold;">DEFINED</span>: This is the initial state for all plugins. Plugin meta-data has been read by <span style="font-style: italic;">PluginManager</span> and the corresponding iframe element has been created for the plugin. Note that at this point, the iframe element is not attached to DOM yet.<br><br></li><li><span style="font-weight: bold;">LOADING</span>: The iframe element for the plugin has been attached to DOM, which causes <u>plugin host page</u> (previously known as plugin source page) to be fetched asynchronously in the background. We are now waiting for plugin to report in as ready. In practice, due to JavaScript runtime being single-threaded, WebAdmin startup logic will continue to execute until the JavaScript runtime is "idle" (browser event loop returns), and at this point JavaScript plugin code gets invoked through the plugin host page.<br><br></li><li><span style="font-weight: bold;">READY</span>: The plugin has indicated that it is ready for use. We assume the plugin has already registered its event handler object (object containing various event handler functions to be called by WebAdmin) at this point. We can now proceed with plugin initialization.<br><br></li><li><span style="font-weight: bold;">INITIALIZED</span>: The plugin has been initialized by calling <span style="font-style: italic;">UiInit</span> function on its event handler object. We can now call other event handler functions, the plugin is now initialized and in use.</li></ul><p><span style="font-style: italic; text-decoration: underline;">Note on plugin initialization:</span> the <span style="font-style: italic;">UiInit</span> function will be called just once during the lifetime of the plugin, after the plugin reports in as ready AND WebAdmin enters the state that allows plugins to be invoked (entering main section for logged-in users), and before other event handler functions are invoked by the plugin infrastructure.</p><p><br></p><hr style="width: 100%; height: 2px;"><br><p style="font-weight: bold;">Plugin meta-data is now passed to client using different format</p><p><br></p><p>Previously, plugin meta-data was embedded into WebAdmin host page as a simple JavaScript object, like so:</p><p><br></p><p style="font-family: courier new,courier,monaco,monospace,sans-serif;">var pluginDefinitions = { myPlugin: "&lt;URL&gt;", anotherPlugin: "&lt;URL&gt;" }<br></p><p><br></p><p>Now, plugin meta-data is embedded into WebAdmin host page as a JavaScript array, like so:<br></p><p><br></p><p style="font-family: courier new,courier,monaco,monospace,sans-serif;">var pluginDefinitions = [</p><p style="font-family: courier new,courier,monaco,monospace,sans-serif;">&nbsp; { name: "myPlugin", url: "&lt;URL&gt;", config: { "foo": 1, "bar": "whatever" } },</p><p style="font-family: courier new,courier,monaco,monospace,sans-serif;">&nbsp; { name: "anotherPlugin", url: "&lt;URL&gt;" }<br></p><p style="font-family: courier new,courier,monaco,monospace,sans-serif;">];</p><p><br></p><p>As you can see, <span style="font-style: italic;">pluginDefinitions</span> is now an array of JavaScript objects, with each object representing plugin meta-data. The "name" and "url" attributes are mandatory (we need to check them when loading plugin descriptors). "config" is the plugin configuration (JSON) object, obtained by merging default plugin configuration (defined in plugin descriptor) with custom plugin configuration (defined in external plugin configuration file). Note that the "config" attribute is optional.<br></p><p><br></p><p>In terms of Java classes, <span style="font-style: italic;">pluginDefinitions</span> is mapped to <span style="font-style: italic;">PluginDefinitions</span> overlay type, and each meta-data object within the array is mapped to <span style="font-style: italic;">PluginMetaData</span> overlay type.<br></p><p><br></p><hr style="width: 100%; height: 2px;"><br><p><span style="font-style: italic; text-decoration: underline;">Note on using assert statements in client code:</span> you might notice that I'm using a lot of assert statements in <span style="font-style: italic;">Plugin</span> class. This is to ensure consistency and guard against corrupted state during development. In GWT, assert statements work in a different way than in standard Java VM. When debugging GWT application using Development Mode, assert statements are checked and throw assertion errors during runtime (they are displayed in Development Mode console). However, when compiling GWT application to JavaScript (Production Mode), assert statements are removed by GWT compiler, so they don't affect the application running in Production Mode.<br></p><p><br></p><p>Cheers,</p><p>Vojtech</p><p><br></p></div></div></div></body></html>