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