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", centered: true,
42 onHide: "notifyHidden",
43 modal: true, floating: true, scrim: true, components: [
44 {name: "notifyMessage"},
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}
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"}
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.
65 activate: function () {
67 this.$.navigator.activate();
69 addSearch: function (inSender, inEvent) {
72 panel = this.createComponent({kind: "XV.SearchContainer"});
75 panel.setList(inEvent);
79 XT.log("No list associated with this model for searching. Are you sure you've registered it?");
84 Do the exact same thing as addWorkspace, just use a ChildWorkspaceContainer
85 instead of a WorkspaceContainer.
87 addChildWorkspacePanel: function (inSender, inEvent) {
88 this.addWorkspace(inSender, inEvent, "XV.ChildWorkspaceContainer");
90 addTransactionList: function (inSender, inEvent) {
91 var panel = this.createComponent({kind: inEvent.kind}),
92 order = panel.$.parameterWidget.$.order;
94 panel.setCallback(inEvent.callback);
98 order.setValue(inEvent.key);
99 order.setDisabled(true);
101 this.setIndex(this.getPanels().length - 1);
106 Create and drill down into a new workspace as defined by the inEvent object
108 addWorkspace: function (inSender, inEvent, workspaceContainerKind) {
111 workspaceContainerKind = workspaceContainerKind || "XV.WorkspaceContainer"; // default
113 if (inEvent.workspace) {
114 panel = this.createComponent({kind: workspaceContainerKind});
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) {
122 panel = this.createComponent({kind: inEvent.kind, model: inEvent.model});
124 panel = this.createComponent({kind: inEvent.kind});
128 // this workspace is now the last panel. Go to it.
129 this.setIndex(this.getPanels().length - 1);
134 Add panels to a module. If any are found to already
135 exist by the same name they will be ignored.
137 @param {String} Module name
138 @param {Array} Panels
139 @param {Boolean} Append panels first
141 appendPanels: function (moduleName, panels, first) {
142 var modules = this.getModules(),
143 module = _.find(modules, function (mod) {
144 return mod.name === moduleName;
150 // crash coming soon!
151 XT.log("Error: trying to insert panel into nonexistent module", moduleName);
153 existing = _.pluck(module.panels, "name");
155 for (i = 0; i < panels.length; i++) {
156 if (!_.contains(existing, panels[i].name)) {
158 module.panels.unshift(panels[i]);
161 module.panels.push(panels[i]);
165 if (module.sortAlpha) {
166 // keep these alphabetically sorted
167 module.panels = _.sortBy(module.panels, function (panel) {
173 create: function () {
174 this.inherited(arguments);
177 getNavigator: function () {
178 return this.$.navigator;
180 getNotifyButtons: function () {
181 return _.filter(this.$, function (control) {
182 return control.name.substring(0, 6) === 'notify' && control.kind === 'onyx.Button';
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.$.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);
268 // not in the show-me array, so hide
269 component.setShowing(false);
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());
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);
287 // delete out any previously added customComponents/customComponentControls
288 if (this.$.notifyPopup.$.customComponent) {
289 this.$.notifyPopup.removeComponent(this.$.notifyPopup.$.customComponent);
291 customComponentControls = _.filter(that.$.notifyPopup.controls, function (control) {
292 return control.name === "customComponent";
295 if (customComponentControls) {
296 _.each(customComponentControls, function (control) {
297 that.$.notifyPopup.removeControl(control);
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);
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.
318 notifyHidden: function () {
319 if (!this._notifyDone) {
320 this.$.notifyPopup.show();
323 notifyKey: function (keyCode, isShift) {
324 var activeIndex = this._activeNotify,
325 notifyButtons = this.getNotifyButtons(),
328 if (keyCode === 13) {
330 this.notifyTap(null, {originator: notifyButtons[activeIndex]});
332 } else if (keyCode === 37 || (keyCode === 9 && isShift)) {
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;
341 if (notifyButtons[nextShowing].showing) {
345 if (nextShowing && nextShowing >= 0) {
346 activeIndex = nextShowing;
348 this._activeNotify = activeIndex;
349 notifyButtons[activeIndex].addClass("onyx-blue");
351 } else if (keyCode === 39 || keyCode === 9) {
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;
360 if (notifyButtons[nextShowing].showing) {
364 if (nextShowing && nextShowing < notifyButtons.length) {
365 activeIndex = nextShowing;
367 this._activeNotify = activeIndex;
369 notifyButtons[activeIndex].addClass("onyx-blue");
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.
376 notifyTap: function (inSender, inEvent) {
380 optionsObj = this._notifyOptions || {};
382 this._notifyDone = true;
383 if (typeof this._notifyCallback === 'function') {
384 switch (inEvent.originator.name) {
386 notifyParameter = undefined;
389 notifyParameter = true;
392 notifyParameter = false;
395 notifyParameter = null;
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);
409 callbackObj.componentValue = this.$.notifyPopup.$.customComponent.getValue();
411 this._notifyCallback(callbackObj, optionsObj);
413 this.$.notifyPopup.hide();
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.
423 previous: function () {
424 // Stock implementation is screwy, do our own
425 var last = this.getActive(),
426 previous = this.index - 1,
428 this.setIndex(previous);
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});
436 popupWorkspaceNotify: function (inSender, inEvent) {
437 this.notify(inSender, inEvent);
439 popupWorkspace: function (inSender, inEvent) {
440 this._popupWorkspaceCallback = inEvent.callback;
442 this.$.popupWorkspace.destroyClientControls();
443 this.$.popupWorkspace.createComponent({content: inEvent.message});
444 this.$.popupWorkspace.createComponent({
445 kind: "enyo.Scroller",
446 name: "popupScroller",
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({
455 content: "_save".loc(),
456 name: "popupWorkspaceSave",
457 ontap: "popupWorkspaceTap",
458 classes: "onyx-blue xv-popup-button"
460 this.$.popupWorkspace.createComponent({
462 content: "_cancel".loc(),
463 name: "popupWorkspaceCancel",
464 ontap: "popupWorkspaceTap",
465 classes: "xv-popup-button"
468 this.$.popupWorkspace.render();
469 this.$.popupWorkspace.show();
470 this.$.popupWorkspace.applyStyle("opacity", 1); // XXX not sure why this hack is necessary.
472 popupWorkspaceTap: function (inSender, inEvent) {
473 var model = this.$.popupWorkspace.$.workspace.value,
474 response = inEvent.originator.name === 'popupWorkspaceCancel' ? false : model,
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});
486 this.$.popupWorkspace.hide();
487 this._popupWorkspaceCallback(response);
489 _setModules: function () {
490 var modules = this.getModules();
491 this.$.navigator.setModules(modules);