Jump to: navigation, search

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.

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.

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.

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. It is assumed that any information to be collected by the dialog should be triggered by an interaction inside the dialog.

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 cleint. 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 using the show() function.