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