Notice: This Wiki is now read only and edits are no longer possible. Please see: https://gitlab.eclipse.org/eclipsefdn/helpdesk/-/wikis/Wiki-shutdown-plan for the plan.
Orion/Dialog Support
In Orion 2.0, we no longer rely on the dijit toolkit for our dialog implementations. Instead, we use lightweight Orion constructs and CSS. This document describes how to implement an Orion dialog.
Contents
- 1 Dialogs (modal and modeless)
- 1.1 Defining a dialog
- 1.2 The Dialog life-cycle
- 1.2.1 Setting up the dialog template
- 1.2.2 Dialog definition and setting up the basic (non-DOM) options
- 1.2.3 Defining Buttons
- 1.2.4 Binding to the DOM elements
- 1.2.5 Modal behavior
- 1.2.6 Focus in the dialog
- 1.2.7 Showing the dialog
- 1.2.8 Finishing the dialog
- 1.2.9 Hiding and cleaning up
- 1.2.10 Destroying DOM nodes
- 1.3 Launching a dialog
- 2 Popup dialogs
Dialogs (modal and modeless)
The module "orion/webui/dialog" provides support for dialog behavior. This class defines a lightweight life-cycle for dialogs, provides optional modal behavior, template support, and automatic button creation.
Defining a dialog
Dialogs are implemented as an object which uses the Dialog prototype. By convention, most Orion dialogs define a constructor whose sole parameter is an options object describing how the dialog is to be used. This will be shown in the example code, but keep in mind that the client-facing API of the dialog is completely up to the developer. The main requirement for implementation is that the Dialog prototype be used and the life-cycle functions called appropriately. An example is the best way to demonstrate the behavior.
define(['i18n!orion/widgets/nls/messages','orion/webui/dialog'], function(messages, dialog) { /* This is the client facing API of OpenResourceDialog. Up to the developer to decide what this looks like. */ /** * Usage: <code>new OpenResourceDialog(options).show();</code> * * @name orion.webui.dialogs.OpenResourceDialog * @class A dialog that searches for files by name or wildcard. * @param {String} [options.title] Text to display in the dialog's titlebar. * @param {orion.searchClient.Searcher} options.searcher The searcher to use for displaying results. * @param {Function} options.onHide a function to call when the dialog is hidden. Optional. */ function OpenResourceDialog(options) { this._init(options); } /* Use the Dialog prototype to inherit the common dialog behavior. */ OpenResourceDialog.prototype = new dialog.Dialog();
The Dialog life-cycle
The dialog life-cycle is driven by the client code. The Dialog prototype contains life-cycle functions that must be called by the client to initiate the dialog. The stages of a dialog's life and sample code are as follows:
Setting up the dialog template
The dialog's content is described in an HTML string specified in a dialog variable called TEMPLATE. By convention, most Orion dialogs define this variable in-line in the .js file that defines the dialog. Developers who prefer to use separate HTML files may do so using any preferred template/fragment mechanism. The key point is that the HTML string must be bound to the TEMPLATE variable before calling the _initialize method in the dialog.
The OpenResourceDialog defines the TEMPLATE in line.
OpenResourceDialog.prototype.TEMPLATE = '<div role="search">' + //$NON-NLS-0$ '<div><label for="fileName">${Type the name of a file to open (? = any character, * = any string):}</label></div>' + //$NON-NLS-0$ '<div><input id="fileName" type="text" /></div>' + //$NON-NLS-0$ '<div id="crawlingProgress"></div>' + //$NON-NLS-0$ '<div id="favresults" style="max-height:400px; height:auto; overflow-y:auto;"></div>' + //$NON-NLS-0$ '<div id="results" style="max-height:400px; height:auto; overflow-y:auto;" aria-live="off"></div>' + //$NON-NLS-0$ '<div id="statusbar"></div>' + //$NON-NLS-0$ '</div>'; //$NON-NLS-0$
- The concatenation of strings shown above is for readability only.
- Concatenation should not be used to insert strings from NLS catalogs. Because these catalogs are obtained from plugins, inserting a message catalog value directly into the HTML is a security risk.
- As shown above, use the ${messageKey} syntax in the template. These variables will be parsed by the dialog prototype and the catalog strings will be substituted if found.
- You must set the messages field in your dialog so that the correct catalog will be used.
Dialog definition and setting up the basic (non-DOM) options
The specific dialog drives creation with its API. Usually the client creates the dialog with a constructor and then shows it. Our convention is to call an internal _init function during construction. This function reads the client options and sets up any necessary internal state. The last thing to do in this function is to call the Dialog._initialize function. The initialize sequence will look for special variables that drive the behavior of a dialog. For example, a title will cause a title string to appear in the dialog frame. In this example, we are just setting a title. The rest of the work in here pertains to our specific implementation until we call _initialize.
OpenResourceDialog.prototype._init = function(options) { // Set values for things that dialog cares about, such as title and message catalog. this.title = options.title || messages['Find File Named']; this.messages = messages; // Set internal values, validate the options, etc. this._searcher = options.searcher; this._onHide = options.onHide; this._contentTypeService = new mContentTypes.ContentTypeService(this._searcher.registry); if (!this._searcher) { throw new Error("Missing required argument: searcher"); //$NON-NLS-0$ } ... // If the dialog template is not in the TEMPLATE variable by default, then read the template from a file and set into the variable now. // Start the dialog initialization. this._initialize(); };
Defining Buttons
Another property you can set before calling the _initialize function is an array of buttons. Each button should have a text property that defines the button label, and a callback property that defines what to do when the button is pressed. The buttons will be generated when the DOM elements are built. If the button has an id property, it will be bound to a field as described below. If the button defines isDefault (set to true), then the button's callback will be called when the user presses the Enter key in the dialog.
Binding to the DOM elements
Hooking events and other behavior that is dependent on the existence of the DOM elements happens in the _bindToDOM function. This function is called by the dialog during the initialization process. When this function is called, you can use the following variables to access the DOM:
- this.$frameParent refers to the parent node of the entire dialog. This is usually the document body, and typically does not need to be accessed by application dialog code.
- this.$frame refers to the div element that frames the dialog. This typically does not need to be accessed by application dialogs.
- this.$parent refers to the div element that parents the client template. Clients may want to refer to this parent when querying the dialog content for a particular node.
- this.$buttonContainer refers to the div element containing any autogenerated buttons. This won't exist if no buttons were defined.
- Your content field id's are bound to $ instance variables during the _initialize function. For example:
this.$fileName.addEventListener("input", function() { // do something }, false);
The variable $fileName refers to the DOM node whose id is "fileName". All nodes assigned an id in the template will be bound to a $ variable.
Modal behavior
If the dialog should have modal behavior, set a modal flag before initialization.
MyModalDialog.prototype._init = function(options) { // Set values for things that dialog cares about, such as title. this.title = "My Modal Dialog"; this.modal = true; ...
Modal behavior will prevent non-dialog DOM elements from keeping focus. If there are additional DOM elements that should be allowed focus (such as sub-dialogs), they should be enumerated in an array assigned to this.$$modalExclusions before or during the _bindToDOM phase.
Focus in the dialog
The focus in the dialog will be set automatically to the first content field with a tabIndex >= 0. If none, the default button or first button will be chosen. If none, the close box. For custom focus setting, set the customFocus field to true and set the focus in your dialog in the _afterShowing function.
Showing the dialog
The dialog will not appear until the show() function is called. This function is implemented by the Dialog prototype. If your dialog needs to do some extra work during this time, the following optional functions may be implemented:
- _beforeShowing is called just before the dialog is made visible. Here, dom node values may be populated, etc.
- _afterShowing is called just after the dialog is made visible. This is a good place to set initial focus in the dialog if using customFocus.
Finishing the dialog
Most dialogs provide a button that the user pushes to indicate that the dialog is completed. The text for the button is specified, in addition to the callback to use when the button is pressed. In this example, an "OK" button is created, and it will call the application's done function. If the dialog should be closed when the button is pressed, then the hide function should be called from this function.
MyModalDialog.prototype._init = function(options) { // Set values for things that dialog cares about, such as title. this.title = "My Modal Dialog"; this.modal = true; ... this.buttons = [{text: messages['OK'], callback: this.done.bind(this)}]; this._initialize(); }
Hiding and cleaning up
The standard dialog frame provides a close icon that can be used to hide the dialog. The application dialog will also typically hide the dialog when it's finished. Either way, the hide() function is used to hide the dialog. To clean up any listeners or other resources allocated by the dialog, use the following functions:
- _beforeHiding is called just before the dialog is hidden. Typically listeners are removed here.
- _afterHiding is called just after the dialog is hidden for any final cleanup. The DOM nodes still exist during this call.
Destroying DOM nodes
As of this writing, the DOM nodes are destroyed after the "hiding" sequence. We assume clients recreate the dialog each time it is used. If this usage pattern changes, we could always add an option that determines whether the dialog is actually destroyed when hidden
Launching a dialog
As mentioned previously, the developer of an individual dialog determines the API, but using the conventions discussed above, a typical sequence is to create the dialog using a constructor, passing in some function that is called when the dialog is finished. Then, the client calls the show() function to show the dialog. Most of the nitty gritty work done by the caller is done in the function passed to the dialog on creation.
var dialog = new openResource.OpenResourceDialog({ searcher: searcher, searchRenderer:searcher.defaultRenderer, favoriteService:favoriteService, onHide: function() { if (editor) { editor.getTextView().focus(); } } }); dialog.show();
Popup dialogs
Popup dialogs provide lightweight, tooltip-style dialogs. The dialog is not modal, does not have standard buttons, and is automatically dismissed if the user clicks outside of the dialog. A popup's lifecycle is simpler than a regular dialog. Many popup dialogs are read only, in that the user is not expected to interact with the dialog. If there is information to be collected by the dialog, it is assumed that the content of the dialog provides buttons or other mechanisms to collect the information and close the dialog. There are no standard sets of buttons provided.
Programming a popup
The concepts are similar to regular dialogs. API definition is up to the client and the life-cycle is driven by the client. The PopupDialog prototype is used for popups.
/** * Usage: <code>new OperationsDialog(options).show();</code> * * @name orion.webui.dialogs.OperationsDialog * @class A dialog that shows running operations. * @param {DOMNode} [options.triggerNode] The node that triggers the dialog. */ function OperationsDialog(options) { this._init(options); } OperationsDialog.prototype = new popupdialog.PopupDialog(); OperationsDialog.prototype._init = function(options) { this._myOperations = []; this._initialize(options.triggerNode); }; OperationsDialog.prototype._bindToDom = function(parent) { this.$allOperationsLink.href = require.toUrl("operations/list.html"); //$NON-NLS-0$ this._setOperationsVisibility(); };
Templates are defined in a TEMPLATE variable as before, and DOM elements with id's are bound to $ variables in the popup. The main difference in popups is that there is no frame or button container. The client may still refer to the this.$parent variable to do DOM queries on the popup's content nodes.
Launching a popup
Like regular dialogs, dialogs are opened programmatically using the show() function. More often, they are attached to a trigger node which will always open the dialog when clicked. Focus is automatically assigned to the first tabbable field unless the customFocus field is set.
Hooking into the show and hide sequence is a little different than dialogs. To perform processing after showing or hiding, afterShowing and afterHiding functions can be passed as parameters in the _initialize function. The following example shows a popup dialog using an "afterShowing" function in order to set custom focus.
/** * Usage: <code>new MyDialog(options).show();</code> * * @param {DOMNode} [options.triggerNode] The node that triggers the dialog. */ function MyDialog(options) { this._init(options); } MyDialog.prototype = new popupdialog.PopupDialog(); MyDialog.prototype._init = function(options) { this.customFocus = true; this._initialize(options.triggerNode, /* afterShowing */ this.setFocus.bind(this)); }; MyDialog.prototype.setFocus = function() { this.$myCustomFocusField.focus(); };