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