better support for mailchimp emails
[roojs1] / Roo / bootstrap / form / PhoneInput.js
1 /**
2 *    This script refer to:
3 *    Title: International Telephone Input
4 *    Author: Jack O'Connor
5 *    Code version:  v12.1.12
6 *    Availability: https://github.com/jackocnr/intl-tel-input.git
7 **/
8
9 /**
10  * @class Roo.bootstrap.form.PhoneInput
11  * @extends Roo.bootstrap.form.TriggerField
12  * An input with International dial-code selection
13  
14  * @cfg {String} defaultDialCode default '+852'
15  * @cfg {Array} preferedCountries default []
16   
17  * @constructor
18  * Create a new PhoneInput.
19  * @param {Object} config Configuration options
20  */
21
22 Roo.bootstrap.form.PhoneInput = function(config) {
23     Roo.bootstrap.form.PhoneInput.superclass.constructor.call(this, config);
24 };
25
26 Roo.extend(Roo.bootstrap.form.PhoneInput, Roo.bootstrap.form.TriggerField, {
27         /**
28         * @cfg {Roo.data.Store} store [required] The data store to which this combo is bound (defaults to undefined)
29         */
30         listWidth: undefined,
31         
32         selectedClass: 'active',
33         
34         invalidClass : "has-warning",
35         
36         validClass: 'has-success',
37         
38         allowed: '0123456789',
39         
40         max_length: 15,
41         
42         /**
43          * @cfg {String} defaultDialCode The default dial code when initializing the input
44          */
45         defaultDialCode: '+852',
46         
47         /**
48          * @cfg {Array} preferedCountries A list of iso2 in array (e.g. ['hk','us']). Those related countries will show at the top of the input's choices
49          */
50         preferedCountries: false,
51         
52         getAutoCreate : function()
53         {
54             var data = Roo.bootstrap.form.PhoneInputData();
55             var align = this.labelAlign || this.parentLabelAlign();
56             var id = Roo.id();
57             
58             this.allCountries = [];
59             this.dialCodeMapping = [];
60             
61             for (var i = 0; i < data.length; i++) {
62               var c = data[i];
63               this.allCountries[i] = {
64                 name: c[0],
65                 iso2: c[1],
66                 dialCode: c[2],
67                 priority: c[3] || 0,
68                 areaCodes: c[4] || null
69               };
70               this.dialCodeMapping[c[2]] = {
71                   name: c[0],
72                   iso2: c[1],
73                   priority: c[3] || 0,
74                   areaCodes: c[4] || null
75               };
76             }
77             
78             var cfg = {
79                 cls: 'form-group',
80                 cn: []
81             };
82             
83             var input =  {
84                 tag: 'input',
85                 id : id,
86                 // type: 'number', -- do not use number - we get the flaky up/down arrows.
87                 maxlength: this.max_length,
88                 cls : 'form-control tel-input',
89                 autocomplete: 'new-password'
90             };
91             
92             var hiddenInput = {
93                 tag: 'input',
94                 type: 'hidden',
95                 cls: 'hidden-tel-input'
96             };
97             
98             if (this.name) {
99                 hiddenInput.name = this.name;
100             }
101             
102             if (this.disabled) {
103                 input.disabled = true;
104             }
105             
106             var flag_container = {
107                 tag: 'div',
108                 cls: 'flag-box',
109                 cn: [
110                     {
111                         tag: 'div',
112                         cls: 'flag'
113                     },
114                     {
115                         tag: 'div',
116                         cls: 'caret'
117                     }
118                 ]
119             };
120             
121             var box = {
122                 tag: 'div',
123                 cls: this.hasFeedback ? 'has-feedback' : '',
124                 cn: [
125                     hiddenInput,
126                     input,
127                     {
128                         tag: 'input',
129                         cls: 'dial-code-holder',
130                         disabled: true
131                     }
132                 ]
133             };
134             
135             var container = {
136                 cls: 'roo-select2-container input-group',
137                 cn: [
138                     flag_container,
139                     box
140                 ]
141             };
142             
143             if (this.fieldLabel.length) {
144                 var indicator = {
145                     tag: 'i',
146                     tooltip: 'This field is required'
147                 };
148                 
149                 var label = {
150                     tag: 'label',
151                     'for':  id,
152                     cls: 'control-label',
153                     cn: []
154                 };
155                 
156                 var label_text = {
157                     tag: 'span',
158                     html: this.fieldLabel
159                 };
160                 
161                 indicator.cls = 'roo-required-indicator text-danger fa fa-lg fa-star left-indicator';
162                 label.cn = [
163                     indicator,
164                     label_text
165                 ];
166                 
167                 if(this.indicatorpos == 'right') {
168                     indicator.cls = 'roo-required-indicator text-danger fa fa-lg fa-star right-indicator';
169                     label.cn = [
170                         label_text,
171                         indicator
172                     ];
173                 }
174                 
175                 if(align == 'left') {
176                     container = {
177                         tag: 'div',
178                         cn: [
179                             container
180                         ]
181                     };
182                     
183                     if(this.labelWidth > 12){
184                         label.style = "width: " + this.labelWidth + 'px';
185                     }
186                     if(this.labelWidth < 13 && this.labelmd == 0){
187                         this.labelmd = this.labelWidth;
188                     }
189                     if(this.labellg > 0){
190                         label.cls += ' col-lg-' + this.labellg;
191                         input.cls += ' col-lg-' + (12 - this.labellg);
192                     }
193                     if(this.labelmd > 0){
194                         label.cls += ' col-md-' + this.labelmd;
195                         container.cls += ' col-md-' + (12 - this.labelmd);
196                     }
197                     if(this.labelsm > 0){
198                         label.cls += ' col-sm-' + this.labelsm;
199                         container.cls += ' col-sm-' + (12 - this.labelsm);
200                     }
201                     if(this.labelxs > 0){
202                         label.cls += ' col-xs-' + this.labelxs;
203                         container.cls += ' col-xs-' + (12 - this.labelxs);
204                     }
205                 }
206             }
207             
208             cfg.cn = [
209                 label,
210                 container
211             ];
212             
213             var settings = this;
214             
215             ['xs','sm','md','lg'].map(function(size){
216                 if (settings[size]) {
217                     cfg.cls += ' col-' + size + '-' + settings[size];
218                 }
219             });
220             
221             this.store = new Roo.data.Store({
222                 proxy : new Roo.data.MemoryProxy({}),
223                 reader : new Roo.data.JsonReader({
224                     fields : [
225                         {
226                             'name' : 'name',
227                             'type' : 'string'
228                         },
229                         {
230                             'name' : 'iso2',
231                             'type' : 'string'
232                         },
233                         {
234                             'name' : 'dialCode',
235                             'type' : 'string'
236                         },
237                         {
238                             'name' : 'priority',
239                             'type' : 'string'
240                         },
241                         {
242                             'name' : 'areaCodes',
243                             'type' : 'string'
244                         }
245                     ]
246                 })
247             });
248             
249             if(!this.preferedCountries) {
250                 this.preferedCountries = [
251                     'hk',
252                     'gb',
253                     'us'
254                 ];
255             }
256             
257             var p = this.preferedCountries.reverse();
258             
259             if(p) {
260                 for (var i = 0; i < p.length; i++) {
261                     for (var j = 0; j < this.allCountries.length; j++) {
262                         if(this.allCountries[j].iso2 == p[i]) {
263                             var t = this.allCountries[j];
264                             this.allCountries.splice(j,1);
265                             this.allCountries.unshift(t);
266                         }
267                     } 
268                 }
269             }
270             
271             this.store.proxy.data = {
272                 success: true,
273                 data: this.allCountries
274             };
275             
276             return cfg;
277         },
278         
279         initEvents : function()
280         {
281             this.createList();
282             Roo.bootstrap.form.PhoneInput.superclass.initEvents.call(this);
283             
284             this.indicator = this.indicatorEl();
285             this.flag = this.flagEl();
286             this.dialCodeHolder = this.dialCodeHolderEl();
287             
288             this.trigger = this.el.select('div.flag-box',true).first();
289             this.trigger.on("click", this.onTriggerClick, this, {preventDefault:true});
290             
291             var _this = this;
292             
293             (function(){
294                 var lw = _this.listWidth || Math.max(_this.inputEl().getWidth(), _this.minListWidth);
295                 _this.list.setWidth(lw);
296             }).defer(100);
297             
298             this.list.on('mouseover', this.onViewOver, this);
299             this.list.on('mousemove', this.onViewMove, this);
300             this.inputEl().on("keyup", this.onKeyUp, this);
301             this.inputEl().on("keypress", this.onKeyPress, this);
302             
303             this.tpl = '<li><a href="#"><div class="flag {iso2}"></div>{name} <span class="dial-code">+{dialCode}</span></a></li>';
304
305             this.view = new Roo.View(this.list, this.tpl, {
306                 singleSelect:true, store: this.store, selectedClass: this.selectedClass
307             });
308             
309             this.view.on('click', this.onViewClick, this);
310             this.setValue(this.defaultDialCode);
311         },
312         
313         onTriggerClick : function(e)
314         {
315             Roo.log('trigger click');
316             if(this.disabled){
317                 return;
318             }
319             
320             if(this.isExpanded()){
321                 this.collapse();
322                 this.hasFocus = false;
323             }else {
324                 this.store.load({});
325                 this.hasFocus = true;
326                 this.expand();
327             }
328         },
329         
330         isExpanded : function()
331         {
332             return this.list.isVisible();
333         },
334         
335         collapse : function()
336         {
337             if(!this.isExpanded()){
338                 return;
339             }
340             this.list.hide();
341             Roo.get(document).un('mousedown', this.collapseIf, this);
342             Roo.get(document).un('mousewheel', this.collapseIf, this);
343             this.fireEvent('collapse', this);
344             this.validate();
345         },
346         
347         expand : function()
348         {
349             Roo.log('expand');
350
351             if(this.isExpanded() || !this.hasFocus){
352                 return;
353             }
354             
355             var lw = this.listWidth || Math.max(this.inputEl().getWidth(), this.minListWidth);
356             this.list.setWidth(lw);
357             
358             this.list.show();
359             this.restrictHeight();
360             
361             Roo.get(document).on('mousedown', this.collapseIf, this);
362             Roo.get(document).on('mousewheel', this.collapseIf, this);
363             
364             this.fireEvent('expand', this);
365         },
366         
367         restrictHeight : function()
368         {
369             this.list.alignTo(this.inputEl(), this.listAlign);
370             this.list.alignTo(this.inputEl(), this.listAlign);
371         },
372         
373         onViewOver : function(e, t)
374         {
375             if(this.inKeyMode){
376                 return;
377             }
378             var item = this.view.findItemFromChild(t);
379             
380             if(item){
381                 var index = this.view.indexOf(item);
382                 this.select(index, false);
383             }
384         },
385
386         // private
387         onViewClick : function(view, doFocus, el, e)
388         {
389             var index = this.view.getSelectedIndexes()[0];
390             
391             var r = this.store.getAt(index);
392             
393             if(r){
394                 this.onSelect(r, index);
395             }
396             if(doFocus !== false && !this.blockFocus){
397                 this.inputEl().focus();
398             }
399         },
400         
401         onViewMove : function(e, t)
402         {
403             this.inKeyMode = false;
404         },
405         
406         select : function(index, scrollIntoView)
407         {
408             this.selectedIndex = index;
409             this.view.select(index);
410             if(scrollIntoView !== false){
411                 var el = this.view.getNode(index);
412                 if(el){
413                     this.list.scrollChildIntoView(el, false);
414                 }
415             }
416         },
417         
418         createList : function()
419         {
420             this.list = Roo.get(document.body).createChild({
421                 tag: 'ul',
422                 cls: 'typeahead typeahead-long dropdown-menu tel-list',
423                 style: 'display:none'
424             });
425             
426             this.list.setVisibilityMode(Roo.Element.DISPLAY).originalDisplay = 'block';
427         },
428         
429         collapseIf : function(e)
430         {
431             var in_combo  = e.within(this.el);
432             var in_list =  e.within(this.list);
433             var is_list = (Roo.get(e.getTarget()).id == this.list.id) ? true : false;
434             
435             if (in_combo || in_list || is_list) {
436                 return;
437             }
438             this.collapse();
439         },
440         
441         onSelect : function(record, index)
442         {
443             if(this.fireEvent('beforeselect', this, record, index) !== false){
444                 
445                 this.setFlagClass(record.data.iso2);
446                 this.setDialCode(record.data.dialCode);
447                 this.hasFocus = false;
448                 this.collapse();
449                 this.fireEvent('select', this, record, index);
450             }
451         },
452         
453         flagEl : function()
454         {
455             var flag = this.el.select('div.flag',true).first();
456             if(!flag){
457                 return false;
458             }
459             return flag;
460         },
461         
462         dialCodeHolderEl : function()
463         {
464             var d = this.el.select('input.dial-code-holder',true).first();
465             if(!d){
466                 return false;
467             }
468             return d;
469         },
470         
471         setDialCode : function(v)
472         {
473             this.dialCodeHolder.dom.value = '+'+v;
474         },
475         
476         setFlagClass : function(n)
477         {
478             this.flag.dom.className = 'flag '+n;
479         },
480         
481         getValue : function()
482         {
483             var v = this.inputEl().getValue();
484             if(this.dialCodeHolder) {
485                 v = this.dialCodeHolder.dom.value+this.inputEl().getValue();
486             }
487             return v;
488         },
489         
490         setValue : function(v)
491         {
492             var d = this.getDialCode(v);
493             
494             //invalid dial code
495             if(v.length == 0 || !d || d.length == 0) {
496                 if(this.rendered){
497                     this.inputEl().dom.value = (v === null || v === undefined ? '' : v);
498                     this.hiddenEl().dom.value = (v === null || v === undefined ? '' : v);
499                 }
500                 return;
501             }
502             
503             //valid dial code
504             this.setFlagClass(this.dialCodeMapping[d].iso2);
505             this.setDialCode(d);
506             this.inputEl().dom.value = v.replace('+'+d,'');
507             this.hiddenEl().dom.value = this.getValue();
508             
509             this.validate();
510         },
511         
512         getDialCode : function(v)
513         {
514             v = v ||  '';
515             
516             if (v.length == 0) {
517                 return this.dialCodeHolder.dom.value;
518             }
519             
520             var dialCode = "";
521             if (v.charAt(0) != "+") {
522                 return false;
523             }
524             var numericChars = "";
525             for (var i = 1; i < v.length; i++) {
526               var c = v.charAt(i);
527               if (!isNaN(c)) {
528                 numericChars += c;
529                 if (this.dialCodeMapping[numericChars]) {
530                   dialCode = v.substr(1, i);
531                 }
532                 if (numericChars.length == 4) {
533                   break;
534                 }
535               }
536             }
537             return dialCode;
538         },
539         
540         reset : function()
541         {
542             this.setValue(this.defaultDialCode);
543             this.validate();
544         },
545         
546         hiddenEl : function()
547         {
548             return this.el.select('input.hidden-tel-input',true).first();
549         },
550         
551         // after setting val
552         onKeyUp : function(e){
553             this.setValue(this.getValue());
554         },
555         
556         onKeyPress : function(e){
557             if(this.allowed.indexOf(String.fromCharCode(e.getCharCode())) === -1){
558                 e.stopEvent();
559             }
560         }
561         
562 });