summaryrefslogtreecommitdiff
path: root/editor/mxDefaultPopupMenu.js
blob: 01c65b59c4bdb9530bc22b789b523ce98e8d2395 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
/**
 * $Id: mxDefaultPopupMenu.js,v 1.29 2012-07-03 06:30:25 gaudenz Exp $
 * Copyright (c) 2006-2010, JGraph Ltd
 */
/**
 * 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).
 * 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');
				
				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);
				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.
 */
mxDefaultPopupMenu.prototype.addAction = function(menu, editor, lab, icon, funct, action, cell, parent, iconCls)
{
	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);
};

/**
 * 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;
};