fixed errors
[xtuple] / lib / enyo-x / source / views / list_relations_editor_box.js
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 XM:true, XV:true, XT:true, _:true, enyo:true*/
5
6 (function () {
7
8   var _events = "change readOnlyChange statusChange refreshView";
9
10   XV.RelationsEditorMixin = enyo.mixin({
11     events: {
12       onNotify: ""
13     },
14     published: {
15       value: null
16     },
17     handlers: {
18       onValueChange: "controlValueChanged"
19     },
20     bind: function (action) {
21       if (this.value) {
22         this.value[action](_events, this.attributesChanged, this);
23         this.value[action]("notify", this.notify, this);
24         if (this.value.meta) {
25           this.value.meta[action]("change", this.attributesChanged, this);
26         }
27       }
28     },
29     destroy: function () {
30       this.bind("off");
31       this.value = null;
32       this.inherited(arguments);
33     },
34     setValue: function (value) {
35       this.bind("off");
36       this.value = value;
37       this.bind("on");
38       this.attributesChanged(value);
39       if (this.valueChanged) { this.valueChanged(value); }
40     }
41
42   }, XV.EditorMixin);
43
44   /**
45     @name XV.RelationsEditor
46     @class Use to define the editor for {@link XV.ListRelationsEditorBox}.
47     @extends XV.Groupbox
48   */
49   var editor = enyo.mixin({
50     name: "XV.RelationsEditor",
51     kind: "XV.Groupbox",
52   }, XV.RelationsEditorMixin);
53   enyo.kind(editor);
54
55   /**
56     @name XV.ListRelationsEditorBox
57     @class Provides a container in which its components
58     are a vertically stacked group of horizontal rows.<br />
59     Made up of a header, panels, and a row of navigation buttons.<br />
60     Must include a component called `list`.
61     List must be of subkind {@link XV.ListRelations}.
62     The `value` must be set to a collection of `XM.Model`.
63     @extends XV.Groupbox
64   */
65   enyo.kind(/** @lends XV.ListRelationsEditorBox# */{
66     name: "XV.ListRelationsEditorBox",
67     kind: "XV.Groupbox",
68     classes: "panel xv-relations-editor-box",
69     published: {
70       attr: null,
71       disabled: false,
72       value: null,
73       title: "",
74       parentKey: "",
75       listRelations: "",
76       editor: null,
77       summary: null,
78       childWorkspace: null,
79       fitButtons: true
80     },
81     events: {
82       onError: "",
83       onChildWorkspace: ""
84     },
85     handlers: {
86       onSelect: "selectionChanged",
87       onDeselect: "selectionChanged",
88       onTransitionFinish: "transitionFinished",
89       onValueChange: "controlValueChanged"
90     },
91     /**
92     @todo Document the attrChanged method.
93     */
94     attrChanged: function () {
95       this.$.list.setAttr(this.attr);
96     },
97     /**
98     @todo Document the controlValueChanged method.
99     */
100     controlValueChanged: function () {
101       // this is getting called before the list is created
102       //this.$.list.refresh();
103       return true;
104     },
105     /**
106     @todo Document the create method.
107     */
108     create: function () {
109       this.inherited(arguments);
110       var editor = this.getEditor(),
111         panels,
112         control;
113
114       // Header
115       this.createComponent({
116         kind: "onyx.GroupboxHeader",
117         content: this.getTitle()
118       });
119
120       // List
121       panels = {
122         kind: "Panels",
123         fit: true,
124         arrangerKind: "CollapsingArranger",
125         components: [
126           {kind: editor, name: "editor"},
127           {kind: this.getListRelations(), name: "list",
128             attr: this.getAttr()}
129         ]
130       };
131       control = this.createComponent(panels);
132       control.setIndex(1);
133
134       // Buttons
135       this.createComponent({
136         kind: "FittableColumns",
137         name: "navigationButtonPanel",
138         classes: "xv-buttons",
139         components: [
140           {kind: "onyx.Button", name: "newButton", onclick: "newItem",
141           classes: "icon icon-plus"},
142           {kind: "onyx.Button", name: "deleteButton", onclick: "deleteItem",
143             disabled: true, classes: "icon-minus"},
144           {kind: "onyx.Button", name: "prevButton", onclick: "prevItem",
145             disabled: true, classes: "icon-chevron-left"},
146           {kind: "onyx.Button", name: "nextButton", onclick: "nextItem",
147             disabled: true, classes: "icon-chevron-right"},
148           {kind: "onyx.Button", name: "doneButton", onclick: "doneItem",
149             disabled: true, classes: "icon-ok"},
150           {kind: "onyx.Button", name: "expandButton", ontap: "launchWorkspace",
151             classes: "icon-resize-full"}
152         ]
153       });
154       this.$.expandButton.setShowing(_.isString(this.getChildWorkspace()));
155     },
156
157     /**
158       Marks the model of the selected item to be deleted on save
159       and remove it from its parent collection and the Enyo list
160     */
161     deleteItem: function () {
162       var index = this.$.list.getFirstSelected(),
163         model = index ? this.$.list.getModel(index) : null;
164       this.$.list.getSelection().deselect(index, false);
165       model.destroy();
166       this.$.list.lengthChanged();
167     },
168     destroy: function () {
169       this.unbind();
170       this.getValue().off("add remove", this.valueChanged, this);
171       this.inherited(arguments);
172     },
173     /**
174       Disables or enables the view
175      */
176     disabledChanged: function () {
177       this.$.newButton.setDisabled(this.getDisabled());
178       // complicated logic we need to disable and enable the
179       // done and delete buttons is here:
180       this.selectionChanged();
181       this.$.expandButton.setDisabled(this.getDisabled());
182     },
183     /**
184       Close the edit session and return to read-only summary view
185     */
186     doneItem: function () {
187       var index = this.$.list.getFirstSelected(),
188         selection = this.$.list.getSelection();
189       if (this.validate() && index) {
190         selection.deselect(index);
191         if (this.$.list.$.page0 && !this.$.list.$.page0.hasNode()) {
192           XT.log("Warning: page0 doesn't hasNode");
193         } else if (this.$.list.$.page1 && !this.$.list.$.page1.hasNode()) {
194           XT.log("Warning: page1 doesn't hasNode");
195         }
196         this.$.list.render();
197       }
198     },
199     error: function (model, error) {
200       var inEvent = {
201         model: model,
202         error: error
203       };
204       this.doError(inEvent);
205     },
206     launchWorkspace: function (inSender, inEvent) {
207       var index = Number(this.$.list.getFirstSelected());
208       this.doChildWorkspace({
209         workspace: this.getChildWorkspace(),
210         collection: this.getValue(),
211         index: index,
212         listRelations: this.$.list
213       });
214       return true;
215     },
216     /**
217       Add a new model to the collection and bring up a blank editor to fill it in
218     */
219     newItem: function () {
220       var collection = this.$.list.getValue(),
221         Klass = collection.model,
222         model = new Klass(null, {isNew: true}),
223         components = this.$.editor.getComponents(),
224         scroller,
225         length,
226         first;
227       if (this.validate()) {
228         this.$.editor.clear();
229         collection.add(model);
230         if (collection.comparator) { collection.sort(); }
231
232         // Exclude models marked for deletion
233         length = _.filter(collection.models, function (model) {
234           return !model.isDestroyed();
235         }).length;
236         this.$.list.select(length - 1);
237
238         // Scroll to top and set focus on first available widget
239         scroller = _.find(components, function (c) {
240           return c instanceof enyo.Scroller;
241         });
242         if (scroller) { scroller.scrollToTop(); }
243         first = _.find(components, function (c) {
244           return c.attr && !model.isReadOnly(c.attr);
245         });
246         if (first && first.focus) {
247           first.focus();
248         }
249       }
250     },
251     /**
252       Move to edit the next item in the collection.
253     */
254     nextItem: function () {
255       var index = this.$.list.getFirstSelected() - 0;
256       if (this.validate()) {
257         this.$.list.select(index + 1);
258       }
259     },
260     /**
261       Move to edit the previous line in the collection.
262     */
263     prevItem: function () {
264       var index = this.$.list.getFirstSelected() - 0;
265       if (this.validate()) {
266         this.$.list.select(index - 1);
267       }
268     },
269     /**
270     @todo Document the selectionChanged method.
271     */
272     selectionChanged: function () {
273       var index = this.$.list.getFirstSelected(),
274         model = index ? this.$.list.getModel(index) : null,
275         K = XM.Model,
276         that = this;
277       this.unbind();
278       this.$.deleteButton.setDisabled(true);
279       this.$.doneButton.setDisabled(!index || this.getDisabled());
280       if (model) {
281         model.on("invalid", this.error, this); // Error event binding
282         this.$.editor.setValue(model);
283         if (model.isNew() ||
284           model.isBusy() && model._prevStatus === K.READY_NEW) {
285           this.$.deleteButton.setDisabled(this.getDisabled());
286         } else {
287           model.used({
288             success: function (resp) {
289               if (that.$.deleteButton) { // Sometimes the workspace has been closed
290                 that.$.deleteButton.setDisabled(resp || that.getDisabled());
291               }
292             }
293           });
294         }
295         if (this.$.panels.getIndex()) { this.$.panels.setIndex(0); }
296         this.$.prevButton.setDisabled(index - 0 === 0);
297         this.$.nextButton.setDisabled(index - 0 === this.$.list.value.length - 1);
298       } else {
299         if (!this.$.panels.getIndex()) { this.$.panels.setIndex(1); }
300         this.$.prevButton.setDisabled(true);
301         this.$.nextButton.setDisabled(true);
302       }
303     },
304     /**
305     @todo Document the transitionFinished method.
306     */
307     transitionFinished: function (inSender, inEvent) {
308       if (inEvent.originator.name === 'panels') {
309         if (this.$.panels.getIndex() === 1) {
310           this.doneItem();
311         }
312         return true;
313       }
314     },
315     /**
316       Remove current model bindings.
317     */
318     unbind: function () {
319       var model = this.$.editor.getValue();
320       if (model) {
321         model.off("invalid", this.error, this);
322       }
323     },
324     /**
325       Returns whether a selected model is validate. If
326       none selected returns `true`. If an error is found
327       an error event is raised.
328
329       @returns {Boolean}
330     */
331     validate: function () {
332       var list = this.$.list,
333         index = list.getFirstSelected() - 0,
334         model = isNaN(index) ? false : list.getModel(index),
335         error = model ? model.validate(model.attributes) : false;
336       if (error) {
337         this.error(model, error);
338         return false;
339       }
340       return true;
341     },
342     /**
343     @todo Document the valueChanged method.
344     */
345     valueChanged: function () {
346       var value = this.getValue();
347       // Make sure list refreshes if collection changed
348       if (value) {
349         value.once("add remove", this.valueChanged, this);
350       }
351       this.$.list.setValue(value);
352     }
353   });
354
355 }());