1 /*jshint bitwise:false, indent:2, curly:true, eqeqeq:true, immed:true,
2 latedef:true, newcap:true, noarg:true, regexp:true, undef:true,
3 trailing:true, white:true, strict: false*/
4 /*global XV:true, XM:true, _:true, enyo:true, XT:true, Globalize:true, window:true */
13 @class A mixin that contains functionality common to {@link XV.Workspace}
14 and {@link XV.ListRelationsEditorBox}.
18 @class A mixin that contains functionality common to {@link XV.Workspace}
19 and {@link XV.ListRelationsEditorBox}.
22 controlValueChanged: function (inSender, inEvent) {
24 attr = inEvent.originator.attr;
26 if (this.value && attr) {
27 // If the value is an object, then the object is already mapped
28 if (attr instanceof Object) {
29 attrs = inEvent.value;
31 // Otherwise map a basic value to its attribute
33 attrs[attr] = inEvent.value;
35 this.value.setValue(attrs);
37 } else if (XT.session.config.debugging) {
38 XT.log("Ignoring content-less model update event", this.value, attr);
42 Updates all child controls on the editor where the name of
43 the control matches the name of an attribute on the model.
45 @param {XM.Model} model
46 @param {Object} options
48 attributesChanged: function (model, options) {
49 // If the model is meta, then move up to the workspace model
50 if (!(model instanceof XM.Model)) { model = this.value; }
52 options = options || {};
55 status = model.getStatus(),
57 canUpdate = (status === K.READY_NEW) ||
58 ((status & K.READY) && model.canUpdate()),
67 // loop through each of the controls and set the value using the
68 // attribute on the control
69 _.each(ctrls, function (control) {
71 if (!control.getAttr) {
72 XT.log("Warning: " + control.kind + " is not a valid XV control");
74 attribute = control.getAttr();
76 // for compound controls, the attribute is an object with key/value pairs.
77 if (_.isObject(attribute)) {
78 obj = _.clone(attribute);
79 // replace the current values in the object (attribute names)
80 // with the values from the model
81 for (var attr in obj) {
82 if (obj.hasOwnProperty(attr)) {
83 prop = model.attributeDelegates && model.attributeDelegates[attr] ?
84 model.attributeDelegates[attr] : attr;
85 // replace the current value of the property name with the value from the model
86 modelPropName = obj[prop];
87 obj[prop] = model.getValue(modelPropName);
92 // only worry about the one mapping to isEditableProperty
93 if (control.isEditableProperty) {
94 attribute = attribute[control.isEditableProperty];
96 // If not isEditableProperty defined one is as good as another, just pick one
98 attribute = attribute[_.first(_.keys(attribute))];
102 prop = model.attributeDelegates && model.attributeDelegates[attribute] ?
103 model.attributeDelegates[attribute] : attribute;
105 if (!obj) { value = model.getValue(prop); }
107 canView = model.canView(prop);
108 isReadOnly = model.isReadOnly(prop) || !model.canEdit(prop);
109 isRequired = model.isRequired(prop);
112 control.setShowing(true);
113 if (control.setPlaceholder && isRequired && !control.getPlaceholder()) {
114 control.setPlaceholder("_required".loc());
116 if (control.setValue && !(status & K.BUSY)) {
117 control.setValue(value, {silent: true});
119 if (control.setDisabled) {
120 control.setDisabled(!canUpdate || isReadOnly);
123 control.setShowing(false);
132 @todo Document the clear method.
135 var attrs = this.value ? this.value.getAttributeNames() : [],
139 for (i = 0; i < attrs.length; i++) {
141 control = this.findControl(attr);
142 if (control && control.clear) {
143 control.clear({silent: true});
148 Returns the control that contains the attribute string.
150 findControl: function (attr) {
151 return _.find(this.$, function (ctl) {
152 if (_.isObject(ctl.attr)) {
153 return _.find(ctl.attr, function (str) {
157 return ctl.attr === attr;
161 Bubble up an event to ask a question to the user. The user interaction
162 is handled by XV.ModuleContainer.
164 notify: function (model, message, options) {
165 var inEvent = _.extend(options, {
170 this.doNotify(inEvent);
175 Set model bindings on a workspace
177 @param{Object} Workspace
178 @param{String} Action: 'on' or 'off'
180 var _setBindings = function (ws, action) {
181 var headerAttrs = ws.getHeaderAttrs() || [],
187 model[action]("change", ws.attributesChanged, ws);
188 model[action]("lockChange", ws.lockChanged, ws);
189 model[action]("readOnlyChange", ws.attributesChanged, ws);
190 model[action]("statusChange", ws.statusChanged, ws);
191 model[action]("invalid", ws.error, ws);
192 model[action]("error", ws.error, ws);
193 model[action]("notify", ws.notify, ws);
194 if (headerAttrs.length) {
195 for (i = 0; i < headerAttrs.length; i++) {
196 attr = headerAttrs[i];
197 if (attr.indexOf('.') !== -1 ||
198 _.contains(model.getAttributeNames(), attr)) {
199 observers = observers ? observers + " change:" + attr : "change:" + attr;
202 model[action](observers, ws.headerValuesChanged, ws);
204 // If meta data exists, handle that too.
206 model.meta[action]("change", ws.attributesChanged, ws);
210 XV.WorkspacePanelsRefactor = {
212 rendered: function () {
213 if (!this.$.panels || !this.$.panels.hasClass('xv-workspace-panel')) {
214 this.inherited(arguments);
218 _.each(this.$.panels.getClientControls(), function (control) {
219 control.addClass('xv-workspace-panel');
221 this.$.panels.removeClass('xv-workspace-panel');
222 this.inherited(arguments);
228 @class Contains a set of fittable rows which are laid out
229 using a collapsing arranger and fitted to the size of the viewport.<br />
230 Its components can be extended via {@link XV.ExtensionsMixin}.<br />
231 Derived from <a href="http://enyojs.com/api/#enyo.FittableRows">enyo.FittableRows</a>.
233 Supported properties on the action array are:
235 * label: Menu label. Defaults to name if not present.
236 * privilege: The privilege required by the user to enable the menu. Defaults enabled if not present.
237 * prerequisite: A function on the model that returns a boolean dictating whether to show the menu item or not.
238 * method: The function to call. Defaults to name if not present.
239 * isViewMethod: Boolean value dictates whether method is called on the view or the view's model. Default false.
241 @extends XV.WorkspacePanels
242 @extends XV.EditorMixin
243 @extends XV.ExtensionsMixin
244 @see XV.WorkspaceContainer
246 enyo.kind(_.extend({ },
247 XV.EditorMixin, XV.ExtensionsMixin,
248 XV.WorkspacePanelsRefactor, {
249 name: "XV.Workspace",
250 kind: "XV.WorkspacePanels",
251 classes: 'xv-workspace',
255 title: "_none".loc(),
259 modelAmnesty: false, // do we keep the model even if the workspace is destroyed?
260 // typically no, but yes for child workspaces
261 printOnSaveSetting: "", // some workspaces have a setting that cause them to be
262 // automatically printed upon saving
266 saveText: "_save".loc(),
267 backText: "_back".loc(),
268 hideSaveAndNew: false,
274 * The type of the backing model.
279 * The backing model for this component.
280 * @see XM.EnyoView#model
285 * @see XM.View#workspace.template
301 onSaveTextChange: "",
302 onTransactionList: "",
306 onValueChange: "controlValueChanged"
310 {kind: "XV.Groupbox", name: "mainPanel",
312 {kind: "onyx.GroupboxHeader", content: "_overview".loc()},
313 {kind: "XV.ScrollableGroupbox", name: "mainGroup",
314 classes: "in-panel", fit: true, components: [
315 {kind: "XV.InputWidget", attr: "name"},
316 {kind: "XV.InputWidget", attr: "description"}
321 create: function () {
322 this.inherited(arguments);
323 XM.View.setPresenter(this, 'workspace');
324 this.processExtensions();
329 @todo Document the destroy method.
331 destroy: function () {
332 var model = this.getValue(),
333 modelAmnesty = this.getModelAmnesty(),
334 wasNew = model.isNew();
335 this.setRecordId(null);
336 // If we never saved a new model, make the callback
337 // so the caller can deal with that and destroy it.
338 if (wasNew || modelAmnesty) {
339 if (this.callback) { this.callback(false); }
341 this.inherited(arguments);
344 @todo Document the error method.
346 error: function (model, error) {
352 this.doError(inEvent);
353 this.attributesChanged(this.getValue());
356 @todo Document the fetch method.
358 fetch: function (options) {
359 options = options || {};
360 var wsSuccess = this.getSuccess(),
361 success = options.success;
364 wsSuccess = _.bind(wsSuccess, this);
367 options.success = function (model, resp, options) {
368 if (wsSuccess) { wsSuccess(model, resp, options); }
369 if (success) { success(model, resp, options); }
371 if (!this.value) { return; }
372 this.value.fetch(options);
375 Implementation is up to subkinds
377 handleHotKey: function (keyValue) {},
379 @todo Document the headerValuesChanged method.
381 headerValuesChanged: function () {
382 var headerAttrs = this.getHeaderAttrs() || [],
388 if (headerAttrs.length && model) {
389 for (i = 0; i < headerAttrs.length; i++) {
390 attr = headerAttrs[i];
391 if (attr.indexOf('.') !== -1 ||
392 _.contains(model.getAttributeNames(), attr)) {
393 value = model.getValue(headerAttrs[i]) || "";
394 header = header ? header + " " + value : value;
396 header = header ? header + " " + attr : attr;
400 this.doHeaderChange({originator: this, header: header });
403 @todo Document the isDirty method.
405 isDirty: function () {
406 return this.value ? this.value.isDirty() : false;
408 lockChanged: function () {
409 this.doLockChange({hasKey: this.getValue().hasLockKey()});
412 @todo Document the newRecord method.
414 newRecord: function (attributes, options) {
415 options = options || {};
416 var model = this.getModel(),
417 Klass = XT.getObjectByName(model),
422 success: function () {
423 that.attributesChanged(that.value);
426 // Update record id directly when we get it, but don't trigger changes
427 updateRecordId = function (model) {
428 that.recordId = model.id;
429 that.value.off("change:" + that.value.idAttribute, updateRecordId, that);
431 this.setRecordId(null);
432 this.value = new Klass();
433 _setBindings(this, "on");
434 this.value.on("change:" + this.value.idAttribute, updateRecordId, this);
436 this.headerValuesChanged();
437 this.value.initialize(null, {isNew: true});
438 if (options.success) { options.success.call(this); }
439 this.value.set(attributes, {force: true});
440 for (attr in attributes) {
441 if (attributes.hasOwnProperty(attr)) {
442 this.value.setReadOnly(attr);
443 if (this.value.getRelation(attr)) {
444 this.value.fetchRelated(attr, relOptions);
446 changes[attr] = true;
447 this.attributesChanged(this.value, {changes: changes});
452 // TODO: in 1.8.0 make this the default, move the buttons into a gear
453 // menu, and follow the same option convention as in the list
454 download: function () {
455 this.openReport(XT.getOrganizationPath() + this.getValue().getReportUrl("download"));
458 Email the model's data, either silently or by opening a tab
461 if (XT.session.config.emailAvailable) {
462 // send it to be printed silently by the server
463 this.getValue().doEmail();
465 this.openReport(XT.getOrganizationPath() + this.getValue().getReportUrl());
469 Print the model's data, either silently or by opening a tab
472 if (XT.session.config.printAvailable) {
473 // send it to be printed silently by the server
474 this.getValue().doPrint();
476 this.openReport(XT.getOrganizationPath() + this.getValue().getReportUrl());
480 Open the report pdf in a new tab
482 openReport: function (path) {
483 window.open(path, "_newtab");
486 Handle clearing and reseting of model if the record id changes.
488 recordIdChanged: function () {
489 var model = this.getModel(),
490 Klass = model ? XT.getObjectByName(model) : null,
491 recordId = this.getRecordId(),
496 _setBindings(this, "off");
497 this.value.releaseLock();
500 // the configuration workspaces, notably, need to be fetched despite
501 // having an id of false. It works because the XM.Settings models use
502 // a dispatch for fetch.
503 if (recordId === undefined || recordId === null) {
504 if (this.value.isNew() && !this.modelAmnesty) {
505 this.value.destroy();
511 // Create new instance and bindings
512 if (recordId === false && this.singletonModel) {
513 this.setValue(XT.getObjectByName(this.singletonModel));
515 attrs[Klass.prototype.idAttribute] = recordId;
516 this.setValue(Klass.findOrCreate(attrs));
518 _setBindings(this, "on");
522 Refresh the model behind this workspace. Note that we
523 have to release the lock first.
525 If this is being called on a new model, it means we
526 want to throw away the data in the model and start with
527 a new record. We don't worry about the lock in this case.
529 requery: function () {
530 if (this.getValue().isNew()) {
535 // model has already been saved
538 success: function () {
542 XT.log("Error releasing lock.");
543 // fetch anyway. Why not!?
548 // first we want to release the lock we have on this record
549 // TODO #refactor move this into model layer
550 this.value.releaseLock(options);
553 @todo Document the save method.
555 save: function (options) {
556 options = options || {};
558 success = options.success,
561 model: this.getModel(),
563 done: options.modelChangeDone
565 options.success = function (model, resp, options) {
566 that.doModelChange(inEvent);
567 that.parent.parent.modelSaved();
568 if (that.callback) { that.callback(model); }
569 if (success) { success(model, resp, options); }
571 this.value.save(null, options);
574 Set save text when it is changed
576 saveTextChanged: function () {
578 content: this.getSaveText()
580 this.doSaveTextChange(inEvent);
583 @todo Document the statusChanged method.
585 statusChanged: function (model, status, options) {
586 options = options || {};
587 var inEvent = {model: model, status: status},
588 attrs = model.getAttributeNames(),
593 // Add to history if appropriate.
594 if (model.id && model.keepInHistory) {
595 XT.addToHistory(this.kind, model, function (historyArray) {
596 dbName = XT.session.details.organization;
597 enyo.setCookie("history_" + dbName, JSON.stringify(historyArray));
599 this.doHistoryChange(this);
603 for (i = 0; i < attrs.length; i++) {
604 changes[attrs[i]] = true;
606 options.changes = changes;
608 // Update header if applicable
609 if (model.isReady()) {
610 this.headerValuesChanged();
611 this.attributesChanged(model, options);
614 this.doStatusChange(inEvent);
617 This function sets the title widget in the workspace Toolbar to the title
618 specified in the Workspace specification. If one is not specified, then "_none" is used.
620 titleChanged: function () {
621 var inEvent = { title: this.getTitle(), originator: this };
622 this.doTitleChange(inEvent);
627 @name XV.WorkspaceContainer
628 @class Contains the navigation and content panels which wrap around a workspace.<br />
629 See also {@link XV.Workspace}.<br />
630 Derived from <a href="http://enyojs.com/api/#enyo.Panels">enyo.Panels</a>.
632 @extends XV.ListMenuManagerMixin
634 enyo.kind(/** @lends XV.WorkspaceContainer# */{
635 name: "XV.WorkspaceContainer",
636 kind: "XV.ContainerPanels",
637 classes: "xv-workspace-container",
648 onError: "errorNotify",
649 onHeaderChange: "headerChanged",
650 onHotKey: "handleHotKey",
651 onListItemMenuTap: "showListItemMenu",
652 onLockChange: "lockChanged",
653 onMenuChange: "menuChanged",
657 onStatusChange: "statusChanged",
658 onTitleChange: "titleChanged",
659 onSaveTextChange: "saveTextChanged",
660 onExportAttr: "exportAttr"
663 {kind: "FittableRows", name: "navigationPanel", classes: "xv-menu-container", components: [
664 {kind: "onyx.Toolbar", name: "menuToolbar", components: [
665 {kind: "font.TextIcon", name: "backButton",
666 content: "_back".loc(), ontap: "close", icon: "chevron-left"},
667 {kind: "onyx.MenuDecorator", onSelect: "actionSelected", components: [
668 {kind: "font.TextIcon", icon: "cog",
669 content: "_actions".loc(), name: "actionButton"},
670 {kind: "onyx.Menu", name: "actionMenu", floating: true}
673 {name: "loginInfo", classes: "xv-header"},
674 {name: "menu", kind: "List", fit: true, touch: true, classes: 'xv-navigator-menu',
675 onSetupItem: "setupItem", components: [
676 {name: "item", classes: "item enyo-border-box xv-list-item", ontap: "itemTap"}
679 {kind: "FittableRows", name: "contentPanel", classes: 'xv-content-panel', components: [
680 {kind: "onyx.MoreToolbar", name: "contentToolbar", components: [
681 {kind: "onyx.Grabber", classes: "spacer", unmoveable: true,},
682 {name: "title", classes: "xv-toolbar-label", unmoveable: true,},
683 {name: "space", classes: "spacer", fit: true},
684 {kind: "font.TextIcon", name: "lockImage", showing: false,
685 content: "Locked", ontap: "lockTapped", icon: "lock", classes: "lock"},
686 {kind: "font.TextIcon", name: "backPanelButton", unmoveable: true,
687 content: "_back".loc(), ontap: "close", icon: "chevron-left"},
688 {kind: "font.TextIcon", name: "refreshButton",
689 content: "_refresh".loc(), onclick: "requery", icon: "rotate-right"},
690 {kind: "font.TextIcon", name: "saveAndNewButton", disabled: false,
691 content: "_new".loc(), ontap: "saveAndNew", icon: "plus"},
692 {kind: "font.TextIcon", name: "applyButton", disabled: true,
693 content: "_apply".loc(), ontap: "apply", icon: "ok"},
694 {kind: "font.TextIcon", name: "saveButton",
695 disabled: true, icon: "save", classes: "save",
696 content: "_save".loc(), ontap: "saveAndClose"}
698 {name: "header", content: "_loading".loc(), classes: "xv-header"},
699 {kind: "onyx.Popup", name: "spinnerPopup", centered: true,
700 modal: true, floating: true, scrim: true,
701 onHide: "popupHidden", components: [
702 {kind: "onyx.Spinner"},
703 {name: "spinnerMessage", content: "_loading".loc() + "..."}
705 {kind: "onyx.Popup", name: "lockPopup", centered: true,
706 modal: true, floating: true, components: [
707 {name: "lockMessage", content: ""},
709 {kind: "onyx.Button", content: "_ok".loc(), ontap: "lockOk",
710 classes: "onyx-blue xv-popup-button"}
712 {name: "listItemMenu", kind: "onyx.Menu", floating: true, onSelect: "listActionSelected",
713 maxHeight: 500, components: []
717 actionSelected: function (inSender, inEvent) {
718 // Could have come from an action, or a an action button
719 var selected = inEvent.selected || inEvent.originator;
721 // If it's a view method then call function on the workspace.
722 if (selected.isViewMethod || selected.container.isViewMethod) {
723 this.$.workspace[selected.method || selected.container.method](inEvent);
725 // Otherwise call it on the workspace's model.
727 this.$.workspace.getValue()[selected.method]();
730 allowNewChanged: function () {
731 var allowNew = this.getAllowNew();
732 this.$.saveAndNewButton.setShowing(allowNew);
735 @todo Document the apply method.
738 this._saveState = SAVE_APPLY;
741 askAboutUnsaved: function (shouldClose) {
743 message = "_unsavedChanges".loc() + " " + "_saveYourWork?".loc(),
744 callback = function (response) {
745 var answer = response.answer;
747 if (answer === true && shouldClose) {
748 that.saveAndClose({force: true});
749 } else if (answer === true) {
751 } else if (answer === false && shouldClose) {
752 that.close({force: true});
753 } else if (answer === false) {
754 that.$.workspace.requery();
755 } // else answer === undefined means cancel, so do nothing
758 type: XM.Model.YES_NO_CANCEL,
760 yesLabel: "_save".loc(),
761 noLabel: "_discard".loc(),
765 buildMenus: function () {
766 var actionMenu = this.$.actionMenu,
767 workspace = this.$.workspace,
768 actions = workspace.getActions(),
769 actionButtons = workspace.getActionButtons(),
770 model = workspace.getValue(),
774 // Handle menu actions
778 actionMenu.destroyClientControls();
780 // Add whatever actions are applicable to the current context.
781 _.each(actions, function (action) {
782 var name = action.name,
783 prerequisite = action.prerequisite,
784 privilege = action.privilege,
785 isDisabled = privilege ? !XT.session.privileges.get(privilege) : false;
787 // Only create menu item if prerequisites are met.
788 if (!prerequisite || model[prerequisite]()) {
789 actionMenu.createComponent({
792 content: action.label || ("_" + name).loc(),
793 method: action.method || action.action || name,
794 disabled: isDisabled,
795 isViewMethod: action.isViewMethod
802 if (actions.length && !count) {
803 actionMenu.createComponent({
806 content: "_noEligibleActions".loc(),
813 this.$.actionButton.setShowing(actions && actions.length);
815 // Handle button actions
817 _.each(actionButtons, function (action) {
818 var privs = XT.session.privileges,
819 noPriv = action.privilege ? !privs.get(action.privilege): false,
820 noCanDo = action.prerequisite ? !model[action.prerequisite]() : false;
822 that.$[action.name].setDisabled(noPriv || noCanDo);
826 create: function () {
827 this.inherited(arguments);
830 closed: function (inSender, inEvent) {
834 Backs out of the workspace. This can be done using the back button, or
835 during the end of the save-and-close process.
837 close: function (options) {
839 workspace = this.$.workspace,
840 model = this.$.workspace.getValue();
842 options = options || {};
843 if (!options.force) {
844 if (workspace.getDirtyWarn() && workspace.isDirty()) {
845 this.askAboutUnsaved(true);
850 if (workspace.value.getStatus() === XM.Model.READY_DIRTY &&
851 !workspace.getModelAmnesty()) {
852 // Revert because this model may be referenced elsewhere
853 _setBindings(this.$.workspace, "off");
857 if (model && model.hasLockKey && model.hasLockKey()) {
859 success: function () {
863 XT.log("Error releasing lock");
872 @todo Document the destroyWorkspace method.
874 destroyWorkspace: function () {
875 var workspace = this.$.workspace;
877 this.removeComponent(workspace);
882 Legacy case for notify() function
884 errorNotify: function (inSender, inEvent) {
885 var message = inEvent.error.message ? inEvent.error.message() : "Error";
886 inEvent.message = message;
887 inEvent.type = XM.Model.CRITICAL;
888 this.doNotify(inEvent);
890 setLoginInfo: function () {
891 var details = XT.session.details;
892 this.$.loginInfo.setContent(details.username + " · " + details.organization);
894 handleHotKey: function (inSender, inEvent) {
895 var keyCode = inEvent.keyCode;
897 switch (String.fromCharCode(keyCode)) {
912 // else see if the workspace has a specific implementation
913 this.$.workspace.handleHotKey(keyCode);
916 @todo Document the headerChanged method.
918 headerChanged: function (inSender, inEvent) {
919 this.$.header.setContent(inEvent.header);
923 @todo Document the itemTap method.
925 itemTap: function (inSender, inEvent) {
926 var workspace = this.$.workspace,
927 panel = this.getMenuItems()[inEvent.index],
931 // Find the panel in the workspace and set it to current
932 // XXX let's find a better way to keep track of this
933 for (prop in workspace.$) {
934 if (workspace.$.hasOwnProperty(prop) &&
935 workspace.$[prop] instanceof enyo.Panels) {
936 panels = workspace.$[prop].getPanels();
937 for (i = 0; i < panels.length; i++) {
938 if (panels[i] === panel) {
939 workspace.$[prop].setIndex(i);
946 // Mobile device view
947 if (enyo.Panels.isScreenNarrow()) {
951 lockChanged: function (inSender, inEvent) {
952 this.$.lockImage.setShowing(!inEvent.hasKey);
954 lockOk: function () {
955 this.$.lockPopup.hide();
958 If the user clicks on the lock icon, we tell them who got the lock and when
960 lockTapped: function () {
961 var lock = this.$.workspace.getValue().lock,
962 effective = Globalize.format(new Date(lock.effective), "t");
963 this.$.lockMessage.setContent("_lockInfo".loc()
964 .replace("{user}", lock.username)
965 .replace("{effective}", effective));
966 this.$.lockPopup.render();
967 this.$.lockPopup.show();
970 Once a model has been saved we take our next action depending on
971 which of the save-and-X actions were actually requested. This
972 is part of the callback of the save operation.
974 modelSaved: function () {
975 if (this._saveState === SAVE_CLOSE) {
977 } else if (this._saveState === SAVE_NEW) {
982 @todo Document the newRecord method.
984 newRecord: function () {
985 this.$.workspace.newRecord();
988 Although the main processing of the notify request happens
989 in XV.ModuleContainer, we do want to make sure that the spinner
990 goes away if it's present.
992 notify: function () {
996 @todo Document the popupHidden method.
998 popupHidden: function (inSender, inEvent) {
999 if (!this._popupDone) {
1000 inEvent.originator.show();
1004 Refreshes the workspace.
1006 requery: function (options) {
1007 options = options || {};
1008 if (!options.force) {
1009 if (this.$.workspace.isDirty()) {
1010 this.askAboutUnsaved(false);
1014 this.$.workspace.requery();
1017 All the other save functions flow through here.
1019 save: function (options) {
1020 var workspace = this.$.workspace,
1021 print = workspace.printOnSaveSetting &&
1022 XT.session.config.printAvailable &&
1023 XT.session.settings.get(workspace.printOnSaveSetting);
1024 if (!this._saveState) { this._saveState = SAVE_APPLY; }
1025 workspace.save(options);
1027 // some workspaces are set up with a setting to have them automatically
1028 // print when they're saved.
1030 this.$.workspace.print();
1034 Save the model and close out the workspace.
1036 saveAndClose: function () {
1037 this._saveState = SAVE_CLOSE;
1038 this.save({requery: false});
1041 Handles the save-and-new button click. Note that this button displays "new"
1042 if the model is clean, and we want it exhibit that conditional behavior.
1044 saveAndNew: function () {
1045 if (this.$.workspace.getValue().isDirty()) {
1046 this._saveState = SAVE_NEW;
1047 this.save({requery: false});
1053 saveTextChanged: function (inSender, inEvent) {
1054 this.$.saveButton.setContent(inEvent.content);
1057 exportAttr: function (inSender, inEvent) {
1058 this.openExportTab('export', inEvent.recordType, inEvent.uuid, inEvent.attr);
1062 // export just one attribute of the model displayed by the workspace
1063 openExportTab: function (routeName, recordType, id, attr) {
1064 var query = { parameters: [{ attribute: "uuid", value: id }],
1065 details: { attr: attr }
1067 // sending the locale information back over the wire saves a call to the db
1068 window.open(XT.getOrganizationPath() +
1069 '/%@?details={"nameSpace":"%@","type":"%@","query":%@,"culture":%@,"print":%@}'
1071 recordType.prefix(),
1072 recordType.suffix(),
1073 JSON.stringify(query),
1074 JSON.stringify(XT.locale.culture),
1079 This is called for each row in the menu List.
1080 The menu text is derived from the corresponding panel index.
1081 If the panel is not visible, then the menu item is also not visible.
1084 setupItem: function (inSender, inEvent) {
1085 var box = this.getMenuItems()[inEvent.index],
1086 defaultTitle = "_overview".loc(),
1087 title = box.getTitle ? box.getTitle() ||
1088 defaultTitle : box.title ? box.title || defaultTitle : defaultTitle,
1089 visible = box.showing;
1090 this.$.item.setContent(title);
1091 this.$.item.box = box;
1092 this.$.item.addRemoveClass("onyx-selected", inSender.isSelected(inEvent.index));
1093 this.$.item.setShowing(visible);
1099 Loads a workspace into the workspace container.
1100 Accepts the following options:
1101 * workspace: class name (required)
1102 * id: record id to load. If none, a new record will be created.
1103 * allowNew: boolean indicating whether Save and New button is shown.
1104 * attributes: default attribute values for a new record.
1105 * success: function to call from the workspace when the workspace
1106 has either succefully fetched or created a model.
1107 * callback: function to call on either a successful save, or the user
1108 leaves the workspace without saving a new record. Passes the new or
1109 updated model as an argument.
1111 setWorkspace: function (options) {
1116 workspace = options.workspace,
1118 callback = options.callback,
1119 // if the options do not specify allowNew, default it to true
1121 attributes = options.attributes;
1124 this.destroyWorkspace();
1127 container: this.$.contentPanel,
1133 workspace = this.createComponent(workspace);
1135 // Handle save and new button
1136 allowNew = _.isBoolean(options.allowNew) ?
1137 options.allowNew : !workspace.getHideSaveAndNew();
1138 this.setAllowNew(allowNew);
1140 headerAttrs = workspace.getHeaderAttrs() || [];
1142 // Set the button texts
1143 this.$.saveButton.setContent(workspace.getSaveText());
1144 this.$.backButton.setContent(workspace.getBackText());
1145 this.$.backPanelButton.setContent(workspace.getBackText());
1146 this.$.backPanelButton.setShowing(enyo.Panels.isScreenNarrow());
1148 // Hide buttons if applicable
1149 this.$.applyButton.setShowing(!workspace.getHideApply());
1150 this.$.refreshButton.setShowing(!workspace.getHideRefresh());
1152 // Add any extra action buttons to the toolbar
1153 _.each(this.$.workspace.actionButtons, function (action) {
1154 var actionIcon = {kind: "font.TextIcon",
1156 content: action.label || ("_" + action.name).loc(),
1158 method: action.method || action.name,
1159 isViewMethod: action.isViewMethod,
1160 ontap: "actionSelected"};
1161 this.$.contentToolbar.createComponent(
1162 actionIcon, {owner: this});
1164 this.$.contentToolbar.resized();
1167 if (id || id === false) {
1168 workspace.setSuccess(options.success);
1169 workspace.setRecordId(id);
1171 workspace.newRecord(attributes, options);
1175 // Build menu by finding all panels
1176 this.$.menu.setCount(0);
1177 for (prop in workspace.$) {
1178 if (workspace.$.hasOwnProperty(prop) &&
1179 workspace.$[prop] instanceof enyo.Panels) {
1180 menuItems = menuItems.concat(workspace.$[prop].getPanels());
1183 this.setMenuItems(menuItems);
1184 this.$.menu.setCount(menuItems.length);
1185 this.$.menu.render();
1187 // Mobile device view
1188 if (enyo.Panels.isScreenNarrow()) {
1193 Hides the spinner popup
1195 spinnerHide: function () {
1196 this._popupDone = true;
1197 this.$.spinnerPopup.hide();
1200 Show the modal spinner popup when the model
1203 spinnerShow: function (message) {
1204 message = message || "_loading".loc() + "...";
1205 this._popupDone = false;
1206 this.$.spinnerMessage.setContent(message);
1207 this.$.spinnerPopup.show();
1210 This function is called by the statusChange handler and
1211 controls button and spinner functions based on the changed
1212 status of the backing model of the workspace.
1214 statusChanged: function (inSender, inEvent) {
1215 var model = inEvent.model,
1217 status = inEvent.status,
1218 canCreate = model.getClass().canCreate(),
1219 canUpdate = model.canUpdate() || status === K.READY_NEW,
1220 isEditable = canUpdate && !model.isReadOnly(),
1221 canNotSave = !model.isDirty() || !isEditable,
1224 // Status dictates whether buttons are actionable
1225 this.$.saveAndNewButton.setShowing(canCreate && this.getAllowNew());
1226 this.$.saveAndNewButton.setContent("_new".loc());
1228 // XXX we really only have to do this if there's a *change* to the button content
1229 this.$.contentToolbar.render();
1231 this.$.applyButton.setDisabled(canNotSave);
1232 this.$.saveAndNewButton.setDisabled(!isEditable);
1233 this.$.saveButton.setDisabled(canNotSave);
1235 // Toggle spinner popup
1236 if (status & K.BUSY) {
1237 if (status === K.BUSY_COMMITTING) {
1238 message = "_saving".loc() + "...";
1240 this.spinnerShow(message);
1248 @todo Document titleChanged method.
1250 titleChanged: function (inSender, inEvent) {
1251 var title = inEvent.title || "";
1252 this.$.title.setContent(title);
1257 This function forces the menu to render and call
1258 its setup function for the List.
1260 menuChanged: function () {
1261 this.$.menu.render();