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