1 /*jshint bitwise:true, indent:2, curly:true, eqeqeq:true, immed:true,
2 latedef:true, newcap:true, noarg:true, regexp:true, undef:true,
4 /*global XT:true, XV:true, XM:true, _:true, enyo:true, window:true */
12 @class Contains a set of panels for navigating the app and modules within the app.<br />
13 Navigation within the app is accomplished by elements within the menu tool bar, such as history, search, the back button or logout.<br />
14 Navigation within modules in the app is accomplished with a list within the panel menu which displays the menu items for each context.<br />
15 The root menu (module menu) contains the list of modules and the logout.<br />
16 Only three menus are cached at one time.<br />
17 Layout: Collapsing Arranger.<br />
18 Use to implement the high-level container of all business object lists.<br />
19 Derived from <a href="http://enyojs.com/api/#enyo.Panels">enyo.Panels</a>.
21 @extends XV.ListMenuManager
23 var navigator = /** @lends XV.Navigator# */{
26 classes: "app enyo-unselectable",
31 @property {Array} modules A DOM-free representation of all of the modules
32 contained in the navigator. The details of these module objects will
33 inform the creation of the panel components.
35 @property {Object} panelCache A hashmap of cached panels where the key is
36 the global ID of the panel and the value is the enyo panel component.
42 {name: "newTabItem", label: "_openNewTab".loc(), method: "newTab", alwaysShowing: true},
43 {name: "preferencesItem", label: "_preferences".loc(), method: "openPreferencesWorkspace" },
44 {name: "myAccountItem", label: "_changePassword".loc(), method: "showMyAccount"},
45 {name: "helpItem", label: "_help".loc(), method: "showHelp", alwaysShowing: true},
46 {name: "aboutItem", label: "_about".loc(), method: "showAbout"}
56 onDeleteTap: "showDeletePopup",
57 onListItemMenuTap: "showListItemMenu",
58 onMessage: "setMessageContent",
59 onParameterChange: "requery",
60 onColumnsChange: "changeLayout",
62 onExportList: "exportList",
63 onHotKey: "handleHotKey",
64 onPrintList: "printList"
67 arrangerKind: "CollapsingArranger",
69 {kind: "FittableRows", classes: "left", components: [
70 {kind: "onyx.Toolbar", classes: "onyx-menu-toolbar", components: [
71 {kind: "onyx.Button", name: "backButton", content: "_logout".loc(),
73 {kind: "Group", name: "iconButtonGroup", tag: null, components: [
74 {kind: "XV.IconButton", name: "historyIconButton",
75 src: "/assets/menu-icon-bookmark.png",
76 ontap: "showHistory", content: "_history".loc()},
77 {kind: "XV.IconButton", name: "searchIconButton",
78 src: "/assets/menu-icon-search.png",
79 ontap: "showParameters", content: "_advancedSearch".loc(), showing: false}
81 {kind: "onyx.MenuDecorator", style: "margin: 0;", onSelect: "actionSelected", components: [
82 {kind: "XV.IconButton", src: "/assets/menu-icon-gear.png",
83 content: "_actions".loc(), name: "actionButton"},
84 {kind: "onyx.Menu", name: "actionMenu"}
86 {kind: "onyx.Popup", name: "aboutPopup", centered: true,
87 modal: true, floating: true, scrim: true, components: [
88 {content: "Copyright xTuple 2013" },
89 {name: "aboutVersion", allowHtml: true },
90 {kind: "onyx.Button", content: "_ok".loc(), ontap: "closeAboutPopup"}
93 {name: "loginInfo", content: "", classes: "xv-navigator-header"},
94 {name: "menuPanels", kind: "Panels", draggable: false, fit: true,
95 margin: 0, components: [
96 {name: "moduleMenu", kind: "List", touch: true,
97 onSetupItem: "setupModuleMenuItem", ontap: "menuTap",
99 {name: "moduleItem", classes: "item enyo-border-box"}
101 {name: "panelMenu", kind: "List", touch: true,
102 onSetupItem: "setupPanelMenuItem", ontap: "panelTap", components: [
103 {name: "listItem", classes: "item enyo-border-box"}
105 {} // Why do panels only work when there are 3+ objects?
108 {kind: "FittableRows", components: [
109 // the onyx-menu-toolbar class keeps the popups from being hidden
110 {kind: "onyx.MoreToolbar", name: "contentToolbar",
111 classes: "onyx-menu-toolbar", movedClass: "xv-toolbar-moved", components: [
112 {kind: "onyx.Grabber"},
113 {name: "rightLabel", style: "width: 180px"},
114 // The MoreToolbar is a FittableColumnsLayout, so this spacer takes up all available space
115 {name: "spacer", content: "", fit: true},
116 // Selectable "New" menu
117 {kind: "onyx.MenuDecorator", style: "margin: 0;", onSelect: "newRecord", components: [
118 {kind: "XV.IconButton", src: "/assets/menu-icon-new.png",
119 content: "_new".loc(), name: "newMenuButton", showing: false},
120 {kind: "onyx.Menu", name: "newMenu"}
122 // Button to initiate new workspace
123 {kind: "XV.IconButton", src: "/assets/menu-icon-new.png",
124 content: "_new".loc(), name: "newButton", ontap: "newRecord", showing: false},
125 {kind: "onyx.MenuDecorator", style: "margin: 0;", onSelect: "exportSelected", components: [
126 {kind: "XV.IconButton", src: "/assets/menu-icon-export.png",
127 content: "_export".loc(), name: "exportButton"},
128 {kind: "onyx.Menu", name: "exportMenu", showing: false}
130 {kind: "XV.IconButton", name: "sortButton", content: "_sort".loc(),
131 src: "/assets/sort-icon.png", ontap: "showSortPopup", showing: false},
132 {kind: "XV.SortPopup", name: "sortPopup", showing: false},
133 {name: "refreshButton", kind: "XV.IconButton",
134 src: "/assets/menu-icon-refresh.png", content: "_refresh".loc(),
135 ontap: "requery", showing: false},
136 {name: "search", kind: "onyx.InputDecorator",
137 showing: false, components: [
138 {name: 'searchInput', kind: "onyx.Input", style: "width: 200px;",
139 placeholder: "_search".loc(), onchange: "inputChanged"},
140 {kind: "Image", src: "/assets/search-input-search.png",
141 name: "searchJump", ontap: "jump"}
144 {name: "messageHeader", content: "", classes: ""},
145 {name: "header", content: "", classes: ""},
146 {name: "contentPanels", kind: "Panels", margin: 0, fit: true,
147 draggable: false, panelCount: 0, classes: "scroll-ios"},
148 {name: "myAccountPopup", kind: "XV.MyAccountPopup"},
149 {name: "listItemMenu", kind: "onyx.Menu", floating: true, onSelect: "listActionSelected",
150 maxHeight: 500, components: []
155 Keeps track of whether any list has already been fetched, to avoid unnecessary
159 actionSelected: function (inSender, inEvent) {
160 var index = this.$.menuPanels.getIndex(),
162 action = inEvent.originator.action,
163 method = action.method || action.name;
165 // Determine if action is coming from a more specific context
166 if (action.context) {
167 context = this.$.contentPanels.getActive();
168 } else if (index && !action.alwaysShowing) {
169 context = this.getSelectedModule();
172 context[method](inSender, inEvent);
174 activate: function () {
175 this.setMenuPanel(MODULE_MENU);
178 The back button is a logout button if you're at the root menu. Otherwise it's a
179 back button that takes you to the root menu.
181 backTapped: function () {
182 var index = this.$.menuPanels.getIndex();
183 if (index === MODULE_MENU) {
186 type: XM.Model.QUESTION,
187 callback: function (response) {
188 if (response.answer) {
192 message: "_logoutConfirmation".loc()
194 this.doNotify(inEvent);
196 this.setHeaderContent("");
197 this.setMenuPanel(MODULE_MENU);
201 If we're on the main menu, then use the navigator actions defined
202 in its `actions` property. If we're in a module, then use the module's
205 buildMenus: function () {
206 var actionMenu = this.$.actionMenu,
207 exportMenu = this.$.exportMenu,
208 newMenu = this.$.newMenu,
209 // This is the index of the active panel. All panels
210 // that have been selected from the menus have an index of
211 // greater than 0. The welcome page has an index of 0.
212 idx = this.$.menuPanels.getIndex(),
213 activePanel = this.$.contentPanels.getActive(),
214 ary = idx ? (this.getSelectedModule().actions || []).concat(this.getActions(true)) : this.getActions(),
215 actions = ary.slice(0),
216 privileges = XT.session.privileges;
220 actionMenu.destroyClientControls();
222 if (idx && activePanel.getNavigatorActions) {
223 _.each(activePanel.getNavigatorActions(), function (action) {
224 action.context = activePanel;
225 actions.push(action);
229 // then add whatever actions are applicable to the current context
230 _.each(actions, function (action) {
231 var name = action.name,
232 privilege = action.privilege,
233 isDisabled = privilege ? privileges.get(privilege) : false;
234 actionMenu.createComponent({
237 content: action.label || ("_" + name).loc(),
244 this.$.actionButton.setShowing(actions.length);
250 exportMenu.destroyClientControls();
252 if (idx && activePanel.getExportActions) {
253 _.each(activePanel.getExportActions(), function (action) {
254 action.context = activePanel;
255 actions.push(action);
259 // then add whatever actions are applicable to the current context
260 _.each(actions, function (action) {
261 var name = action.name,
262 privilege = action.privilege,
263 isDisabled = privilege ? privileges.get(privilege) : false;
264 exportMenu.createComponent({
267 content: action.label || ("_" + name).loc(),
273 this.$.exportButton.setShowing(actions.length);
275 // HANDLE SORT BUTTON
276 // if the activepanel is a list of some kind, show the button
277 if (activePanel.kindClasses) {
278 this.$.sortButton.setShowing(activePanel.kindClasses.indexOf("list") !== -1);
280 this.$.sortButton.setShowing(false);
287 newMenu.destroyClientControls();
289 if (idx && activePanel.getNewActions) {
290 _.each(activePanel.getNewActions(), function (action) {
291 action.context = activePanel;
292 actions.push(action);
296 // then add whatever actions are applicable to the current context
297 _.each(actions, function (action) {
298 newMenu.createComponent({
301 content: action.label || ("_" + action.name).loc(),
302 // this item is the payload from the menu item
303 // that can be handled differently depending on the panel.
310 If there is a parameter widget, send the current list to the
311 layout form to build the list of columns.
313 buildLayout: function () {
314 var list = this.$.contentPanels.getActive(),
315 parameterWidget = XT.app ? XT.app.$.pullout.getParameterWidget(list.name) : null;
316 if (parameterWidget && parameterWidget.showLayout) {
317 parameterWidget.buildColumnList(list);
321 The navigator only keeps three panels in the DOM at a time. Anything extra panels
322 will be periodically cached into the panelCache published field and removed from the DOM.
324 cachePanels: function () {
325 var contentPanels = this.$.contentPanels,
330 findPanel = function (panel) {
331 return panel.index === globalIndex;
333 findModule = function (module) {
334 var panel = _.find(module.panels, findPanel);
335 return panel !== undefined;
338 while (contentPanels.children.length > 3) {
339 panelToCache = contentPanels.children[0];
340 globalIndex = panelToCache.index;
342 // Panels are abstractly referenced in this.getModules().
343 // Find the abstract panel of the panelToCache
344 // XXX this would be cleaner if we kept a backwards reference
345 // from the panel to its containing module (and index therein)
346 pertinentModule = _.find(this.getModules(), findModule);
347 panelReference = _.find(pertinentModule.panels, findPanel);
349 contentPanels.removeChild(panelToCache);
350 // only render the most recent (i.e. active) child
351 contentPanels.children[2].render();
352 panelReference.status = "cached";
353 this.getPanelCache()[globalIndex] = panelToCache;
357 When a new column value is selected in the layout panel,
358 this value replaces the old attribute value in the list
361 changeLayout: function (inSender, inEvent) {
362 var newValue = inEvent.value ? inEvent.value : "",
363 order = inEvent.order,
364 list = this.$.contentPanels.getActive(),
365 // get the current list of attribute kinds
366 currentColumns = _.filter(list.$, function (item) {
367 return item.kind === "XV.ListAttr";
370 // When we get to the current selected list attribute,
371 // set the new value in place of the old attribute
372 for (var i = 0; i < currentColumns.length; i++) {
373 var col = currentColumns[i];
374 if (order === (i + 1)) {
375 col.setAttr(newValue);
379 // requery this list with the new attribute values
382 // rebuild the tree of columns
385 return true; // stop right here
387 clearMessage: function () {
388 this.$.messageHeader.setContent("");
389 this.$.messageHeader.setClasses("");
391 closeAboutPopup: function () {
392 this.$.aboutPopup.hide();
394 create: function () {
395 this.inherited(arguments);
397 callback = function () {
401 // If not everything is loaded yet, come back to it later
402 if (!XT.session || !XT.session.privileges) {
403 XT.getStartupManager().registerCallback(callback);
408 exportSelected: function (inSender, inEvent) {
409 var index = this.$.menuPanels.getIndex(),
411 action = inEvent.originator.action,
412 method = action.method || action.name;
414 action.context[method](inSender, inEvent);
416 getActions: function (alwaysShowingOnly) {
417 var actions = this.actions;
418 if (alwaysShowingOnly) {
419 actions = _.filter(actions, function (action) {
420 return action.alwaysShowing === true;
425 getSelectedModule: function () {
426 return this._selectedModule;
429 Exports the contents of a list to CSV. Note that it will export the entire
430 list, not just the part that's been lazy-loaded. Of course, it will apply
431 the filter criteria as selected. Goes to the server for this.
432 Avoids websockets or AJAX because the server will prompt the browser to download
433 the file by setting the Content-Type of the response, which is not possible with
436 exportList: function (inSender, inEvent) {
437 this.openExportTab('export');
440 newTab: function () {
441 window.open(XT.getOrganizationPath() + '/app', '_newtab');
443 openPreferencesWorkspace: function () {
444 this.doWorkspace({workspace: "XV.UserPreferenceWorkspace", id: false});
446 printList: function (inSender, inEvent) {
447 this.openExportTab('report');
450 openExportTab: function (routeName) {
451 var list = this.$.contentPanels.getActive(),
452 recordType = list.value.model.prototype.recordType,
453 query = JSON.parse(JSON.stringify(list.getQuery())); // clone
455 delete query.rowLimit;
456 delete query.rowOffset;
458 // sending the locale information back over the wire saves a call to the db
459 window.open(XT.getOrganizationPath() +
460 '/%@?details={"nameSpace":"%@","type":"%@","query":%@,"culture":%@}'
464 JSON.stringify(query),
465 JSON.stringify(XT.locale.culture)),
468 showSortPopup: function (inSender, inEvent) {
469 this.$.sortPopup.setList(this.$.contentPanels.getActive());
470 this.$.sortPopup.setNav(this);
471 this.$.sortPopup.setPickerStrings();
472 this.$.sortPopup.show();
478 fetch: function (options) {
479 options = options ? _.clone(options) : {};
480 var list = this.$.contentPanels.getActive(),
481 name = list ? list.name : "",
488 // in order to continue to the fetch, this needs to be a list
490 if (!(list instanceof XV.List) && !(list instanceof XV.Dashboard)) { return; }
492 query = list.getQuery() || {};
493 input = this.$.searchInput.getValue();
495 // if the "list" doesn't allow dynamic searching, skip this
496 // Dashboards have an allowFilter of false
497 if (list.allowFilter) {
498 parameterWidget = XT.app ? XT.app.$.pullout.getParameterWidget(name) : null;
499 parameters = parameterWidget ? parameterWidget.getParameters() : [];
500 options.showMore = _.isBoolean(options.showMore) ?
501 options.showMore : false;
503 // Get information from filters and set description
504 filterDescription = this.formatQuery(parameterWidget ? parameterWidget.getSelectedValues() : null, input);
505 list.setFilterDescription(filterDescription);
506 this.setHeaderContent(filterDescription);
508 delete query.parameters;
510 if (input || parameters.length) {
511 query.parameters = [];
513 // Input search parameters
515 query.parameters.push({
516 attribute: list.getSearchableAttributes(),
518 value: this.$.searchInput.getValue()
522 // Advanced parameters
524 query.parameters = query.parameters.concat(parameters);
528 // if there is a parameter widget for this list, build the columns
529 if (parameterWidget && parameterWidget.showLayout) {
534 list.setQuery(query);
537 formatQuery: function (advancedSearch, simpleSearch) {
541 for (key in advancedSearch) {
542 formattedQuery += (key + ": " + advancedSearch[key] + ", ");
545 if (simpleSearch && formattedQuery) {
546 formattedQuery += "_match".loc() + ": " + simpleSearch;
547 } else if (simpleSearch) {
548 formattedQuery += simpleSearch;
551 if (formattedQuery) {
552 formattedQuery = "_filterBy".loc() + ": " + formattedQuery;
555 if (formattedQuery.lastIndexOf(", ") + 2 === formattedQuery.length) {
556 // chop off trailing comma
557 formattedQuery = formattedQuery.substring(0, formattedQuery.length - 2);
560 return formattedQuery;
562 handleHotKey: function (inSender, inEvent) {
563 var destinationIndex,
564 isWelcome = this.getSelectedModule().name === 'welcome',
566 keyCode = inEvent.keyCode;
568 // numbers navigate to the nth menu option
569 if (keyCode >= 49 && keyCode <= 57) {
570 destinationIndex = keyCode - 49;
572 this.setModule(Math.min(destinationIndex, this.getModules().length - 1));
574 this.setContentPanel(Math.min(destinationIndex, this.getSelectedModule().panels.length - 1));
578 } else if (!isWelcome) {
579 currentIndex = this.$.panelMenu.getSelection().lastSelected;
580 if (keyCode === 38) {
581 this.setContentPanel(Math.max(currentIndex - 1, 0));
583 } else if (keyCode === 40) {
584 this.setContentPanel(Math.min(currentIndex + 1, this.getSelectedModule().panels.length - 1));
589 switch(String.fromCharCode(keyCode)) {
591 this.showParameters();
600 this.newRecord({}, {originator: {}});
607 inputChanged: function (inSender, inEvent) {
612 Drills down into a workspace if a user clicks a list item.
614 itemTap: function (inSender, inEvent) {
615 var list = inEvent.list,
616 workspace = list ? list.getWorkspace() : null,
617 model = list.getModel(inEvent.index),
618 canNotRead = model.couldRead ? !model.couldRead() : !model.getClass().canRead(),
619 id = model && model.id ? model.id : false;
621 // Check privileges first
623 this.showError("_insufficientViewPrivileges".loc());
627 // Bubble requset for workspace view, including the model id payload
628 if (workspace) { this.doWorkspace({workspace: workspace, id: id}); }
632 var list = this.$.contentPanels.getActive(),
633 workspace = list ? list.getWorkspace() : null,
634 Klass = list.getValue().model,
635 upper = this._getModelProperty(Klass, 'enforceUpperKey'),
636 input = this.$.searchInput.getValue(),
639 key = this._getModelProperty(Klass, 'documentKey'),
642 if (this._busy || !input || !key) { return; }
645 // First find a matching id
646 options.success = function (id) {
650 // Next fetch the model, see if we have privs
651 options.success = function () {
652 var canNotRead = model.couldRead ?
653 !model.couldRead() : !model.getClass().canRead();
655 // Check privileges first
657 this.showError("_insufficientViewPrivileges".loc());
660 // Bubble requset for workspace view, including the model id payload
661 if (workspace) { that.doWorkspace({workspace: workspace, id: id}); }
665 attrs[Klass.prototype.idAttribute] = id;
666 model = Klass.findOrCreate(attrs);
667 model.fetch(options);
669 that.showError("_noDocumentFound".loc());
670 that.$.searchInput.clear();
674 input = upper ? input.toUpperCase() : input;
675 Klass.findExisting(key, input, options);
677 loginInfo: function () {
678 return this.$.loginInfo;
681 Handles additive changes only
683 modulesChanged: function () {
684 var modules = this.getModules() || [],
685 existingModules = this._modules || [],
692 findExistingModule = function (name) {
693 return _.find(existingModules, function (module) {
694 return module.name === name;
697 findExistingPanel = function (panels, name) {
698 return _.find(panels, function (panel) {
699 return panel.name === name;
704 for (i = 0; i < modules.length; i++) {
705 panels = modules[i].panels || [];
706 existingModule = findExistingModule(modules[i].name);
707 for (n = 0; n < panels.length; n++) {
709 // If the panel already exists, move on
710 if (existingModule) {
711 existingPanel = findExistingPanel(existingModule.panels, panels[n].name);
712 if (existingPanel) { continue; }
715 // Keep track of where this panel is being placed for later reference
716 panels[n].index = this.$.contentPanels.panelCount++;
718 // XXX try this: only create the first three
719 if (panels[n].index < 3) {
720 panels[n].status = "active";
722 // Default behavior for Lists is toggle selections
723 // So we can perform actions on rows. If not a List
724 // this property shouldn't hurt anything
725 if (panels[n].toggleSelected === undefined) {
726 panels[n].toggleSelected = true;
728 panel = this.$.contentPanels.createComponent(panels[n]);
729 if (panel instanceof XV.List) {
731 // Bubble parameter widget up to pullout
732 this.doListAdded(panel);
735 panels[n].status = "unborn";
739 this.$.moduleMenu.setCount(modules.length);
741 this._modules = JSON.parse(JSON.stringify(modules));
745 Fired when the user clicks the "New" button. Takes the user to a workspace
746 backed by an empty object of the type displayed in the current list.
748 newRecord: function (inSender, inEvent) {
749 var list = this.$.contentPanels.getActive(),
750 workspace = list instanceof XV.List ? list.getWorkspace() : null,
751 item = inEvent.originator.item,
756 if (list instanceof XV.Dashboard && item) {
757 list.newRecord(item);
761 if (!list instanceof XV.List) {
766 Model = list.getValue().model;
767 canCreate = Model.couldCreate ? Model.couldCreate() : Model.canCreate();
769 this.showError("_insufficientCreatePrivileges".loc());
779 // In addition to preventing Enyo event propagation,
780 // we need to prevent propagation of DOM events to support
781 // mobile browsers and long button clicks
782 // check to make sure this is a button click before calling inEvent function
783 if (inEvent && inEvent.preventDefault) {
784 inEvent.preventDefault();
788 popupHidden: function (inSender, inEvent) {
789 if (!this._popupDone) {
790 inEvent.originator.show();
793 requery: function (inSender, inEvent) {
797 Determines whether the advanced search or the history icon (or neither) is
800 setActiveIconButton: function (buttonName) {
801 var activeIconButton = null;
802 // Null deactivates both
803 if (buttonName === 'search') {
804 activeIconButton = this.$.searchIconButton;
805 } else if (buttonName === 'history') {
806 activeIconButton = this.$.historyIconButton;
808 this.$.iconButtonGroup.setActive(activeIconButton);
811 Renders a list and performs all the necessary auxilliary work such as hiding/showing
812 the advanced search icon if appropriate. Called when a user chooses a menu item.
814 setContentPanel: function (index) {
815 var contentPanels = this.$.contentPanels,
816 module = this.getSelectedModule(),
817 panelIndex = module && module.panels ? module.panels[index].index : -1,
818 panelStatus = module && module.panels ? module.panels[index].status : 'unknown',
826 if (panelStatus === 'active') {
827 panel = _.find(contentPanels.children, function (child) {
828 return child.index === panelIndex;
830 } else if (panelStatus === 'unborn') {
831 // panel exists but has not been rendered. Render it.
832 module.panels[index].status = 'active';
834 // Default behavior for Lists is toggle selections
835 // So we can perform actions on rows. If not a List
836 // this property shouldn't hurt anything
837 if (module.panels[index].toggleSelected === undefined) {
838 module.panels[index].toggleSelected = true;
840 panel = contentPanels.createComponent(module.panels[index]);
842 if (panel instanceof XV.List) {
844 // Bubble parameter widget up to pullout
845 this.doListAdded(panel);
848 } else if (panelStatus === 'cached') {
849 module.panels[index].status = 'active';
850 panel = this.panelCache[panelIndex];
851 contentPanels.addChild(panel);
852 panel.node = undefined; // new to enyo2.2! wipe out the node so that it can get re-rendered fresh
856 XT.error("Don't know what to do with this panel status");
859 // Mobile device view
860 if (enyo.Panels.isScreenNarrow()) {
864 // If we're already here, bail
865 if (contentPanels.index === this.$.contentPanels.indexOfChild(panel)) {
869 // cache any extraneous content panels
872 label = panel && panel.label ? panel.label : "";
873 collection = panel && panel.getCollection ? XT.getObjectByName(panel.getCollection()) : false;
875 if (!panel) { return; }
877 // Make sure the advanced search icon is visible iff there is an advanced
878 // search for this list
879 if (panel.parameterWidget) {
880 this.$.searchIconButton.setShowing(true);
882 this.$.searchIconButton.setShowing(false);
884 this.doNavigatorEvent({name: panel.name, show: false});
887 this.$.newButton.setShowing(panel.canAddNew && !panel.newActions);
888 this.$.newMenuButton.setShowing(panel.canAddNew && panel.newActions);
890 if (panel.canAddNew && collection) {
891 // Check 'couldCreate' first in case it's an info model.
892 model = collection.prototype.model;
893 canNotCreate = model.prototype.couldCreate ? !model.prototype.couldCreate() : !model.canCreate();
895 this.$.newButton.setDisabled(canNotCreate);
898 if (!this.$.panelMenu.isSelected(index)) {
899 this.$.panelMenu.select(index);
903 contentPanels.setIndex(this.$.contentPanels.indexOfChild(panel));
905 this.$.rightLabel.setContent(label);
906 if (panel.getFilterDescription) {
907 this.setHeaderContent(panel.getFilterDescription());
909 if (panel.fetch && !this.fetched[panelIndex]) {
911 this.fetched[panelIndex] = true;
915 this.$.contentToolbar.resized();
919 The header content typically describes to the user the particular query filter in effect.
921 setHeaderContent: function (content) {
922 this.$.header.setContent(content);
923 if (content !== "") {
924 this.$.header.setClasses("xv-navigator-header");
926 this.$.header.setClasses("");
929 setMenuPanel: function (index) {
930 var label = index ? "_back".loc() : "_logout".loc();
931 this.$.menuPanels.setIndex(index);
932 // only automatically select the first screen if it's the module menu
933 if (!enyo.Panels.isScreenNarrow()) {
934 this.$.menuPanels.getActive().select(0);
935 this.setContentPanel(0);
937 this.$.backButton.setContent(label);
938 this.$.refreshButton.setShowing(index);
939 this.$.search.setShowing(index);
940 this.$.contentToolbar.resized();
942 setMessageContent: function (inSender, inEvent) {
943 var content = inEvent.message;
945 this.$.messageHeader.setContent(content);
946 if (content !== "") {
947 this.$.messageHeader.setClasses("xv-navigator-header");
949 this.$.messageHeader.setClasses("");
952 setModule: function (index) {
953 var module = this.getModules()[index],
954 panels = module.panels || [],
955 hasSubmenu = module.hasSubmenu !== false && panels.length;
956 if (module !== this._selectedModule || enyo.Panels.isScreenNarrow()) {
957 this._selectedModule = module;
959 this.$.panelMenu.setCount(panels.length);
960 this.$.panelMenu.render();
961 this.setMenuPanel(PANEL_MENU);
963 // if no submenus, treat lke a panel
964 this.setContentPanel(index);
968 setModules: function (modules) {
969 this.modules = modules;
970 this.modulesChanged();
973 Renders a list of modules from the root menu.
975 setupModuleMenuItem: function (inSender, inEvent) {
976 var index = inEvent.index,
977 label = this.modules[index].label,
978 isSelected = inSender.isSelected(index);
979 this.$.moduleItem.setContent(label);
980 this.$.moduleItem.addRemoveClass("onyx-selected", isSelected);
981 if (isSelected) { this.setModule(index); }
984 Renders the leftbar list of objects within a given module. This function
985 is also called when a leftbar item is tapped, per enyo's List conventions.
987 setupPanelMenuItem: function (inSender, inEvent) {
988 var module = this.getSelectedModule(),
989 index = inEvent.index,
990 isSelected = inSender.isSelected(index),
991 panel = module.panels[index],
992 name = panel && panel.name ? module.panels[index].name : "",
993 // peek inside the kind to see what the label should be
994 kind = panel && panel.kind ? XT.getObjectByName(panel.kind) : null,
995 label = kind && kind.prototype.label ? kind.prototype.label : "",
998 if (!label && kind && kind.prototype.determineLabel) {
999 // some of these lists have labels that are dynamically computed,
1000 // so we can't rely on their being statically defined. We have to
1001 // compute them in the same way that their create() method would.
1002 shortKindName = panel.kind.substring(0, panel.kind.length - 4).substring(3);
1003 label = kind.prototype.determineLabel(shortKindName);
1005 } else if (!label) {
1006 label = panel ? panel.label || name : name;
1009 this.$.listItem.setContent(label);
1010 this.$.listItem.addRemoveClass("onyx-selected", isSelected);
1014 This function is called when a panel is selected. If the selection is valid,
1015 then the content panel is set for that panel selection.
1017 panelTap: function (inSender, inEvent) {
1018 var index = inEvent.index, validIndex = index || index === 0;
1019 if (validIndex && inSender.isSelected(index)) { // make sure an item in the list was clicked and is selected
1020 this.setContentPanel(index);
1025 This function is called when a module is selected. If the selection is valid,
1026 then the list of panels is shown for that module.
1028 menuTap: function (inSender, inEvent) {
1029 var validIndex = inEvent.index || inEvent.index === 0;
1030 if (validIndex) { // make sure an item in the list was clicked
1031 this.setupModuleMenuItem(inSender, inEvent);
1034 showAbout: function () {
1035 this.$.aboutPopup.show();
1038 Error notification, using XV.ModuleContainer notify mechanism
1040 showError: function (message) {
1043 type: XM.Model.CRITICAL,
1046 this.doNotify(inEvent);
1048 showHelp: function () {
1049 var listName = this.$.contentPanels.getActive().name,
1050 // this will work fine starting in 1.4.6:
1051 //culture = XT.locale.culture,
1052 culture = XT.locale.culture || XT.session.locale.attributes.culture,
1053 objectName = (listName.indexOf("List") >= 0 || listName.indexOf("Page") >= 0) ?
1054 listName.substring(0, listName.length - 4) : // get rid of the word "List" or "Page"
1056 pageName = objectName.decamelize().replace(/_/g, "-"),
1057 url = XT.HELP_URL_ROOT + pageName + "?culture=" + culture,
1058 panel = {name: 'help', show: true, url: url};
1060 this.doNavigatorEvent(panel);
1061 //window.open(url, "_blank", "width=400,height=600");
1064 Displays the history panel.
1066 showHistory: function (inSender, inEvent) {
1067 var panel = {name: 'history', show: true};
1068 this.doNavigatorEvent(panel);
1071 Displays the advanced search panel.
1073 showParameters: function (inSender, inEvent) {
1074 var list = this.$.contentPanels.getActive();
1075 this.doNavigatorEvent({name: list.name, show: true});
1078 Displays the My Account popup.
1080 showMyAccount: function (inSender, inEvent) {
1081 this.$.myAccountPopup.show();
1084 _getModelProperty: function (Klass, prop) {
1086 // Get the key if it's a document model
1087 if (Klass.prototype[prop]) {
1088 ret = Klass.prototype[prop];
1090 // Hopefully it's an info model
1091 } else if (Klass.prototype.editableModel) {
1092 Klass = XT.getObjectByName(Klass.prototype.editableModel);
1093 ret = Klass.prototype[prop];
1100 enyo.mixin(navigator, XV.ListMenuManagerMixin);
1101 enyo.kind(navigator);