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