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