Fix jshint errors.
[xtuple] / lib / enyo-x / source / core.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 XV:true, XM:true, _:true, onyx:true, enyo:true, document:true, XT:true, Globalize:true */
5
6 (function () {
7
8   /**
9     XV is the global namespace for all the "xTuple Views" defined in
10     enyo-x and elsewhere
11
12     @namespace XV
13    */
14   XV = {};
15   XV._modelCaches = {};
16   XV._modelLists = {};
17   XV._modelWorkspaces = {};
18
19   // Class methods
20   enyo.mixin(XV, /** @lends XV */{
21
22     KEY_UP: 38,
23     KEY_DOWN: 40,
24     KEY_TAB: 9,
25     KEY_ENTER: 13,
26
27     /**
28       Key/value mapping of widget class names that correspond with object definitions
29       to implement a corresponding editor widget.
30     */
31     widgetTypeMap: {
32       Cost: "XV.Cost",
33       Date: "XV.DateWidget",
34       DueDate: "XV.DateWidget",
35       ExtendedPrice: {
36         kind: "XV.MoneyWidget",
37         scale: XT.EXTENDED_PRICE_SCALE
38       },
39       Money: {
40         kind: "XV.MoneyWidget",
41         scale: XT.MONEY_SCALE
42       },
43       Number: "XV.NumberWidget",
44       PurchasePrice: {
45         kind: "XV.MoneyWidget",
46         scale: XT.PURCHASE_PRICE_SCALE
47       },
48       Quantity: "XV.QuantityWidget",
49       QuantityPer: "XV.QuantityPerWidget",
50       SalesPrice: {
51         kind: "XV.MoneyWidget",
52         scale: XT.SALES_PRICE_SCALE
53       },
54       String: "XV.InputWidget",
55       Unit: "XV.UnitPicker",
56       UnitRatio: "XV.UnitRatioWidget",
57       UserAccountRelation: "XV.UserAccountWidget",
58       Weight: "XV.WeightWidget"
59     },
60
61     /**
62       Accepts a model and an attribute and returns a standard widget definition
63       mapped to the attribute.
64
65       *Warning* This implementation is incomplete. Widgets that reference object
66       based attributes are not handled well and need to be refactored.
67
68       @param {String} Model class name
69       @param {String} Attribute name
70     */
71     getEditor: function (model, attr) {
72       var Klass = XT.getObjectByName(model),
73         type = Klass.getType(attr),
74         widget = this.widgetTypeMap[type];
75
76       // Handle normal widgets
77       if (_.isString(widget)) {
78         widget = {
79           kind: widget,
80           attr: attr
81         };
82
83       // Handle widgets with complex attributes
84       } else if (widget.kind === "XV.MoneyWidget") {
85         widget.localValue = attr;
86       }
87
88       return widget;
89     },
90
91     /**
92       Add component or array of component view(s) to a view class that
93       has implemented the `extensionsMixin`.
94
95       Examples of classes that support extensions are:
96         * `Workspace`
97         * `ParameterWidget`
98
99       @param {String} Class name
100       @param {Object|Array} Component(s)
101     */
102     appendExtension: function (workspace, extension) {
103       var Klass = XT.getObjectByName(workspace),
104         extensions = Klass.prototype.extensions || [];
105       if (!_.isArray(extension)) {
106         extension = [extension];
107       }
108       Klass.prototype.extensions = extensions.concat(extension);
109     },
110
111     /**
112       Helper function for enyo unit testing
113
114       @param expected
115       @param actual
116       @param {String} message
117          Only displayed in the case of a failed test
118       @return {String} Per enyo's conventions, the empty string means the test is passed.
119      */
120     applyTest: function (expected, actual, message) {
121       if (expected === actual) {
122         return "";
123       } else {
124         if (message) {
125           message = ". " + message;
126         } else {
127           message = ".";
128         }
129         return "Expected " + expected + ", saw " + actual + message;
130       }
131     },
132
133     /**
134       The javascript download method avoids target = "_blank" and the need to
135       reload the app to see the new client. Pop up blockers should not be an issue.
136       @See: http://stackoverflow.com/questions/3749231/download-file-using-javascript-jquery/3749395#3749395
137
138       @param {String} The URL for the download route.
139     */
140     downloadURL: function (url) {
141       var hiddenIFrameID = 'hiddenDownloader',
142         iframe = document.getElementById(hiddenIFrameID);
143
144       if (iframe === null) {
145         iframe = document.createElement('iframe');
146         iframe.id = hiddenIFrameID;
147         iframe.style.display = 'none';
148         document.body.appendChild(iframe);
149       }
150
151       iframe.src = url;
152     },
153
154     getCache: function (recordType) {
155       return XV._modelCaches[recordType];
156     },
157
158     getList: function (recordType) {
159       return XV._modelLists[recordType];
160     },
161
162     getWorkspace: function (recordType) {
163       return XV._modelWorkspaces[recordType];
164     },
165
166     /*
167       Is the ancestor a superkind (or supersuperkind, etc.) of the object?
168
169       @param {Object} intantiated enyo kind
170         You can use Kind.prototype if that's what you have to work with.
171       @param {String} ancestor kind name
172     */
173     inheritsFrom: function (object, ancestor) {
174       if (!object || !object.ctor) {
175         return false;
176       }
177       while (object.kindName !== 'enyo.Object') {
178         if (object.ctor.prototype.base.prototype.kindName === ancestor) {
179           return true;
180         }
181         object = object.ctor.prototype.base.prototype;
182       }
183     },
184
185     registerModelCache: function (recordType, cache) {
186       XV._modelCaches[recordType] = cache;
187     },
188
189     registerModelList: function (recordType, list) {
190       XV._modelLists[recordType] = list;
191     },
192
193     registerModelWorkspace: function (recordType, workspace) {
194       XV._modelWorkspaces[recordType] = workspace;
195     }
196
197   });
198
199   /**
200     @class
201
202     A mixin that allows the components of a class to be extended.
203   */
204   XV.ExtensionsMixin = {
205     extensions: null,
206
207     /**
208       This function should be run in the create function of a class
209       using this mixin. It will add any extensions to the class at run time.
210       @parameter {Boolean} forceDeferred allows us to set a defer attribute
211         on the extension so that it will not get processed during the usual
212         processExtensions calls in the create. If you need to put an extension
213         in a subkind of workspace requires the subkind's create() function
214         to have already run, put a processExtensions(true) at the end
215         of that subkind create() function.
216     */
217     processExtensions: function (forceDeferred) {
218       var extensions = this.extensions || [],
219         ext,
220         containerString,
221         container,
222         i;
223
224       if (this._extLength === undefined) {
225         this._extLength = 0;
226       }
227       if (this._extLength === extensions.length) { return; }
228       for (i = 0; i < extensions.length; i++) {
229         ext = _.clone(this.extensions[i]);
230
231         if (ext.defer !== forceDeferred) {
232           // the workspace is not ready to add this extension yet,
233           // or, it's probably been added already
234           continue;
235         }
236         // Resolve name of container to the instance
237         if (_.isString(ext.container)) {
238           containerString = ext.container;
239           container = this;
240           while (containerString.indexOf(".") >= 0) {
241             container = container.$[containerString.substring(0, containerString.indexOf("."))];
242             containerString = containerString.substring(containerString.indexOf(".") + 1);
243           }
244           if (container) {
245             // avoid a crash if the asked-for container doesn't exist
246             ext.container = container.$[containerString];
247           } else {
248             XT.log("Requested container", ext.container, "not found");
249             return;
250           }
251         }
252         // Resolve `addBefore`
253         if (_.isString(ext.addBefore)) {
254           ext.addBefore = this.$[ext.addBefore];
255         }
256         this.createComponent(ext);
257         this._extLength++;
258       }
259     }
260   };
261
262   /**
263     @class
264
265     A mixin with functions used for formatting display data.
266   */
267   XV.FormattingMixin = /** @lends XV.FormattingMixin# */{
268
269     /**
270       An array of data types that require special formatting in displays
271     */
272     formatted: ["Date", "DueDate", "Cost", "ExtendedPrice", "Hours",
273       "Money", "Percent", "PurchasePrice", "Quantity", "SalesPrice",
274       "UnitRatio", "Weight", "Boolean", "EffectiveDate", "ExpireDate",
275       "AddressInfo"
276     ],
277
278     formatAddressInfo: function (value, view, model) {
279       return XM.Address.formatShort(value);
280     },
281
282     /**
283       Localize a boolean to yes/no text.
284
285       @param {Number} Value
286       @returns {String}
287     */
288     formatBoolean: function (value) {
289       return value ? "_yes".loc() : "_no".loc();
290     },
291
292     /**
293       Localize a number to cost string in the base currency.
294
295       @param {Number} Value
296       @returns {String}
297     */
298     formatCost: function (value) {
299       return Globalize.format(value, "c" + XT.locale.costScale);
300     },
301
302     /**
303       Localize a date.
304
305       @param {Date} Date
306       @returns {String}
307     */
308     formatDate: function (value) {
309       var date = _.isDate(value) ? XT.date.applyTimezoneOffset(value, true) : false;
310       return date ? Globalize.format(date, "d") : "";
311     },
312
313     /**
314       Localize a date and add the class for `error` to the view if the date is before today.
315
316       @param {Date} Date
317       @param {Object} View
318       @param {Object} Model
319       @returns {String}
320     */
321     formatDueDate: function (value, view, model) {
322       var today = XT.date.today(),
323         date = _.isDate(value) ? XT.date.applyTimezoneOffset(value, true) : false,
324         isLate = date ? (model.getValue('isActive') && XT.date.compareDate(value, today) < 1) : false;
325       view.addRemoveClass("error", isLate);
326       return date ? Globalize.format(date, "d") : "";
327     },
328
329     /*
330       Dates greater than today are highlight as errors. Start of time dates return "Always,"
331
332       @param {Date} Date
333       @param {Object} View
334       @returns {String}
335     */
336     formatEffectiveDate: function (value, view) {
337       var date = XT.date.applyTimezoneOffset(value, true),
338         isFuture = (XT.date.compareDate(date, XT.date.today()) === 1);
339       view.addRemoveClass("error", isFuture);
340       if (value.valueOf() === XT.date.startOfTime().valueOf()) {
341         return "_always".loc();
342       }
343       return Globalize.format(date, "d");
344     },
345
346     /*
347       Dates greater than today are highlight as errors. End of time dates return "Never,"
348
349       @param {Date} Date
350       @param {Object} View
351       @returns {String}
352     */
353     formatExpireDate: function (value, view) {
354       var date = XT.date.applyTimezoneOffset(value, true),
355         isExpired = (XT.date.compareDate(date, XT.date.today()) < 1);
356       view.addRemoveClass("error", isExpired);
357       if (value.valueOf() === XT.date.endOfTime().valueOf()) {
358         return "_never".loc();
359       }
360       return Globalize.format(date, "d");
361     },
362
363     /**
364       Localize a number to an extended price string in the base currency.
365
366       @param {Number} Value
367       @returns {String}
368     */
369     formatExtendedPrice: function (value) {
370       return Globalize.format(value, "c" + XT.locale.extendedPriceScale);
371     },
372
373     /**
374       Localize a number to an hours string in the base currency.
375
376       @param {Number} Value
377       @returns {String}
378     */
379     formatHours: function (value, view) {
380       view.addRemoveClass("error", value < 0);
381       return Globalize.format(value, "n" + XT.locale.hoursScale);
382     },
383
384     /**
385       Localize a number to a currency string using the base currency.
386
387       @param {Number} Value
388       @returns {String}
389     */
390     formatMoney: function (value, view) {
391       view.addRemoveClass("error", value < 0);
392       return Globalize.format(value, "c" + XT.locale.currencyScale);
393     },
394
395     /**
396       Localize a number to a percent string.
397
398       @param {Number} Value
399       @returns {String}
400     */
401     formatPercent: function (value) {
402       return Globalize.format(value, "p" + XT.locale.percentScale);
403     },
404
405     /**
406     Localize a number to a purchase price string in the base currency.
407
408       @param {Number} Value
409       @returns {String}
410     */
411     formatPurchasePrice: function (value) {
412       return Globalize.format(value, "c" + XT.locale.purchasePriceScale);
413     },
414
415     /**
416       Localize a number to a quantity string.
417
418       @param {Number} Value
419       @returns {String}
420     */
421     formatQuantity: function (value, view) {
422       view.addRemoveClass("error", value < 0);
423       return Globalize.format(value, "n" + XT.locale.quantityScale);
424     },
425
426     /**
427       Localize a number to a quantity string.
428
429       @param {Number} Value
430       @returns {String}
431     */
432     formatQuantityPer: function (value) {
433       return Globalize.format(value, "n" + XT.locale.quantityPerScale);
434     },
435
436     /**
437       Localize a number to an sales price string in the base currency.
438
439       @param {Number} Value
440       @returns {String}
441     */
442     formatSalesPrice: function (value) {
443       return Globalize.format(value, "c" + XT.locale.salesPriceScale);
444     },
445
446     /**
447       Localize a number to a unit ratio string.
448
449       @param {Number} Value
450       @returns {String}
451     */
452     formatUnitRatio: function (value) {
453       return Globalize.format(value, "n" + XT.locale.unitRatioScale);
454     },
455
456     /**
457       Localize a number to a weight string.
458
459       @param {Number} Value
460       @returns {String}
461     */
462     formatWeight: function (value) {
463       return Globalize.format(value, "n" + XT.locale.weightScale);
464     }
465
466   };
467
468 }());