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 */
9 XV is the global namespace for all the "xTuple Views" defined in
17 XV._modelWorkspaces = {};
20 enyo.mixin(XV, /** @lends XV */{
28 Key/value mapping of widget class names that correspond with object definitions
29 to implement a corresponding editor widget.
33 Date: "XV.DateWidget",
34 DueDate: "XV.DateWidget",
36 kind: "XV.MoneyWidget",
37 scale: XT.EXTENDED_PRICE_SCALE
40 kind: "XV.MoneyWidget",
43 Number: "XV.NumberWidget",
45 kind: "XV.MoneyWidget",
46 scale: XT.PURCHASE_PRICE_SCALE
48 Quantity: "XV.QuantityWidget",
49 QuantityPer: "XV.QuantityPerWidget",
51 kind: "XV.MoneyWidget",
52 scale: XT.SALES_PRICE_SCALE
54 String: "XV.InputWidget",
55 Unit: "XV.UnitPicker",
56 UnitRatio: "XV.UnitRatioWidget",
57 UserAccountRelation: "XV.UserAccountWidget",
58 Weight: "XV.WeightWidget"
62 Accepts a model and an attribute and returns a standard widget definition
63 mapped to the attribute.
65 *Warning* This implementation is incomplete. Widgets that reference object
66 based attributes are not handled well and need to be refactored.
68 @param {String} Model class name
69 @param {String} Attribute name
71 getEditor: function (model, attr) {
72 var Klass = XT.getObjectByName(model),
73 type = Klass.getType(attr),
74 widget = this.widgetTypeMap[type];
76 // Handle normal widgets
77 if (_.isString(widget)) {
83 // Handle widgets with complex attributes
84 } else if (widget.kind === "XV.MoneyWidget") {
85 widget.localValue = attr;
92 Add component or array of component view(s) to a view class that
93 has implemented the `ExtensionsMixin`.
95 Examples of classes that support extensions are:
100 @param {String} Class name
101 @param {Object|Array} Component(s)
103 appendExtension: function (workspace, extension) {
104 var Klass = XT.getObjectByName(workspace),
105 extensions = Klass.prototype.extensions || [];
106 if (!_.isArray(extension)) {
107 extension = [extension];
109 Klass.prototype.extensions = extensions.concat(extension);
113 Helper function for enyo unit testing
117 @param {String} message
118 Only displayed in the case of a failed test
119 @return {String} Per enyo's conventions, the empty string means the test is passed.
121 applyTest: function (expected, actual, message) {
122 if (expected === actual) {
126 message = ". " + message;
130 return "Expected " + expected + ", saw " + actual + message;
135 The javascript download method avoids target = "_blank" and the need to
136 reload the app to see the new client. Pop up blockers should not be an issue.
137 @See: http://stackoverflow.com/questions/3749231/download-file-using-javascript-jquery/3749395#3749395
139 @param {String} The URL for the download route.
141 downloadURL: function (url) {
142 var hiddenIFrameID = 'hiddenDownloader',
143 iframe = document.getElementById(hiddenIFrameID);
145 if (iframe === null) {
146 iframe = document.createElement('iframe');
147 iframe.id = hiddenIFrameID;
148 iframe.style.display = 'none';
149 document.body.appendChild(iframe);
155 getCache: function (recordType) {
156 return XV._modelCaches[recordType];
159 getList: function (recordType) {
160 return XV._modelLists[recordType];
163 getWorkspace: function (recordType) {
164 return XV._modelWorkspaces[recordType];
168 Is the ancestor a superkind (or supersuperkind, etc.) of the object?
170 @param {Object} intantiated enyo kind
171 You can use Kind.prototype if that's what you have to work with.
172 @param {String} ancestor kind name
174 inheritsFrom: function (object, ancestor) {
175 if (!object || !object.ctor) {
178 while (object.kindName !== 'enyo.Object') {
179 if (object.ctor.prototype.base.prototype.kindName === ancestor) {
182 object = object.ctor.prototype.base.prototype;
186 registerModelCache: function (recordType, cache) {
187 XV._modelCaches[recordType] = cache;
190 registerModelList: function (recordType, list) {
191 XV._modelLists[recordType] = list;
194 registerModelWorkspace: function (recordType, workspace) {
195 XV._modelWorkspaces[recordType] = workspace;
203 A mixin that allows the components of a class to be extended.
205 XV.ExtensionsMixin = {
209 This function should be run in the create function of a class
210 using this mixin. It will add any extensions to the class at run time.
211 @parameter {Boolean} forceDeferred allows us to set a defer attribute
212 on the extension so that it will not get processed during the usual
213 processExtensions calls in the create. If you need to put an extension
214 in a subkind of workspace requires the subkind's create() function
215 to have already run, put a processExtensions(true) at the end
216 of that subkind create() function.
218 processExtensions: function (forceDeferred) {
219 var extensions = this.extensions || [],
225 if (this._extLength === undefined) {
228 if (this._extLength === extensions.length) { return; }
229 for (i = 0; i < extensions.length; i++) {
230 ext = _.clone(this.extensions[i]);
232 if (ext.defer !== forceDeferred) {
233 // the workspace is not ready to add this extension yet,
234 // or, it's probably been added already
237 // Resolve name of container to the instance
238 if (_.isString(ext.container)) {
239 containerString = ext.container;
241 while (containerString.indexOf(".") >= 0) {
242 container = container.$[containerString.substring(0, containerString.indexOf("."))];
243 containerString = containerString.substring(containerString.indexOf(".") + 1);
246 // avoid a crash if the asked-for container doesn't exist
247 ext.container = container.$[containerString];
249 XT.log("Requested container", ext.container, "not found");
253 // Resolve `addBefore`
254 if (_.isString(ext.addBefore)) {
255 ext.addBefore = this.$[ext.addBefore];
257 this.createComponent(ext);
266 A mixin with functions used for formatting display data.
268 XV.FormattingMixin = /** @lends XV.FormattingMixin# */{
271 An array of data types that require special formatting in displays
273 formatted: ["Date", "DueDate", "Cost", "ExtendedPrice", "Hours",
274 "Money", "Percent", "PurchasePrice", "Quantity", "SalesPrice",
275 "UnitRatio", "Weight", "Boolean", "EffectiveDate", "ExpireDate",
279 formatAddressInfo: function (value, view, model) {
280 return XM.Address.formatShort(value);
284 Localize a boolean to yes/no text.
286 @param {Number} Value
289 formatBoolean: function (value) {
290 return value ? "_yes".loc() : "_no".loc();
294 Localize a number to cost string in the base currency.
296 @param {Number} Value
299 formatCost: function (value) {
300 return Globalize.format(value, "c" + XT.locale.costScale);
309 formatDate: function (value) {
310 var date = _.isDate(value) ? XT.date.applyTimezoneOffset(value, true) : false;
311 return date ? Globalize.format(date, "d") : "";
315 Localize a date and add the class for `error` to the view if the date is before today.
319 @param {Object} Model
322 formatDueDate: function (value, view, model) {
323 var today = XT.date.today(),
324 date = _.isDate(value) ? XT.date.applyTimezoneOffset(value, true) : false,
325 isLate = date ? (model.getValue('isActive') && XT.date.compareDate(value, today) < 1) : false;
326 view.addRemoveClass("error", isLate);
327 return date ? Globalize.format(date, "d") : "";
331 Dates greater than today are highlight as errors. Start of time dates return "Always,"
337 formatEffectiveDate: function (value, view) {
338 var date = XT.date.applyTimezoneOffset(value, true),
339 isFuture = (XT.date.compareDate(date, XT.date.today()) === 1);
340 view.addRemoveClass("error", isFuture);
341 if (value.valueOf() === XT.date.startOfTime().valueOf()) {
342 return "_always".loc();
344 return Globalize.format(date, "d");
348 Dates greater than today are highlight as errors. End of time dates return "Never,"
354 formatExpireDate: function (value, view) {
355 var date = XT.date.applyTimezoneOffset(value, true),
356 isExpired = (XT.date.compareDate(date, XT.date.today()) < 1);
357 view.addRemoveClass("error", isExpired);
358 if (value.valueOf() === XT.date.endOfTime().valueOf()) {
359 return "_never".loc();
361 return Globalize.format(date, "d");
365 Localize a number to an extended price string in the base currency.
367 @param {Number} Value
370 formatExtendedPrice: function (value) {
371 return Globalize.format(value, "c" + XT.locale.extendedPriceScale);
375 Localize a number to an hours string in the base currency.
377 @param {Number} Value
380 formatHours: function (value, view) {
381 view.addRemoveClass("error", value < 0);
382 return Globalize.format(value, "n" + XT.locale.hoursScale);
386 Localize a number to a currency string using the base currency.
388 @param {Number} Value
391 formatMoney: function (value, view) {
392 view.addRemoveClass("error", value < 0);
393 return Globalize.format(value, "c" + XT.locale.currencyScale);
397 Localize a number to a percent string.
399 @param {Number} Value
402 formatPercent: function (value) {
403 return Globalize.format(value, "p" + XT.locale.percentScale);
407 Localize a number to a purchase price string in the base currency.
409 @param {Number} Value
412 formatPurchasePrice: function (value) {
413 return Globalize.format(value, "c" + XT.locale.purchasePriceScale);
417 Localize a number to a quantity string.
419 @param {Number} Value
422 formatQuantity: function (value, view) {
423 view.addRemoveClass("error", value < 0);
424 return Globalize.format(value, "n" + XT.locale.quantityScale);
428 Localize a number to a quantity string.
430 @param {Number} Value
433 formatQuantityPer: function (value) {
434 return Globalize.format(value, "n" + XT.locale.quantityPerScale);
438 Localize a number to an sales price string in the base currency.
440 @param {Number} Value
443 formatSalesPrice: function (value) {
444 return Globalize.format(value, "c" + XT.locale.salesPriceScale);
448 Localize a number to a unit ratio string.
450 @param {Number} Value
453 formatUnitRatio: function (value) {
454 return Globalize.format(value, "n" + XT.locale.unitRatioScale);
458 Localize a number to a weight string.
460 @param {Number} Value
463 formatWeight: function (value) {
464 return Globalize.format(value, "n" + XT.locale.weightScale);