Roo/bootstrap/MoneyField.js
[roojs1] / Roo / bootstrap / MoneyField.js
1
2 /**
3  * @class Roo.bootstrap.MoneyField
4  * @extends Roo.bootstrap.ComboBox
5  * Bootstrap MoneyField class
6  * 
7  * @constructor
8  * Create a new MoneyField.
9  * @param {Object} config Configuration options
10  */
11
12 Roo.bootstrap.MoneyField = function(config) {
13     
14     Roo.bootstrap.MoneyField.superclass.constructor.call(this, config);
15     
16 };
17
18 Roo.extend(Roo.bootstrap.MoneyField, Roo.bootstrap.ComboBox, {
19     
20     /**
21      * @cfg {Boolean} allowDecimals False to disallow decimal values (defaults to true)
22      */
23     allowDecimals : true,
24     /**
25      * @cfg {String} decimalSeparator Character(s) to allow as the decimal separator (defaults to '.')
26      */
27     decimalSeparator : ".",
28     /**
29      * @cfg {Number} decimalPrecision The maximum precision to display after the decimal separator (defaults to 2)
30      */
31     decimalPrecision : 2,
32     /**
33      * @cfg {Boolean} allowNegative False to prevent entering a negative sign (defaults to true)
34      */
35     allowNegative : true,
36     /**
37      * @cfg {Number} minValue The minimum allowed value (defaults to Number.NEGATIVE_INFINITY)
38      */
39     minValue : Number.NEGATIVE_INFINITY,
40     /**
41      * @cfg {Number} maxValue The maximum allowed value (defaults to Number.MAX_VALUE)
42      */
43     maxValue : Number.MAX_VALUE,
44     /**
45      * @cfg {String} minText Error text to display if the minimum value validation fails (defaults to "The minimum value for this field is {minValue}")
46      */
47     minText : "The minimum value for this field is {0}",
48     /**
49      * @cfg {String} maxText Error text to display if the maximum value validation fails (defaults to "The maximum value for this field is {maxValue}")
50      */
51     maxText : "The maximum value for this field is {0}",
52     /**
53      * @cfg {String} nanText Error text to display if the value is not a valid number.  For example, this can happen
54      * if a valid character like '.' or '-' is left in the field with no number (defaults to "{value} is not a valid number")
55      */
56     nanText : "{0} is not a valid number",
57     /**
58      * @cfg {Boolean} castInt (true|false) cast int if true (defalut true)
59      */
60     castInt : true,
61     
62     inputlg : 9,
63     inputmd : 9,
64     inputsm : 9,
65     inputxs : 6,
66     
67     store : false,
68     
69     getAutoCreate : function()
70     {
71         var align = this.labelAlign || this.parentLabelAlign();
72         
73         var id = Roo.id();
74
75         var cfg = {
76             cls: 'form-group',
77             cn: []
78         };
79
80         var input =  {
81             tag: 'input',
82             id : id,
83             cls : 'form-control roo-money-amount-input',
84             autocomplete: 'new-password'
85         };
86         
87         if (this.name) {
88             input.name = this.name;
89         }
90
91         if (this.disabled) {
92             input.disabled = true;
93         }
94
95         var clg = 12 - this.inputlg;
96         var cmd = 12 - this.inputmd;
97         var csm = 12 - this.inputsm;
98         var cxs = 12 - this.inputxs;
99         
100         var container = {
101             tag : 'div',
102             cls : 'row roo-money-field',
103             cn : [
104                 {
105                     tag : 'div',
106                     cls : 'roo-money-currency column col-lg-' + clg + ' col-md-' + cmd + ' col-sm-' + csm + ' col-xs-' + cxs,
107                     cn : [
108                         {
109                             tag : 'div',
110                             cls: 'roo-select2-container input-group',
111                             cn: [
112                                 {
113                                     tag : 'input',
114                                     cls : 'form-control roo-money-currency-input',
115                                     autocomplete: 'new-password'
116                                 },
117                                 {
118                                     tag :'span',
119                                     cls : 'input-group-addon',
120                                     cn : [
121                                         {
122                                             tag: 'span',
123                                             cls: 'caret'
124                                         }
125                                     ]
126                                 }
127                             ]
128                         }
129                     ]
130                 },
131                 {
132                     tag : 'div',
133                     cls : 'roo-money-amount column col-lg-' + this.inputlg + ' col-md-' + this.inputmd + ' col-sm-' + this.inputsm + ' col-xs-' + this.inputxs,
134                     cn : [
135                         {
136                             tag: 'div',
137                             cls: this.hasFeedback ? 'has-feedback' : '',
138                             cn: [
139                                 input
140                             ]
141                         }
142                     ]
143                 }
144             ]
145             
146         };
147         
148         if (this.fieldLabel.length) {
149             var indicator = {
150                 tag: 'i',
151                 tooltip: 'This field is required'
152             };
153
154             var label = {
155                 tag: 'label',
156                 'for':  id,
157                 cls: 'control-label',
158                 cn: []
159             };
160
161             var label_text = {
162                 tag: 'span',
163                 html: this.fieldLabel
164             };
165
166             indicator.cls = 'roo-required-indicator text-danger fa fa-lg fa-star left-indicator';
167             label.cn = [
168                 indicator,
169                 label_text
170             ];
171
172             if(this.indicatorpos == 'right') {
173                 indicator.cls = 'roo-required-indicator text-danger fa fa-lg fa-star right-indicator';
174                 label.cn = [
175                     label_text,
176                     indicator
177                 ];
178             }
179
180             if(align == 'left') {
181                 container = {
182                     tag: 'div',
183                     cn: [
184                         container
185                     ]
186                 };
187
188                 if(this.labelWidth > 12){
189                     label.style = "width: " + this.labelWidth + 'px';
190                 }
191                 if(this.labelWidth < 13 && this.labelmd == 0){
192                     this.labelmd = this.labelWidth;
193                 }
194                 if(this.labellg > 0){
195                     label.cls += ' col-lg-' + this.labellg;
196                     input.cls += ' col-lg-' + (12 - this.labellg);
197                 }
198                 if(this.labelmd > 0){
199                     label.cls += ' col-md-' + this.labelmd;
200                     container.cls += ' col-md-' + (12 - this.labelmd);
201                 }
202                 if(this.labelsm > 0){
203                     label.cls += ' col-sm-' + this.labelsm;
204                     container.cls += ' col-sm-' + (12 - this.labelsm);
205                 }
206                 if(this.labelxs > 0){
207                     label.cls += ' col-xs-' + this.labelxs;
208                     container.cls += ' col-xs-' + (12 - this.labelxs);
209                 }
210             }
211         }
212
213         cfg.cn = [
214             label,
215             container
216         ];
217
218         var settings = this;
219
220         ['xs','sm','md','lg'].map(function(size){
221             if (settings[size]) {
222                 cfg.cls += ' col-' + size + '-' + settings[size];
223             }
224         });
225         
226         return cfg;
227         
228     },
229     
230     initEvents : function()
231     {
232         this.initCurrencyEvent();
233         
234         this.initNumberEvent();
235         
236     },
237     
238     initCurrencyEvent : function()
239     {
240         if (!this.store) {
241             throw "can not find store for combo";
242         }
243         
244         this.store = Roo.factory(this.store, Roo.data);
245         this.store.parent = this;
246         
247         this.createList();
248         
249         this.indicator = this.indicatorEl();
250         
251         this.triggerEl = this.el.select('.input-group-addon', true).first();
252         
253         this.triggerEl.on("click", this.onTriggerClick, this, { preventDefault : true });
254         
255         this.currencyEl = this.el.select('.roo-money-currency-input', true).first();
256         
257         this.amountEl = this.el.select('.roo-money-amount-input', true).first();
258         
259         var _this = this;
260         
261         (function(){
262             var lw = _this.listWidth || Math.max(_this.inputEl().getWidth(), _this.minListWidth);
263             _this.list.setWidth(lw);
264         }).defer(100);
265         
266         this.list.on('mouseover', this.onViewOver, this);
267         this.list.on('mousemove', this.onViewMove, this);
268         this.list.on('scroll', this.onViewScroll, this);
269         
270         if(!this.tpl){
271             this.tpl = '<li><a href="#">{' + this.currencyField + '}</a></li>';
272         }
273         
274         this.view = new Roo.View(this.list, this.tpl, {
275             singleSelect:true, store: this.store, selectedClass: this.selectedClass
276         });
277         
278         this.view.on('click', this.onViewClick, this);
279         
280         this.store.on('beforeload', this.onBeforeLoad, this);
281         this.store.on('load', this.onLoad, this);
282         this.store.on('loadexception', this.onLoadException, this);
283         
284         this.keyNav = new Roo.KeyNav(this.currencyEl, {
285             "up" : function(e){
286                 this.inKeyMode = true;
287                 this.selectPrev();
288             },
289
290             "down" : function(e){
291                 if(!this.isExpanded()){
292                     this.onTriggerClick();
293                 }else{
294                     this.inKeyMode = true;
295                     this.selectNext();
296                 }
297             },
298
299             "enter" : function(e){
300                 this.collapse();
301                 
302                 if(this.fireEvent("specialkey", this, e)){
303                     this.onViewClick(false);
304                 }
305                 
306                 return true;
307             },
308
309             "esc" : function(e){
310                 this.collapse();
311             },
312
313             "tab" : function(e){
314                 this.collapse();
315                 
316                 if(this.fireEvent("specialkey", this, e)){
317                     this.onViewClick(false);
318                 }
319                 
320                 return true;
321             },
322
323             scope : this,
324
325             doRelay : function(foo, bar, hname){
326                 if(hname == 'down' || this.scope.isExpanded()){
327                    return Roo.KeyNav.prototype.doRelay.apply(this, arguments);
328                 }
329                 return true;
330             },
331
332             forceKeyDown: true
333         });
334         
335     },
336     
337     initNumberEvent : function(e)
338     {
339         var allowed = "0123456789";
340         
341         if(this.allowDecimals){
342             allowed += this.decimalSeparator;
343         }
344         
345         if(this.allowNegative){
346             allowed += "-";
347         }
348         
349         this.stripCharsRe = new RegExp('[^'+allowed+']', 'gi');
350         
351         var keyPress = function(e){
352             
353             var k = e.getKey();
354             
355             var c = e.getCharCode();
356             
357             if(
358                     (String.fromCharCode(c) == '.' || String.fromCharCode(c) == '-') &&
359                     allowed.indexOf(String.fromCharCode(c)) === -1
360             ){
361                 e.stopEvent();
362                 return;
363             }
364             
365             if(!Roo.isIE && (e.isSpecialKey() || k == e.BACKSPACE || k == e.DELETE)){
366                 return;
367             }
368             
369             if(allowed.indexOf(String.fromCharCode(c)) === -1){
370                 e.stopEvent();
371             }
372         };
373         
374         this.amountEl.on("keypress", keyPress, this);
375         
376     },
377     
378     onTriggerClick : function(e)
379     {   
380         if(this.disabled){
381             return;
382         }
383         
384         this.page = 0;
385         this.loadNext = false;
386         
387         if(this.isExpanded()){
388             this.collapse();
389             return;
390         }
391         
392         this.hasFocus = true;
393         
394         if(this.triggerAction == 'all') {
395             this.doQuery(this.allQuery, true);
396             return;
397         }
398         
399         this.doQuery(this.getRawValue());
400     },
401     
402     getCurrency : function()
403     {   
404         var v = this.currencyEl.getValue();
405         
406         return v;
407     },
408     
409     restrictHeight : function()
410     {
411         this.list.alignTo(this.currencyEl, this.listAlign);
412         this.list.alignTo(this.currencyEl, this.listAlign);
413     },
414     
415     onViewClick : function(view, doFocus, el, e)
416     {
417         var index = this.view.getSelectedIndexes()[0];
418         
419         var r = this.store.getAt(index);
420         
421         if(r){
422             this.onSelect(r, index);
423         }
424     },
425     
426     onSelect : function(record, index){
427         
428         if(this.fireEvent('beforeselect', this, record, index) !== false){
429         
430             this.setFromCurrencyData(index > -1 ? record.data : false);
431             
432             this.collapse();
433             
434             this.fireEvent('select', this, record, index);
435         }
436     },
437     
438     setFromCurrencyData : function(o)
439     {
440         var currency = '';
441         
442         this.lastCurrency = o;
443         
444         if (this.currencyName) {
445             currency = !o || typeof(o[this.currencyName]) == 'undefined' ? '' : o[this.currencyName];
446         } else {
447             Roo.log('no  currencyName value set for '+ (this.name ? this.name : this.id));
448         }
449         
450         this.lastSelectionText = currency;
451         
452         this.setCurrency(currency);
453     },
454     
455     setFromData : function(o)
456     {
457         this.setFromCurrencyData(o);
458         
459         var value = '';
460         
461         if (this.name) {
462             value = !o || typeof(o[this.name]) == 'undefined' ? '' : o[this.name];
463         } else {
464             Roo.log('no value set for '+ (this.name ? this.name : this.id));
465         }
466         
467         this.setValue(value);
468         
469     },
470     
471     setCurrency : function(v)
472     {   
473         this.currencyValue = v;
474         
475         if(this.rendered){
476             this.currencyEl.dom.value = (v === null || v === undefined ? '' : v);
477             this.validate();
478         }
479     },
480     
481     setValue : function(v)
482     {
483         v = this.fixPrecision(v);
484         
485         v = String(v).replace(".", this.decimalSeparator);
486         
487         this.value = v;
488         
489         if(this.rendered){
490             this.amountEl.dom.value = (v === null || v === undefined ? '' : v);
491             this.validate();
492         }
493     },
494     
495     getRawValue : function()
496     {
497         var v = this.amountEl.getValue();
498         
499         return v;
500     },
501     
502     getValue : function()
503     {
504         return this.fixPrecision(this.parseValue(this.getRawValue()));
505     },
506     
507     parseValue : function(value)
508     {
509         value = parseFloat(String(value).replace(this.decimalSeparator, "."));
510         return isNaN(value) ? '' : value;
511     },
512     
513     fixPrecision : function(value)
514     {
515         var nan = isNaN(value);
516         
517         if(!this.allowDecimals || this.decimalPrecision == -1 || nan || !value){
518             return nan ? '' : value;
519         }
520         
521         return parseFloat(value).toFixed(this.decimalPrecision);
522     },
523     
524     decimalPrecisionFcn : function(v)
525     {
526         return Math.floor(v);
527     },
528     
529     validateValue : function(value)
530     {
531         if(!Roo.bootstrap.MoneyField.superclass.validateValue.call(this, value)){
532             return false;
533         }
534         
535         var num = this.parseValue(value);
536         
537         if(isNaN(num)){
538             this.markInvalid(String.format(this.nanText, value));
539             return false;
540         }
541         
542         if(num < this.minValue){
543             this.markInvalid(String.format(this.minText, this.minValue));
544             return false;
545         }
546         
547         if(num > this.maxValue){
548             this.markInvalid(String.format(this.maxText, this.maxValue));
549             return false;
550         }
551         
552         return true;
553     },
554     
555     validate : function()
556     {
557         if(this.disabled){
558             this.markValid();
559             return true;
560         }
561         
562         var currency = this.getCurrency();
563         
564         if(this.validateValue(this.getRawValue()) && currency.length){
565             this.markValid();
566             return true;
567         }
568         
569         this.markInvalid();
570         return false;
571     }
572     
573 });