dijit/popup

Project owner:Bill Keese
since:V?

Introduction

dijit/popup is the main mechanism within dijit that enables the creation of pop-ups like dropdowns and tooltips. It is used by every widget that creates a pop-up around another element.

Note that often custom widgets will want to extend dijit/_HasDropDown rather than using dijit/popup directly.

When displaying a pop-up, there are usually two widgets involved:

  • The parent widget, which controls opening and closing of the pop-up (by using dijit/popup)
  • The pop-up/dropdown widget itself

API

Here’s an example that illustrates how a widget might open and close a drop down using dijit/popup. It's the basic pattern followed by dijit/_HasDropDown.

The example involves two widgets:
  • this - The parent widget, which controls opening and closing of the pop-up
  • dropDown - The pop-up (aka dropdown) widget itself
define(["dijit/popup"], function(popup){
    ...

    // wrap the pop-up widget and position it offscreen so
    // that it can be measured by the widget’s startup method
    popup.moveOffScreen(dropDown);

    // if the pop-up has not been started yet, start it now
    if(dropDown.startup && !dropDown._started){
        dropDown.startup();
    }

    // make the pop-up appear around my node
    popup.open({
        parent: this,
        popup: dropDown,
        around: this.domNode,
        orient: ["below-centered", "above-centered"],
        onExecute: function(){
            popup.close(dropDown);
        },
        onCancel: function(){
            popup.close(dropDown);
        },
        onClose: function(){
        }
    });

    ...
}

As you can see, there are three essential calls here, popup.moveOffScreen, popup.open, and popup.close. popup.moveOffScreen wraps the popup widget in a container, appends it to the <body>, then moves it off-screen so that any measurement dropDown.startup needs to do is possible. Once that’s done, it opens the pop-up by calling popup.open. Finally, the onExecute and onCancel callbacks both call popup.close, passing in the correct pop-up widget to close.

It’s important to note here that the parent widget is responsible for both opening and closing the pop-up. This architecture was used so that the parent widget is always aware of whether or not its child pop-up is open, and so that it can easily perform any necessary clean-up or other relevant activity once its pop-up has closed.

Details of dijit/popup::open()

Opening a pop-up from a parent widget involves calling popup.open with a kwArgs object that provides information about the pop-up and its related parent widget. The available properties for this object are:

parent (Widget)
The widget that is displaying the pop-up.
popup (Widget, required)
The widget to display as a pop-up. This can be any dijit widget; some widgets that are commonly used as popups include dijit.ColorPalette, dijit.Menu, and dijit.Calendar.
around (DomNode)
A DOM node that should be used as a reference point for placing the pop-up. For pop-ups that are not meant to be placed around an element, use x and y instead
x (number)
The absolute horizontal position in pixels at which to place the pop-up.
y (number)
The absolute vertical position in pixels at which to place the pop-up.
orient (string[])

When placing a pop-up around a DOM node, it is possible to specify where the pop-up should appear around it by providing an array of position strings. Dijit will try each position in order until the pop-up appears fully within the viewport. Possible values are:

  • before: places drop down to the left of the anchor node/widget, or to the right in the case of RTL scripts like Hebrew and Arabic; aligns either the top of the drop down with the top of the anchor, or the bottom of the drop down with bottom of the anchor.
  • after: places drop down to the right of the anchor node/widget, or to the left in the case of RTL scripts like Hebrew and Arabic; aligns either the top of the drop down with the top of the anchor, or the bottom of the drop down with bottom of the anchor.
  • before-centered: centers drop down to the left of the anchor node/widget, or to the right in the case of RTL scripts like Hebrew and Arabic
  • after-centered: centers drop down to the right of the anchor node/widget, or to the left in the case of RTL scripts like Hebrew and Arabic
  • above-centered: drop down is centered above anchor node
  • above: drop down goes above anchor node, left sides aligned
  • above-alt: drop down goes above anchor node, right sides aligned
  • below-centered: drop down is centered above anchor node
  • below: drop down goes below anchor node
  • below-alt: drop down goes below anchor node, right sides aligned

If left undefined, the default value is [ "below", "below-alt", "above", "above-alt" ].

onCancel (function())
A callback that is executed when the user has tried to cancel the pop-up by either hitting ESC or by using the pop-up’s cancel mechanism.
onClose (function())
A callback that is executed when the pop-up is actually closed by popup.close.
onExecute (function())
A callback that is executed when a user has “executed” a function in the pop-up, like selecting a menu option.
padding ({x: Number, y: Number})
An object that specifies extra padding that should be given to the area around the pop-up when determining its placement.

While only the popup property is required, most pop-ups will normally need to also provide onCancel and onExecute callbacks (as explained below) as well as either an around or x and y properties.

Notes on Widgets Used as Popups

Any normal widget can be used as a pop-up. For example, is a normal widget that can be displayed inline in the page, but is used as a pop-up by the DateTextBox widget. In other words, there’s no PopupWidget base class (and no need for one).

However, there are two important methods that the pop-up widget can use to hint to the parent widget that it's ready to be closed:

onExecute: function(){
    // summary: attach point for notification about when a menu item has been executed
},

onCancel: function(/*Boolean*/ closeAll){
    // summary: attach point for notification about when the user cancels the current menu
}

dijit/popup will monitor calls to these two methods and inform the parent widget when either of them is executed.

Here’s an example from a pop-up widget that triggers onExecute when it’s been clicked:

onItemClick: function(/*Widget*/ item, /*Event*/ evt){
    ...
    // before calling user defined handler, close hierarchy of menus
    // and restore focus to place it was when menu was opened
    this.onExecute();

    // user defined handler for click
    item.onClick(evt);
    ...
}

Lifecycle

The lifecycle of a pop-up widget looks like this:

  1. Parent widget calls popup.open to display the pop-up, passing onExecute and onCancel callbacks for when it needs to close
  2. User interacts with the pop-up, causing this.onExecute() or this.onCancel() to be called on the pop-up widget
  3. dijit/popup code notices the onExecute/onCancel method has been called and informs the parent widget by calling the onExecute function defined in the popup.open call
  4. Parent widget calls popup.close, which closes the pop-up
  5. popup.close calls the onClose callback defined in the original popup.open call

If the user clicks a blank section of the screen in order to close the pop-up instead of interacting with the widget, then the ending steps of the lifecycle are slightly different:

  1. dijit/popup code notices the click on the blank area of the screen
  2. dijit/popup code doesn’t close the pop-up widget directly, but rather calls the onCancel callback from the original popup.open call
  3. Parent widget calls popup.close, which closes the pop-up

Stacks

Pop-ups can open other pop-ups. This ability is leveraged heavily by . To facilitate this, dijit/popup keeps track of the entire stack of open pop-ups. In the case when a hierarchy of pop-ups all need to be closed at once, calling popup.close on the top-most pop-up will close all child pop-ups. This means that parent widgets do not need to maintain their own stack of pop-ups in order to ensure that they can clean up properly after themselves.

Keyboard handling

dijit/popup automatically listens for key presses on the ESC key as a way to cancel the highest pop-up and return to the parent node (which may itself be a pop-up). When the ESC key is pressed, the onCancel callback passed in the call to popup.open is called. dijit/popup also listens for the TAB key, and if it sees it, the entire stack of pop-ups is cancelled (in the case of menus, where one pop-up has opened another and so forth).

Note that in neither of these cases does the dijit/popup code directly close any pop-ups. It just calls the onCancel callback defined in the call to popup.open. That callback then is responsible for calling popup.close(popupWidget).