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         listWidth: undefined,
29         
30         selectedClass: 'active',
31         
32         invalidClass : "has-warning",
33         
34         validClass: 'has-success',
35         
36         allowed: '0123456789',
37         
38         defaultDialCode: '+852',
39         
40         preferedCountries: false,
41         
42         //white list / black list for countries?
43         
44         getAutoCreate : function()
45         {
46             var data = Roo.bootstrap.PhoneInputData();
47             var align = this.labelAlign || this.parentLabelAlign();
48             var id = Roo.id();
49             
50             this.allCountries = [];
51             this.dialCodeMapping = [];
52             
53             for (var i = 0; i < data.length; i++) {
54               var c = data[i];
55               this.allCountries[i] = {
56                 name: c[0],
57                 iso2: c[1],
58                 dialCode: c[2],
59                 priority: c[3] || 0,
60                 areaCodes: c[4] || null
61               };
62               this.dialCodeMapping[c[2]] = {
63                   name: c[0],
64                   iso2: c[1],
65                   priority: c[3] || 0,
66                   areaCodes: c[4] || null
67               };
68             }
69             
70             var cfg = {
71                 cls: 'form-group',
72                 cn: []
73             };
74             
75             var input =  {
76                 tag: 'input',
77                 id : id,
78                 cls : 'form-control tel-input',
79                 autocomplete: 'new-password'
80             };
81             
82             var hiddenInput = {
83                 tag: 'input',
84                 type: 'hidden',
85                 cls: 'hidden-tel-input'
86             };
87             
88             if (this.name) {
89                 hiddenInput.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                     hiddenInput,
116                     input,
117                     {
118                         tag: 'input',
119                         cls: 'dial-code-holder',
120                         disabled: true
121                     }
122                 ]
123             };
124             
125             var container = {
126                 cls: 'roo-select2-container input-group',
127                 cn: [
128                     flag_container,
129                     box
130                 ]
131             };
132             
133             if (this.fieldLabel.length) {
134                 var indicator = {
135                     tag: 'i',
136                     tooltip: 'This field is required'
137                 };
138                 
139                 var label = {
140                     tag: 'label',
141                     'for':  id,
142                     cls: 'control-label',
143                     cn: []
144                 };
145                 
146                 var label_text = {
147                     tag: 'span',
148                     html: this.fieldLabel
149                 };
150                 
151                 indicator.cls = 'roo-required-indicator text-danger fa fa-lg fa-star left-indicator';
152                 label.cn = [
153                     indicator,
154                     label_text
155                 ];
156                 
157                 if(this.indicatorpos == 'right') {
158                     indicator.cls = 'roo-required-indicator text-danger fa fa-lg fa-star right-indicator';
159                     label.cn = [
160                         label_text,
161                         indicator
162                     ];
163                 }
164                 
165                 if(align == 'left') {
166                     container = {
167                         tag: 'div',
168                         cn: [
169                             container
170                         ]
171                     };
172                     
173                     if(this.labelWidth > 12){
174                         label.style = "width: " + this.labelWidth + 'px';
175                     }
176                     if(this.labelWidth < 13 && this.labelmd == 0){
177                         this.labelmd = this.labelWidth;
178                     }
179                     if(this.labellg > 0){
180                         label.cls += ' col-lg-' + this.labellg;
181                         input.cls += ' col-lg-' + (12 - this.labellg);
182                     }
183                     if(this.labelmd > 0){
184                         label.cls += ' col-md-' + this.labelmd;
185                         container.cls += ' col-md-' + (12 - this.labelmd);
186                     }
187                     if(this.labelsm > 0){
188                         label.cls += ' col-sm-' + this.labelsm;
189                         container.cls += ' col-sm-' + (12 - this.labelsm);
190                     }
191                     if(this.labelxs > 0){
192                         label.cls += ' col-xs-' + this.labelxs;
193                         container.cls += ' col-xs-' + (12 - this.labelxs);
194                     }
195                 }
196             }
197             
198             cfg.cn = [
199                 label,
200                 container
201             ];
202             
203             var settings = this;
204             
205             ['xs','sm','md','lg'].map(function(size){
206                 if (settings[size]) {
207                     cfg.cls += ' col-' + size + '-' + settings[size];
208                 }
209             });
210             
211             this.store = new Roo.data.Store({
212                 proxy : new Roo.data.MemoryProxy({}),
213                 reader : new Roo.data.JsonReader({
214                     fields : [
215                         {
216                             'name' : 'name',
217                             'type' : 'string'
218                         },
219                         {
220                             'name' : 'iso2',
221                             'type' : 'string'
222                         },
223                         {
224                             'name' : 'dialCode',
225                             'type' : 'string'
226                         },
227                         {
228                             'name' : 'priority',
229                             'type' : 'string'
230                         },
231                         {
232                             'name' : 'areaCodes',
233                             'type' : 'string'
234                         }
235                     ]
236                 })
237             });
238             
239             if(!this.preferedCountries) {
240                 this.preferedCountries = [
241                     'hk',
242                     'gb',
243                     'us'
244                 ];
245             }
246             
247             var p = this.preferedCountries.reverse();
248             
249             if(p) {
250                 for (var i = 0; i < p.length; i++) {
251                     for (var j = 0; j < this.allCountries.length; j++) {
252                         if(this.allCountries[j].iso2 == p[i]) {
253                             var t = this.allCountries[j];
254                             this.allCountries.splice(j,1);
255                             this.allCountries.unshift(t);
256                         }
257                     } 
258                 }
259             }
260             
261             this.store.proxy.data = {
262                 success: true,
263                 data: this.allCountries
264             };
265             
266             return cfg;
267         },
268         
269         initEvents : function()
270         {
271             this.createList();
272             Roo.bootstrap.PhoneInput.superclass.initEvents.call(this);
273             
274             this.indicator = this.indicatorEl();
275             this.flag = this.flagEl();
276             this.dialCodeHolder = this.dialCodeHolderEl();
277             
278             this.trigger = this.el.select('div.flag-box',true).first();
279             this.trigger.on("click", this.onTriggerClick, this, {preventDefault:true});
280             
281             var _this = this;
282             
283             (function(){
284                 var lw = _this.listWidth || Math.max(_this.inputEl().getWidth(), _this.minListWidth);
285                 _this.list.setWidth(lw);
286             }).defer(100);
287             
288             this.list.on('mouseover', this.onViewOver, this);
289             this.list.on('mousemove', this.onViewMove, this);
290             this.inputEl().on("keypress", this.onKeyPress, 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             
482             //invalid dial code
483             if(v.length == 0 || !d || d.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                 return;
489             }
490             
491             //valid dial code
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             this.validate();
530         },
531         
532         hiddenEl : function()
533         {
534             return this.el.select('input.hidden-tel-input',true).first();
535         },
536         
537         onKeyPress : function(e){
538             
539             var k = e.getKey();
540             var c = e.getCharCode();
541             
542             if(
543                     (String.fromCharCode(c) == '.' || String.fromCharCode(c) == '-') &&
544                     this.allowed.indexOf(String.fromCharCode(c)) === -1
545             ){
546                 e.stopEvent();
547                 return;
548             }
549             
550             if(!Roo.isIE && (e.isSpecialKey() || k == e.BACKSPACE || k == e.DELETE)){
551                 return;
552             }
553             if(this.allowed.indexOf(String.fromCharCode(c)) === -1){
554                 e.stopEvent();
555             }
556             
557             this.setValue(this.getValue());
558         }
559         
560 });