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 activate: function () {
62 this.$.navigator.activate();
64 addSearch: function (inSender, inEvent) {
67 panel = this.createComponent({kind: "XV.SearchContainer"});
70 panel.setList(inEvent);
74 XT.log("No list associated with this model for searching. Are you sure you've registered it?");
79 Do the exact same thing as addWorkspace, just use a ChildWorkspaceContainer
80 instead of a WorkspaceContainer.
82 addChildWorkspacePanel: function (inSender, inEvent) {
83 this.addWorkspace(inSender, inEvent, "XV.ChildWorkspaceContainer");
85 addTransactionList: function (inSender, inEvent) {
86 var panel = this.createComponent({kind: inEvent.kind}),
87 order = panel.$.parameterWidget.$.order;
89 panel.setCallback(inEvent.callback);
93 order.setValue(inEvent.key);
94 order.setDisabled(true);
96 this.setIndex(this.getPanels().length - 1);
101 Create and drill down into a new workspace as defined by the inEvent object
103 addWorkspace: function (inSender, inEvent, workspaceContainerKind) {
106 workspaceContainerKind = workspaceContainerKind || "XV.WorkspaceContainer"; // default
108 if (inEvent.workspace) {
109 panel = this.createComponent({kind: workspaceContainerKind});
112 panel.setWorkspace(inEvent);
113 // this workspace is now the last panel. Go to it.
114 this.setIndex(this.getPanels().length - 1);
115 } else if (inEvent.kind) {
117 panel = this.createComponent({kind: inEvent.kind, model: inEvent.model});
119 panel = this.createComponent({kind: inEvent.kind});
123 // this workspace is now the last panel. Go to it.
124 this.setIndex(this.getPanels().length - 1);
129 Add panels to a module. If any are found to already
130 exist by the same name they will be ignored.
132 @param {String} Module name
133 @param {Array} Panels
134 @param {Boolean} Append panels first
136 appendPanels: function (moduleName, panels, first) {
137 var modules = this.getModules(),
138 module = _.find(modules, function (mod) {
139 return mod.name === moduleName;
145 // crash coming soon!
146 XT.log("Error: trying to insert panel into nonexistent module", moduleName);
148 existing = _.pluck(module.panels, "name");
150 for (i = 0; i < panels.length; i++) {
151 if (!_.contains(existing, panels[i].name)) {
153 module.panels.unshift(panels[i]);
156 module.panels.push(panels[i]);
160 if (module.sortAlpha) {
161 // keep these alphabetically sorted
162 module.panels = _.sortBy(module.panels, function (panel) {
168 create: function () {
169 this.inherited(arguments);
172 getNavigator: function () {
173 return this.$.navigator;
175 getNotifyButtons: function () {
176 return this.$.notifyButtons.controls;
178 getStartupProgressBar: function () {
179 return this.$.startupProgressBar;
181 getStartupText: function () {
182 return this.$.startupText;
184 goToNavigator: function () {
187 _.each(this.getPanels(), function (panel, index) {
188 if (panel.name === 'navigator') {
189 that.setIndex(index);
193 handleNotify: function () {
194 if (this._pendingNotify) {
196 this._pendingNotify.inSender,
197 this._pendingNotify.inEvent
199 delete this._pendingNotify;
203 Insert a new `module`. `Index` is currently ignored,
204 but may be used in the future. The modules are appended to the
205 end of the menu (before setup) based on extension load order.
207 @param {Object} Module
208 @param {Number} Index
210 insertModule: function (module, index) {
211 var modules = this.getModules(),
212 count = modules.length;
214 modules.splice(index - 1, 0, module);
217 isNotifyPopupShowing: function () {
218 return this.$.notifyPopup.showing;
221 The model wants to ask the user something.
223 notify: function (inSender, inEvent) {
225 customComponentControls,
226 typeToButtonMap = {};
228 // If we're still animating, then we'll do this when
229 // that's done via `handleNotify`. Otherwise the popup
230 // will get tangled up in events and lost.
231 if (this.transitionPoints.length) {
232 this._pendingNotify = {
239 typeToButtonMap[String(XM.Model.NOTICE)] = ["notifyOk"];
240 typeToButtonMap[String(XM.Model.WARNING)] = ["notifyOk"];
241 typeToButtonMap[String(XM.Model.CRITICAL)] = ["notifyOk"];
242 typeToButtonMap[String(XM.Model.QUESTION)] = ["notifyYes", "notifyNo"];
243 typeToButtonMap[String(XM.Model.OK_CANCEL)] = ["notifyOk", "notifyCancel"];
244 typeToButtonMap[String(XM.Model.YES_NO_CANCEL)] = ["notifyYes", "notifyNo", "notifyCancel"];
246 this.$.notifyMessage.setContent(inEvent.message);
247 this._notifyCallback = inEvent.callback;
248 this._notifyOptions = inEvent.options;
250 // events are of type NOTICE by default
251 inEvent.type = inEvent.type || XM.Model.NOTICE;
253 // show the appropriate buttons
254 _.each(this.getNotifyButtons(), function (component) {
255 component.setShowing(_.indexOf(typeToButtonMap[String(inEvent.type)], component.name) >= 0);
258 // allow custom button text
259 this.$.notifyYes.setContent(inEvent.yesLabel || "_yes".loc());
260 this.$.notifyNo.setContent(inEvent.noLabel || "_no".loc());
261 this.$.notifyOk.setContent(inEvent.okLabel || "_ok".loc());
262 this.$.notifyCancel.setContent(inEvent.cancelLabel || "_cancel".loc());
264 // highlight the index active button
265 // it's the OK button unless it's a 2- or 3- way question, in which case it's YES
266 this._activeNotify = inEvent.type === XM.Model.QUESTION || inEvent.type === XM.Model.YES_NO_CANCEL ? 1 : 0;
267 _.each(this.getNotifyButtons(), function (button, index) {
268 button.addRemoveClass("selected", index === that._activeNotify);
271 // delete out any previously added customComponents/customComponentControls
272 if (this.$.notifyPopup.$.customComponent) {
273 this.$.notifyPopup.removeComponent(this.$.notifyPopup.$.customComponent);
275 customComponentControls = _.filter(that.$.notifyPopup.controls, function (control) {
276 return control.name === "customComponent";
279 if (customComponentControls) {
280 _.each(customComponentControls, function (control) {
281 that.$.notifyPopup.removeControl(control);
286 // Add the custom component
287 if (inEvent.component) {
288 inEvent.component.name = "customComponent";
289 inEvent.component.addBefore = this.$.notifyOk;
290 this.$.notifyPopup.createComponent(inEvent.component);
291 // TODO: this inline style needs to go away
292 this.$.notifyPopup.$.customComponent.addStyles("color:black;");
293 if (inEvent.componentModel) {
294 this.$.notifyPopup.$.customComponent.setValue(inEvent.componentModel);
298 this._notifyDone = false;
299 this.$.notifyPopup.render();
300 this.$.notifyPopup.show();
301 this.$.notifyPopup.applyStyle("opacity", 1); // XXX not sure why this hack is necessary.
303 notifyHidden: function () {
304 if (!this._notifyDone) {
305 this.$.notifyPopup.show();
308 notifyKey: function (keyCode, isShift) {
309 var activeIndex = this._activeNotify,
310 notifyButtons = this.getNotifyButtons(),
313 if (keyCode === 13) {
315 this.notifyTap(null, {originator: notifyButtons[activeIndex]});
317 } else if (keyCode === 37 || (keyCode === 9 && isShift)) {
319 notifyButtons[activeIndex].removeClass("onyx-blue");
320 for (nextShowing = activeIndex - 1; nextShowing >= 0; nextShowing--) {
321 if (nextShowing === 0 && !notifyButtons[nextShowing].showing) {
322 // there are no showing buttons to the left
323 nextShowing = undefined;
326 if (notifyButtons[nextShowing].showing) {
330 if (nextShowing && nextShowing >= 0) {
331 activeIndex = nextShowing;
333 this._activeNotify = activeIndex;
334 notifyButtons[activeIndex].addClass("onyx-blue");
336 } else if (keyCode === 39 || keyCode === 9) {
338 notifyButtons[activeIndex].removeClass("onyx-blue");
339 for (nextShowing = activeIndex + 1; nextShowing < notifyButtons.length; nextShowing++) {
340 if (nextShowing + 1 === notifyButtons.length && !notifyButtons[nextShowing].showing) {
341 // there are no showing buttons to the right
342 nextShowing = undefined;
345 if (notifyButtons[nextShowing].showing) {
349 if (nextShowing && nextShowing < notifyButtons.length) {
350 activeIndex = nextShowing;
352 this._activeNotify = activeIndex;
354 notifyButtons[activeIndex].addClass("onyx-blue");
358 The OK button has been clicked from the notification popup. Close the popup and call
359 the callback with the appropriate parameter if the callback exists.
361 notifyTap: function (inSender, inEvent) {
365 optionsObj = this._notifyOptions || {};
367 this._notifyDone = true;
368 if (typeof this._notifyCallback === 'function') {
369 switch (inEvent.originator.name) {
371 notifyParameter = undefined;
374 notifyParameter = true;
377 notifyParameter = false;
380 notifyParameter = null;
383 // the callback might make its own popup, which we do not want to hide.
384 this.$.notifyPopup.hide();
385 callbackObj.answer = notifyParameter;
386 if (this.$.notifyPopup.$.customComponent) {
387 if (this.$.notifyPopup.$.customComponent.getValueAsync) {
388 this.$.notifyPopup.$.customComponent.getValueAsync(function (result) {
389 callbackObj.componentValue = result;
390 that._notifyCallback(callbackObj, optionsObj);
394 callbackObj.componentValue = this.$.notifyPopup.$.customComponent.getValue();
396 this._notifyCallback(callbackObj, optionsObj);
398 this.$.notifyPopup.hide();
402 Go back to the previous panel. Note the implementation
403 here means that we can't just put components anywhere
404 we want in the component array. The navigator has to
405 be the last one, so we can simply go "back" to it coming
406 back out of a workspace.
408 previous: function () {
409 // Stock implementation is screwy, do our own
410 var last = this.getActive(),
411 previous = this.index - 1,
413 this.setIndex(previous);
415 // Provide a way to let panels or their children know they have been activated
416 active = this.getActive();
417 active.waterfallDown("onActivatePanel", {activated: active});
421 popupWorkspaceNotify: function (inSender, inEvent) {
422 this.notify(inSender, inEvent);
424 popupWorkspace: function (inSender, inEvent) {
425 this._popupWorkspaceCallback = inEvent.callback;
427 this.$.popupWorkspace.destroyClientControls();
428 this.$.popupWorkspace.createComponent({content: inEvent.message});
429 this.$.popupWorkspace.createComponent({
430 kind: "enyo.Scroller",
431 name: "popupScroller",
435 this.$.popupWorkspace.createComponent({name: "workspace", kind: inEvent.workspace, container: this.$.popupScroller});
436 this.$.popupWorkspace.$.workspace.addStyles("color:black;");
437 this.$.popupWorkspace.$.workspace.setValue(inEvent.model);
438 this.$.popupWorkspace.createComponent({
440 content: "_save".loc(),
441 name: "popupWorkspaceSave",
442 ontap: "popupWorkspaceTap",
443 classes: "onyx-blue xv-popup-button"
445 this.$.popupWorkspace.createComponent({
447 content: "_cancel".loc(),
448 name: "popupWorkspaceCancel",
449 ontap: "popupWorkspaceTap",
450 classes: "xv-popup-button"
453 this.$.popupWorkspace.render();
454 this.$.popupWorkspace.show();
455 this.$.popupWorkspace.applyStyle("opacity", 1); // XXX not sure why this hack is necessary.
457 popupWorkspaceTap: function (inSender, inEvent) {
458 var model = this.$.popupWorkspace.$.workspace.value,
459 response = inEvent.originator.name === 'popupWorkspaceCancel' ? false : model,
464 validationError = model.validate(model.attributes);
465 if (validationError) {
466 errorMessage = validationError.message ? validationError.message() : "Error";
467 this.notify(null, {message: validationError.message(), type: XM.Model.CRITICAL});
471 this.$.popupWorkspace.hide();
472 this._popupWorkspaceCallback(response);
474 _setModules: function () {
475 var modules = this.getModules();
476 this.$.navigator.setModules(modules);