d777f720e0f0feb84d40020be58577510ec87ea8
[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 : 0,
32     /**
33      * @cfg {Boolean} allowNegative False to prevent entering a negative sign (defaults to true)
34      */
35     allowNegative : true,
36     /**
37      * @cfg {Boolean} allowZero False to blank out if the user enters '0' (defaults to true)
38      */
39     allowZero: true,
40     /**
41      * @cfg {Number} minValue The minimum allowed value (defaults to Number.NEGATIVE_INFINITY)
42      */
43     minValue : Number.NEGATIVE_INFINITY,
44     /**
45      * @cfg {Number} maxValue The maximum allowed value (defaults to Number.MAX_VALUE)
46      */
47     maxValue : Number.MAX_VALUE,
48     /**
49      * @cfg {String} minText Error text to display if the minimum value validation fails (defaults to "The minimum value for this field is {minValue}")
50      */
51     minText : "The minimum value for this field is {0}",
52     /**
53      * @cfg {String} maxText Error text to display if the maximum value validation fails (defaults to "The maximum value for this field is {maxValue}")
54      */
55     maxText : "The maximum value for this field is {0}",
56     /**
57      * @cfg {String} nanText Error text to display if the value is not a valid number.  For example, this can happen
58      * if a valid character like '.' or '-' is left in the field with no number (defaults to "{value} is not a valid number")
59      */
60     nanText : "{0} is not a valid number",
61     /**
62      * @cfg {Boolean} castInt (true|false) cast int if true (defalut true)
63      */
64     castInt : true,
65     /**
66      * @cfg {String} defaults currency of the MoneyField
67      * value should be in lkey
68      */
69     defaultCurrency : false,
70     /**
71      * @cfg {String} thousandsDelimiter Symbol of thousandsDelimiter
72      */
73     thousandsDelimiter : false,
74     /**
75      * @cfg {Number} max_length Maximum input field length allowed (defaults to Number.MAX_VALUE)
76      */
77     max_length: false,
78     
79     inputlg : 9,
80     inputmd : 9,
81     inputsm : 9,
82     inputxs : 6,
83     
84     store : false,
85     
86     getAutoCreate : function()
87     {
88         var align = this.labelAlign || this.parentLabelAlign();
89         
90         var id = Roo.id();
91
92         var cfg = {
93             cls: 'form-group',
94             cn: []
95         };
96
97         var input =  {
98             tag: 'input',
99             id : id,
100             cls : 'form-control roo-money-amount-input',
101             autocomplete: 'new-password'
102         };
103         
104         var hiddenInput = {
105             tag: 'input',
106             type: 'hidden',
107             id: Roo.id(),
108             cls: 'hidden-number-input'
109         };
110         
111         if(
112             this.max_length &&
113             Number.isInteger(this.max_length * 1)
114         ) {
115             input.maxlength = this.max_length; 
116         }
117         
118         if (this.name) {
119             hiddenInput.name = this.name;
120         }
121
122         if (this.disabled) {
123             input.disabled = true;
124         }
125
126         var clg = 12 - this.inputlg;
127         var cmd = 12 - this.inputmd;
128         var csm = 12 - this.inputsm;
129         var cxs = 12 - this.inputxs;
130         
131         var container = {
132             tag : 'div',
133             cls : 'row roo-money-field',
134             cn : [
135                 {
136                     tag : 'div',
137                     cls : 'roo-money-currency column col-lg-' + clg + ' col-md-' + cmd + ' col-sm-' + csm + ' col-xs-' + cxs,
138                     cn : [
139                         {
140                             tag : 'div',
141                             cls: 'roo-select2-container input-group',
142                             cn: [
143                                 {
144                                     tag : 'input',
145                                     cls : 'form-control roo-money-currency-input',
146                                     autocomplete: 'new-password',
147                                     readOnly : 1,
148                                     name : this.currencyName
149                                 },
150                                 {
151                                     tag :'span',
152                                     cls : 'input-group-addon',
153                                     cn : [
154                                         {
155                                             tag: 'span',
156                                             cls: 'caret'
157                                         }
158                                     ]
159                                 }
160                             ]
161                         }
162                     ]
163                 },
164                 {
165                     tag : 'div',
166                     cls : 'roo-money-amount column col-lg-' + this.inputlg + ' col-md-' + this.inputmd + ' col-sm-' + this.inputsm + ' col-xs-' + this.inputxs,
167                     cn : [
168                         {
169                             tag: 'div',
170                             cls: this.hasFeedback ? 'has-feedback' : '',
171                             cn: [
172                                 input
173                             ]
174                         }
175                     ]
176                 }
177             ]
178             
179         };
180         
181         if (this.fieldLabel.length) {
182             var indicator = {
183                 tag: 'i',
184                 tooltip: 'This field is required'
185             };
186
187             var label = {
188                 tag: 'label',
189                 'for':  id,
190                 cls: 'control-label',
191                 cn: []
192             };
193
194             var label_text = {
195                 tag: 'span',
196                 html: this.fieldLabel
197             };
198
199             indicator.cls = 'roo-required-indicator text-danger fa fa-lg fa-star left-indicator';
200             label.cn = [
201                 indicator,
202                 label_text
203             ];
204
205             if(this.indicatorpos == 'right') {
206                 indicator.cls = 'roo-required-indicator text-danger fa fa-lg fa-star right-indicator';
207                 label.cn = [
208                     label_text,
209                     indicator
210                 ];
211             }
212
213             if(align == 'left') {
214                 container = {
215                     tag: 'div',
216                     cn: [
217                         container
218                     ]
219                 };
220
221                 if(this.labelWidth > 12){
222                     label.style = "width: " + this.labelWidth + 'px';
223                 }
224                 if(this.labelWidth < 13 && this.labelmd == 0){
225                     this.labelmd = this.labelWidth;
226                 }
227                 if(this.labellg > 0){
228                     label.cls += ' col-lg-' + this.labellg;
229                     input.cls += ' col-lg-' + (12 - this.labellg);
230                 }
231                 if(this.labelmd > 0){
232                     label.cls += ' col-md-' + this.labelmd;
233                     container.cls += ' col-md-' + (12 - this.labelmd);
234                 }
235                 if(this.labelsm > 0){
236                     label.cls += ' col-sm-' + this.labelsm;
237                     container.cls += ' col-sm-' + (12 - this.labelsm);
238                 }
239                 if(this.labelxs > 0){
240                     label.cls += ' col-xs-' + this.labelxs;
241                     container.cls += ' col-xs-' + (12 - this.labelxs);
242                 }
243             }
244         }
245
246         cfg.cn = [
247             label,
248             container,
249             hiddenInput
250         ];
251         
252         var settings = this;
253
254         ['xs','sm','md','lg'].map(function(size){
255             if (settings[size]) {
256                 cfg.cls += ' col-' + size + '-' + settings[size];
257             }
258         });
259         
260         return cfg;
261     },
262     
263     initEvents : function()
264     {
265         this.indicator = this.indicatorEl();
266         
267         this.initCurrencyEvent();
268         
269         this.initNumberEvent();
270     },
271     
272     initCurrencyEvent : function()
273     {
274         if (!this.store) {
275             throw "can not find store for combo";
276         }
277         
278         this.store = Roo.factory(this.store, Roo.data);
279         this.store.parent = this;
280         
281         this.createList();
282         
283         this.triggerEl = this.el.select('.input-group-addon', true).first();
284         
285         this.triggerEl.on("click", this.onTriggerClick, this, { preventDefault : true });
286         
287         var _this = this;
288         
289         (function(){
290             var lw = _this.listWidth || Math.max(_this.inputEl().getWidth(), _this.minListWidth);
291             _this.list.setWidth(lw);
292         }).defer(100);
293         
294         this.list.on('mouseover', this.onViewOver, this);
295         this.list.on('mousemove', this.onViewMove, this);
296         this.list.on('scroll', this.onViewScroll, this);
297         
298         if(!this.tpl){
299             this.tpl = '<li><a href="#">{' + this.currencyField + '}</a></li>';
300         }
301         
302         this.view = new Roo.View(this.list, this.tpl, {
303             singleSelect:true, store: this.store, selectedClass: this.selectedClass
304         });
305         
306         this.view.on('click', this.onViewClick, this);
307         
308         this.store.on('beforeload', this.onBeforeLoad, this);
309         this.store.on('load', this.onLoad, this);
310         this.store.on('loadexception', this.onLoadException, this);
311         
312         this.keyNav = new Roo.KeyNav(this.currencyEl(), {
313             "up" : function(e){
314                 this.inKeyMode = true;
315                 this.selectPrev();
316             },
317
318             "down" : function(e){
319                 if(!this.isExpanded()){
320                     this.onTriggerClick();
321                 }else{
322                     this.inKeyMode = true;
323                     this.selectNext();
324                 }
325             },
326
327             "enter" : function(e){
328                 this.collapse();
329                 
330                 if(this.fireEvent("specialkey", this, e)){
331                     this.onViewClick(false);
332                 }
333                 
334                 return true;
335             },
336
337             "esc" : function(e){
338                 this.collapse();
339             },
340
341             "tab" : function(e){
342                 this.collapse();
343                 
344                 if(this.fireEvent("specialkey", this, e)){
345                     this.onViewClick(false);
346                 }
347                 
348                 return true;
349             },
350
351             scope : this,
352
353             doRelay : function(foo, bar, hname){
354                 if(hname == 'down' || this.scope.isExpanded()){
355                    return Roo.KeyNav.prototype.doRelay.apply(this, arguments);
356                 }
357                 return true;
358             },
359
360             forceKeyDown: true
361         });
362         
363         this.currencyEl().on("click", this.onTriggerClick, this, { preventDefault : true });
364         
365     },
366     
367     initNumberEvent : function(e)
368     {
369         this.inputEl().on("keydown" , this.fireKey,  this);
370         this.inputEl().on("focus", this.onFocus,  this);
371         this.inputEl().on("blur", this.onBlur,  this);
372         
373         this.inputEl().relayEvent('keyup', this);
374         
375         if(this.indicator){
376             this.indicator.addClass('invisible');
377         }
378  
379         this.originalValue = this.getValue();
380         
381         if(this.validationEvent == 'keyup'){
382             this.validationTask = new Roo.util.DelayedTask(this.validate, this);
383             this.inputEl().on('keyup', this.filterValidation, this);
384         }
385         else if(this.validationEvent !== false){
386             this.inputEl().on(this.validationEvent, this.validate, this, {buffer: this.validationDelay});
387         }
388         
389         if(this.selectOnFocus){
390             this.on("focus", this.preFocus, this);
391             
392         }
393         if(this.maskRe || (this.vtype && this.disableKeyFilter !== true && (this.maskRe = Roo.form.VTypes[this.vtype+'Mask']))){
394             this.inputEl().on("keypress", this.filterKeys, this);
395         } else {
396             this.inputEl().relayEvent('keypress', this);
397         }
398         
399         var allowed = "0123456789";
400         
401         if(this.allowDecimals){
402             allowed += this.decimalSeparator;
403         }
404         
405         if(this.allowNegative){
406             allowed += "-";
407         }
408         
409         if(this.thousandsDelimiter) {
410             allowed += ",";
411         }
412         
413         this.stripCharsRe = new RegExp('[^'+allowed+']', 'gi');
414         
415         var keyPress = function(e){
416             
417             var k = e.getKey();
418             
419             var c = e.getCharCode();
420             
421             if(
422                     (String.fromCharCode(c) == '.' || String.fromCharCode(c) == '-') &&
423                     allowed.indexOf(String.fromCharCode(c)) === -1
424             ){
425                 e.stopEvent();
426                 return;
427             }
428             
429             if(!Roo.isIE && (e.isSpecialKey() || k == e.BACKSPACE || k == e.DELETE)){
430                 return;
431             }
432             
433             if(allowed.indexOf(String.fromCharCode(c)) === -1){
434                 e.stopEvent();
435             }
436         };
437         
438         this.inputEl().on("keypress", keyPress, this);
439         
440     },
441     
442     onTriggerClick : function(e)
443     {   
444         if(this.disabled){
445             return;
446         }
447         
448         this.page = 0;
449         this.loadNext = false;
450         
451         if(this.isExpanded()){
452             this.collapse();
453             return;
454         }
455         
456         this.hasFocus = true;
457         
458         if(this.triggerAction == 'all') {
459             this.doQuery(this.allQuery, true);
460             return;
461         }
462         
463         this.doQuery(this.getRawValue());
464     },
465     
466     getCurrency : function()
467     {   
468         var v = this.currencyEl().getValue();
469         
470         return v;
471     },
472     
473     restrictHeight : function()
474     {
475         this.list.alignTo(this.currencyEl(), this.listAlign);
476         this.list.alignTo(this.currencyEl(), this.listAlign);
477     },
478     
479     onViewClick : function(view, doFocus, el, e)
480     {
481         var index = this.view.getSelectedIndexes()[0];
482         
483         var r = this.store.getAt(index);
484         
485         if(r){
486             this.onSelect(r, index);
487         }
488     },
489     
490     onSelect : function(record, index){
491         
492         if(this.fireEvent('beforeselect', this, record, index) !== false){
493         
494             this.setFromCurrencyData(index > -1 ? record.data : false);
495             
496             this.collapse();
497             
498             this.fireEvent('select', this, record, index);
499         }
500     },
501     
502     setFromCurrencyData : function(o)
503     {
504         var currency = '';
505         
506         this.lastCurrency = o;
507         
508         if (this.currencyField) {
509             currency = !o || typeof(o[this.currencyField]) == 'undefined' ? '' : o[this.currencyField];
510         } else {
511             Roo.log('no  currencyField value set for '+ (this.name ? this.name : this.id));
512         }
513         
514         this.lastSelectionText = currency;
515         
516         //setting default currency
517         if(o[this.currencyField] * 1 == 0 && this.defaultCurrency) {
518             this.setCurrency(this.defaultCurrency);
519             return;
520         }
521         
522         this.setCurrency(currency);
523     },
524     
525     setFromData : function(o)
526     {
527         var c = {};
528         
529         c[this.currencyField] = !o || typeof(o[this.currencyName]) == 'undefined' ? '' : o[this.currencyName];
530         
531         this.setFromCurrencyData(c);
532         
533         var value = '';
534         
535         if (this.name) {
536             value = !o || typeof(o[this.name]) == 'undefined' ? '' : o[this.name];
537         } else {
538             Roo.log('no value set for '+ (this.name ? this.name : this.id));
539         }
540         
541         this.setValue(value);
542         
543     },
544     
545     setCurrency : function(v)
546     {   
547         this.currencyValue = v;
548         
549         if(this.rendered){
550             this.currencyEl().dom.value = (v === null || v === undefined ? '' : v);
551             this.validate();
552         }
553     },
554     
555     setValue : function(v)
556     {
557         v = String(this.fixPrecision(v)).replace(".", this.decimalSeparator);
558         
559         this.value = v;
560         
561         if(this.rendered){
562             
563             this.hiddenEl().dom.value = (v === null || v === undefined ? '' : v);
564             
565             this.inputEl().dom.value = (v == '') ? '' :
566                 Roo.util.Format.number(v, this.decimalPrecision, this.thousandsDelimiter || '');
567             
568             if(!this.allowZero && v === '0') {
569                 this.hiddenEl().dom.value = '';
570                 this.inputEl().dom.value = '';
571             }
572             
573             this.validate();
574         }
575     },
576     
577     getRawValue : function()
578     {
579         var v = this.inputEl().getValue();
580         
581         return v;
582     },
583     
584     getValue : function()
585     {
586         return this.fixPrecision(this.parseValue(this.getRawValue()));
587     },
588     
589     parseValue : function(value)
590     {
591         if(this.thousandsDelimiter) {
592             value += "";
593             r = new RegExp(",", "g");
594             value = value.replace(r, "");
595         }
596         
597         value = parseFloat(String(value).replace(this.decimalSeparator, "."));
598         return isNaN(value) ? '' : value;
599         
600     },
601     
602     fixPrecision : function(value)
603     {
604         if(this.thousandsDelimiter) {
605             value += "";
606             r = new RegExp(",", "g");
607             value = value.replace(r, "");
608         }
609         
610         var nan = isNaN(value);
611         
612         if(!this.allowDecimals || this.decimalPrecision == -1 || nan || !value){
613             return nan ? '' : value;
614         }
615         return parseFloat(value).toFixed(this.decimalPrecision);
616     },
617     
618     decimalPrecisionFcn : function(v)
619     {
620         return Math.floor(v);
621     },
622     
623     validateValue : function(value)
624     {
625         if(!Roo.bootstrap.MoneyField.superclass.validateValue.call(this, value)){
626             return false;
627         }
628         
629         var num = this.parseValue(value);
630         
631         if(isNaN(num)){
632             this.markInvalid(String.format(this.nanText, value));
633             return false;
634         }
635         
636         if(num < this.minValue){
637             this.markInvalid(String.format(this.minText, this.minValue));
638             return false;
639         }
640         
641         if(num > this.maxValue){
642             this.markInvalid(String.format(this.maxText, this.maxValue));
643             return false;
644         }
645         
646         return true;
647     },
648     
649     validate : function()
650     {
651         if(this.disabled || this.allowBlank){
652             this.markValid();
653             return true;
654         }
655         
656         var currency = this.getCurrency();
657         
658         if(this.validateValue(this.getRawValue()) && currency.length){
659             this.markValid();
660             return true;
661         }
662         
663         this.markInvalid();
664         return false;
665     },
666     
667     getName: function()
668     {
669         return this.name;
670     },
671     
672     beforeBlur : function()
673     {
674         if(!this.castInt){
675             return;
676         }
677         
678         var v = this.parseValue(this.getRawValue());
679         
680         if(v || v == 0){
681             this.setValue(v);
682         }
683     },
684     
685     onBlur : function()
686     {
687         this.beforeBlur();
688         
689         if(!Roo.isOpera && this.focusClass){ // don't touch in Opera
690             //this.el.removeClass(this.focusClass);
691         }
692         
693         this.hasFocus = false;
694         
695         if(this.validationEvent !== false && this.validateOnBlur && this.validationEvent != "blur"){
696             this.validate();
697         }
698         
699         var v = this.getValue();
700         
701         if(String(v) !== String(this.startValue)){
702             this.fireEvent('change', this, v, this.startValue);
703         }
704         
705         this.fireEvent("blur", this);
706     },
707     
708     inputEl : function()
709     {
710         return this.el.select('.roo-money-amount-input', true).first();
711     },
712     
713     currencyEl : function()
714     {
715         return this.el.select('.roo-money-currency-input', true).first();
716     },
717     
718     hiddenEl : function()
719     {
720         return this.el.select('input.hidden-number-input',true).first();
721     }
722     
723 });