Issue #24224:correct margin calc on booking display
[xtuple] / lib / enyo-x / source / views / module_container.js
1 /*jshint bitwise:true, indent:2, curly:true, eqeqeq:true, immed:true,
2 latedef:true, newcap:true, noarg:true, regexp:true, undef:true,
3 trailing:true, white:true*/
4 /*global XT:true, XM:true, _:true, enyo:true*/
5
6 (function () {
7
8   /**
9     @class
10     @name XV.ModuleContainer
11     @extends enyo.Panels
12     */
13   enyo.kind(
14     /** @lends XV.ModuleContainer# */{
15     name: "XV.ModuleContainer",
16     kind: "Panels",
17     arrangerKind: "CardArranger",
18     draggable: false,
19     classes: "enyo-fit",
20     handlers: {
21       onPrevious: "previous",
22       onSearch: "addSearch",
23       onTransactionList: "addTransactionList",
24       onWorkspace: "addWorkspace",
25       onChildWorkspace: "addChildWorkspacePanel",
26       onPopupWorkspace: "popupWorkspace",
27       onNotify: "notify",
28       onTransitionFinish: "handleNotify"
29     },
30     published: {
31       modules: null
32     },
33     components: [
34       {name: "startup", classes: "xv-startup-panel",
35         components: [
36         {name: "startupText", classes: "xv-startup-divider",
37           content: "_loadingSessionData".loc() + "..."},
38         {name: "startupProgressBar", kind: "onyx.ProgressBar",
39           classes: "xv-startup-progress  onyx-progress-button", progress: 0}
40       ]},
41       {kind: "onyx.Popup", name: "notifyPopup", centered: true,
42         onHide: "notifyHidden",
43         modal: true, floating: true, scrim: true, components: [
44         {name: "notifyMessage"},
45         {tag: "br"},
46         {kind: "onyx.Button", content: "_ok".loc(), name: "notifyOk", ontap: "notifyTap",
47           classes: "xv-popup-button", showing: false},
48         {kind: "onyx.Button", content: "_yes".loc(), name: "notifyYes", ontap: "notifyTap",
49           classes: "xv-popup-button", showing: false},
50         {kind: "onyx.Button", content: "_no".loc(), name: "notifyNo", ontap: "notifyTap",
51           classes: "xv-popup-button", showing: false},
52         {kind: "onyx.Button", content: "_cancel".loc(), name: "notifyCancel", ontap: "notifyTap",
53           classes: "xv-popup-button", showing: false}
54       ]},
55       {kind: "onyx.Popup", name: "popupWorkspace", classes: "xv-popup xv-groupbox-popup", centered: true,
56         autoDismiss: false, modal: true, floating: true, scrim: true},
57       {name: "navigator", kind: "XV.Navigator"}
58     ],
59     resizeHandler: function () {
60       this.inherited(arguments);
61       if (this.$.notifyPopup.showing) {
62         this.$.notifyPopup.applyStyle("opacity", 1); // XXX not sure why this hack is necessary.
63       }
64     },
65     activate: function () {
66       this.goToNavigator();
67       this.$.navigator.activate();
68     },
69     addSearch: function (inSender, inEvent) {
70       var panel;
71       if (inEvent.list) {
72         panel = this.createComponent({kind: "XV.SearchContainer"});
73         panel.render();
74         this.reflow();
75         panel.setList(inEvent);
76         panel.fetch();
77         this.next();
78       } else {
79         XT.log("No list associated with this model for searching. Are you sure you've registered it?");
80       }
81       return true;
82     },
83     /**
84       Do the exact same thing as addWorkspace, just use a ChildWorkspaceContainer
85       instead of a WorkspaceContainer.
86      */
87     addChildWorkspacePanel: function (inSender, inEvent) {
88       this.addWorkspace(inSender, inEvent, "XV.ChildWorkspaceContainer");
89     },
90     addTransactionList: function (inSender, inEvent) {
91       var panel = this.createComponent({kind: inEvent.kind}),
92         order = panel.$.parameterWidget.$.order;
93
94       panel.setCallback(inEvent.callback);
95       panel.render();
96       this.reflow();
97       if (inEvent.key) {
98         order.setValue(inEvent.key);
99         order.setDisabled(true);
100       }
101       this.setIndex(this.getPanels().length - 1);
102
103       return true;
104     },
105     /**
106       Create and drill down into a new workspace as defined by the inEvent object
107      */
108     addWorkspace: function (inSender, inEvent, workspaceContainerKind) {
109       var panel;
110
111       workspaceContainerKind = workspaceContainerKind || "XV.WorkspaceContainer"; // default
112
113       if (inEvent.workspace) {
114         panel = this.createComponent({kind: workspaceContainerKind});
115         panel.render();
116         this.reflow();
117         panel.setWorkspace(inEvent);
118         // this workspace is now the last panel. Go to it.
119         this.setIndex(this.getPanels().length - 1);
120       } else if (inEvent.kind) {
121         if (inEvent.model) {
122           panel = this.createComponent({kind: inEvent.kind, model: inEvent.model});
123         } else {
124           panel = this.createComponent({kind: inEvent.kind});
125         }
126         panel.render();
127         this.reflow();
128         // this workspace is now the last panel. Go to it.
129         this.setIndex(this.getPanels().length - 1);
130       }
131       return true;
132     },
133     /**
134       Add panels to a module. If any are found to already
135       exist by the same name they will be ignored.
136
137       @param {String} Module name
138       @param {Array} Panels
139       @param {Boolean} Append panels first
140     */
141     appendPanels: function (moduleName, panels, first) {
142       var modules = this.getModules(),
143         module = _.find(modules, function (mod) {
144           return mod.name === moduleName;
145         }),
146         existing,
147         i;
148
149       if (!module) {
150         // crash coming soon!
151         XT.log("Error: trying to insert panel into nonexistent module", moduleName);
152       }
153       existing = _.pluck(module.panels, "name");
154
155       for (i = 0; i < panels.length; i++) {
156         if (!_.contains(existing, panels[i].name)) {
157           if (first) {
158             module.panels.unshift(panels[i]);
159           }
160           else {
161             module.panels.push(panels[i]);
162           }
163         }
164       }
165       if (module.sortAlpha) {
166         // keep these alphabetically sorted
167         module.panels = _.sortBy(module.panels, function (panel) {
168           return panel.name;
169         });
170       }
171       this._setModules();
172     },
173     create: function () {
174       this.inherited(arguments);
175       this._setModules();
176     },
177     getNavigator: function () {
178       return this.$.navigator;
179     },
180     getNotifyButtons: function () {
181       return _.filter(this.$, function (control) {
182         return control.name.substring(0, 6) === 'notify' && control.kind === 'onyx.Button';
183       });
184     },
185     getStartupProgressBar: function () {
186       return this.$.startupProgressBar;
187     },
188     getStartupText: function () {
189       return this.$.startupText;
190     },
191     goToNavigator: function () {
192       var that = this;
193
194       _.each(this.getPanels(), function (panel, index) {
195         if (panel.name === 'navigator') {
196           that.setIndex(index);
197         }
198       });
199     },
200     handleNotify: function () {
201       if (this._pendingNotify) {
202         this.notify(
203           this._pendingNotify.inSender,
204           this._pendingNotify.inEvent
205         );
206         delete this._pendingNotify;
207       }
208     },
209     /**
210       Insert a new `module`. `Index` is currently ignored,
211       but may be used in the future. The modules are appended to the
212       end of the menu (before setup) based on extension load order.
213
214       @param {Object} Module
215       @param {Number} Index
216     */
217     insertModule: function (module, index) {
218       var modules = this.getModules(),
219         count = modules.length;
220       index = count;
221       modules.splice(index - 1, 0, module);
222       this._setModules();
223     },
224     isNotifyPopupShowing: function () {
225       return this.$.notifyPopup.showing;
226     },
227     /**
228       The model wants to ask the user something.
229      */
230     notify: function (inSender, inEvent) {
231       var that = this,
232         customComponentControls,
233         typeToButtonMap = {};
234
235       // If we're still animating, then we'll do this when
236       // that's done via `handleNotify`. Otherwise the popup
237       // will get tangled up in events and lost.
238       if (this.transitionPoints.length) {
239         this._pendingNotify = {
240           inSender: inSender,
241           inEvent: inEvent
242         };
243         return;
244       }
245
246       typeToButtonMap[String(XM.Model.NOTICE)] = ["notifyOk"];
247       typeToButtonMap[String(XM.Model.WARNING)] = ["notifyOk"];
248       typeToButtonMap[String(XM.Model.CRITICAL)] = ["notifyOk"];
249       typeToButtonMap[String(XM.Model.QUESTION)] = ["notifyYes", "notifyNo"];
250       typeToButtonMap[String(XM.Model.OK_CANCEL)] = ["notifyOk", "notifyCancel"];
251       typeToButtonMap[String(XM.Model.YES_NO_CANCEL)] = ["notifyYes", "notifyNo", "notifyCancel"];
252
253       this.$.notifyMessage.setContent(inEvent.message);
254       this._notifyCallback = inEvent.callback;
255       this._notifyOptions = inEvent.options;
256
257       // events are of type NOTICE by default
258       inEvent.type = inEvent.type || XM.Model.NOTICE;
259
260       // show the appropriate buttons
261       _.each(this.$.notifyPopup.children, function (component) {
262         if (component.kind !== "onyx.Button") {
263           // not a button: do nothing.
264         } else if (_.indexOf(typeToButtonMap[String(inEvent.type)], component.name) >= 0) {
265           // in the show-me array, so show
266           component.setShowing(true);
267         } else {
268           // not in the show-me array, so hide
269           component.setShowing(false);
270         }
271       });
272
273
274       // allow custom button text
275       this.$.notifyYes.setContent(inEvent.yesLabel || "_yes".loc());
276       this.$.notifyNo.setContent(inEvent.noLabel || "_no".loc());
277       this.$.notifyOk.setContent(inEvent.okLabel || "_ok".loc());
278       this.$.notifyCancel.setContent(inEvent.cancelLabel || "_cancel".loc());
279
280       // highlight the index active button
281       // it's the OK button unless it's a 2- or 3- way question, in which case it's YES
282       this._activeNotify = inEvent.type === XM.Model.QUESTION || inEvent.type === XM.Model.YES_NO_CANCEL ? 1 : 0;
283       _.each(this.getNotifyButtons(), function (button, index) {
284         button.addRemoveClass("onyx-blue", index === that._activeNotify);
285       });
286
287       // delete out any previously added customComponents/customComponentControls
288       if (this.$.notifyPopup.$.customComponent) {
289         this.$.notifyPopup.removeComponent(this.$.notifyPopup.$.customComponent);
290
291         customComponentControls = _.filter(that.$.notifyPopup.controls, function (control) {
292           return control.name === "customComponent";
293         });
294
295         if (customComponentControls) {
296           _.each(customComponentControls, function (control) {
297             that.$.notifyPopup.removeControl(control);
298           });
299         }
300       }
301
302       // Add the custom component
303       if (inEvent.component) {
304         inEvent.component.name = "customComponent";
305         inEvent.component.addBefore = this.$.notifyOk;
306         this.$.notifyPopup.createComponent(inEvent.component);
307         this.$.notifyPopup.$.customComponent.addStyles("color:black;");
308         if (inEvent.componentModel) {
309           this.$.notifyPopup.$.customComponent.setValue(inEvent.componentModel);
310         }
311       }
312
313       this._notifyDone = false;
314       this.$.notifyPopup.render();
315       this.$.notifyPopup.show();
316       this.$.notifyPopup.applyStyle("opacity", 1); // XXX not sure why this hack is necessary.
317     },
318     notifyHidden: function () {
319       if (!this._notifyDone) {
320         this.$.notifyPopup.show();
321       }
322     },
323     notifyKey: function (keyCode, isShift) {
324       var activeIndex = this._activeNotify,
325         notifyButtons = this.getNotifyButtons(),
326         nextShowing;
327
328       if (keyCode === 13) {
329         //enter
330         this.notifyTap(null, {originator: notifyButtons[activeIndex]});
331
332       } else if (keyCode === 37 || (keyCode === 9 && isShift)) {
333         // left or shift-tab
334         notifyButtons[activeIndex].removeClass("onyx-blue");
335         for (nextShowing = activeIndex - 1; nextShowing >= 0; nextShowing--) {
336           if (nextShowing === 0 && !notifyButtons[nextShowing].showing) {
337             // there are no showing buttons to the left
338             nextShowing = undefined;
339             break;
340           }
341           if (notifyButtons[nextShowing].showing) {
342             break;
343           }
344         }
345         if (nextShowing && nextShowing >= 0) {
346           activeIndex = nextShowing;
347         }
348         this._activeNotify = activeIndex;
349         notifyButtons[activeIndex].addClass("onyx-blue");
350
351       } else if (keyCode === 39 || keyCode === 9) {
352         // right or tab
353         notifyButtons[activeIndex].removeClass("onyx-blue");
354         for (nextShowing = activeIndex + 1; nextShowing < notifyButtons.length; nextShowing++) {
355           if (nextShowing + 1 === notifyButtons.length && !notifyButtons[nextShowing].showing) {
356             // there are no showing buttons to the right
357             nextShowing = undefined;
358             break;
359           }
360           if (notifyButtons[nextShowing].showing) {
361             break;
362           }
363         }
364         if (nextShowing && nextShowing < notifyButtons.length) {
365           activeIndex = nextShowing;
366         }
367         this._activeNotify = activeIndex;
368
369         notifyButtons[activeIndex].addClass("onyx-blue");
370       }
371     },
372     /**
373       The OK button has been clicked from the notification popup. Close the popup and call
374       the callback with the appropriate parameter if the callback exists.
375      */
376     notifyTap: function (inSender, inEvent) {
377       var notifyParameter,
378         callbackObj = {},
379         that = this,
380         optionsObj = this._notifyOptions || {};
381
382       this._notifyDone = true;
383       if (typeof this._notifyCallback === 'function') {
384         switch (inEvent.originator.name) {
385         case 'notifyOk':
386           notifyParameter = undefined;
387           break;
388         case 'notifyYes':
389           notifyParameter = true;
390           break;
391         case 'notifyNo':
392           notifyParameter = false;
393           break;
394         case 'notifyCancel':
395           notifyParameter = null;
396           break;
397         }
398         // the callback might make its own popup, which we do not want to hide.
399         this.$.notifyPopup.hide();
400         callbackObj.answer = notifyParameter;
401         if (this.$.notifyPopup.$.customComponent) {
402           if (this.$.notifyPopup.$.customComponent.getValueAsync) {
403             this.$.notifyPopup.$.customComponent.getValueAsync(function (result) {
404               callbackObj.componentValue = result;
405               that._notifyCallback(callbackObj, optionsObj);
406             });
407             return;
408           }
409           callbackObj.componentValue = this.$.notifyPopup.$.customComponent.getValue();
410         }
411         this._notifyCallback(callbackObj, optionsObj);
412       } else {
413         this.$.notifyPopup.hide();
414       }
415     },
416     /**
417       Go back to the previous panel. Note the implementation
418       here means that we can't just put components anywhere
419       we want in the component array. The navigator has to
420       be the last one, so we can simply go "back" to it coming
421       back out of a workspace.
422      */
423     previous: function () {
424       // Stock implementation is screwy, do our own
425       var last = this.getActive(),
426         previous = this.index - 1,
427         active;
428       this.setIndex(previous);
429
430       // Provide a way to let panels or their children know they have been activated
431       active = this.getActive();
432       active.waterfallDown("onActivatePanel", {activated: active});
433
434       last.destroy();
435     },
436     popupWorkspaceNotify: function (inSender, inEvent) {
437       this.notify(inSender, inEvent);
438     },
439     popupWorkspace: function (inSender, inEvent) {
440       this._popupWorkspaceCallback = inEvent.callback;
441
442       this.$.popupWorkspace.destroyClientControls();
443       this.$.popupWorkspace.createComponent({content: inEvent.message});
444       this.$.popupWorkspace.createComponent({
445         kind: "enyo.Scroller",
446         name: "popupScroller",
447         maxHeight: "400px",
448         horizontal: "hidden"
449       }, {owner: this});
450       this.$.popupWorkspace.createComponent({name: "workspace", kind: inEvent.workspace, container: this.$.popupScroller});
451       this.$.popupWorkspace.$.workspace.addStyles("color:black;");
452       this.$.popupWorkspace.$.workspace.setValue(inEvent.model);
453       this.$.popupWorkspace.createComponent({
454         kind: "onyx.Button",
455         content: "_save".loc(),
456         name: "popupWorkspaceSave",
457         ontap: "popupWorkspaceTap",
458         classes: "onyx-blue xv-popup-button"
459       }, {owner: this});
460       this.$.popupWorkspace.createComponent({
461         kind: "onyx.Button",
462         content: "_cancel".loc(),
463         name: "popupWorkspaceCancel",
464         ontap: "popupWorkspaceTap",
465         classes: "xv-popup-button"
466       }, {owner: this});
467
468       this.$.popupWorkspace.render();
469       this.$.popupWorkspace.show();
470       this.$.popupWorkspace.applyStyle("opacity", 1); // XXX not sure why this hack is necessary.
471     },
472     popupWorkspaceTap: function (inSender, inEvent) {
473       var model = this.$.popupWorkspace.$.workspace.value,
474         response = inEvent.originator.name === 'popupWorkspaceCancel' ? false : model,
475         validationError,
476         errorMessage;
477
478       if (response) {
479         validationError = model.validate(model.attributes);
480         if (validationError) {
481           errorMessage = validationError.message ? validationError.message() : "Error";
482           this.notify(null, {message: validationError.message(), type: XM.Model.CRITICAL});
483           return;
484         }
485       }
486       this.$.popupWorkspace.hide();
487       this._popupWorkspaceCallback(response);
488     },
489     _setModules: function () {
490       var modules = this.getModules();
491       this.$.navigator.setModules(modules);
492     }
493
494   });
495
496 }());