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