`

一个基于jQuery的可重用的无限制级别联动Select。

阅读更多
这标题起得好长好牛B,喔呵呵呵呵……

忙活一下午,写出这么个玩意,其中emptySelect和loadSelect方法的代码来自《jQuery in Action》一书,稍有改动。childSelect是自己照着加的,可能写得不太好看。

(function($){$.fn.loadSelect = function(optionsDataArray){
    return this.each(function(){
        if(this.tagName == 'SELECT'){
            var selectElement = this;
            $.each(optionsDataArray, function(index, optionData){
                selectElement.options.add(new Option(optionData.text, optionData.value));
            });
            var defaultOption = new Option('\u8bf7\u9009\u62e9', '');
            defaultOption.selected = true;
            selectElement.options.add(defaultOption);
        }
    });
};

$.fn.childSelect = function(selector, makeURL){
    var elem = this[0];
    if(selector === undefined){
        return elem.child ? this.pushStack(elem.child) : undefined;
    }

    elem.child = $(selector)[0];
    this.change(function(event){
        var childSelect = $(this).childSelect();
        childSelect.emptySelect();
        if($(this).val()){
            childSelect.attr('disabled', false);
            $.getJSON(makeURL.apply($(this)), function(data){
                childSelect.loadSelect(data).change();
            });
        }else{
            childSelect.attr('disabled', true);
        }
        if(childSelect.childSelect()) childSelect.change();
    });
    return this.pushStack(elem.child);
};

$.fn.emptySelect = function(){
    return this.each(function(){
        if(this.tagName == 'SELECT')
            this.options.length = 0;
    });
};})(jQuery);


这里解释一下childSelect的用法:
这个方法是用来给一个select元素设置子级select元素的,一旦设置了子级select元素,父select元素的change事件触发时将通过$.getJSON向服务器发送一个请求,并将返回的json填充到子级select元素中。如果想让父select元素的某一项选中时不发送请求,只要将这一项的value设置成空字符串''即可:如
<select><option value=''>请选择</option></select>

参数selector用来选择子select元素,跟$(selector)的用法一样。
makeURL必须是一个方法,这个方法用于创建要请求的url,上下文(即this)为父select元素的包装集对象(即jQuery包装后的父select元素)。
这个方法类似于val()方法——省略参数的时候是用于获取子select元素的包装集。
这个版本的代码对服务端返回的JSON的格式有一些要求,返回的JSON数组必须是这种格式:
[{text:'一年1班', value:'1'},{text:'一年2班', value:'2'}]


使用方法如下:
$('#grade').childSelect('#class', function(){return 'grades/'+this.val()+'/classes'}).end().change()

也可以链式地继续往后面添加子select元素的子select元素,或者直接给子select元素添加事件。

接着我遇到了一些问题。rails的to_json生成的json格式一般是这样的
[{class_name1:{attr1.1:value1.1,attr1.2:value1.2}},{class_name2:{attr2.1:value2.1,attr2.2:value2.2}}]
,没什么规律的,或者说规律比较复杂,要给select元素填充不同的ruby类数组的话,就必须知道这个ruby类的类名、用来做text和value的属性名。
我修改了一下loadSelect和childSelect方法,如下:
(function($){$.fn.loadSelect = function(optionsDataArray, hash){
    return this.each(function(){
        if(this.tagName == 'SELECT'){
            var selectElement = this;
            $.each(optionsDataArray, function(index, optionData){
                selectElement.options.add(new Option(optionData[hash.className][hash.text], optionData[hash.className][hash.value]));
            var defaultOption = new Option('\u8bf7\u9009\u62e9', '');
            defaultOption.selected = true;
            selectElement.options.add(defaultOption);
            });
        }
    });
};

$.fn.childSelect = function(selector, makeURL, hash){
    var elem = this[0];
    if(selector === undefined){
        return elem.child ? this.pushStack(elem.child) : undefined;
    }

    elem.child = $(selector)[0];
    this.change(function(event){
        var childSelect = $(this).childSelect();
        childSelect.emptySelect();
        if($(this).val()){
            childSelect.attr('disabled', false);
            $.getJSON(makeURL.apply($(this)), function(data){
                childSelect.loadSelect(data, hash).change();
            });
        }else{
            childSelect.attr('disabled', true);
        }
        if(childSelect.childSelect()) childSelect.change();
    });
    return this.pushStack(elem.child);
};})(jQuery);

就是给方法添加了一个hash(或者说对象)作为参数,让客户端代码来指定ruby类的类名、用于text和value的属性名,使用的时候就是多传一个hash,像这样:
$('#grade').childSelect('#clazz', function(){return 'grades/'+this.val()+'/classes'}, {className:'clazz', value:'id', text:'class_no'}).end().change();


还有另外一种解决方法,就是给rails的每个对象添加一个json_for_select方法,生成这种格式的JSON:
[{text:'一年1班', value:'1'},{text:'一年2班', value:'2'}]
,这样就可以免去上面多出来的那个hash。

问题是……json_for_select该怎么写,我还没有头绪……似乎要同时去修改ActiveSuppor::JSON和Object。

===========================================================
改好了,在config/initializers下添加一个extension.rb,内容:
class Object
  def json_for_select(hash)
    json = to_json(:only => [hash[:text], hash[:value]])
    json = json.gsub(Regexp.new("\"#{hash[:text]}\":"), "\"text\":").gsub(Regexp.new("\"#{hash[:value]}\":"), "\"value\":")
    json = json.gsub(/\{"\w+":\s\{/, '{').gsub(/\}\}/, '}') if ActiveRecord::Base.include_root_in_json
    json
  end
end

解释:rails启动的时候会自动加载config/initializers下的文件并执行。ActiveRecord::Base.include_root_in_json如果为true,to_json生成的JSON带有类名,如果为false,则不带类名。最后把controller中的
format.html { render :json => @classes.to_json }
换成
format.html { render :json => @classes.json_for_select(:text =>'class_no', :value=>'id')
即可。
分享到:
评论
2 楼 Hooopo 2009-10-14  
or:
format.html { render :text => @classes.to_json }
1 楼 Hooopo 2009-10-14  
format.html { render :json => @classes.to_json }

可以这样
format.html { render :json => @classes }

相关推荐

Global site tag (gtag.js) - Google Analytics