blob: 2f2e6e702537eb1d0de6599c143c0b069cbbd133 [file] [log] [blame]
/**
* Copyright (c) 2006-2015, JGraph Ltd
* Copyright (c) 2006-2015, Gaudenz Alder
*/
/**
* Class: mxDefaultPopupMenu
*
* Creates popupmenus for mouse events. This object holds an XML node
* which is a description of the popup menu to be created. In
* <createMenu>, the configuration is applied to the context and
* the resulting menu items are added to the menu dynamically. See
* <createMenu> for a description of the configuration format.
*
* This class does not create the DOM nodes required for the popup menu, it
* only parses an XML description to invoke the respective methods on an
* <mxPopupMenu> each time the menu is displayed.
*
* Codec:
*
* This class uses the <mxDefaultPopupMenuCodec> to read configuration
* data into an existing instance, however, the actual parsing is done
* by this class during program execution, so the format is described
* below.
*
* Constructor: mxDefaultPopupMenu
*
* Constructs a new popupmenu-factory based on given configuration.
*
* Paramaters:
*
* config - XML node that contains the configuration data.
*/
function mxDefaultPopupMenu(config)
{
this.config = config;
};
/**
* Variable: imageBasePath
*
* Base path for all icon attributes in the config. Default is null.
*/
mxDefaultPopupMenu.prototype.imageBasePath = null;
/**
* Variable: config
*
* XML node used as the description of new menu items. This node is
* used in <createMenu> to dynamically create the menu items if their
* respective conditions evaluate to true for the given arguments.
*/
mxDefaultPopupMenu.prototype.config = null;
/**
* Function: createMenu
*
* This function is called from <mxEditor> to add items to the
* given menu based on <config>. The config is a sequence of
* the following nodes and attributes.
*
* Child Nodes:
*
* add - Adds a new menu item. See below for attributes.
* separator - Adds a separator. No attributes.
* condition - Adds a custom condition. Name attribute.
*
* The add-node may have a child node that defines a function to be invoked
* before the action is executed (or instead of an action to be executed).
*
* Attributes:
*
* as - Resource key for the label (needs entry in property file).
* action - Name of the action to execute in enclosing editor.
* icon - Optional icon (relative/absolute URL).
* iconCls - Optional CSS class for the icon.
* if - Optional name of condition that must be true (see below).
* enabled-if - Optional name of condition that specifies if the menu item
* should be enabled.
* name - Name of custom condition. Only for condition nodes.
*
* Conditions:
*
* nocell - No cell under the mouse.
* ncells - More than one cell selected.
* notRoot - Drilling position is other than home.
* cell - Cell under the mouse.
* notEmpty - Exactly one cell with children under mouse.
* expandable - Exactly one expandable cell under mouse.
* collapsable - Exactly one collapsable cell under mouse.
* validRoot - Exactly one cell which is a possible root under mouse.
* swimlane - Exactly one cell which is a swimlane under mouse.
*
* Example:
*
* To add a new item for a given action to the popupmenu:
*
* (code)
* <mxDefaultPopupMenu as="popupHandler">
* <add as="delete" action="delete" icon="images/delete.gif" if="cell"/>
* </mxDefaultPopupMenu>
* (end)
*
* To add a new item for a custom function:
*
* (code)
* <mxDefaultPopupMenu as="popupHandler">
* <add as="action1"><![CDATA[
* function (editor, cell, evt)
* {
* editor.execute('action1', cell, 'myArg');
* }
* ]]></add>
* </mxDefaultPopupMenu>
* (end)
*
* The above example invokes action1 with an additional third argument via
* the editor instance. The third argument is passed to the function that
* defines action1. If the add-node has no action-attribute, then only the
* function defined in the text content is executed, otherwise first the
* function and then the action defined in the action-attribute is
* executed. The function in the text content has 3 arguments, namely the
* <mxEditor> instance, the <mxCell> instance under the mouse, and the
* native mouse event.
*
* Custom Conditions:
*
* To add a new condition for popupmenu items:
*
* (code)
* <condition name="condition1"><![CDATA[
* function (editor, cell, evt)
* {
* return cell != null;
* }
* ]]></condition>
* (end)
*
* The new condition can then be used in any item as follows:
*
* (code)
* <add as="action1" action="action1" icon="action1.gif" if="condition1"/>
* (end)
*
* The order in which the items and conditions appear is not significant as
* all connditions are evaluated before any items are created.
*
* Parameters:
*
* editor - Enclosing <mxEditor> instance.
* menu - <mxPopupMenu> that is used for adding items and separators.
* cell - Optional <mxCell> which is under the mousepointer.
* evt - Optional mouse event which triggered the menu.
*/
mxDefaultPopupMenu.prototype.createMenu = function(editor, menu, cell, evt)
{
if (this.config != null)
{
var conditions = this.createConditions(editor, cell, evt);
var item = this.config.firstChild;
this.addItems(editor, menu, cell, evt, conditions, item, null);
}
};
/**
* Function: addItems
*
* Recursively adds the given items and all of its children into the given menu.
*
* Parameters:
*
* editor - Enclosing <mxEditor> instance.
* menu - <mxPopupMenu> that is used for adding items and separators.
* cell - Optional <mxCell> which is under the mousepointer.
* evt - Optional mouse event which triggered the menu.
* conditions - Array of names boolean conditions.
* item - XML node that represents the current menu item.
* parent - DOM node that represents the parent menu item.
*/
mxDefaultPopupMenu.prototype.addItems = function(editor, menu, cell, evt, conditions, item, parent)
{
var addSeparator = false;
while (item != null)
{
if (item.nodeName == 'add')
{
var condition = item.getAttribute('if');
if (condition == null || conditions[condition])
{
var as = item.getAttribute('as');
as = mxResources.get(as) || as;
var funct = mxUtils.eval(mxUtils.getTextContent(item));
var action = item.getAttribute('action');
var icon = item.getAttribute('icon');
var iconCls = item.getAttribute('iconCls');
var enabledCond = item.getAttribute('enabled-if');
var enabled = enabledCond == null || conditions[enabledCond];
if (addSeparator)
{
menu.addSeparator(parent);
addSeparator = false;
}
if (icon != null && this.imageBasePath)
{
icon = this.imageBasePath + icon;
}
var row = this.addAction(menu, editor, as, icon, funct, action, cell, parent, iconCls, enabled);
this.addItems(editor, menu, cell, evt, conditions, item.firstChild, row);
}
}
else if (item.nodeName == 'separator')
{
addSeparator = true;
}
item = item.nextSibling;
}
};
/**
* Function: addAction
*
* Helper method to bind an action to a new menu item.
*
* Parameters:
*
* menu - <mxPopupMenu> that is used for adding items and separators.
* editor - Enclosing <mxEditor> instance.
* lab - String that represents the label of the menu item.
* icon - Optional URL that represents the icon of the menu item.
* action - Optional name of the action to execute in the given editor.
* funct - Optional function to execute before the optional action. The
* function takes an <mxEditor>, the <mxCell> under the mouse and the
* mouse event that triggered the call.
* cell - Optional <mxCell> to use as an argument for the action.
* parent - DOM node that represents the parent menu item.
* iconCls - Optional CSS class for the menu icon.
* enabled - Optional boolean that specifies if the menu item is enabled.
* Default is true.
*/
mxDefaultPopupMenu.prototype.addAction = function(menu, editor, lab, icon, funct, action, cell, parent, iconCls, enabled)
{
var clickHandler = function(evt)
{
if (typeof(funct) == 'function')
{
funct.call(editor, editor, cell, evt);
}
if (action != null)
{
editor.execute(action, cell, evt);
}
};
return menu.addItem(lab, icon, clickHandler, parent, iconCls, enabled);
};
/**
* Function: createConditions
*
* Evaluates the default conditions for the given context.
*/
mxDefaultPopupMenu.prototype.createConditions = function(editor, cell, evt)
{
// Creates array with conditions
var model = editor.graph.getModel();
var childCount = model.getChildCount(cell);
// Adds some frequently used conditions
var conditions = [];
conditions['nocell'] = cell == null;
conditions['ncells'] = editor.graph.getSelectionCount() > 1;
conditions['notRoot'] = model.getRoot() !=
model.getParent(editor.graph.getDefaultParent());
conditions['cell'] = cell != null;
var isCell = cell != null && editor.graph.getSelectionCount() == 1;
conditions['nonEmpty'] = isCell && childCount > 0;
conditions['expandable'] = isCell && editor.graph.isCellFoldable(cell, false);
conditions['collapsable'] = isCell && editor.graph.isCellFoldable(cell, true);
conditions['validRoot'] = isCell && editor.graph.isValidRoot(cell);
conditions['emptyValidRoot'] = conditions['validRoot'] && childCount == 0;
conditions['swimlane'] = isCell && editor.graph.isSwimlane(cell);
// Evaluates dynamic conditions from config file
var condNodes = this.config.getElementsByTagName('condition');
for (var i=0; i<condNodes.length; i++)
{
var funct = mxUtils.eval(mxUtils.getTextContent(condNodes[i]));
var name = condNodes[i].getAttribute('name');
if (name != null && typeof(funct) == 'function')
{
conditions[name] = funct(editor, cell, evt);
}
}
return conditions;
};