/* * Jquery UI city selector widget. * * Depends on: * jquery.core.js v1.7 * jquery.ui.core.js v1.8 * jquery.ui.widget.js v1.8 * * You can grab the rest of the code at https://bitbucket.org/JustDelight/city_selector */ (function($) { "use strict"; var META = { RES: { COUNTRIES : 'countries', REGIONS : 'regions', CITIES : 'cities' } }, CachItem = { store: function (data) { if (!this.isNotEmpty()) { this.data = data Object.freeze(this) } }, isNotEmpty: function () { return Object.isFrozen(this) } }, AbstractLoader = { cache: {}, queues: {}, _getNestedItem: function (obj, descriptor) { var nested = obj; descriptor.forEach(function (item) { if (!(item in nested)) { nested[item] = {} } nested = nested[item] }) return nested }, _getQueue: function (descriptor) { var last = descriptor.pop(), nested = this._getNestedItem(this.queues, descriptor); if (!(nested[last] instanceof Array)) { nested[last] = [] } return nested[last] }, getCacheItem: function (descriptor) { var item = this._getNestedItem(this.cache, descriptor); if (item.__proto__ != CachItem) { item.__proto__ = CachItem; } return item; }, _getData: function (descriptor, callback, path_item) { var res, queue, cache_item = this.getCacheItem(descriptor); if (cache_item.isNotEmpty()) { callback.call(null, cache_item.data) } else { queue = this._getQueue(descriptor) if (queue.push(callback) == 1) { $.ajax({ context: this, url: this.CONFIG.base_url + path_item, dataType: 'json', success: function (data, status, xhr) { var callback; while (callback = queue.pop()) { callback.call(null, data) } cache_item.store(data) }, error: function (e) { console.error(e) } }) } } }, }, GeoItemsLoader = { CONFIG: { base_url: '/some_path/' }, __proto__: AbstractLoader, load: function (resource_description, callback) { this._getData(resource_description, callback, resource_description.join('/') + '/') } }; $.widget("ui.city_selector", { options : { position: 'bottom', base_url: '/select/', locale: 'ru' }, strings_pack: { 'ru': { city: 'Город', region: 'Регион', country: 'Страна', prompt: 'выберите город' }, 'en': { city: 'City', region: 'Region', country: 'Country', prompt: 'select the city' } }, _create: function () { this._setLocale(this.options.locale) GeoItemsLoader.CONFIG.base_url = this.options.base_url this._buildWidget() if ('data' in this.options) { this.options.data.forEach(function (item) { GeoItemsLoader.getCacheItem(item[0]).store(item[1]) }) } if ('initial' in this.options) { this._loadInitialData('data' in this.options) } this._loadCountires() this._kbdHideHandler = this._handleDocMouseDown.bind(this) this._mouseHideHandler = this._handleDocKeyPress.bind(this) }, destroy: function () { //TODO: implement it! }, _setOption: function(option, value) { if (option == 'locale') { this._setLocale(value) } else if ((option == 'position') && ((value != 'top') || (value != 'bottom'))) { throw 'unsupported postion' } this.options[option] = value }, _: function(name) { return this._localeStrings[name] }, _setLocale: function(locale) { if (!(locale in this.strings_pack)) { throw 'Unsupported locale: ' + locale } else { this._localeStrings = this.strings_pack[locale] } }, _loadInitialData: function () { var res, value; for (res in this.options['initial']) { value = this.options['initial'][res] $(this).bind(res, (function (res, value, e) { $(this).unbind(e) this._setSelectedValue(res, value) }).bind(this, res, value)) } }, _buildWidget: function () { var title = this.element.attr('value') ? this.element.attr('value') : this._('prompt'), value, name, id, comp_style, show_handler = (function () { this._root.is(":visible") ? this.hide() : this.show() }).bind(this), country_change_handler = this._loadRegions.bind(this), region_change_handler = this._loadCities.bind(this); this.anchor = $('' + title + ''); this.element.after(this.anchor) this.element.hide() this.anchor.click(show_handler) this.anchor.addClass(this.options.position == 'top' ? 'bottom' : 'top') this._root = $('
').addClass("ui-widget ui-widget-content ui-city_celector").appendTo($(document.body)).hide() this._selects = {} this._selects[META.RES.COUNTRIES] = $('').attr('name', 'country').appendTo(this._root) this._selects[META.RES.REGIONS] = $('').attr('name', 'region').appendTo(this._root).hide() this._selects[META.RES.CITIES] = $('').attr('name', 'city').appendTo(this._root).hide() this._selects[META.RES.COUNTRIES].change(function (e) { country_change_handler($(this).attr('value')) }) this._selects[META.RES.REGIONS].change(function (e) { region_change_handler($(this).attr('value')) }) this._selects[META.RES.CITIES].change(this._setValue.bind(this)) }, show: function () { var root_element = this._root, root_style = { left: this.anchor.offset().left }; switch (this.options.position) { case 'bottom': root_style.top = parseInt(this.anchor.offset().top) - 1 root_style.paddingTop = this.anchor.outerHeight() break; case 'top': root_style.bottom = parseInt($(document).height() - this.anchor.offset().top - this.anchor.outerHeight()) root_style.paddingBottom = this.anchor.outerHeight() } root_element.css(root_style) window.setTimeout(function () { root_element.show() root_element.focus() }, 0) $(document).bind('mousedown', this._kbdHideHandler) $(document).bind('keypress', this._mouseHideHandler) this.anchor && this.anchor.toggleClass('ui-state-active') return false; }, _handleDocMouseDown: function (e) { //TODO: try to simplify hiding if (!($(e.target).closest('.ui-city_celector').is(this._root) || $(e.target).is(this.anchor))) { this.hide() } }, _handleDocKeyPress: function (e) { if (e.keyCode == 27) { this.hide() } }, hide: function () { $(document).unbind('mousedown', this._kbdHideHandler) $(document).unbind('keypress', this._mouseHideHandl) this.anchor.toggleClass('ui-state-active') this._root.hide() }, _generateOptionTag: function (title, value) { return $('').text(title).attr('value', value) }, _fillSelect: function (kind, value_field, title, options) { var select = this._selects[kind]; select.children().detach() for(var i = options.length - 1; i >= 0; i--) { this._generateOptionTag(options[i].name, options[i][value_field]).prependTo(select) } if (title !== null) { this._generateOptionTag(title, 0).prependTo(select) } select.show() select.children(':first-child').attr('selected', true) $(this).trigger(kind) }, _lockSelect: function (kind) { var select = this._selects[kind]; select.children().detach() select.hide() }, _setSelectedValue: function (kind, value, silently) { var select = this._selects[kind]; $('option[value=' + value + ']', select).attr('selected', true) !silently && select.change() }, _loadCountires: function () { GeoItemsLoader.load([META.RES.COUNTRIES], this._fillSelect.bind(this, META.RES.COUNTRIES, 'code', padString(this._('country'), 50, '-'))) }, _loadRegions: function (country_code) { this._lockSelect(META.RES.CITIES) if (country_code != 0) { GeoItemsLoader.load([META.RES.REGIONS, country_code], this._fillSelect.bind(this, META.RES.REGIONS, 'id', padString(this._('region'), 50, '-'))) } else { this._lockSelect(META.RES.REGIONS) } }, _loadCities: function (region_id) { if (region_id != 0) { GeoItemsLoader.load([META.RES.CITIES, region_id], this._fillSelect.bind(this, META.RES.CITIES, 'id', padString(this._('city'), 50, '-'))) } else { this._lockSelect(META.RES.CITIES) } }, _setValue: function (e) { var select = this._selects[META.RES.CITIES], value = select.attr('value'), city_name = $('option[value=' + value + ']', select).text(); if (value != 0) { this.element.attr('value', city_name) this.anchor.text(city_name) } } }); function padString(str, len, spacer) { var pad_len = (len - str.length) / 2, res = []; spacer = spacer || ' ' for (var i = Math.ceil(pad_len); i >=0; i--) { res.push(spacer) } res.push(str) for (var i = Math.floor(pad_len); i >=0; i--) { res.push(spacer) } return res.join('') } })(jQuery);