Do not modify the content of web-platform.js, use separate JS file to implement your own methods.

<!-- Adding a VM Monitor tab -->
<extension id="com.vmware.samples.vspherewssdk.vm.monitor">
<extendedPoint>vsphere.core.vm.monitorViews</extendedPoint>
<object>
<name>#{monitorTab.label}</name>
<componentClass className="com.vmware.vsphere.client.htmlbridge.HtmlView">
<object>
<root>
<url>/vsphere-client/vspherewssdk/resources/vm-monitor.html</url>
</root>
</object>
</componentClass>
</object>
</extension>
The <url> value is a relative URL starting with your plugin web context path, here /vsphere-client/vspherewssdk, which is also the Web-ContextPath defined in MANIFEST.MF (except for the starting /).
The relative URL path must end with .html in order for the view's http session to be authenticated properly.
https URLs can also be used to display content from another domain in that view, but the content won't load the first time the user goes there, unless the certificate was already verified. http URLs won't work, modern browsers blocks insecure content inside the secure Web Client domain.
Notice that the view name "WSSDK sample" is localized like all labels in plugin.xml by using the syntax #{monitorTab.label} where monitorTab.label is the resource key in the plugin's properties file.
Sample running in Flex client 6.0
The view document vm-monitor.html is opened with a request containing the following parameters:
Note that when the HtmlView is first created the objectId parameter may be null, so your view code must check for a null objectId before it runs.
objectType is only provided for vSphere objects in the case of the HTML Client, i.e. VirtualMachine, HostSystem, etc.
<componentClass className="com.vmware.vsphere.client.htmlbridge.HtmlView">
<object>
<root>
<url>/vsphere-client/vspherewssdk/resources/vm-monitor.html</url>
<useHtmlContentImage>false</useHtmlContentImage>
</root>
</object>
</componentClass>
<!-- VM summary sample view -->
<extension id="com.vmware.samples.vspherewssdk.vm.summary">
<extendedPoint>vsphere.core.vm.summarySectionViews</extendedPoint>
<object>
<name>#{summaryView.title}</name>
<componentClass className="com.vmware.vsphere.client.htmlbridge.HtmlView">
<object>
<root>
<url>/vsphere-client/vspherewssdk/resources/vm-summary.html</url>
<dialogTitle>WSSDK Summary Sample</dialogTitle>
<dialogSize>440,400</dialogSize>
</root>
</object>
</componentClass>
</object>
</extension>
<!-- Chassis summary view -->
<extension id="com.vmware.samples.chassisb.SummaryView">
<extendedPoint>com.vmware.samples.chassisb.summaryViews</extendedPoint>
<object>
<name>(not used)</name>
<componentClass className="com.vmware.vsphere.client.htmlbridge.HtmlView">
<object>
<root>
<url>/vsphere-client/chassisb/resources/chassis-summary.html</url>
</root>
</object>
</componentClass>
</object>
</extension>
The example below uses JQuery UI's accordion widget which is well suited for a view with separate summary data boxes. See the chassisA-html or chassisB-html samples.
<servlet-mapping>
<servlet-name>springServlet</servlet-name>
<url-pattern>/rest/*</url-pattern>
</servlet-mapping>
<bean name="dataAccessController" class="com.vmware.samples.mvc.DataAccessController" />The plugin's java service bundle contain the DataAccessController class, with its @RequestMapping set to /data. It has a generic getProperties() method mapped to /properties/{objectId} which will handle the Ajax request above.
/**
* A controller to serve HTTP JSON GET requests to the endpoint "/data".
*/
@Controller
@RequestMapping(value = "/data", method = RequestMethod.GET)
public class DataAccessController {
...
@RequestMapping(value = "/properties/{objectId}")
@ResponseBody
public Map<String, Object> getProperties(
@PathVariable("objectId") String objectId,
@RequestParam(value = "properties", required = true) String properties) throws Exception {
getProperties() uses a QueryUtil class to transform the list of properties into the proper Data Service query and make the calls. Results are returned to the browser as JSON data.
Object ref = _objectReferenceService.getReference(objectId);
String[] props = properties.split(",");
PropertyValue[] pvs = QueryUtil.getProperties(_dataService, ref, props);
Map<String, Object> propsMap = new HashMap<String, Object>();
propsMap.put(OBJECT_ID, objectId);
for (PropertyValue pv : pvs) {
propsMap.put(pv.propertyName, pv.value);
}
return propsMap;
When the Ajax call returns the javascript code can display the data as needed.
<!-- Define the chassis list columns -->
<extension id="com.vmware.samples.chassisa.list.sampleColumns">
<extendedPoint>com.vmware.samples.chassisa.list.columns</extendedPoint>
<object>
<items>
<!-- Chassis name column -->
<com.vmware.ui.lists.ColumnContainer>
<uid>com.vmware.samples.chassisa.column.name</uid>
<dataInfo>
<com.vmware.ui.lists.ColumnDataSourceInfo>
<headerText>#{name}</headerText>
<!-- Object property whose text value will be displayed (array of 1 element) -->
<requestedProperties>
<String>name</String>
</requestedProperties>
<sortProperty>name</sortProperty>
<exportProperty>name</exportProperty>
</com.vmware.ui.lists.ColumnDataSourceInfo>
</dataInfo>
</com.vmware.ui.lists.ColumnContainer>
etc...
The requestedProperties values are fetched directly by the platform, i.e. no UI code is involved. You just need to implement a DataProviderAdapter or PropertyProviderAdapter on the java side to return the data.

<extension id="com.vmware.samples.chassisa.listActionSet">
<extendedPoint>vise.actions.sets</extendedPoint>
<object>
<actions>
<com.vmware.actionsfw.ActionSpec>
<uid>com.vmware.samples.chassisa.createChassis</uid>
<label>#{chassis.createAction}</label>
<icon>#{addChassis}</icon>
<delegate>
<className>com.vmware.vsphere.client.htmlbridge.HtmlActionDelegate</className>
<object><root>
<actionUrl>/vsphere-client/chassis/resources/editChassisAction.html</actionUrl>
<dialogTitle>#{chassis.createAction}</dialogTitle>
<dialogSize>500,400</dialogSize>
<dialogIcon>#{addChassis}</dialogIcon>
</root></object>
</delegate>
<privateAction>true</privateAction>
</com.vmware.actionsfw.ActionSpec>
</actions>
</object>
</extension>
The implementation works as follows:
The actionUrl relative path must end with .html in order for the http session to be authenticated.
The dialogTitle, dialogSize and dialogIcon properties apply to the dialog box: dialogTitle can be localized, dialogSize is the width and height in pixels, dialogIcon is an optional icon resource.
<extension id="com.vmware.samples.chassisa.actionSet">
<extendedPoint>vise.actions.sets</extendedPoint>
<object>
<actions>
<com.vmware.actionsfw.ActionSpec>
<uid>com.vmware.samples.chassisa.deleteChassis</uid>
<label>#{chassis.deleteAction}</label>
<icon>#{deleteChassis}</icon>
<delegate>
<className>com.vmware.vsphere.client.htmlbridge.HtmlActionDelegate</className>
<object><root>
<actionUrl>/vsphere-client/chassis/rest/actions</actionUrl>
</root></object>
</delegate>
</com.vmware.actionsfw.ActionSpec>
...
The implementation works as follows:
@RequestMapping(method = RequestMethod.POST) @ResponseBody public Mapinvoke( @RequestParam(value = "actionUid", required = true) String actionUid, @RequestParam(value = "targets", required = false) String targets, @RequestParam(value = "json", required = false) String json) throws Exception { ... ActionResult actionResult = new ActionResult(actionUid, RESOURCE_BUNDLE); if (actionUid.equals("com.vmware.samples.chassisa.editChassis")) { boolean result = _chassisService.updateChassis(objectRef, chassisInfo); actionResult.setObjectChangedResult(result, "editAction.notFound"); } else if (actionUid.equals("com.vmware.samples.chassisa.deleteChassis")) { boolean result = _chassisService.deleteChassis(objectRef); actionResult.setObjectDeletedResult(result, "deleteAction.notFound"); } else if (actionUid.equals("com.vmware.samples.chassisa.createChassis")) { Object result = _chassisService.createChassis(chassisInfo); if (result != null) { actionResult.setObjectAddedResult((URI)result, "samples:Chassis", null); } else { // Case where the name is already taken String[] params = new String[] { chassisInfo.name }; actionResult.setErrorMessage("createAction.nameTaken", params); } } else { String warning = "Action not implemented yet! "+ actionUid; _logger.warn(warning); actionResult.setErrorLocalizedMessage(warning); } return actionResult.getJsonMap(); }
Note that it is not possible to display other custom UI in response to a headless action. Instead you must handle your service call from a UI action dialog and not rely the action pattern.
// Example of a modal dialog opened outside the HTML view, not constrained in size,
// for instance a wizard for which you need the rest of the application to be blocked.
$("#editChassis").click(function() {
// The objectId parameter is required only because editChassisDialog.js uses
// WEB_PLATFORM.callActionsController to perform an action on that object.
var url = "/chassisa/resources/editChassisDialog.html";
WEB_PLATFORM.openModalDialog("Edit Chassis", url, 500, 300, objectId);
});
In that sample we re-use the same kind of dialog as the editChassis action extension. Modal dialogs can interact with the back-end with any type of service calls. It is also possible to call back the parent view through javascript functions (see the callback example in chassis-summary.js).
openModalDialog calls cannot be nested, i.e. you must not call openModalDialog() again from the view displayed by openModalDialog (use a regular Javascript popup instead). Also, in the Flex Client, since the dialog is modal at the application level the view's content disappears temporarily until the dialog is dismissed.
If you prefer not to use openModalDialog() but use a regular Javascript popup inside the view, be aware of the following issue: the popup is only modal within the view. A user can click elsewhere in the application without the view being notified, and if the user returns to that view you are responsible to restoring the same state.

Global views are views that are not attached to a specific object context, for instance the main view of an application or an admin view for editing some global configuration.
A global view is created with the extension vise.global.views. The componentClass is the same com.vmware.vsphere.client.htmlbridge.HtmlView as with object views. The url points either to a local HTML document (recommended) or uses an absolute https URL and the same restrictions applies.
<!-- Global app main view -->
<extension id="com.vmware.samples.h5.globalview.mainView">
<extendedPoint>vise.global.views</extendedPoint>
<object>
<name>#{app.name}</name>
<componentClass className="com.vmware.vsphere.client.htmlbridge.HtmlView">
<object>
<root>
<url>/vsphere-client/globalview/resources/mainView.html</url>
</root>
</object>
</componentClass>
</object>
</extension>
Since there is no context object the global view document is opened with a request containing only the locale parameter.
When a plugin view needs to display data for a particular vCenter a common UI is to use a vCenter Selector at the top, i.e. a drop-down listing all connected vCenters connected and allowing the user to switch quickly between them.
The HtmlView includes this selector when the flag <showVCenterSelector> is set to true:
<extension id="com.vmware.samples.htmltest.vcSelectorView">
<extendedPoint>vise.global.views</extendedPoint>
<object>
<name>#{app.name}</name>
<componentClass className="com.vmware.vsphere.client.htmlbridge.HtmlView">
<object>
<root>
<url>/vsphere-client/htmltest/resources/vcSelectorTest.html</url>
<showVCenterSelector>true</showVCenterSelector>
</root>
</object>
</componentClass>
</object>
</extension>
In that case the document request contains the following parameters, which can in turn be passed to a Java service in order to access vCenter data through the vSphere Web Service SDK.

// Navigate to a global view using its extensionId
WEB_PLATFORM.sendNavigationRequest("com.vmware.samples.htmltest.settingView");
// Navigate to an object view, so the objectId is required.
WEB_PLATFORM.sendNavigationRequest("com.vmware.samples.chassisManageView1", objectId);
$(document).ready(function() {
function refreshData() {
// load the page data
...
}
// Set refreshData to be called when the user hits the top bar's Refresh button.
WEB_PLATFORM.setGlobalRefreshHandler(refreshData);
}

It is important to use fully qualified bundle names in order to avoid clashes with other plugins!
<plugin id="com.vmware.samples.chassisa"
defaultBundle="com_vmware_samples_chassisa">
<resources>
<resource locale="{locale}">
<module uri="locales/chassisa-{locale}.swf"/>
</resource>
</resources>
...
<templateInstance id="com.vmware.samples.lists.allChassis">
<templateId>vsphere.core.inventorylist.objectCollectionTemplate</templateId>
<variable name="namespace" value="com.vmware.samples.chassisa_collection"/>
<variable name="title" value="#{chassisLabel}"/>
<variable name="icon" value="#{chassis}"/>
...
The english version for ''chassisLabel'' and the ''chassis'' icon are defined in
locale/en_US/com_vmware_samples_chassisa.properties like this:
# ------- String properties --------
chassisLabel = ChassisA
summary.title = Chassis main info
...
# ------------- Images -------------
chassis = Embed("../../assets/images/chassis.png")
localizedImage.url = assets/images/localizedImage-en_US.png
...
var ns = com_vmware_samples_chassisa;
ns.getString = getString;
// Get a string from the resource bundle defined in plugin.xml
function getString(key, params) {
return WEB_PLATFORM.getString("com_vmware_samples_chassisa", key, params);
}
And here is how the chassisA sample uses JQuery.text() to set the h3 title at runtime:
// insert the localized value of "summary.title" in the #title node
$("#title").text(ns.getString("summary.title"));
<body>
<!-- Static HTML text is replaced at runtime by localized text -->
<h3 id="title">Chassis main info</h3>
...
There are other ways to load the proper string resources
depending on the Javascript framework you are using. Refer to your framework's documentation.
// see the vsphere-wssdk-service sample for injecting _userSessionService in your class UserSession userSession = _userSessionService.getUserSession(); String locale = userSession.locale; ...Another option is to treat the server-side text as resource keys and defer the localization to the client side. For instance if you want to display the VM power-state the vSphere API only gives you enums ("poweredOff", "poweredOn", "suspended"). Instead of translating those on the Java side use the enum values as resource keys on the client side and keep everything in the same .properties file as the rest of your UI strings.

<extension id="com.vmware.samples.vspherewssdk.vm.monitor">
<extendedPoint>vsphere.core.vm.monitorViews</extendedPoint>
<object>
<name>#{monitorTab.label}</name>
<componentClass className="com.vmware.vsphere.client.htmlbridge.HtmlView">
<object>
<root>
<url>/vsphere-client/vspherewssdk/resources/vm-monitor.html</url>
</root>
</object>
</componentClass>
</object>
</extension>
Properties supported by HtmlView:
<extension id="com.vmware.samples.chassisa.listActionSet">
<extendedPoint>vise.actions.sets</extendedPoint>
<object>
<actions>
<com.vmware.actionsfw.ActionSpec>
<uid>com.vmware.samples.chassisa.createChassis</uid>
<label>#{chassis.createAction}</label>
<icon>#{addChassis}</icon>
<delegate>
<className>com.vmware.vsphere.client.htmlbridge.HtmlActionDelegate</className>
<object><root>
<actionUrl>/vsphere-client/chassis/resources/editChassisAction.html</actionUrl>
<dialogTitle>#{chassis.createAction}</dialogTitle>
<dialogSize>500,400</dialogSize>
<dialogIcon>#{addChassis}</dialogIcon>
</root></object>
</delegate>
<privateAction>true</privateAction>
</com.vmware.actionsfw.ActionSpec>
</actions>
</object>
</extension>
Properties supported by HtmlActionDelegate:
| WEB_PLATFORM APIs | Arguments | Description |
|---|---|---|
| callActionsController(url, jsonData) | url: like /vsphere-client/chassis/rest/actions.html?actionUid=...
jsonData: the optional data used by the action. |
Used to invoke a headless action from within a UI action dialog.
The target objectId(s) will be added automatically to the url. |
| closeDialog() | Allows to close a dialog that was opened through a UI action, for instance after submitting a form. | |
| getActionUid() | Get the id of the action invoked to open a UI dialog or wizard. This id can be used in the url argument of callActionsController(). | |
| getActionTargets() | Get the comma-separated list of object ids selected for an action, or null for a global action. This is valid only within a UI action dialog. | |
| getClientType() | Returns "flex" when the plugin runs in the Flex Client and "html" when it runs in the HTML Client. | |
| getClientVersion() | Returns a version in format "6.0", "6.5.0", "6.5.1" etc. | |
| getObjectId() | Get the context object id within an object view or a modal dialog. This object id can be used in turn to retrieve object data from the server. | |
| getString(bundleName, key, params) | bundleName: plugin resource bundle.
url: string resource key. params: optional array of values to replace placeholders {0}, {1}, ... in the string. |
Retrieve the localized value of a key defined in the plugin resource bundle.
See also getString(key, param) defined in your plugin namespace variable. |
| getVcSelectorInfo() | Returns the info provided by the vCenter Selector added in the HtmlView. Properties are: serviceGuid, sessionId, and serviceUrl. | |
| getUserSession() | Returns the current user session info. Properties are: userName, locale and serversInfo.
Note that usually it is better to access UserSession with the Java UserSessionService. |
|
| setGlobalRefreshHandler(callback) | callback: JS function name | Use with the view's refreshData function, it will be called when the user clicks on top Refresh button. |
| openModalDialog(title, url, width, height, objectId) |
title: the dialog title.
url: url to the html content. width, height: the width and height in pixels objectId: (optional) id of the target object |
Since 6.0. Open a modal dialog from within an html view, for instance a wizard which needs to block the rest of the UI and not be limited to the view. Because the dialog is modal at the application level the view's content disappears temporarily until the dialog is dismissed. |
| sendModelChangeEvent(objectId, opType) | objectId: the object id if the view is an object view.
opType: "add", "change", "delete" or "relationshipChange" |
Raise an event to update the object model after something changed. Note that this can be done directly through a headless action, see the chassis-app sample. |
| sendNavigationRequest(targetViewId, objectId) | targetViewId: the view extension id,
objectId: the object id if the view is an object view. |
Open a global view or object view. Similar to the Flex NavigationRequest API. |
| setDialogSize(width,height) | width, height: the width and height in pixels. | Since 6.0. Change the dialog size at runtime (i.e. overrides the dialogSize value defined in plugin.xml)
Not supported in HTML Client SDK 6.5
|
| setDialogTitle(title) | title: the dialog title. | Since 6.0. Change the dialog title at runtime (i.e. overrides the dialogTitle value defined in plugin.xml) |
| plugin namespace APIs | Arguments | Description |
|---|---|---|
| buildDataUrl(objectId, propList) | objectId: the objectId passed as parameter to the view
propList: an array of property names to be retrieved. |
Creates the REST url to use to retrieve a set of object properties through the DataAccessController.
Use WEB_PLATFORM.getObjectId() to get the objectId. |
| getString(key, params) | url: string resource key.
params: optional array of values to replace placeholders {0}, {1}, ... in the string. |
Retrieve the localized value of a key defined in the plugin resource bundle. |
Keeping Java Services and Data Adapters separate from the controllers is also a recommended design. The service and data level integration should remain independent of the UI technology. The SDK Java APIs are the same for both type of client plugin.
See also: Getting Started with HTML Client SDK - Eclipse Setup - Extensions Points - FAQ - Java API - Tutorial