API Docs for: 0.1
Show:

File: ../comps/Form/UXC.Form.initAutoSelect.js

 ;(function($){
    /**
     * 初始化 级联下拉框
     * <br />只要引用本脚本, 页面加载完毕时就会自动初始化级联下拉框功能
     * <br /><br />动态添加的 DOM 需要显式调用 AutoSelect( domSelector ) 进行初始化
     * <br /><br />要使页面上的级联下拉框功能能够自动初始化, 需要在select标签上加入一些HTML 属性
     * <br /><b>defaultselect</b>: none, 该属性声明这是级联下拉框的第一个下拉框, <b>这是必填项,初始化时以这个为入口</b>
     * <br /><b>selectvalue</b>: string, 下拉框的默认选中值
     * <br /><b>selecturl</b>: string, 下拉框的数据请求接口, 符号 {0} 代表下拉框值的占位符
     * <br /><b>selecttarget</b>: selector, 下一级下拉框的选择器语法
     * <br /><b>defaultoption</b>: none, 声明默认 option 选项, 更新option时, 有该属性的项不会被清除
     * <p>
     *      数据格式: [ [id, name], [id, name] ... ]
     *      <br /> 如果获取到的数据格式不是默认格式,
     *      可以通过 <a href='UXC.Form.html#property_initAutoSelect.dataFilter'>AutoSelect.dataFilter</a> 属性自定义函数, 进行数据过滤
     * </p>
     * @method  initAutoSelect
     * @static
     * @for UXC.Form
     * @version dev 0.2
     * @author  qiushaowei   <suches@btbtd.org> | 360 75 Team
     * @date    2013-07-28(.2), 2013-06-11(.1)
     * @param   {selector}  _selector   要初始化的级联下拉框父级节点
     * @example
        <h2>AJAX 返回内容</h2>
        <code>
            <dd>    
                <select name='select102_1' defaultselect selecturl="data/shengshi_with_error_code.php?id=0" selecttarget="select[name=select102_2]">
                    <option value="-1" defaultoption>请选择</option>
                </select>
                <select name='select102_2' selecturl="data/shengshi_with_error_code.php?id={0}" selecttarget="select[name=select102_3]">
                    <option value="-1" defaultoption>请选择</option>
                </select>
                <select name='select102_3' selecturl="data/shengshi_with_error_code.php?id={0}">
                    <option value="-1" defaultoption>请选择</option>
                </select>
            </dd>
        </code>
        <script>
            $.get( './data/shengshi_html.php?rnd='+new Date().getTime(), function( _r ){
                var _selector = $(_r);
                $( 'dl.def > dt' ).after( _selector );
                UXC.Form.initAutoSelect( _selector );
            });

            UXC.Form.initAutoSelect.dataFilter = 
                function( _data, _select ){
                    var _r = _data;
                    if( _data && !_data.length && _data.data ){
                        _r = _data.data;
                    }
                    return _r;
                };
        </script>
     */
    UXC.Form.initAutoSelect = AutoSelect;

    function AutoSelect( _selector ){
        var _ins = [];
        _selector && ( _selector = $(_selector) );

        if( AutoSelect.isSelect( _selector ) ){
            _ins.push( new Control( _selector ) );
        }else if( _selector && _selector.length ){
            _selector.find( 'select[defaultselect]' ).each( function(){
                _ins.push( new Control( $(this ) ));
            });
        }
        return _ins;
    }
    var AutoSelectProp = {
        /**
         * 判断 selector 是否为符合自动初始化联动框的要求
         * @method  initAutoSelect.isSelect
         * @param   {selector}  _selector
         * @return  bool
         * @static
         */
        isSelect: 
            function( _selector ){
                var _r;
                _selector 
                    && ( _selector = $(_selector) ) 
                    && _selector.is( 'select' ) 
                    && _selector.is( '[defaultselect]' )
                    && ( _r = true );
                return _r;
            }
        /**
         * 是否自动隐藏空值的级联下拉框, 起始下拉框不会被隐藏
         * <br />这个是全局设置, 如果需要对具体某个select进行处理, 对应的 HTML 属性 selecthideempty
         * @property    initAutoSelect.hideEmpty
         * @type    bool
         * @default false
         * @static
         * @example
                AutoSelect.hideEmpty = true;
         */
        , hideEmpty: false
        /**
         * 级联下拉框的数据过滤函数
         * <br />默认数据格式: [ [id, name], [id, name] ... ]
         * <br />如果获取到的数据格式非默认格式, 可通过本函数进行数据过滤
         * <p>
         *  <b>注意, 这个是全局过滤, 如果要使用该函数进行数据过滤, 判断逻辑需要比较具体</b>
         *  <br />如果要对具体 select 进行数据过滤, 可以使用HTML属性 selectdatafilter 指定过滤函数
         * </p>
         * @property    initAutoSelect.dataFilter
         * @type    function
         * @default null
         * @static
         * @example
                 AutoSelect.dataFilter = 
                    function( _data, _select ){
                        var _r = _data;
                        if( _data && !_data.length && _data.data ){
                            _r = _data.data;
                        }
                        return _r;
                    };
         */
        , dataFilter: null
        /**
         * 下拉框初始化功能都是未初始化 数据之前的回调
         * <br />这个是全局回调, 如果需要对具体某一组进行处理, 对应的 HTML 属性 selectbeforeInited
         * @property    initAutoSelect.beforeInited
         * @type    function
         * @default null
         * @static
         */
        , beforeInited: null
        /**
         * 下拉框第一次初始完所有数据的回调
         * <br />这个是全局回调, 如果需要对具体某一组进行处理, 对应的 HTML 属性 selectinited
         * @property    initAutoSelect.inited
         * @type    function
         * @default null
         * @static
         */
        , inited: null
        /**
         * 下拉框每个项数据变更后的回调
         * <br />这个是全局回调, 如果需要对具体某一组进行处理, 对应的 HTML 属性 selectchange
         * @property    initAutoSelect.change
         * @type    function
         * @default null
         * @static
         */
        , change: null
        /**
         * 下拉框所有项数据变更后的回调
         * <br />这个是全局回调, 如果需要对具体某一组进行处理, 对应的 HTML 属性 selectallchanged
         * @property    initAutoSelect.allChanged
         * @type    function
         * @default null
         * @static
         */
        , allChanged: null
        /**
         * 第一次初始化所有联动框时, 是否触发 change 事件
         * <br />这个是全局回调, 如果需要对具体某一组进行处理, 对应的 HTML 属性 selecttriggerinitchange
         * @property    initAutoSelect.triggerInitChange
         * @type    bool
         * @default false
         * @static
         */
        , triggerInitChange: true
        /**
         * ajax 请求数据时, 是否添加随机参数防止缓存
         * <br />这个是全局回调, 如果需要对具体某一组进行处理, 对应的 HTML 属性 selectrandomurl
         * @property    initAutoSelect.randomurl
         * @type    bool
         * @default true
         * @static
         */
        , randomurl: false
        /**
         * 处理 ajax url 的回调处理函数
         * <br />这个是全局回调, 如果需要对具体某一组进行处理, 对应的 HTML 属性 selectprocessurl
         * @property    initAutoSelect.processUrl
         * @type    function
         * @default null
         * @static
         */
        , processUrl: null
        /**
         * 获取或设置 selector 的实例引用
         * @method  initAutoSelect.getInstance
         * @param   {selector}  _selector
         * @param   {AutoSelectControlerInstance}   _ins
         * @return AutoSelectControlerInstance
         * @static
         */
        , getInstance:
            function( _selector, _ins ){
                var _r;
                _selector 
                    && ( _selector = $( _selector ) ) 
                    && ( typeof _ins != 'undefined' && _selector.data('SelectIns', _ins )
                            , _r = _selector.data('SelectIns') );
                return _r;
            }
    };
    for( var k in AutoSelectProp ) AutoSelect[k] = AutoSelectProp[k];

    function Control( _selector ){
        this._model = new Model( _selector );
        this._view = new View( this._model, this );

        this._init();
    }

    Control.prototype = {
        _init:
            function(){
                var _p = this;

                $.each( _p._model.items(), function( _ix, _item ){
                    AutoSelect.getInstance( $(_item), _p );
                });

                _p._model.beforeInited() && _p.on( 'SelectBeforeInited', _p._model.beforeInited() );
                _p.on('SelectInited', function(){
                    var _tmp = _p._model.first();
                    while( _p._model.next( _tmp ) ){
                        _tmp.on( 'change', _p._responeChange );
                        _tmp = _p._model.next( _tmp );
                    }
                    _p._model.isInited( true );

                    //alert( _p._model.inited() );

                    _p._model.inited() && _p._model.inited().call( _p );
                });
                _p.on('SelectChange', function( _evt, _selector ){
                    _p._model.change( _selector ) && _p._model.change( _selector ).call( _selector, _evt, _p );
                });
                _p._model.allChanged() && _p.on( 'SelectAllChanged', _p._model.allChanged() );

                _p.trigger('SelectBeforeInited');
                
                _p._update( _p._model.first(), _p._firstInitCb );
                
                return _p;
            }    

        , on: function( _evt, _cb ){ $(this).on( _evt, _cb ); return this; }
        , trigger: function( _evt, _args ){ $(this).trigger( _evt, _args ); return this; }

        , first: function(){ return this._model.first(); }
        , last: function(){ return this._model.last(); }
        , items: function(){ return this._model.items(); }

        , isFirst: function( _selector ){ return this._model.isFirst( _selector ); }
        , isLast: function( _selector ){ return this._model.isLast( _selector ); }
        , isInited: function(){ return this._model.isInited(); }

        , data: function( _selector ){ return this._model.data( _selector ); }

        , _responeChange:
            function( _evt ){
                var _sp = $(this)
                    , _p = AutoSelect.getInstance( _sp )
                    , _next = _p._model.next( _sp );
                    ;
                UXC.log( '_responeChange:', _sp.attr('name'), _sp.val() );

                if( !( _next&& _next.length ) ){
                    _p.trigger( 'SelectChange' );
                }else{
                    _p._update( _next, _p._changeCb, _sp.val() );
                }
            }

        , _changeCb:
            function( _selector, _data ){
                var _p = this
                    , _next = _p._model.next( _selector );
                ;

                _p.trigger( 'SelectChange', [ _selector ] );

                if( _p._model.isLast( _selector ) ){
                    _p.trigger( 'SelectAllChanged' );
                }

                if( _next && _next.length ){
                    UXC.log( '_changeCb:', _selector.val(), _next.attr('name'), _selector.attr('name') );
                    _p._update( _next, _p._firstInitCb, _selector.val() );
                }
                return this;
            }

        , _firstInitCb:
            function( _selector, _data ){
                var _p = this
                    , _next = _p._model.next( _selector );
                ;

                _p._model.triggerInitChange() 
                    && ( UXC.log('triggerInitChange', _selector.attr('name')), _selector.trigger('change') );

                _p.trigger( 'SelectChange', [ _selector ] );

                if( _p._model.isLast( _selector ) ){
                    _p.trigger( 'SelectAllChanged' );
                    _p.trigger( 'SelectInited' );
                }

                if( _next && _next.length ){
                    UXC.log( '_firstInitCb:', _selector.val(), _next.attr('name'), _selector.attr('name') );
                    _p._update( _next, _p._firstInitCb, _selector.val() );
                }

                return this;
            }


        , _update:
            function( _selector, _cb, _pid ){
                if( this._model.isStatic( _selector ) ){
                    this._updateStatic( _selector, _cb, _pid );
                }else if( this._model.isAjax( _selector ) ){
                    this._updateAjax( _selector, _cb, _pid );
                }else{
                    this._updateNormal( _selector, _cb, _pid );
                }
                return this;
            }

        , _updateStatic:
            function( _selector, _cb, _pid ){
                var _p = this, _data;
                UXC.log( 'static select' );
                if( _p._model.isFirst( _selector ) ){
                    typeof _pid == 'undefined' && ( _pid = _p._model.selectparentid( _selector ) || '' );
                    if( typeof _pid != 'undefined' ){
                        _data = _p._model.datacb( _selector )( _pid );
                    }
                }else{
                    _data = _p._model.datacb( _selector )( _pid );
                }
                _p._view.update( _selector, _data );
                _cb && _cb.call( _p, _selector, _data );
                return this;
            }

        , _updateAjax:
            function( _selector, _cb, _pid ){
                var _p = this, _data, _next = _p._model.next( _selector ), _url;
                UXC.log( 'ajax select' );

                if( _p._model.isFirst( _selector ) ){
                    typeof _pid == 'undefined' && ( _pid = _p._model.selectparentid( _selector ) || '' );
                    if( typeof _pid != 'undefined' ){
                        _url = _p._model.selecturl( _selector, _pid );
                        $.get( _url, function( _data ){
                            _data = $.parseJSON( _data );
                            _p._view.update( _selector, _data );
                            _cb && _cb.call( _p, _selector, _data );
                        });
                    }
                }else{
                   _url = _p._model.selecturl( _selector, _pid );
                    $.get( _url, function( _data ){
                        UXC.log( '_url:', _url, _pid );
                        _data = $.parseJSON( _data );
                        _p._view.update( _selector, _data );
                        _cb && _cb.call( _p, _selector, _data );
                    });
                }
                return this;
            }

        , _updateNormal:
            function( _selector, _cb, _pid ){
               var _p = this, _data;
                UXC.log( 'normal select' );
                if( _p._model.isFirst( _selector ) ){
                    var _next = _p._model.next( _selector );
                    if( _next && _next.length ){
                        _p._update( _next, _cb, _selector.val() );
                        return this;
                    }
                }else{
                    _data = _p._model.datacb( _selector )( _pid );
                }
                _p._view.update( _selector, _data );
                _cb && _cb.call( _p, _selector, _data );
                return this;
            }
    }
    
    function Model( _selector ){
        this._selector = _selector;
        this._items = [];
        this._isInited = false;

        this._init();
    }
    
    Model.prototype = {
        _init:
            function(){
                this._findAllItems( this._selector );
                UXC.log( 'select items.length:', this._items.length );
                this._initRelationship();
                return this;
            }

        , _findAllItems:
            function( _selector ){
                this._items.push( _selector );
                if( _selector.is( '[selecttarget]' ) ) 
                    this._findAllItems( $( _selector.attr('selecttarget') ) );
            }

        , _initRelationship:
            function(){
                this._selector.data( 'FirstSelect', true );
                if( this._items.length > 1 ){
                    this._items[ this._items.length - 1 ].data('LastSelect', true);
                    for( var i = 0; i < this._items.length; i ++ ){
                        var item = this._items[i]
                            , preItem = this._items[i-1];
                            ;
                        if( preItem ){
                            item.data('PrevSelect', preItem);
                            preItem.data('NextSelect', item );

                            item.data('parentSelect', preItem); //向后兼容0.1
                        }
                    }
                }
            }

        , items: function(){ return this._items; }
        , first: function(){ return this._items[0]; }
        , last: function(){ return this._items[ this._items -1 ]; }
        , next: function( _selector ){ return _selector.data('NextSelect'); }
        , prev: function( _selector ){ return _selector.data('PrevSelect'); }
        , isFirst: function( _selector ){ return !!_selector.data('FirstSelect'); }
        , isLast: function( _selector ){ return !!_selector.data('LastSelect'); }
        , isStatic: function( _selector ){ return _selector.is('[selectdatacb]'); }
        , isAjax: function( _selector ){ return _selector.is('[selecturl]'); }

        , isInited: 
            function( _setter ){ 
                typeof _setter != 'undefined' && ( this._isInited = _setter )
                return this._isInited;
            }

        , datacb:
            function( _selector ){
                var _r;
                _selector.attr('selectdatacb') 
                    && ( _r = window[ _selector.attr('selectdatacb') ] )
                    ;
                return _r;
            }

        , selectparentid:
            function( _selector ){
                var _r;
                _selector.attr('selectparentid') 
                    && ( _r = _selector.attr('selectparentid') )
                    ;
                _selector.removeAttr( 'selectparentid' );
                return _r || '';
            }

        , selectvalue:
            function( _selector ){
                var _r = _selector.attr('selectvalue');
                _selector.removeAttr( 'selectvalue' );
                return _r || '';
            }

        , randomurl:
            function( _selector ){
                var _r = AutoSelect.randomurl;
                _selector.is('[selectrandomurl]')
                    && ( _r = parseBool( _selector.attr('selectrandomurl') ) )
                    ;
                return _r;
            }
        
        , triggerInitChange:
            function(){
                var _r = AutoSelect.triggerInitChange, _selector = this.first();
                _selector.attr('selecttriggerinitchange')
                    && ( _r = parseBool( _selector.attr('selecttriggerinitchange') ) )
                    ;
                return _r;
            }

        , hideempty:
            function( _selector ){
                var _r = AutoSelect.hideEmpty;
                _selector 
                    && _selector.length
                    && _selector.is('[selecthideempty]')
                    && ( _r = parseBool( _selector.attr('selecthideempty') ) )
                    ;
                return _r;
            }

        , selecturl:
            function( _selector, _pid ){
                var _cb = AutoSelect.processUrl, _r = _selector.attr('selecturl') || '';
                    _selector.attr('selectprocessurl') 
                        && window[ _selector.attr('selectprocessurl' ) ]
                        && ( _cb = window[ _selector.attr('selectprocessurl' ) ] )
                        ;
                    _r = printf( _r, _pid );
                    this.randomurl( _selector ) && ( _r = add_url_params( _r, {'rnd': new Date().getTime() } ) );
                    _cb && ( _r = _cb.call( _selector, _r, _pid ) );
                return _r;
            }

        , _userdatafilter:
            function( _selector ){
                var _r;
                _selector.attr('selectdatafilter') 
                    && ( _r = window[ _selector.attr('selectdatafilter') ] )
                    ;
                return _r;
            }

        , dataFilter:
            function( _selector, _data ){
                var _cb = this._userdatafilter( _selector ) || AutoSelect.dataFilter;
                _cb && ( _data = _cb( _data, _selector ) );
                return _data;
            }

        , beforeInited:
            function(){
                var _cb = AutoSelect.beforeInited, _selector = this.first();
                    _selector.attr('selectbeforeInited') 
                        && window[ _selector.attr('selectbeforeInited' ) ]
                        && ( _cb = window[ _selector.attr('selectbeforeinited' ) ] )
                        ;
                return _cb;
            }

        , inited:
            function(){
               var _cb = AutoSelect.inited, _selector = this.first();
                    _selector.attr('selectinited') 
                        && window[ _selector.attr('selectinited' ) ]
                        && ( _cb = window[ _selector.attr('selectinited' ) ] )
                        ;
                return _cb;
            }

        , change:
            function( _selector ){
               var _cb = AutoSelect.change;
                    _selector.attr('selectchange') 
                        && window[ _selector.attr('selectchange' ) ]
                        && ( _cb = window[ _selector.attr('selectchange' ) ] )
                        ;
                return _cb;
            }

        , allChanged:
            function(){
               var _cb = AutoSelect.allChanged, _selector = this.first();
                    _selector.attr('selectallchanged') 
                        && window[ _selector.attr('selectallchanged' ) ]
                        && ( _cb = window[ _selector.attr('selectallchanged' ) ] )
                        ;
                return _cb;
            }

        , data:
            function( _selector, _setter ){
                typeof _setter != 'undefined' && ( _selector.data('SelectData', _setter ) );
                return _selector.data( 'SelectData' );
            }

        /**
         * 判断下拉框的option里是否有给定的值
         * @param   {selector}  _select
         * @param   {string}    _val    要查找的值
         */
        , hasVal: 
            function ( _selector, _val ){
                var _r = false, _val = _val.toString();
                _selector.find('option').each( function(){
                    var _tmp = $(this);
                    if( _tmp.val() == _val ){
                        _r = true;
                        return false;
                    }
                });
                return _r;
            }
    };
    
    function View( _model, _control ){
        this._model = _model;
        this._control = _control;

        this._init();
    }
    
    View.prototype = {
        _init:
            function() {
                return this;
            }

        , update:
            function( _selector, _data ){
                var _default = this._model.selectvalue( _selector );
                _data = this._model.dataFilter( _selector, _data );
                this._model.data( _selector, _data );
                
                this._control.trigger( 'SelectItemBeforeUpdate', [ _selector, _data ] );
                this._removeExists( _selector );

                if( !_data.length ){
                    if( this._model.hideempty( _selector ) ){
                        _selector.hide();
                        this._control.trigger( 'SelectItemUpdated', [ _selector, _data ] );
                        return;
                    }
                }else{
                    !_selector.is(':visible') && _selector.show();
                }

                var _html = [], _tmp, _selected;
                for( var i = 0, j = _data.length; i < j; i++ ){
                    _tmp = _data[i];
                    _html.push( printf( '<option value="{0}" {2}>{1}</option>', _tmp[0], _tmp[1], _selected ) );
                }
                $( _html.join('') ).appendTo( _selector );

                if( this._model.hasVal( _selector, _default ) ){
                    _selector.val( _default );
                }
                this._control.trigger( 'SelectItemUpdated', [ _selector, _data ] );
            }

        , _removeExists:
            function( _selector ){
                _selector.find('> option:not([defaultoption])').remove();
            }
        
    };
    /**
     * 页面加载完毕时, 延时进行自动化, 延时可以避免来自其他逻辑的干扰
     */
    $(document).ready( function( _evt ){
        setTimeout( function(){ AutoSelect( document.body ); }, 100 );
    });

}(jQuery));