Authored by yyq

找回地址联动

  1 +/**
  2 + * 区域城市级联选择
  3 + * @author: dongjinhu(jinhu.dong@yoho.cn)
  4 + * @date: 2016/06/30
  5 + * @desc:
  6 + * 1. 为了减少库的复杂度,应用层的地址接口应该统一并且固定
  7 + * @examle:
  8 + * CascadingAddress({
  9 + * el: '#address',
  10 + * resources: 'areas',
  11 + * url: '', // 初始化数据请求
  12 + * onCreated: function () {} // 初始化完成回调
  13 + * })
  14 + */
  15 +
  16 +var $ = require('yoho-jquery');
  17 +
  18 +// 默认配置
  19 +var settings = {
  20 + url: '',
  21 + containerClass: 'cascading-address',
  22 + levels: 3,
  23 + resources: 'areas',
  24 + names: ['province', 'city', 'district'],
  25 + indicators: ['请选择省份', '请选择城市', '请选择区/县'],
  26 + defaultDes: '加载中...',
  27 + ajaxSettings: {
  28 + type: 'GET',
  29 + url: '',
  30 + contentType: 'application/json; charset=utf-8',
  31 + dataType: 'json'
  32 + }
  33 +};
  34 +
  35 +// 显示或隐藏下拉区域
  36 +function toggleDistItem() {
  37 + var $this = $(this),
  38 + resetHeight = $this.css('height'),
  39 + openHeight = (parseInt(resetHeight.match(/\d*/)[0], 10) + 5) + 'px',
  40 + openAndResetHeight = (parseInt(resetHeight.match(/\d*/)[0], 10) - 5) + 'px';
  41 +
  42 + if ($this.hasClass('closed')) {
  43 + // 如果有其他开着,先关掉
  44 + $('.items-indicator .open').each(function() {
  45 + $(this).find('.iconfont').replaceWith('<span class="iconfont up">&#xe60a;</span>')
  46 + .end()
  47 + .removeClass('open')
  48 + .addClass('closed')
  49 + .css({
  50 + height: resetHeight
  51 + }).next().hide();
  52 + });
  53 +
  54 + // open
  55 + $this.find('.iconfont').replaceWith('<span class="iconfont up">&#xe609;</span>')
  56 + .end()
  57 + .removeClass('closed')
  58 + .addClass('open')
  59 + .css({
  60 + height: openHeight
  61 + })
  62 + .next()
  63 + .show();
  64 +
  65 + // 有视觉差
  66 + // setTimeout(function(){
  67 + // $('.items-panel').slideDown('fast');
  68 + // }, 300)
  69 + } else {
  70 + // close
  71 + $this.find('.iconfont').replaceWith('<span class="iconfont up">&#xe60a;</span>')
  72 + .end()
  73 + .removeClass('open')
  74 + .addClass('closed')
  75 + .css({
  76 + height: openAndResetHeight
  77 + }).next().hide();
  78 + }
  79 +}
  80 +
  81 +// 重置下一级文本显示和值
  82 +function resetNextLevel(level, config) {
  83 + $('#level_' + level + '_panel').empty()
  84 + .addClass('empty')
  85 + .text(config.defaultDes)
  86 + .prev().find('.indicator-name')
  87 + .text(config.indicators[config.levels - level - 1])
  88 + .next().val('');
  89 +}
  90 +
  91 +// 获取初始化数据
  92 +function fetchInitialData(evt, config, level) {
  93 + var distItem, // 请选择...
  94 + distItemAreaId = $('.items-indicator .open input').val(),
  95 + ajaxSettings = $.extend({}, config.ajaxSettings, {
  96 + url: config.url
  97 + });
  98 +
  99 + var ulElement = $('<ul/>'),
  100 + liElement,
  101 + distElement;
  102 +
  103 + var urlArr;
  104 +
  105 + // 当前选中区域id
  106 + var currAreaId,
  107 + currPanel;
  108 +
  109 + $.ajax(ajaxSettings).done(function(res) {
  110 + // 渲染后台区域信息列表
  111 + $(res.data).each(function(index, item) {
  112 + liElement = $('<li/>');
  113 +
  114 + $('<span class="check-icon iconfont">&#xe60b;</span>').appendTo(liElement);
  115 + $('<span/>', {
  116 + class: 'dist-name',
  117 + title: item.text,
  118 + text: item.text,
  119 + id: 'area_' + item.value
  120 + }).appendTo(liElement);
  121 +
  122 + liElement.appendTo(ulElement);
  123 + });
  124 +
  125 + // 填充下拉
  126 + if (level >= 0) {
  127 + currPanel = $('#level_' + level + '_panel');
  128 + ulElement.appendTo($(config.el).find('#level_' + level + '_panel').empty().removeClass('empty'));
  129 + }
  130 +
  131 + // 选择区域
  132 + currPanel.off('click', '.dist-name').on('click', '.dist-name', function() {
  133 + distElement = $(this);
  134 + currAreaId = distElement.attr('id').split('_')[1];
  135 + distItem = $('#level_' + level + '_panel').prev();
  136 +
  137 + if (distElement.hasClass('checked')) {
  138 + // TODO
  139 + // distElement.removeClass('checked');
  140 + } else {
  141 + currPanel.find('span.checked').removeClass('checked').prev().hide();
  142 + distElement.addClass('checked').prev().show();
  143 + distItem.find('.indicator-name')
  144 + .html(distElement.text())
  145 + .next()
  146 + .val(currAreaId);
  147 +
  148 + // 填充下一级
  149 + if (level < 0) {
  150 + level = distElement.parents('.items-panel').attr('id').split('_')[1];
  151 + }
  152 +
  153 + urlArr = config.url.split(config.resources + '/');
  154 + urlArr[1] = currAreaId;
  155 + config.url = urlArr.join(config.resources + '/');
  156 +
  157 + // 选择之后关闭当前panel
  158 + // distItem.trigger('click');
  159 +
  160 + // 当前点击的和已选择的不同,再渲染下一级目录
  161 + if (level && distItemAreaId !== currAreaId) {
  162 + resetNextLevel(level - 1, config);
  163 + $(config.el).trigger('ca.afterInit', [config, level - 1]);
  164 + }
  165 + }
  166 + });
  167 + }).fail(function() {
  168 + // 如果出错,将下级所有都重置
  169 + var from = level;
  170 +
  171 + while (from >= 0) {
  172 + resetNextLevel(from, config);
  173 + from--;
  174 + }
  175 + });
  176 +}
  177 +
  178 +// 构造器
  179 +function CascadingAddress(options) {
  180 + var widget = this,
  181 + element;
  182 +
  183 + widget.config = $.extend(true, {}, settings, options); // Object.assign({}, settings, options);
  184 + element = widget.element = $(widget.config.el);
  185 +
  186 + element.on('ca.afterInit', fetchInitialData);
  187 + element.on('click', '.dist-item', toggleDistItem);
  188 +
  189 + // 绑定自定义事件处理
  190 + $.each(widget.config, function(key, value) {
  191 + if ($.isFunction(value)) {
  192 + element.on('ca.' + key, function(evt, params) {
  193 + // 处理自定义事件
  194 + return value(evt, element, params);
  195 + });
  196 + }
  197 + });
  198 +
  199 + // 初始化
  200 + this.init();
  201 +}
  202 +
  203 +// 初始化
  204 +CascadingAddress.prototype.init = function() {
  205 + // 配置
  206 + var config = this.config,
  207 + el = this.element,
  208 + target = el.addClass(config.containerClass),
  209 + levels = config.levels;
  210 +
  211 + // DOM相关
  212 + var names = config.names,
  213 + indicators = config.indicators,
  214 + distItem,
  215 + inputName,
  216 + indicatorName;
  217 +
  218 + // 省份,城市,区县容器
  219 + var distItemContainer = $('<ul class="items-indicator"/>'),
  220 + liElement,
  221 + distPanel;
  222 +
  223 + // ======生成DOM结构======
  224 + while (levels--) {
  225 + indicatorName = indicators[config.levels - levels - 1];
  226 +
  227 + // li element
  228 + liElement = $('<li/>');
  229 +
  230 + // div element
  231 + distItem = $('<div></div>', {
  232 + class: 'dist-item closed'
  233 + });
  234 +
  235 + // 标签
  236 + $('<span/>', {
  237 + class: 'indicator-name',
  238 + text: indicatorName
  239 + }).appendTo(distItem);
  240 +
  241 + // input element
  242 + inputName = names[config.levels - levels - 1];
  243 + $('<input/>', {
  244 + type: 'hidden',
  245 + name: inputName,
  246 + id: inputName,
  247 + value: ''
  248 + }).appendTo(distItem);
  249 +
  250 + // icon element
  251 + $('<span class="iconfont">&#xe60a;</span>').appendTo(distItem);
  252 +
  253 + distItem.appendTo(liElement);
  254 +
  255 + // 省份城市区县选择panel
  256 + distPanel = $('<div/>', {
  257 + class: 'items-panel empty',
  258 + id: 'level_' + levels + '_panel',
  259 + text: config.defaultDes
  260 + });
  261 + distPanel.appendTo(liElement);
  262 +
  263 + liElement.appendTo(distItemContainer);
  264 + }
  265 +
  266 + distItemContainer.appendTo(target);
  267 +
  268 + // ======生成DOM结构======
  269 +
  270 + // 获取初始化数据
  271 + el.trigger('ca.afterInit', [config, config.levels - 1]);
  272 +
  273 + // 初始化完成,提供回调执行
  274 + el.trigger('ca.onCreated');
  275 +};
  276 +
  277 +module.exports = function(options) {
  278 + return new CascadingAddress(options);
  279 +};