cascading-address.js 8.17 KB
/**
 * 区域城市级联选择
 * @author: dongjinhu(jinhu.dong@yoho.cn)
 * @date: 2016/06/30
 * @desc:
 *     1. 为了减少库的复杂度,应用层的地址接口应该统一并且固定
 * @examle:
 *     CascadingAddress({
 *         el: '#address',
 *         resources: 'areas',
 *         url: '',                       // 初始化数据请求
 *         onCreated: function () {}      // 初始化完成回调
 *     })
 */

var $ = require('yoho-jquery');

// 默认配置
var settings = {
    url: '',
    containerClass: 'cascading-address',
    levels: 3,
    resources: 'areas',
    names: ['province', 'city', 'district'],
    indicators: ['请选择省份', '请选择城市', '请选择区/县'],
    defaultDes: '加载中...',
    ajaxSettings: {
        type: 'GET',
        url: '',
        contentType: 'application/json; charset=utf-8',
        dataType: 'json'
    }
};

// 显示或隐藏下拉区域
function toggleDistItem() {
    var $this = $(this),
        resetHeight = $this.css('height'),
        openHeight = (parseInt(resetHeight.match(/\d*/)[0], 10) + 5) + 'px',
        openAndResetHeight = (parseInt(resetHeight.match(/\d*/)[0], 10) - 5) + 'px';

    if ($this.hasClass('closed')) {
        // 如果有其他开着,先关掉
        $('.items-indicator .open').each(function() {
            $(this).find('.iconfont').replaceWith('<span class="iconfont up">&#xe60a;</span>')
             .end()
             .removeClass('open')
             .addClass('closed')
             .css({
                 height: resetHeight
             }).next().hide();
        });

        // open
        $this.find('.iconfont').replaceWith('<span class="iconfont up">&#xe609;</span>')
             .end()
             .removeClass('closed')
             .addClass('open')
             .css({
                 height: openHeight
             })
             .next()
             .show();

        // 有视觉差
        // setTimeout(function(){
        //     $('.items-panel').slideDown('fast');
        // }, 300)
    } else {
        // close
        $this.find('.iconfont').replaceWith('<span class="iconfont up">&#xe60a;</span>')
             .end()
             .removeClass('open')
             .addClass('closed')
             .css({
                 height: openAndResetHeight
             }).next().hide();
    }
}

// 重置下一级文本显示和值
function resetNextLevel(level, config) {
    $('#level_' + level + '_panel').empty()
                                   .addClass('empty')
                                   .text(config.defaultDes)
                                   .prev().find('.indicator-name')
                                          .text(config.indicators[config.levels - level - 1])
                                          .next().val('');
}

// 获取初始化数据
function fetchInitialData(evt, config, level) {
    var distItem,  //  请选择...
        distItemAreaId = $('.items-indicator .open input').val(),
        ajaxSettings = $.extend({}, config.ajaxSettings, {
            url: config.url
        });

    var ulElement = $('<ul/>'),
        liElement,
        distElement;

    var urlArr;

    // 当前选中区域id
    var currAreaId,
        currPanel;

    $.ajax(ajaxSettings).done(function(res) {
        // 渲染后台区域信息列表
        $(res.data).each(function(index, item) {
            liElement = $('<li/>');

            $('<span class="check-icon iconfont">&#xe60b;</span>').appendTo(liElement);
            $('<span/>', {
                class: 'dist-name',
                title: item.text,
                text: item.text,
                id: 'area_' + item.value
            }).appendTo(liElement);

            liElement.appendTo(ulElement);
        });

        // 填充下拉
        if (level >= 0) {
            currPanel = $('#level_' + level + '_panel');
            ulElement.appendTo($(config.el).find('#level_' + level + '_panel').empty().removeClass('empty'));
        }

        // 选择区域
        currPanel.off('click', '.dist-name').on('click', '.dist-name', function() {
            distElement = $(this);
            currAreaId = distElement.attr('id').split('_')[1];
            distItem = $('#level_' + level + '_panel').prev();

            if (distElement.hasClass('checked')) {
                // TODO
                // distElement.removeClass('checked');
            } else {
                currPanel.find('span.checked').removeClass('checked').prev().hide();
                distElement.addClass('checked').prev().show();
                distItem.find('.indicator-name')
                        .html(distElement.text())
                        .next()
                        .val(currAreaId);

                // 填充下一级
                if (level < 0) {
                    level = distElement.parents('.items-panel').attr('id').split('_')[1];
                }

                urlArr = config.url.split(config.resources + '/');
                urlArr[1] = currAreaId;
                config.url = urlArr.join(config.resources + '/');

                // 选择之后关闭当前panel
                // distItem.trigger('click');

                // 当前点击的和已选择的不同,再渲染下一级目录
                if (level && distItemAreaId !== currAreaId) {
                    resetNextLevel(level - 1, config);
                    $(config.el).trigger('ca.afterInit', [config, level - 1]);
                }
            }
        });
    }).fail(function() {
        // 如果出错,将下级所有都重置
        var from = level;

        while (from >= 0) {
            resetNextLevel(from, config);
            from--;
        }
    });
}

// 构造器
function CascadingAddress(options) {
    var widget = this,
        element;

    widget.config = $.extend(true, {}, settings, options); // Object.assign({}, settings, options);
    element = widget.element = $(widget.config.el);

    element.on('ca.afterInit', fetchInitialData);
    element.on('click', '.dist-item', toggleDistItem);

    // 绑定自定义事件处理
    $.each(widget.config, function(key, value) {
        if ($.isFunction(value)) {
            element.on('ca.' + key, function(evt, params) {
                // 处理自定义事件
                return value(evt, element, params);
            });
        }
    });

    // 初始化
    this.init();
}

// 初始化
CascadingAddress.prototype.init = function() {
    // 配置
    var config = this.config,
        el = this.element,
        target = el.addClass(config.containerClass),
        levels = config.levels;

    // DOM相关
    var names = config.names,
        indicators = config.indicators,
        distItem,
        inputName,
        indicatorName;

    // 省份,城市,区县容器
    var distItemContainer = $('<ul class="items-indicator"/>'),
        liElement,
        distPanel;

    // ======生成DOM结构======
    while (levels--) {
        indicatorName = indicators[config.levels - levels - 1];

        // li element
        liElement = $('<li/>');

        // div element
        distItem = $('<div></div>', {
            class: 'dist-item closed'
        });

        // 标签
        $('<span/>', {
            class: 'indicator-name',
            text: indicatorName
        }).appendTo(distItem);

        // input element
        inputName = names[config.levels - levels - 1];
        $('<input/>', {
            type: 'hidden',
            name: inputName,
            id: inputName,
            value: ''
        }).appendTo(distItem);

        // icon element
        $('<span class="iconfont">&#xe60a;</span>').appendTo(distItem);

        distItem.appendTo(liElement);

        // 省份城市区县选择panel
        distPanel = $('<div/>', {
            class: 'items-panel empty',
            id: 'level_' + levels + '_panel',
            text: config.defaultDes
        });
        distPanel.appendTo(liElement);

        liElement.appendTo(distItemContainer);
    }

    distItemContainer.appendTo(target);

    // ======生成DOM结构======

    // 获取初始化数据
    el.trigger('ca.afterInit', [config, config.levels - 1]);

    // 初始化完成,提供回调执行
    el.trigger('ca.onCreated');
};

module.exports = function(options) {
    return new CascadingAddress(options);
};