Виджет выбора кода страны при вводе телефона на jQuery

2014-05-28 в 00:16 JavaScript jQuery Projects Widget

Отвлечемся от разработки PHP фреймворка на создание небольшого интересного jQuery UI виджета. Для тех, кто любит приукрасить свой сайт красиво-полезными примочками, или кому по работе сумасшедшее начальство просит сделать очередную неведомую хуйню – данная статья может быть вполне полезна. Итак пишем виджет для поля ввода телефонного номера с красивым выбором кода страны (прям как в скайпе, только лучше)

skype phone number widget


Суть виджета предельно проста - вешаем на голый инпут выбор телефонного кода страны, дополняя выбор красивым выпадающим списком с поиском. Также виджет автоматически меняет код при вводе в поле номера, начинающегося с «+». Плюс, если value поля непустое, и начинается с «+», то определяем код страны на основе уже введенного телефона.

Приступим!

Для начала, чтобы сделать все красиво – берем спрайт с флагами стран:

flags

И подбираем по него CSS:


    .flag.flag-ad {background-position: -16px 0}
    .flag.flag-ae {background-position: -32px 0}
    .flag.flag-af {background-position: -48px 0}
    .flag.flag-ag {background-position: -64px 0}
    .flag.flag-ai {background-position: -80px 0}
    .flag.flag-al {background-position: -96px 0}
    .flag.flag-am {background-position: -112px 0}
    .flag.flag-an {background-position: -128px 0}
    ....

Еще нам потребуется json массив со списком стран:


[
    {"co":"au","ph":"61","na":"Австрия"},
    {"co":"at","ph":"43","na":"Австралия"},
    ...
]

Теперь начинаем писать сам виджет:


(function($) {
    $.widget('custom.phonecode', {
        options: {
            default_prefix: '',
            prefix: '',
            preferCo: 'ru'
        },
        _create: function() {

        },
    });
})(jQuery);

Как видим, наш виджет будет называться phonecode. Далее идем по шагам, что требуется, чтобы отрисовать виджет:


1. Загрузка списка стран


_loadData : function() {
    var self = this;
    if(!countryCache && !countryRequesting) {
        countryRequesting = $.getJSON('countries.json', {})
            .done(function(json) {
                self.data = json;
                countryCache = self.data;
                self._initSelector();
            })
            .fail(function(xhr, status, error) {
                self.data = countries;
                countryCache = self.data;
                self._initSelector();
            });
    }
    else if(countryCache) {
        this.data = countryCache;
        self._initSelector();
    }
    else if(countryRequesting) {
        countryRequesting.done(function(json) {
            self.data = json;
            countryCache = self.data;
            self._initSelector();
        });
    }
}

Для загрузки данных пишем метод _loadData, сразу обращу внимание, что тут мы используем 2 глобальный переменных: countryRequesting (для хранения объекта xhr запроса) и countryCache (для хранения результатов этого запроса). Это нужно, чтобы при инициализации двух виджетов на странице не происходило нескольких запросов на загрузку данных. Также внимательный читатель заметит, что при фейле getJSON запроса будет использована и треться глобальная переменная – countries. Она здесь исключительно для того, чтобы пример работа без ajax-запроса, т.е. без веб-сервера (достигается это путем сохранения вышеописанного json массива со списком стран в этой переменной)

В остальном в этом методе ничего сложного нет, его мы вызываем прямо внутри _create. После загрузки данных вызывается метод _initSelector


2. Отрисовка выбиралки страны с кодом

Но здесь кроется маленькая хитрость. В теле _create() после вызова _loadData() сразу выполняем такой код следующий код. Он выполнится до завершения ajax-запроса на загрузку данных и подготовит небольшой layout начего виджета:


    this.element.wrap('<div class="country-phone">');
    var container = this.element.parent('.country-phone');
    var selector = $('<div class="country-phone-selector"><div class="country-phone-selected"></div><div class="country-phone-options"></div></div>');
    $(selector).prependTo(container);

    var prefixName = this.options.prefix ?
    this.options.prefix : '__phone_prefix';
    var hidden = $('<input type="hidden" name="'+ prefixName +'" value="'+ this.options.default_prefix +'">');
    $(hidden).appendTo(container);

    this.container = container;
    this.prefixField = hidden;

Тут мы просто оборачиваем переданный нам input в пачку блоков, добавляем hidden поле для хранения префикса (имя этого поле можно передать в опции виджета prefix). Этот лейаут будет уже непосредственно использоваться в методе _initSelector(). Метод довольно большой, поэтому опишу его кратко, опустив пару простых обработчиков


    var options = this.container.find('.country-phone-options'); // берем блок с опциями выбора
    var selector = this.container.find('.country-phone-selected');
    var selected = null;
    var self = this;
    var searchInput = $('<input type="text" class="country-phone-search" value="">');
    $(searchInput).appendTo(options); // создаем инпут для поиска страны
    $(searchInput).bind('keyup', function(e){
        // ... вешаем код, который будет выполнять переключение опций по нажатию стрелок и return
    });
    // дальше рисуем опции выбора
    for(var i = 0; i < this.data.length; i++) {
        if(i == 0) {
            selected = this.data[i];
        }
        var country = this.data[i];
        var prefCountry = country.co;
        var option = $('<div data-phone="'+
                        country.ph + '" data-co="'+ prefCountry.toLowerCase() +'"' +
        ' class="country-phone-option"><span>+'+ country.ph +'<img src="blank.gif" class="flag flag-'+
                        country.co +
                        '"></span>'+ country.na +'</div>'
        );
        $(option).appendTo(options);
        if(this.options.preferCo && (this.options.preferCo != undefined)) {
            if(prefCountry == this.options.preferCo) {
                selected = country;
            }
        }
        else {
            if(country.ph == this.options.default_prefix) {
                selected = country;
            }
        }
    }
    if(selected) {
        this.container.find('.country-phone-selected')
            .html('<img src="blank.gif" class="flag flag-'+ selected.co +'">+'+ selected.ph);
    }
    // добавляем обработчики пользовательских действий
    $(selector).bind('click', function(e){
        self._toggleSelector(); // показываем/скрываем выбор при клике на виджет
    });
    $(options).find('.country-phone-option').bind('click', function(){
        self.setElementSelected(this); // при клики на опцию выбора делаем ее выбранной
        self._toggleSelector(); // и прячем список опций
    });
    $(options).hover(function(){
        // ... и добавляем таймаут на наведение мыши, чтобы автоматически скрывать выбор
    });

    // и в конце инициализируем уже исеющееся в инпуте значение
    this._initInput();

Выше можно заменить вызов еще неописанных методов виджета: _toggleSelector(), setElementSelected(el). Это непосредственно обработчики пользовательских событий, начем их описание:


3. Обработка событий

JavaScript кардинально отличается от серверных языков, тем что очень тесно завязан на взаимодействие с пользователем. За это мы его любим и ненавидим. Первый обработчик события клика для показа выпадающего списка стран, очень простой обработчик:


    _toggleSelector: function(){
        var options = this.container.find('.country-phone-options');
        if($(options).is(':visible')) {
            // если список показан – убираем его
            $(options).hide('fast');
            $(options).find('.country-phone-search').val('').blur(); // убиаем фокус из поля поиска
            this.element.focus(); // возвращаем фокус в поле ввода телефона
            this.suggestCountry(''); // очищаем историю поиска стран
        }
        else {
            // иначе все наоборот :-)
            $(options).show('fast');
            window.setTimeout(function(){
                var searchInp = $(options).find('.country-phone-search');
                $(searchInp).val('').focus();
            }, 300);
        }
    },

Следующий обработчик – при клики на страну, нужно установить выбранный код страны и флаг. В качестве аргумента получает div, являющися элементом списка выбора стран.


    setElementSelected: function(el) {
        var selector = this.container.find('.country-phone-selected');
        var code = $(el).data('phone'); // код страны берем из дата-аттрибута
        var sel = $(el).html();
        sel = sel.split('</span>'); // вытаскиваем флаг их кода опции
        $(selector).html(sel[0] +'</span>'); // отмечаем опцию как выбранную
        this.prefixField.val(code); // записываем код старны в интпут для префикса

        return code;
    },

Осталась совсем маленька часть – реализация поиска страны и переключение между опциями с помощью клавиатуры, но так как статья уже очень длинная и мне хочется спать, расписывать я их не буду: смотрить код в примере и на github по ссыкам ниже. Вот так за 3 простых шага можно запилить крутой виджет для ввода телефона


Репозиторий на Github     Живой пример (Demo)

P.S.: на самом деле начальство, заставившее меня писать данный виджет очень хорошее :-), такие задачи помогают пораскинуть мозгами, сделать что-то новое, не совсем обычное – короче, отвлечься от рутиных задач программиста. Всем приятного кодинга.

Виджет активно используется в одном из моих проектов: Флорист.ру


Автор   

Комментарии (14)    написать


Jacob Akulov

В следующих статьях про JavaScript планирую рассказать о создании виджета для отображения у себя на сайте ближайших терминалов Qiwi на карте.

 Ответить   


Никита Кубаев

За статью и виджет огромное спасибо.
Мне, как человеку с довольно небольшим опытом, этот блог очень сильно помог. Думаю, поможет и людям более опытным.
Буду читать и дальше :)

 Ответить   


как можна поменять язык на английский ?

 Ответить   


Yakov Akulov

: если нужен чисто английский вариант, достаточно заменить countries.json на аналогичный файл с наименованиями стран на английском, думаю сложностей не должно составить.

 Ответить      


Yakov Akulov: вы можете подсказать где можна скачать английский countries.json файл , или мне ручную надо поменять "\u0410\u0432\u0441\u0442\u0440\u0430\u043b\u0438\u044f"
"Australia"
?

 Ответить      


Yakov Akulov

: к сожалению, у меня нет готовой версии на английском, можно сделать вручную. Вот есть табличка, из которой я делал json-файл.
jakulov.ru/public/e/phonecode/countries.html

 Ответить      


any wew

Thanks a lot!

 Ответить   


Комментарий удален


Комментарий удален


Комментарий удален


Алексей Погорелов

подключил ваш виджет к модулю заказа на опенкарте. только вот данные кода не передаются в заказ...не подскажете?

 Ответить   


Yakov Akulov

Алексей Погорелов: привет, Алексей.
Насколько помню в реализации виджета, код страны по-умолчанию записывается в инпут с именем "__phone_prefix", это имя можно изменить параметром виджета "prefix" задав нужное имя инпута.
А на сервере придется добавить обработчик дополнительный.

Надо будет сделать дополнение, в котором решу эту проблему

 Ответить      


Сергей

Добрый день. Пробовал реализовать 2 и более форм на одной странице с вашим виджетом, на сколько понял, работать будет только один, 1й где было выведено поле.

 Ответить   


Владимир Тухляков

Добрый день! Подключил ваш виджет . Но у меня возникла небольшая проблема все названия стран отображаются по середине, как их выровнять по левому краю?

 Ответить   



Написать комментарий:

Чтобы комментрировать укажите свои данные или войдите через один из социальных сервисов

Загрузка...