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