Roo/bootstrap/PhoneInput.js
[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',
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             
300             this.tpl = '<li><a href="#"><div class="flag {iso2}"></div>{name} <span class="dial-code">+{dialCode}</span></a></li>';
301
302             this.view = new Roo.View(this.list, this.tpl, {
303                 singleSelect:true, store: this.store, selectedClass: this.selectedClass
304             });
305             
306             this.view.on('click', this.onViewClick, this);
307             this.setValue(this.defaultDialCode);
308         },
309         
310         onTriggerClick : function(e)
311         {
312             Roo.log('trigger click');
313             if(this.disabled){
314                 return;
315             }
316             
317             if(this.isExpanded()){
318                 this.collapse();
319                 this.hasFocus = false;
320             }else {
321                 this.store.load({});
322                 this.hasFocus = true;
323                 this.expand();
324             }
325         },
326         
327         isExpanded : function()
328         {
329             return this.list.isVisible();
330         },
331         
332         collapse : function()
333         {
334             if(!this.isExpanded()){
335                 return;
336             }
337             this.list.hide();
338             Roo.get(document).un('mousedown', this.collapseIf, this);
339             Roo.get(document).un('mousewheel', this.collapseIf, this);
340             this.fireEvent('collapse', this);
341             this.validate();
342         },
343         
344         expand : function()
345         {
346             Roo.log('expand');
347
348             if(this.isExpanded() || !this.hasFocus){
349                 return;
350             }
351             
352             var lw = this.listWidth || Math.max(this.inputEl().getWidth(), this.minListWidth);
353             this.list.setWidth(lw);
354             
355             this.list.show();
356             this.restrictHeight();
357             
358             Roo.get(document).on('mousedown', this.collapseIf, this);
359             Roo.get(document).on('mousewheel', this.collapseIf, this);
360             
361             this.fireEvent('expand', this);
362         },
363         
364         restrictHeight : function()
365         {
366             this.list.alignTo(this.inputEl(), this.listAlign);
367             this.list.alignTo(this.inputEl(), this.listAlign);
368         },
369         
370         onViewOver : function(e, t)
371         {
372             if(this.inKeyMode){
373                 return;
374             }
375             var item = this.view.findItemFromChild(t);
376             
377             if(item){
378                 var index = this.view.indexOf(item);
379                 this.select(index, false);
380             }
381         },
382
383         // private
384         onViewClick : function(view, doFocus, el, e)
385         {
386             var index = this.view.getSelectedIndexes()[0];
387             
388             var r = this.store.getAt(index);
389             
390             if(r){
391                 this.onSelect(r, index);
392             }
393             if(doFocus !== false && !this.blockFocus){
394                 this.inputEl().focus();
395             }
396         },
397         
398         onViewMove : function(e, t)
399         {
400             this.inKeyMode = false;
401         },
402         
403         select : function(index, scrollIntoView)
404         {
405             this.selectedIndex = index;
406             this.view.select(index);
407             if(scrollIntoView !== false){
408                 var el = this.view.getNode(index);
409                 if(el){
410                     this.list.scrollChildIntoView(el, false);
411                 }
412             }
413         },
414         
415         createList : function()
416         {
417             this.list = Roo.get(document.body).createChild({
418                 tag: 'ul',
419                 cls: 'typeahead typeahead-long dropdown-menu tel-list',
420                 style: 'display:none'
421             });
422             
423             this.list.setVisibilityMode(Roo.Element.DISPLAY).originalDisplay = 'block';
424         },
425         
426         collapseIf : function(e)
427         {
428             var in_combo  = e.within(this.el);
429             var in_list =  e.within(this.list);
430             var is_list = (Roo.get(e.getTarget()).id == this.list.id) ? true : false;
431             
432             if (in_combo || in_list || is_list) {
433                 return;
434             }
435             this.collapse();
436         },
437         
438         onSelect : function(record, index)
439         {
440             if(this.fireEvent('beforeselect', this, record, index) !== false){
441                 
442                 this.setFlagClass(record.data.iso2);
443                 this.setDialCode(record.data.dialCode);
444                 this.hasFocus = false;
445                 this.collapse();
446                 this.fireEvent('select', this, record, index);
447             }
448         },
449         
450         flagEl : function()
451         {
452             var flag = this.el.select('div.flag',true).first();
453             if(!flag){
454                 return false;
455             }
456             return flag;
457         },
458         
459         dialCodeHolderEl : function()
460         {
461             var d = this.el.select('input.dial-code-holder',true).first();
462             if(!d){
463                 return false;
464             }
465             return d;
466         },
467         
468         setDialCode : function(v)
469         {
470             this.dialCodeHolder.dom.value = '+'+v;
471         },
472         
473         setFlagClass : function(n)
474         {
475             this.flag.dom.className = 'flag '+n;
476         },
477         
478         getValue : function()
479         {
480             var v = this.inputEl().getValue();
481             if(this.dialCodeHolder) {
482                 v = this.dialCodeHolder.dom.value+this.inputEl().getValue();
483             }
484             return v;
485         },
486         
487         setValue : function(v)
488         {
489             var d = this.getDialCode(v);
490             
491             //invalid dial code
492             if(v.length == 0 || !d || d.length == 0) {
493                 if(this.rendered){
494                     this.inputEl().dom.value = (v === null || v === undefined ? '' : v);
495                     this.hiddenEl().dom.value = (v === null || v === undefined ? '' : v);
496                 }
497                 return;
498             }
499             
500             //valid dial code
501             this.setFlagClass(this.dialCodeMapping[d].iso2);
502             this.setDialCode(d);
503             this.inputEl().dom.value = v.replace('+'+d,'');
504             this.hiddenEl().dom.value = this.getValue();
505             
506             this.validate();
507         },
508         
509         getDialCode : function(v)
510         {
511             v = v ||  '';
512             
513             if (v.length == 0) {
514                 return this.dialCodeHolder.dom.value;
515             }
516             
517             var dialCode = "";
518             if (v.charAt(0) != "+") {
519                 return false;
520             }
521             var numericChars = "";
522             for (var i = 1; i < v.length; i++) {
523               var c = v.charAt(i);
524               if (!isNaN(c)) {
525                 numericChars += c;
526                 if (this.dialCodeMapping[numericChars]) {
527                   dialCode = v.substr(1, i);
528                 }
529                 if (numericChars.length == 4) {
530                   break;
531                 }
532               }
533             }
534             return dialCode;
535         },
536         
537         reset : function()
538         {
539             this.setValue(this.defaultDialCode);
540             this.validate();
541         },
542         
543         hiddenEl : function()
544         {
545             return this.el.select('input.hidden-tel-input',true).first();
546         },
547         
548         onKeyUp : function(e){
549             
550             if(this.allowed.indexOf(String.fromCharCode(e.getCharCode())) === -1){
551                 e.stopEvent();
552             }
553             
554             this.setValue(this.getValue());
555         }
556         
557 });