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