fixed most widgets, grid still broken
[xtuple] / lib / enyo-x / source / widgets / characteristics.js
1 /*jshint indent:2, curly:true, eqeqeq:true, immed:true, latedef:true,
2 newcap:true, noarg:true, regexp:true, undef:true, trailing:true,
3 white:true*/
4 /*global enyo:true, XT:true, XM:true, XV:true, _:true */
5
6 (function () {
7
8   var TEXT = 0;
9   var LIST = 1;
10   var DATE = 2;
11
12   /**
13     @name XV.CharacteristicPicker
14     @class For the {@link XV.CharacteristicsWidget} displays the list of characteristics
15     that the user can select from to classify a {@link XV.CharacteristicItem}.
16     @extends XV.PickerWidget
17    */
18   enyo.kind(
19     /** @lends XV.CharacteristicPicker# */{
20     name: "XV.CharacteristicPicker",
21     kind: "XV.PickerWidget",
22     collection: "XM.characteristics",
23     noneText: "_delete".loc(),
24     noneClasses: "xv-negative",
25     orderBy: [
26       {attribute: 'order'},
27       {attribute: 'name'}
28     ]
29   });
30
31   /**
32     @name XV.OptionsPicker
33     @class Displays a list of items that can be selected.
34    */
35   enyo.kind(/** @lends XV.OptionsPicker# */{
36     name: "XV.OptionsPicker",
37     published: {
38       attr: null,
39       collection: null,
40       value: null,
41       disabled: false
42     },
43     events: {
44       onValueChange: ""
45     },
46     handlers: {
47       onSelect: "itemSelected"
48     },
49     components: [
50       {kind: "onyx.InputDecorator", classes: "xv-input-decorator",
51         components: [
52         {kind: "onyx.PickerDecorator",
53           components: [
54           {classes: "xv-picker"},
55           {name: "picker", kind: "onyx.Picker"}
56         ]}
57       ]}
58     ],
59     disabledChanged: function () {
60       // XXX Implementation appears slightly crazy because I can't find a disable
61       // setter for onyx.InputDecorator:
62       // http://enyojs.com/api/#onyx.InputDecorator::disabledChange
63       //
64       this.$.inputDecorator.disabled = this.disabled;
65       this.$.inputDecorator.bubble("onDisabledChange");
66     },
67     /**
68      @todo Document the itemSelected method.
69      */
70     itemSelected: function (inSender, inEvent) {
71       var value = this.$.picker.getSelected().content;
72       this.setValue(value);
73       return true;
74     },
75     /**
76      @todo Document the setCollection method.
77      */
78     setCollection: function (collection) {
79       var model,
80         value,
81         i,
82         c;
83       this.collection = collection;
84       collection.comparator = this.sort;
85       collection.sort();
86       this.$.picker.destroyClientControls();
87       for (i = 0; i < collection.length; i++) {
88         model = collection.at(i);
89         value = model.get('value');
90         c = this.$.picker.createComponent({ content: value });
91         // Autormatically select first
92         if (i === 0) { this.$.picker.setSelected(c); }
93       }
94       this.render();
95     },
96     /**
97      @todo Document the setValue method.
98      */
99     setValue: function (value, options) {
100       options = options || {};
101       var oldValue = this.getValue(),
102         inEvent,
103         components = this.$.picker.getComponents(),
104         component = _.find(components, function (c) {
105           if (c.kind === "onyx.MenuItem") {
106             return c.content === value;
107           }
108         });
109       if (!component) { value = null; }
110       this.$.picker.setSelected(component);
111
112       if (value !== oldValue) {
113         this.value = value;
114         if (!options.silent) {
115           inEvent = { originator: this, value: value };
116           this.doValueChange(inEvent);
117         }
118       }
119       return value;
120     },
121     /**
122      @todo Document the sort method.
123      */
124     sort: function (a, b) {
125       var aord = a.get('order'),
126         bord = b.get('order'),
127         aname,
128         bname;
129       if (aord === bord) {
130         aname = a.get('value');
131         bname = b.get('value');
132         return aname < bname ? -1 : 1;
133       }
134       return aord < bord ? -1 : 1;
135     }
136   });
137
138   /**
139     @name XV.CharacteristicItem
140     @class Contains a set of fittable columns which make up a single row in a list of characteristics
141       for the {@link XV.CharacteristicsWidget}.<br />
142     Components: {@link XV.CharacteristicPicker}, {@link XV.InputWidget},
143       {@link XV.DateWidget}, {@link XV.OptionsPicker}.<br />
144     Derived from <a href="http://enyojs.com/api/#enyo.FittableColumns">enyo.FittableColumns</a>.
145     @extends enyo.FittableColumns
146    */
147   enyo.kind(/** @lends XV.CharacteristicItem# */{
148     name: "XV.CharacteristicItem",
149     kind: "enyo.Control",
150     classes: "xv-characteristic-item",
151     published: {
152       value: null,
153       disabled: false
154     },
155     handlers: {
156       onValueChange: "controlValueChanged"
157     },
158     components: [
159       {controlClasses: 'enyo-inline', components: [
160         {kind: "XV.CharacteristicPicker", attr: "characteristic",
161           showLabel: false},
162         {kind: "XV.InputWidget", attr: "value", showLabel: false},
163         {kind: "XV.DateWidget", attr: "value", showLabel: false,
164           showing: false},
165         {kind: "XV.OptionsPicker", attr: "value", showLabel: false,
166           showing: false}
167       ]}
168     ],
169     disabledChanged: function (oldValue) {
170       this.$.characteristicPicker.setDisabled(this.disabled);
171       this.$.inputWidget.setDisabled(this.disabled);
172       this.$.dateWidget.setDisabled(this.disabled);
173       this.$.optionsPicker.setDisabled(this.disabled);
174     },
175     /**
176      @todo Document the controlValueChanged method.
177      */
178     controlValueChanged: function (inSender, inEvent) {
179       var attr = inSender.getAttr(),
180         value = inSender.getValue(),
181         attributes = {},
182         model = this.getValue(),
183         characteristic,
184         defaultVal;
185       attributes[attr] = _.isDate(value) ? value.toJSON() : value;
186       model.set(attributes);
187       if (attr === 'characteristic') {
188         if (value) {
189           characteristic = model.get('characteristic');
190           switch (characteristic.get('characteristicType'))
191           {
192           case DATE:
193             defaultVal = null;
194             break;
195           case TEXT:
196             defaultVal = "";
197             break;
198           case LIST:
199             defaultVal = characteristic.get('options').models[0].get('value');
200           }
201           model.set('value', defaultVal);
202           this.valueChanged();
203         } else {
204           model.destroy();
205         }
206       }
207       return true;
208     },
209     /**
210      @todo Document the getCharacteristicPicker method.
211      */
212     getCharacteristicPicker: function () {
213       return this.$.characteristicPicker;
214     },
215     /**
216      @todo Document the valueChanged method.
217      */
218     valueChanged: function () {
219       var model = this.getValue(),
220         characteristic = model.get('characteristic'),
221         type = characteristic ?
222           characteristic.get('characteristicType') : TEXT,
223         value = model.get('value'),
224         valueWidget,
225         options;
226       switch (type)
227       {
228       case TEXT:
229         this.$.dateWidget.hide();
230         this.$.optionsPicker.hide();
231         this.$.inputWidget.show();
232         valueWidget = this.$.inputWidget;
233         break;
234       case DATE:
235         this.$.optionsPicker.hide();
236         this.$.inputWidget.hide();
237         this.$.dateWidget.show();
238         valueWidget = this.$.dateWidget;
239         break;
240       case LIST:
241         this.$.dateWidget.hide();
242         this.$.inputWidget.hide();
243         this.$.optionsPicker.show();
244         valueWidget = this.$.optionsPicker;
245         options = characteristic.get('options');
246         this.$.optionsPicker.setCollection(options);
247       }
248       this.$.characteristicPicker.setValue(characteristic, {silent: true});
249       valueWidget.setValue(value, {silent: true});
250     }
251   });
252
253   /**
254     @name XV.CharacteristicsWidget
255     @class Use to implement a box for entering and viewing characteristics.
256     Made up of a header, a repeater (the control for making the list of characteristic items),
257       and fittable columns for the navigation buttons.<br />
258     Components: {@link XV.CharacteristicItem}.
259    */
260   enyo.kind(/** @lends XV.CharacteristicsWidget# */{
261     name: "XV.CharacteristicsWidget",
262     classes: "xv-characteristics-widget xv-input",
263     published: {
264       attr: null,
265       model: null,
266       value: null,
267       // note: which is now being kept track of in the model, and maybe should
268       // only be kept track of in the model.
269       which: null,
270       disabled: false,
271       showLabel: false,
272     },
273     components: [
274       {kind: "onyx.GroupboxHeader", content: "_characteristics".loc()},
275       {kind: "Repeater", count: 0, onSetupItem: "setupItem", components: [
276         {kind: "XV.CharacteristicItem"}
277       ]},
278       {controlClasses: 'enyo-inline', classes: "xv-buttons", components: [
279         {name: "label", classes: "xv-label"},
280         {kind: "onyx.Button", name: "newButton",
281           classes: "icon-plus xv-characteristic-button", onclick: "newItem"}
282       ]}
283     ],
284     create: function () {
285       this.inherited(arguments);
286       this.showLabelChanged();
287     },
288     disabledChanged: function () {
289       this.$.newButton.setDisabled(this.disabled);
290     },
291     /**
292       Remove bindings
293      */
294     destroy: function () {
295       if (this.value) {
296         this.value.off('add', this.lengthChanged, this);
297         this.value.off('statusChange', this.lengthChanged, this);
298       }
299       this.inherited(arguments);
300     },
301     /**
302      Kick off the repeater.
303      */
304     lengthChanged: function () {
305       this.$.repeater.setCount(this.readyModels().length);
306     },
307     /**
308      Add a new model to the collection.
309      */
310     newItem: function () {
311       var Klass = XT.getObjectByName(this.getModel()),
312         model = new Klass(null, { isNew: true });
313       this.value.add(model);
314     },
315     /**
316       Returns an array of models in the collection whose status is ready.
317
318       @return {Array} models
319      */
320     readyModels: function () {
321       return _.filter(this.value.models, function (model) {
322         var status = model.getStatus(),
323           K = XM.Model;
324         // Avoiding bitwise because performance matters here
325         return (status === K.READY_CLEAN ||
326                 status === K.READY_DIRTY ||
327                 status === K.READY_NEW);
328       });
329     },
330     /**
331       Render the repeaterlist.
332      */
333     setupItem: function (inSender, inEvent) {
334       var item = inEvent.item.$.characteristicItem,
335         model = this.readyModels()[inEvent.index],
336         which = this.getWhich(),
337         picker = item.getCharacteristicPicker(),
338         filter = function (models) {
339           return _.filter(models, function (m) {
340             return m.get(which);
341           });
342         };
343       item.setValue(model);
344       item.setDisabled(this.disabled);
345       if (picker) {
346         // quote characteristics, notably, have no picker
347         picker.filter = filter;
348         picker.buildList();
349         if (!model.get('characteristic')) {
350           picker.select(1);
351         }
352       }
353       return true;
354     },
355     /**
356       Sort by status, then order, then name
357      */
358     sort: function (a, b) {
359       var astatus = a.isNew(),
360         bstatus = b.isNew(),
361         achar = a.get('characteristic'),
362         bchar = b.get('characteristic'),
363         aord = achar ? achar.get('order') : null,
364         bord = bchar ? bchar.get('order') : null,
365         aname,
366         bname;
367       if (astatus === bstatus) {
368         if (aord === bord) {
369           aname = achar ? achar.get('name') : null;
370           bname = bchar ? bchar.get('name') : null;
371           return aname < bname ? -1 : 1;
372         }
373         return aord < bord ? -1 : 1;
374       }
375       return astatus < bstatus ? -1 : 1;
376     },
377     /**
378       @param {XM.Collection} value
379      */
380     setValue: function (value) {
381       if (this.value) {
382         this.value.off('add', this.lengthChanged, this);
383         this.value.off('statusChange', this.lengthChanged, this);
384       }
385       this.value = value;
386       this.value.on('add', this.lengthChanged, this);
387       this.value.on('statusChange', this.lengthChanged, this);
388       this.value.comparator = this.sort;
389       this.value.sort();
390       this.lengthChanged();
391     },
392
393     /**
394       Sets visibility of the widget label.
395     */
396     showLabelChanged: function () {
397       this.$.label.setShowing(this.getShowLabel());
398     }
399   });
400
401 }());