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