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:
99 @param {String} Class name
100 @param {Object|Array} Component(s)
102 appendExtension: function (workspace, extension) {
103 var Klass = XT.getObjectByName(workspace),
104 extensions = Klass.prototype.extensions || [];
105 if (!_.isArray(extension)) {
106 extension = [extension];
108 Klass.prototype.extensions = extensions.concat(extension);
112 Helper function for enyo unit testing
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.
120 applyTest: function (expected, actual, message) {
121 if (expected === actual) {
125 message = ". " + message;
129 return "Expected " + expected + ", saw " + actual + message;
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
138 @param {String} The URL for the download route.
140 downloadURL: function (url) {
141 var hiddenIFrameID = 'hiddenDownloader',
142 iframe = document.getElementById(hiddenIFrameID);
144 if (iframe === null) {
145 iframe = document.createElement('iframe');
146 iframe.id = hiddenIFrameID;
147 iframe.style.display = 'none';
148 document.body.appendChild(iframe);
154 getCache: function (recordType) {
155 return XV._modelCaches[recordType];
158 getList: function (recordType) {
159 return XV._modelLists[recordType];
162 getWorkspace: function (recordType) {
163 return XV._modelWorkspaces[recordType];
167 Is the ancestor a superkind (or supersuperkind, etc.) of the object?
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
173 inheritsFrom: function (object, ancestor) {
174 if (!object || !object.ctor) {
177 while (object.kindName !== 'enyo.Object') {
178 if (object.ctor.prototype.base.prototype.kindName === ancestor) {
181 object = object.ctor.prototype.base.prototype;
185 registerModelCache: function (recordType, cache) {
186 XV._modelCaches[recordType] = cache;
189 registerModelList: function (recordType, list) {
190 XV._modelLists[recordType] = list;
193 registerModelWorkspace: function (recordType, workspace) {
194 XV._modelWorkspaces[recordType] = workspace;
202 A mixin that allows the components of a class to be extended.
204 XV.ExtensionsMixin = {
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.
217 processExtensions: function (forceDeferred) {
218 var extensions = this.extensions || [],
224 if (this._extLength === undefined) {
227 if (this._extLength === extensions.length) { return; }
228 for (i = 0; i < extensions.length; i++) {
229 ext = _.clone(this.extensions[i]);
231 if (ext.defer !== forceDeferred) {
232 // the workspace is not ready to add this extension yet,
233 // or, it's probably been added already
236 // Resolve name of container to the instance
237 if (_.isString(ext.container)) {
238 containerString = ext.container;
240 while (containerString.indexOf(".") >= 0) {
241 container = container.$[containerString.substring(0, containerString.indexOf("."))];
242 containerString = containerString.substring(containerString.indexOf(".") + 1);
245 // avoid a crash if the asked-for container doesn't exist
246 ext.container = container.$[containerString];
248 XT.log("Requested container", ext.container, "not found");
252 // Resolve `addBefore`
253 if (_.isString(ext.addBefore)) {
254 ext.addBefore = this.$[ext.addBefore];
256 this.createComponent(ext);
265 A mixin with functions used for formatting display data.
267 XV.FormattingMixin = /** @lends XV.FormattingMixin# */{
270 An array of data types that require special formatting in displays
272 formatted: ["Date", "DueDate", "Cost", "ExtendedPrice", "Hours",
273 "Money", "Percent", "PurchasePrice", "Quantity", "SalesPrice",
274 "UnitRatio", "Weight", "Boolean", "EffectiveDate", "ExpireDate",
278 formatAddressInfo: function (value, view, model) {
279 return XM.Address.formatShort(value);
283 Localize a boolean to yes/no text.
285 @param {Number} Value
288 formatBoolean: function (value) {
289 return value ? "_yes".loc() : "_no".loc();
293 Localize a number to cost string in the base currency.
295 @param {Number} Value
298 formatCost: function (value) {
299 return Globalize.format(value, "c" + XT.locale.costScale);
308 formatDate: function (value) {
309 var date = _.isDate(value) ? XT.date.applyTimezoneOffset(value, true) : false;
310 return date ? Globalize.format(date, "d") : "";
314 Localize a date and add the class for `error` to the view if the date is before today.
318 @param {Object} Model
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") : "";
330 Dates greater than today are highlight as errors. Start of time dates return "Always,"
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();
343 return Globalize.format(date, "d");
347 Dates greater than today are highlight as errors. End of time dates return "Never,"
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();
360 return Globalize.format(date, "d");
364 Localize a number to an extended price string in the base currency.
366 @param {Number} Value
369 formatExtendedPrice: function (value) {
370 return Globalize.format(value, "c" + XT.locale.extendedPriceScale);
374 Localize a number to an hours string in the base currency.
376 @param {Number} Value
379 formatHours: function (value, view) {
380 view.addRemoveClass("error", value < 0);
381 return Globalize.format(value, "n" + XT.locale.hoursScale);
385 Localize a number to a currency string using the base currency.
387 @param {Number} Value
390 formatMoney: function (value, view) {
391 view.addRemoveClass("error", value < 0);
392 return Globalize.format(value, "c" + XT.locale.currencyScale);
396 Localize a number to a percent string.
398 @param {Number} Value
401 formatPercent: function (value) {
402 return Globalize.format(value, "p" + XT.locale.percentScale);
406 Localize a number to a purchase price string in the base currency.
408 @param {Number} Value
411 formatPurchasePrice: function (value) {
412 return Globalize.format(value, "c" + XT.locale.purchasePriceScale);
416 Localize a number to a quantity string.
418 @param {Number} Value
421 formatQuantity: function (value, view) {
422 view.addRemoveClass("error", value < 0);
423 return Globalize.format(value, "n" + XT.locale.quantityScale);
427 Localize a number to a quantity string.
429 @param {Number} Value
432 formatQuantityPer: function (value) {
433 return Globalize.format(value, "n" + XT.locale.quantityPerScale);
437 Localize a number to an sales price string in the base currency.
439 @param {Number} Value
442 formatSalesPrice: function (value) {
443 return Globalize.format(value, "c" + XT.locale.salesPriceScale);
447 Localize a number to a unit ratio string.
449 @param {Number} Value
452 formatUnitRatio: function (value) {
453 return Globalize.format(value, "n" + XT.locale.unitRatioScale);
457 Localize a number to a weight string.
459 @param {Number} Value
462 formatWeight: function (value) {
463 return Globalize.format(value, "n" + XT.locale.weightScale);