]> git.argeo.org Git - gpl/argeo-slc.git/blob - server/org.argeo.slc.ria/src/argeo-ria-src/class/org/argeo/ria/event/CommandsManager.js
Move to src
[gpl/argeo-slc.git] / server / org.argeo.slc.ria / src / argeo-ria-src / class / org / argeo / ria / event / CommandsManager.js
1 /**
2 * The main controller (in a standard MVC point of view) of the application. It is a singleton
3 * thus can be called by any part of the application.
4 * This will wire all the commands that can be defined dynamically by any IView, and add their
5 * corresponding buttons to the application menubar and toolbars.
6 * See the "definitions" property documentation below for more info on how to define new commands.
7 *
8 * @author Charles du Jeu
9 */
10 qx.Class.define("org.argeo.ria.event.CommandsManager",
11 {
12 type : "singleton",
13 extend : qx.core.Object,
14
15 construct : function(){
16 this.base(arguments);
17 this.setInitialDefinitions(qx.lang.Object.copy(this.getDefinitions()));
18 this.addListener("changedCommands", this.createCommands, this);
19 },
20
21 properties :
22 {
23 /**
24 * The commands definitions is a Map described as below
25 * <pre>
26 * {
27 * <b>label : "",</b>
28 * | The label of the action
29 *
30 * <b>icon : "",</b>
31 * | The icon image
32 *
33 * <b>shortcut : "",</b>
34 * | The keyboard shortcut, as defined in qooxdoo (Control+s, Alt+k, etc.). Warning, the letter must be lowercase.
35 *
36 * <b>enabled : true,</b>
37 * | Whether it is enabled or disabled at creation
38 *
39 * <b>menu : ""|null,</b>
40 * | The menu group to which the command will be added. If null, will not appear in the menus.
41 *
42 * <b>menuPosition : "first"|"last"</b>
43 * | Optional : force the menu group to be first or last in the menubar.
44 *
45 * <b>toolbar : ""|null,</b>
46 * | The toolbar group to which the command will be added. If null, will not appear in the toolbars.
47 *
48 * <b>init : function(){},</b>
49 * | Optional function called at command creation.
50 * | Function context : the command itself
51 *
52 * <b>callback : function(e){},</b>
53 * | The main callback to be triggered when command is executed.
54 * | Function context : the current class (not the command!)
55 *
56 * <b>selectionChange : function(viewPaneId, xmlNodes){},</b>
57 * | Optional function called each time a selectionChange is detected in one of the active viewPane.
58 * | The origin viewPaneId and the new selection as a map of nodes are passed as arguments.
59 * | Function context : the command itself.
60 *
61 * <b>submenu : [{label:"", icon:"", commandId:""}, ...],</b>
62 * | If set, the command will create a submenu, being in a menu or in the toolbar.
63 * | The submenu is created with the various array entries, and the submenuCallback function
64 * | will be called with the 'commandId' parameter when a submenu entry is selected.
65 *
66 * <b>submenuCallback : function(commandId){},</b>
67 * | Callback if command is a submenu (cf. above).
68 * | Function context : the current class/
69 *
70 * <b>command : null</b>
71 * | For internal use only, caching the actual org.argeo.ria.event.Command object.
72 * }
73 * </pre>
74 * @see org.argeo.ria.event.Command for the definition Map details.
75 */
76 definitions : {
77 init : {},
78 check : "Map"
79 },
80 /**
81 * For internal use
82 */
83 initialDefinitions : {
84 init : {},
85 check : "Map"
86 },
87 /**
88 * Special command definitions that are shared between focusable parts.
89 */
90 sharedDefinitions : {
91 init: {},
92 check: "Map"
93 }
94 },
95
96 events : {
97 /**
98 * Triggered when the whole commands list is changed. Mainly used internally by the manager.
99 */
100 "changedCommands" : "qx.event.type.Event"
101 },
102
103 /*
104 *****************************************************************************
105 MEMBERS
106 *****************************************************************************
107 */
108
109 members :
110 {
111 /**
112 * Initialize the manager with basic definitions.
113 * @param initDefinitions {Map} A map of commands definitions.
114 */
115 init : function(initDefinitions){
116 this.setDefinitions(initDefinitions);
117 this.setInitialDefinitions(qx.lang.Object.copy(initDefinitions));
118 },
119
120 /**
121 * Creates all the objects (if they are not already existing) from the definitions maps.
122 */
123 createCommands : function(){
124 this.menus = {};
125 this.toolbars = {};
126 var defs = this.getDefinitions();
127 var shared = this.getSharedDefinitions();
128 for(var key in defs){
129 var definition = defs[key];
130 var command;
131 if(!definition.command){
132 command = new org.argeo.ria.event.Command(key, definition.label, definition.icon, definition.shortcut);
133 if(definition.submenu){
134 command.setMenu(definition.submenu);
135 if(definition.submenuCallback){
136 command.setMenuCallback(definition.submenuCallback);
137 command.setMenuContext((definition.callbackContext?definition.callbackContext:null));
138 }
139 }
140 command.setEnabled(definition.enabled);
141 if(definition.toggle){
142 command.setToggle(true);
143 if(definition.toggleInitialState){
144 command.setToggleInitialState(definition.toggleInitialState);
145 }
146 }
147 this._attachListener(command, definition.callback, definition.callbackContext);
148 if(definition.init){
149 var binded = qx.lang.Function.bind(definition.init, command);
150 binded();
151 }
152 definition.command = command;
153 }else{
154 command = definition.command;
155 if(shared[key]){
156
157 for(var focusPartId in shared[key]){
158 var sharedCommand = shared[key][focusPartId];
159 if(sharedCommand.callback){
160 var split = sharedCommand.callbackContext.split(":");
161 var focusPart = split[0];
162 var viewId = split[1];
163 command.registerCallback(sharedCommand.callback, split[1]);
164 //this._attachListener(command, sharedCommand.callback, sharedCommand.callbackContext);
165 }
166 }
167
168 }
169 }
170 if(definition.menu){
171 if(!this.menus[definition.menu]) this.menus[definition.menu] = [];
172 this.menus[definition.menu].push(definition);
173 }
174 if(definition.toolbar){
175 if(!this.toolbars[definition.toolbar]) this.toolbars[definition.toolbar] = [];
176 this.toolbars[definition.toolbar].push(command);
177 }
178 }
179 this.setDefinitions(defs);
180 },
181
182 /**
183 * Refresh the current commands status depending on the viewSelection.
184 * @param viewSelection {org.argeo.ria.components.ViewSelection} The current ViewSelection
185 */
186 refreshCommands : function(viewSelection){
187 var defs = this.getDefinitions();
188 var shared = this.getSharedDefinitions();
189 var xmlNodes = null;
190 if(viewSelection.getCount() > 0){
191 var xmlNodes = viewSelection.getNodes();
192 }
193 for(var key in defs){
194 var definition = defs[key];
195 if(!definition.selectionChange) continue;
196 if(shared[key]){
197 var currentFocus = org.argeo.ria.components.ViewsManager.getInstance().getCurrentFocus();
198 //this.debug(currentFocus);
199 if(!currentFocus) continue;
200 var sharedComm = shared[key][currentFocus.getViewId()];
201 if(sharedComm && sharedComm.selectionChange){
202 var binded = qx.lang.Function.bind(sharedComm.selectionChange, definition.command);
203 binded(viewSelection.getViewId(), xmlNodes);
204 }
205 }
206 var binded = qx.lang.Function.bind(definition.selectionChange, definition.command);
207 binded(viewSelection.getViewId(), xmlNodes);
208 }
209 },
210
211 /**
212 * Record a menubar for the application
213 * @param menuBar {qx.ui.menubar.MenuBar} The application menubar
214 */
215 registerMenuBar : function(menuBar){
216 this.addListener("changedCommands", function(){
217 this.createMenuButtons(menuBar);
218 }, this);
219 this.createMenuButtons(menuBar);
220 },
221
222 /**
223 * Record a toolbar for the application
224 * @param toolBar {qx.ui.toolbar.ToolBar} The application toolbar
225 */
226 registerToolBar : function(toolBar){
227 this.addListener("changedCommands", function(){
228 this.createToolbarParts(toolBar);
229 }, this);
230 this.createToolbarParts(toolBar);
231 },
232
233 /**
234 * Creates the real buttons and add them to the passed menuBar.
235 * @param menuBar {qx.ui.menubar.MenuBar} The application menubar
236 */
237 createMenuButtons : function(menuBar){
238 menuBar.removeAll();
239 var anchors = {};
240 for(var key in this.menus){
241 var menu = new qx.ui.menu.Menu();
242 var button = new qx.ui.menubar.Button(key, null, menu);
243 var anchorDetected = false;
244 for(var i=0; i<this.menus[key].length;i++){
245 var def = this.menus[key][i];
246 menu.add(def.command.getMenuButton());
247 if(!anchorDetected && def.menuPosition){
248 anchorDetected = true;
249 anchors[def.menuPosition] = button;
250 }
251 }
252 if(!anchorDetected){
253 menuBar.add(button);
254 }
255 }
256 // Add specific anchored buttons
257 if(anchors.first) menuBar.addAt(anchors.first, 0);
258 else if(anchors.last){
259 menuBar.add(anchors.last);
260 }
261 },
262
263 /**
264 * Creates the real buttons and add them to the passed toolbar.
265 * @param toolbar {qx.ui.toolbar.ToolBar} The application toolbar
266 */
267 createToolbarParts : function(toolbar){
268 toolbar.removeAll();
269 for(var key in this.toolbars){
270 var tPart = new qx.ui.toolbar.Part();
271 toolbar.add(tPart);
272 this.toolbars[key].map(function(command){
273 tPart.add(command.getToolbarButton());
274 });
275 }
276 },
277 /**
278 * Creates a context menu from an array of commands ids.
279 * @param commandIdsArray {Array} An array of string
280 * @return {qx.ui.menu.Menu}
281 */
282 createMenuFromIds : function(commandIdsArray){
283 var defs = this.getDefinitions();
284 var contextMenu = new qx.ui.menu.Menu();
285 for(var i=0;i<commandIdsArray.length;i++){
286 var definition = defs[commandIdsArray[i]];
287 if(definition){
288 var command = definition.command;
289 contextMenu.add(command.getMenuButton());
290 }
291 }
292 return contextMenu;
293 },
294 /**
295 * Add a new set of commands definitions. See the definitions property of this class.
296 * @param definitions {Map} a set of commands definitions.
297 * @param callbackContext {qx.ui.core.Object} The context used inside the commands callbacks.
298 * @param focusablePartId {String} A string identifying the associated focusable part, like "view:viewId".
299 */
300 addCommands : function(definitions, callbackContext, focusablePartId){
301 var crtDefs = this.getDefinitions();
302 for(var key in definitions){
303 if(callbackContext) definitions[key]['callbackContext'] = callbackContext;
304 if(crtDefs[key] && definitions[key]['shared']){
305 if(focusablePartId) {
306 definitions[key]['focusablePartId'] = focusablePartId;
307 if(!this.getSharedDefinitions()[key]){
308 this.getSharedDefinitions()[key] = {};
309 }
310 this.getSharedDefinitions()[key][focusablePartId] = definitions[key];
311 }
312
313 }else{
314 crtDefs[key] = definitions[key];
315 }
316 }
317 this.setDefinitions(crtDefs);
318 this.fireEvent("changedCommands");
319 },
320 /**
321 * Removes a whole set of commands by their definitions maps.
322 * @param definitions {Map} a set of commands definitions
323 * @param focusablePartId {String} A string identifying the associated focusable part, like "view:viewId".
324 */
325 removeCommands : function(definitions, focusablePartId){
326 var crtDefs = this.getDefinitions();
327 var initDefs = this.getInitialDefinitions();
328 var sharedDefs = this.getSharedDefinitions();
329 for(var key in definitions){
330 if(!crtDefs[key]) continue;
331 if(initDefs[key]){
332 crtDefs[key] = initDefs[key];
333 }else{
334 if(sharedDefs[key] && sharedDefs[key][focusablePartId]){
335 crtDefs[key].command.removeCallback(focusablePartId);
336 delete sharedDefs[key][focusablePartId];
337 }else{
338 delete crtDefs[key];
339 }
340 }
341 }
342 this.setDefinitions(crtDefs);
343 this.fireEvent("changedCommands");
344 },
345 /**
346 * Executes a command by its id.
347 * @param commandId {String} The command id.
348 */
349 executeCommand : function(commandId){
350 var defs = this.getDefinitions();
351 if(defs[commandId] && defs[commandId].command.getEnabled()){
352 defs[commandId].command.execute();
353 }
354 },
355 /**
356 * Retrieves a command by its id.
357 * @param commandId {String} The command id.
358 */
359 getCommandById : function(commandId){
360 var defs = this.getDefinitions();
361 if(defs[commandId] && defs[commandId].command){
362 return defs[commandId].command;
363 }
364 },
365 /**
366 * Add a standard context menu to a toolbar for button look and feel (show icon, text, both).
367 * @param toolbar {qx.ui.toolbar.ToolBar} The toolbar
368 */
369 addToolbarContextMenu : function(toolbar){
370 var menu = new qx.ui.menu.Menu();
371 var icon = new qx.ui.menu.RadioButton("Show Icons");
372 icon.setValue("icon");
373 var text = new qx.ui.menu.RadioButton("Show Text");
374 text.setValue("label");
375 var both = new qx.ui.menu.RadioButton("Show Both");
376 both.setValue("both");
377 var mgr = new qx.ui.form.RadioGroup(icon, text, both);
378 menu.add(icon);
379 menu.add(text);
380 menu.add(both);
381 mgr.setSelected(both);
382 toolbar.setContextMenu(menu);
383 mgr.addListener("changeValue", function(e){
384 this.setShow(e.getData());
385 }, toolbar);
386
387 },
388 /**
389 * Attach a listener to a command, with a context.
390 * The context can be an object, a string like "view:viewId" or null.
391 * If a string, the viewPaneId content will be retrieved at runtime. If null, "this" will be used
392 * as default context.
393 * @param command {org.argeo.ria.event.Command} The command
394 * @param callback {Function} The function to execute
395 * @param callbackContext {Object|String} The context in which the function will be executed.
396 */
397 _attachListener:function(command, callback, callbackContext){
398 if(!callbackContext){
399 command.addListener("execute", callback, this);
400 return;
401 }
402 if(typeof(callbackContext) == "object"){
403 command.addListener("execute", callback, callbackContext);
404 return;
405 }
406 if(typeof(callbackContext) == "string"){
407
408 var split = callbackContext.split(":");
409 var focusPart = split[0];
410 var viewId = split[1];
411 if(command.getCallbacks()[viewId]) return;
412 command.registerCallback(callback, split[1]);
413 command.addListener("execute", function(event){
414 var target = event.getTarget();
415 var callbacks = target.getCallbacks();
416 if(qx.lang.Object.getLength(callbacks) == 0) return;
417 var view = org.argeo.ria.components.ViewsManager.getInstance().getViewPaneById(viewId).getContent();
418 if(qx.lang.Object.getLength(callbacks) == 1){
419 var binded = qx.lang.Function.bind(callbacks[qx.lang.Object.getKeys(callbacks)[0]], view);
420 binded(event);
421 return;
422 }
423 var currentFocus = org.argeo.ria.components.ViewsManager.getInstance().getCurrentFocus();
424 if(currentFocus && currentFocus.getViewId() && callbacks[currentFocus.getViewId()]){
425 var currentViewId = currentFocus.getViewId();
426 view = org.argeo.ria.components.ViewsManager.getInstance().getViewPaneById(currentViewId).getContent();
427 var binded = qx.lang.Function.bind(callbacks[currentFocus.getViewId()], view);
428 binded(event);
429 return;
430 }
431 });
432
433
434 /*
435 if(callbackContext.split(":")[0] == "view"){
436 var viewId = callbackContext.split(":")[1];
437 command.addListener("execute", function(event){
438 if(event.getTarget().getCheckFocusAtCallback()){
439 var currentFocus = org.argeo.ria.components.ViewsManager.getInstance().getCurrentFocus();
440 if(currentFocus.getViewId() != viewId) return;
441 }
442 var view = org.argeo.ria.components.ViewsManager.getInstance().getViewPaneById(viewId).getContent();
443 var binded = qx.lang.Function.bind(callback, view);
444 binded(event);
445 });
446 }else{
447 command.addListener("execute", callback, callbackContext);
448 }
449 */
450 }
451 }
452 }
453 });