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