Issue #23853: Fixed money widget css
[xtuple] / enyo-client / application / source / widgets / money.js
1 /*jshint node:true, indent:2, curly:true, eqeqeq:true, immed:true, latedef:true, newcap:true, noarg:true,
2 regexp:true, undef:true, trailing:true, white:true */
3 /*global XT:true, XV:true, XM:true, Globalize:true, enyo:true, _:true */
4
5 (function () {
6
7   /**
8     The widget is created with an attr value that is an object composed of two key-values pairs.
9     These pairs include the amount (localValue or baseValue) and the currency attribute strings from the model.
10     The workspace replaces the attribute name string value
11     The localValue key should be used if the model stores the local currency and the baseValue key should
12     be used if the models stores the base currency.
13     The effective attribute should contain the date by which calculations
14    */
15   enyo.kind({
16     name: "XV.MoneyWidget",
17     kind: "XV.NumberWidget",
18     classes: "xv-moneywidget",
19     published: {
20       scale: XT.MONEY_SCALE,
21       localValue: null, // {Number} the number in the user field
22       baseValue: null, // {Number} the amount in the base currency
23       effective: null,
24       currency: null,
25       currencyDisabled: false,
26       currencyShowing: true,
27       disabled: false,
28       localMode: true,
29       isEditableProperty: "localValue" // The property mapped to an attribute that checks whether editbale
30     },
31     handlers: {
32       onValueChange: "pickerChanged" // intercept picker events
33     },
34     maxlength: 12,
35     components: [
36       {controlClasses: 'enyo-inline', components: [
37         {name: "label", content: "", classes: "xv-label"},
38         {kind: "onyx.InputDecorator", components: [
39           {name: "input", kind: "onyx.Input",
40             onchange: "inputChanged", onkeydown: "keyDown"}
41         ]},
42         {name: "picker", kind: "XV.CurrencyPicker", classes: "xv-currency-picker", showLabel: false}
43       ]},
44       {controlClasses: 'enyo-inline', name: "basePanel", showing: false, components: [
45         {classes: "xv-label"},
46         {kind: "onyx.InputDecorator", components: [
47           {name: "baseAmountLabel", classes: "xv-label"}
48         ]},
49         {classes: "xv-currency-picker", components: [
50           {kind: 'onyx.InputDecorator', components: [
51             {name: 'baseCurrencyLabel'}
52           ]}
53         ]}
54       ]}
55     ],
56     /**
57       Set the base price into the base amount label
58     */
59     baseValueChanged: function () {
60       var localMode = this.getLocalMode(),
61         baseValue,
62         scale,
63         content;
64       if (localMode) {
65         baseValue = this.getBaseValue();
66         scale = this.getScale();
67         content = baseValue || baseValue === 0 ?
68           Globalize.format(baseValue, "n" + scale) : "";
69         this.$.baseAmountLabel.setContent(content);
70       } else {
71         this.recalculate();
72       }
73     },
74
75     /**
76       If the effective date is provided, the fixedRate is set and the
77       base panel is shown.
78      */
79     create: function () {
80       this.inherited(arguments);
81
82       var baseCurrency = XT.baseCurrency();
83       if (!baseCurrency) {
84         this.bubble("onNotify", {message: "_baseCurrencyMustBeSet".loc()});
85       }
86
87       this.setEffective(new Date());
88       this.setCurrency(baseCurrency);
89       this.setLocalValue(0);
90       this.$.baseCurrencyLabel.setContent(baseCurrency.get('abbreviation'));
91
92       // the currency picker may be disabled or hidden on creation in certain situations
93       this.currencyDisabledChanged();
94       this.currencyShowingChanged();
95
96       // this is for styling of the picker since the PickerWidget has a built in
97       // input decorator
98       this.$.picker.$.inputWrapper.removeClass("onyx-input-decorator");
99
100       // set the "mode" of this widget, whether or not it directly saves the local
101       // value to the model, or if it converts it to and from the base value.
102       this.setLocalMode(_.has(this.getAttr(), "localValue"));
103     },
104
105     clear: function (options) {
106       this.setValue({localValue: null}, options);
107     },
108
109     currencyChanged: function () {
110       this.$.picker.setValue(this.getCurrency(), {silent: true});
111       this.recalculate();
112     },
113
114     currencyDisabledChanged: function () {
115       this.$.picker.setDisabled(this.getCurrencyDisabled());
116     },
117
118     currencyShowingChanged: function () {
119       this.$.picker.setShowing(this.getCurrencyShowing());
120     },
121
122     /**
123       This setDisabled function is all or nothing for both widgets
124       depending on value
125     */
126     disabledChanged: function () {
127       var disabled = this.getDisabled(),
128         currencyDisabled = this.getCurrencyDisabled();
129       this.$.input.setDisabled(disabled);
130       this.$.picker.setDisabled(disabled || currencyDisabled);
131       this.$.label.addRemoveClass("disabled", this.getDisabled());
132     },
133
134     /**
135     @todo Document the labelChanged method.
136     */
137     labelChanged: function () {
138       var attr = this.getAttr(),
139         valueAttr = attr.localValue || attr.baseValue;
140       var label = (this.getLabel() || ("_" + valueAttr || "").loc());
141       this.$.label.setContent(label + ":");
142     },
143
144     effectiveChanged: function () {
145       this.recalculate();
146     },
147
148     localValueChanged: function () {
149       this.valueChanged(this.getLocalValue()); // forward to XV.Number default for formatting
150       this.recalculate();
151     },
152
153     recalculate: function () {
154       var localMode = this.getLocalMode(),
155         value = localMode ? this.getLocalValue() : this.getBaseValue(),
156         request = localMode ? "toBase" : "fromBase",
157         setter = localMode ? "setBaseValue" : "setLocalValue",
158         currency = this.getCurrency(),
159         effective = this.getEffective(),
160         showing = localMode && _.isDate(effective) && currency &&
161           !currency.get("isBase"),
162         options = {},
163         that = this,
164         i;
165
166       if (!currency || !effective) { return; }
167
168       // Keep track of requests, we'll ignore stale ones
169       this._counter = _.isNumber(this._counter) ? this._counter + 1 : 0;
170       i = this._counter;
171
172       if (_.isNumber(value)) {
173         if (currency.get("isBase")) {
174          // we're at base, so just set the fields with the base value we have
175           this[setter](value);
176         } else {
177           options.success = function (calcValue) {
178             // I only smell freshness
179             if (i < that._counter) { return; }
180             that[setter](calcValue);
181           };
182           currency[request](value, effective, options);
183         }
184       } else { // amount is null
185         that[setter](null);
186       }
187
188       this.$.basePanel.setShowing(showing);
189     },
190
191     setDisabled: function (isDisabled) {
192       this.inherited(arguments);
193       this.$.picker.setDisabled(isDisabled || this.getCurrencyDisabled());
194     },
195
196     /**
197       This setValue function handles a value which is an
198       object potentially consisting of multiple key/value pairs for the
199       amount and currency controls.
200
201       @param {Object} Value
202       @param {Object} [value.currency] Currency
203       @param {Date} [value.effective] Effective date
204       @param {Number} [value.localValue] Local value
205       @param {Number} [value.baseValue] Base value
206     */
207     setValue: function (value, options) {
208       options = options || {};
209       var attr = this.getAttr(),
210         changed = {},
211         keys = _.keys(value),
212         key,
213         set,
214         i;
215
216       // Loop through the properties and update calling
217       // appropriate "set" functions and add to "changed"
218       // object if applicable
219       for (i = 0; i < keys.length; i++) {
220         key = keys[i];
221         set = 'set' + key.slice(0, 1).toUpperCase() + key.slice(1);
222         this[set](value[key]);
223         if (attr[key]) {
224           changed[attr[key]] = value[key];
225         }
226       }
227
228       // Bubble changes if applicable
229       if (!_.isEmpty(changed) && !options.silent) {
230         this.doValueChange({value: changed});
231       }
232     },
233
234     inputChanged: function (inSender, inEvent) {
235       var input = this.$.input.getValue(),
236         value = this.validate(input),
237         prop = this.getLocalMode() ? 'localValue' : 'baseValue',
238         obj = {};
239       if (value !== false) {
240         // is valid!
241         obj[prop] = value;
242         this.setValue(obj);
243       } else {
244         // is invalid!
245         obj[prop] = null;
246         this.setValue(obj);
247         this.valueChanged("");
248       }
249     },
250
251     /**
252       Intercept the valueChanged event from picker.
253     */
254     pickerChanged: function (inSender, inEvent) {
255       var value;
256       if (inEvent.originator.name === "picker") {
257         value = XM.currencies.get(inEvent.value);
258         this.setValue({currency: value});
259         return true;
260       }
261     }
262   });
263
264 }());