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*/
10 @name XV.ModuleContainer
14 /** @lends XV.ModuleContainer# */{
15 name: "XV.ModuleContainer",
17 arrangerKind: "CardArranger",
21 onPrevious: "previous",
22 onSearch: "addSearch",
23 onTransactionList: "addTransactionList",
24 onWorkspace: "addWorkspace",
25 onChildWorkspace: "addChildWorkspacePanel",
26 onPopupWorkspace: "popupWorkspace",
28 onTransitionFinish: "handleNotify"
34 {name: "startup", classes: "xv-startup-panel",
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}
41 {kind: "onyx.Popup", name: "notifyPopup", classes: "xv-popup", centered: true,
42 onHide: "notifyHidden",
43 modal: true, floating: true, scrim: true, components: [
44 {name: "notifyMessage", classes: "message"},
45 {classes: "xv-buttons", name: "notifyButtons", components: [
46 {kind: "onyx.Button", content: "_ok".loc(), name: "notifyOk", ontap: "notifyTap",
47 showing: false, classes: "text"},
48 {kind: "onyx.Button", content: "_yes".loc(), name: "notifyYes", ontap: "notifyTap",
49 showing: false, classes: "text"},
50 {kind: "onyx.Button", content: "_no".loc(), name: "notifyNo", ontap: "notifyTap",
51 showing: false, classes: "text"},
52 {kind: "onyx.Button", content: "_cancel".loc(), name: "notifyCancel", ontap: "notifyTap",
53 showing: false, classes: "text"}
56 {kind: "onyx.Popup", name: "popupWorkspace", classes: "xv-popup xv-groupbox-popup", centered: true,
57 autoDismiss: false, modal: true, floating: true, scrim: true},
58 {name: "navigator", kind: "XV.Navigator"}
60 resizeHandler: function () {
61 this.inherited(arguments);
62 if (this.$.notifyPopup.showing) {
63 // This is a fix for an enyo bug that renders the popup as clear
64 this.$.notifyPopup.applyStyle("opacity", 1);
67 activate: function () {
69 this.$.navigator.activate();
71 addSearch: function (inSender, inEvent) {
74 panel = this.createComponent({kind: "XV.SearchContainer"});
77 panel.setList(inEvent);
81 XT.log("No list associated with this model for searching. Are you sure you've registered it?");
86 Do the exact same thing as addWorkspace, just use a ChildWorkspaceContainer
87 instead of a WorkspaceContainer.
89 addChildWorkspacePanel: function (inSender, inEvent) {
90 this.addWorkspace(inSender, inEvent, "XV.ChildWorkspaceContainer");
92 addTransactionList: function (inSender, inEvent) {
93 var panel = this.createComponent({kind: inEvent.kind}),
94 order = panel.$.parameterWidget.$.order;
96 panel.setCallback(inEvent.callback);
100 order.setValue(inEvent.key);
101 order.setDisabled(true);
103 this.setIndex(this.getPanels().length - 1);
108 Create and drill down into a new workspace as defined by the inEvent object
110 addWorkspace: function (inSender, inEvent, workspaceContainerKind) {
113 workspaceContainerKind = workspaceContainerKind || "XV.WorkspaceContainer"; // default
115 if (inEvent.workspace) {
116 panel = this.createComponent({kind: workspaceContainerKind});
119 panel.setWorkspace(inEvent);
120 // this workspace is now the last panel. Go to it.
121 this.setIndex(this.getPanels().length - 1);
122 } else if (inEvent.kind) {
124 panel = this.createComponent({kind: inEvent.kind, model: inEvent.model});
126 panel = this.createComponent({kind: inEvent.kind});
130 // this workspace is now the last panel. Go to it.
131 this.setIndex(this.getPanels().length - 1);
136 Add panels to a module. If any are found to already
137 exist by the same name they will be ignored.
139 @param {String} Module name
140 @param {Array} Panels
141 @param {Boolean} Append panels first
143 appendPanels: function (moduleName, panels, first) {
144 var modules = this.getModules(),
145 module = _.find(modules, function (mod) {
146 return mod.name === moduleName;
152 // crash coming soon!
153 XT.log("Error: trying to insert panel into nonexistent module", moduleName);
155 existing = _.pluck(module.panels, "name");
157 for (i = 0; i < panels.length; i++) {
158 if (!_.contains(existing, panels[i].name)) {
160 module.panels.unshift(panels[i]);
163 module.panels.push(panels[i]);
167 if (module.sortAlpha) {
168 // keep these alphabetically sorted
169 module.panels = _.sortBy(module.panels, function (panel) {
175 create: function () {
176 this.inherited(arguments);
179 getNavigator: function () {
180 return this.$.navigator;
182 getNotifyButtons: function () {
183 return this.$.notifyButtons.controls;
185 getStartupProgressBar: function () {
186 return this.$.startupProgressBar;
188 getStartupText: function () {
189 return this.$.startupText;
191 goToNavigator: function () {
194 _.each(this.getPanels(), function (panel, index) {
195 if (panel.name === 'navigator') {
196 that.setIndex(index);
200 handleNotify: function () {
201 if (this._pendingNotify) {
203 this._pendingNotify.inSender,
204 this._pendingNotify.inEvent
206 delete this._pendingNotify;
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.
214 @param {Object} Module
215 @param {Number} Index
217 insertModule: function (module, index) {
218 var modules = this.getModules(),
219 count = modules.length;
221 modules.splice(index - 1, 0, module);
224 isNotifyPopupShowing: function () {
225 return this.$.notifyPopup.showing;
228 The model wants to ask the user something.
230 notify: function (inSender, inEvent) {
232 customComponentControls,
233 typeToButtonMap = {};
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 = {
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"];
253 this.$.notifyMessage.setContent(inEvent.message);
254 this._notifyCallback = inEvent.callback;
255 this._notifyOptions = inEvent.options;
257 // events are of type NOTICE by default
258 inEvent.type = inEvent.type || XM.Model.NOTICE;
260 // show the appropriate buttons
261 _.each(this.getNotifyButtons(), function (component) {
262 component.setShowing(_.indexOf(typeToButtonMap[String(inEvent.type)], component.name) >= 0);
265 // allow custom button text
266 this.$.notifyYes.setContent(inEvent.yesLabel || "_yes".loc());
267 this.$.notifyNo.setContent(inEvent.noLabel || "_no".loc());
268 this.$.notifyOk.setContent(inEvent.okLabel || "_ok".loc());
269 this.$.notifyCancel.setContent(inEvent.cancelLabel || "_cancel".loc());
271 // highlight the index active button
272 // it's the OK button unless it's a 2- or 3- way question, in which case it's YES
273 this._activeNotify = inEvent.type === XM.Model.QUESTION || inEvent.type === XM.Model.YES_NO_CANCEL ? 1 : 0;
274 _.each(this.getNotifyButtons(), function (button, index) {
275 button.addRemoveClass("selected", index === that._activeNotify);
278 // delete out any previously added customComponents/customComponentControls
279 if (this.$.notifyPopup.$.customComponent) {
280 this.$.notifyPopup.removeComponent(this.$.notifyPopup.$.customComponent);
282 customComponentControls = _.filter(that.$.notifyPopup.controls, function (control) {
283 return control.name === "customComponent";
286 if (customComponentControls) {
287 _.each(customComponentControls, function (control) {
288 that.$.notifyPopup.removeControl(control);
293 // Add the custom component
294 if (inEvent.component) {
295 inEvent.component.name = "customComponent";
296 // can add styling class here instead of inline css
297 inEvent.component.addBefore = this.$.notifyButtons;
298 this.$.notifyPopup.createComponent(inEvent.component);
299 if (inEvent.componentModel) {
300 this.$.notifyPopup.$.customComponent.setValue(inEvent.componentModel);
304 this._notifyDone = false;
305 this.$.notifyPopup.render();
306 this.$.notifyPopup.show();
307 // Without this fix, the popup renders transparent
308 this.$.notifyPopup.applyStyle("opacity", 1);
310 notifyHidden: function () {
311 if (!this._notifyDone) {
312 this.$.notifyPopup.show();
315 notifyKey: function (keyCode, isShift) {
316 var activeIndex = this._activeNotify,
317 notifyButtons = this.getNotifyButtons(),
320 if (keyCode === 13) {
322 this.notifyTap(null, {originator: notifyButtons[activeIndex]});
324 } else if (keyCode === 37 || (keyCode === 9 && isShift)) {
326 notifyButtons[activeIndex].removeClass("selected");
327 for (nextShowing = activeIndex - 1; nextShowing >= 0; nextShowing--) {
328 if (nextShowing === 0 && !notifyButtons[nextShowing].showing) {
329 // there are no showing buttons to the left
330 nextShowing = undefined;
333 if (notifyButtons[nextShowing].showing) {
337 if (nextShowing && nextShowing >= 0) {
338 activeIndex = nextShowing;
340 this._activeNotify = activeIndex;
341 notifyButtons[activeIndex].addClass("selected");
343 } else if (keyCode === 39 || keyCode === 9) {
345 notifyButtons[activeIndex].removeClass("selected");
346 for (nextShowing = activeIndex + 1; nextShowing < notifyButtons.length; nextShowing++) {
347 if (nextShowing + 1 === notifyButtons.length && !notifyButtons[nextShowing].showing) {
348 // there are no showing buttons to the right
349 nextShowing = undefined;
352 if (notifyButtons[nextShowing].showing) {
356 if (nextShowing && nextShowing < notifyButtons.length) {
357 activeIndex = nextShowing;
359 this._activeNotify = activeIndex;
361 notifyButtons[activeIndex].addClass("selected");
365 The OK button has been clicked from the notification popup. Close the popup and call
366 the callback with the appropriate parameter if the callback exists.
368 notifyTap: function (inSender, inEvent) {
372 optionsObj = this._notifyOptions || {};
374 this._notifyDone = true;
375 if (typeof this._notifyCallback === 'function') {
376 switch (inEvent.originator.name) {
378 notifyParameter = undefined;
381 notifyParameter = true;
384 notifyParameter = false;
387 notifyParameter = null;
390 // the callback might make its own popup, which we do not want to hide.
391 this.$.notifyPopup.hide();
392 callbackObj.answer = notifyParameter;
393 if (this.$.notifyPopup.$.customComponent) {
394 if (this.$.notifyPopup.$.customComponent.getValueAsync) {
395 this.$.notifyPopup.$.customComponent.getValueAsync(function (result) {
396 callbackObj.componentValue = result;
397 that._notifyCallback(callbackObj, optionsObj);
401 callbackObj.componentValue = this.$.notifyPopup.$.customComponent.getValue();
403 this._notifyCallback(callbackObj, optionsObj);
405 this.$.notifyPopup.hide();
409 Go back to the previous panel. Note the implementation
410 here means that we can't just put components anywhere
411 we want in the component array. The navigator has to
412 be the last one, so we can simply go "back" to it coming
413 back out of a workspace.
415 previous: function () {
416 // Stock implementation is screwy, do our own
417 var last = this.getActive(),
418 previous = this.index - 1,
420 this.setIndex(previous);
422 // Provide a way to let panels or their children know they have been activated
423 active = this.getActive();
424 active.waterfallDown("onActivatePanel", {activated: active});
428 popupWorkspaceNotify: function (inSender, inEvent) {
429 this.notify(inSender, inEvent);
431 popupWorkspace: function (inSender, inEvent) {
432 this._popupWorkspaceCallback = inEvent.callback;
434 this.$.popupWorkspace.destroyClientControls();
435 this.$.popupWorkspace.createComponent({content: inEvent.message});
436 this.$.popupWorkspace.createComponent({
437 kind: "enyo.Scroller",
438 name: "popupScroller",
442 this.$.popupWorkspace.createComponent({name: "workspace", kind: inEvent.workspace,
443 container: this.$.popupScroller});
444 // TODO: inline css - git rid of it!
445 this.$.popupWorkspace.$.workspace.addStyles("color:black;");
446 this.$.popupWorkspace.$.workspace.setValue(inEvent.model);
448 this.$.popupWorkspace.createComponent({classes: "xv-buttons", name: "workspaceButtons"}, {owner: this});
449 this.$.workspaceButtons.createComponents([{
451 content: "_save".loc(),
452 name: "popupWorkspaceSave",
453 ontap: "popupWorkspaceTap",
454 classes: "selected text"
458 content: "_cancel".loc(),
459 name: "popupWorkspaceCancel",
460 ontap: "popupWorkspaceTap",
463 this.$.popupWorkspace.render();
464 this.$.popupWorkspace.show();
465 // Without this fix, the popup renders transparent
466 this.$.popupWorkspace.applyStyle("opacity", 1);
468 popupWorkspaceTap: function (inSender, inEvent) {
469 var model = this.$.popupWorkspace.$.workspace.value,
470 response = inEvent.originator.name === 'popupWorkspaceCancel' ? false : model,
475 validationError = model.validate(model.attributes);
476 if (validationError) {
477 errorMessage = validationError.message ? validationError.message() : "Error";
478 this.notify(null, {message: validationError.message(), type: XM.Model.CRITICAL});
482 this.$.popupWorkspace.hide();
483 this._popupWorkspaceCallback(response);
485 _setModules: function () {
486 var modules = this.getModules();
487 this.$.navigator.setModules(modules);