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.indicator = this.indicatorEl();
233         
234         this.initCurrencyEvent();
235         
236         this.initNumberEvent();
237         
238     },
239     
240     initCurrencyEvent : function()
241     {
242         if (!this.store) {
243             throw "can not find store for combo";
244         }
245         
246         this.store = Roo.factory(this.store, Roo.data);
247         this.store.parent = this;
248         
249         this.createList();
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         this.inputEl().on("keydown" , this.fireKey,  this);
340         this.inputEl().on("focus", this.onFocus,  this);
341         this.inputEl().on("blur", this.onBlur,  this);
342         
343         this.inputEl().relayEvent('keyup', this);
344         
345         if(this.indicator){
346             this.indicator.addClass('invisible');
347         }
348  
349         this.originalValue = this.getValue();
350         
351         if(this.validationEvent == 'keyup'){
352             this.validationTask = new Roo.util.DelayedTask(this.validate, this);
353             this.inputEl().on('keyup', this.filterValidation, this);
354         }
355         else if(this.validationEvent !== false){
356             this.inputEl().on(this.validationEvent, this.validate, this, {buffer: this.validationDelay});
357         }
358         
359         if(this.selectOnFocus){
360             this.on("focus", this.preFocus, this);
361             
362         }
363         if(this.maskRe || (this.vtype && this.disableKeyFilter !== true && (this.maskRe = Roo.form.VTypes[this.vtype+'Mask']))){
364             this.inputEl().on("keypress", this.filterKeys, this);
365         } else {
366             this.inputEl().relayEvent('keypress', this);
367         }
368         
369         var allowed = "0123456789";
370         
371         if(this.allowDecimals){
372             allowed += this.decimalSeparator;
373         }
374         
375         if(this.allowNegative){
376             allowed += "-";
377         }
378         
379         this.stripCharsRe = new RegExp('[^'+allowed+']', 'gi');
380         
381         var keyPress = function(e){
382             
383             var k = e.getKey();
384             
385             var c = e.getCharCode();
386             
387             if(
388                     (String.fromCharCode(c) == '.' || String.fromCharCode(c) == '-') &&
389                     allowed.indexOf(String.fromCharCode(c)) === -1
390             ){
391                 e.stopEvent();
392                 return;
393             }
394             
395             if(!Roo.isIE && (e.isSpecialKey() || k == e.BACKSPACE || k == e.DELETE)){
396                 return;
397             }
398             
399             if(allowed.indexOf(String.fromCharCode(c)) === -1){
400                 e.stopEvent();
401             }
402         };
403         
404         this.inputEl().on("keypress", keyPress, this);
405         
406     },
407     
408     onTriggerClick : function(e)
409     {   
410         if(this.disabled){
411             return;
412         }
413         
414         this.page = 0;
415         this.loadNext = false;
416         
417         if(this.isExpanded()){
418             this.collapse();
419             return;
420         }
421         
422         this.hasFocus = true;
423         
424         if(this.triggerAction == 'all') {
425             this.doQuery(this.allQuery, true);
426             return;
427         }
428         
429         this.doQuery(this.getRawValue());
430     },
431     
432     getCurrency : function()
433     {   
434         var v = this.currencyEl.getValue();
435         
436         return v;
437     },
438     
439     restrictHeight : function()
440     {
441         this.list.alignTo(this.currencyEl, this.listAlign);
442         this.list.alignTo(this.currencyEl, this.listAlign);
443     },
444     
445     onViewClick : function(view, doFocus, el, e)
446     {
447         var index = this.view.getSelectedIndexes()[0];
448         
449         var r = this.store.getAt(index);
450         
451         if(r){
452             this.onSelect(r, index);
453         }
454     },
455     
456     onSelect : function(record, index){
457         
458         if(this.fireEvent('beforeselect', this, record, index) !== false){
459         
460             this.setFromCurrencyData(index > -1 ? record.data : false);
461             
462             this.collapse();
463             
464             this.fireEvent('select', this, record, index);
465         }
466     },
467     
468     setFromCurrencyData : function(o)
469     {
470         var currency = '';
471         
472         this.lastCurrency = o;
473         
474         if (this.currencyField) {
475             currency = !o || typeof(o[this.currencyField]) == 'undefined' ? '' : o[this.currencyField];
476         } else {
477             Roo.log('no  currencyField value set for '+ (this.name ? this.name : this.id));
478         }
479         
480         this.lastSelectionText = currency;
481         
482         this.setCurrency(currency);
483     },
484     
485     setFromData : function(o)
486     {
487         var c = {};
488         
489         c[this.currencyField] = !o || typeof(o[this.currencyName]) == 'undefined' ? '' : o[this.currencyName];
490         
491         this.setFromCurrencyData(c);
492         
493         var value = '';
494         
495         if (this.name) {
496             value = !o || typeof(o[this.name]) == 'undefined' ? '' : o[this.name];
497         } else {
498             Roo.log('no value set for '+ (this.name ? this.name : this.id));
499         }
500         
501         this.setValue(value);
502         
503     },
504     
505     setCurrency : function(v)
506     {   
507         this.currencyValue = v;
508         
509         if(this.rendered){
510             this.currencyEl.dom.value = (v === null || v === undefined ? '' : v);
511             this.validate();
512         }
513     },
514     
515     setValue : function(v)
516     {
517         v = this.fixPrecision(v);
518         
519         v = String(v).replace(".", this.decimalSeparator);
520         
521         this.value = v;
522         
523         if(this.rendered){
524             this.inputEl().dom.value = (v === null || v === undefined ? '' : v);
525             this.validate();
526         }
527     },
528     
529     getRawValue : function()
530     {
531         var v = this.inputEl().getValue();
532         
533         return v;
534     },
535     
536     getValue : function()
537     {
538         return this.fixPrecision(this.parseValue(this.getRawValue()));
539     },
540     
541     parseValue : function(value)
542     {
543         value = parseFloat(String(value).replace(this.decimalSeparator, "."));
544         return isNaN(value) ? '' : value;
545     },
546     
547     fixPrecision : function(value)
548     {
549         var nan = isNaN(value);
550         
551         if(!this.allowDecimals || this.decimalPrecision == -1 || nan || !value){
552             return nan ? '' : value;
553         }
554         
555         return parseFloat(value).toFixed(this.decimalPrecision);
556     },
557     
558     decimalPrecisionFcn : function(v)
559     {
560         return Math.floor(v);
561     },
562     
563     validateValue : function(value)
564     {
565         if(!Roo.bootstrap.MoneyField.superclass.validateValue.call(this, value)){
566             return false;
567         }
568         
569         var num = this.parseValue(value);
570         
571         if(isNaN(num)){
572             this.markInvalid(String.format(this.nanText, value));
573             return false;
574         }
575         
576         if(num < this.minValue){
577             this.markInvalid(String.format(this.minText, this.minValue));
578             return false;
579         }
580         
581         if(num > this.maxValue){
582             this.markInvalid(String.format(this.maxText, this.maxValue));
583             return false;
584         }
585         
586         return true;
587     },
588     
589     validate : function()
590     {
591         if(this.disabled){
592             this.markValid();
593             return true;
594         }
595         
596         var currency = this.getCurrency();
597         
598         if(this.validateValue(this.getRawValue()) && currency.length){
599             this.markValid();
600             return true;
601         }
602         
603         this.markInvalid();
604         return false;
605     },
606     
607     getName: function()
608     {
609         return this.name;
610     },
611     
612     beforeBlur : function()
613     {
614         if(!this.castInt){
615             return;
616         }
617         
618         var v = this.parseValue(this.getRawValue());
619         
620         if(v){
621             this.setValue(v);
622         }
623     },
624     
625     onBlur : function()
626     {
627         this.beforeBlur();
628         
629         if(!Roo.isOpera && this.focusClass){ // don't touch in Opera
630             //this.el.removeClass(this.focusClass);
631         }
632         
633         this.hasFocus = false;
634         
635         if(this.validationEvent !== false && this.validateOnBlur && this.validationEvent != "blur"){
636             this.validate();
637         }
638         
639         var v = this.getValue();
640         
641         if(String(v) !== String(this.startValue)){
642             this.fireEvent('change', this, v, this.startValue);
643         }
644         
645         this.fireEvent("blur", this);
646     },
647     
648     inputEl : function()
649     {
650         return this.amountEl ? this.amountEl : this.el.select('.roo-money-amount-input', true).first();
651     }
652     
653 });