Django/jQuery 级联选择框?

Posted

技术标签:

【中文标题】Django/jQuery 级联选择框?【英文标题】:Django/jQuery Cascading Select Boxes? 【发布时间】:2011-03-15 02:44:03 【问题描述】:

我想构建一个国家/地区选择器。首先,您选择一个国家,该国家的州显示在第二个选择框中。在 php 和 jQuery 中这样做相当容易,但我发现 Django 表单在这个意义上有点限制。

我可以在页面加载时将 State 字段设置为空,然后用一些 jQuery 填充它,但是如果出现表单错误,它将无法“记住”您选择的 State。我也很确定它会引发验证错误,因为您的选择不是 Python 方面的表单中列出的选项之一。

那么我该如何解决这些问题呢?

【问题讨论】:

试试这个github.com/digi604/django-smart-selects 【参考方案1】:

您可以将隐藏字段设置为具有真正的“状态”值,然后使用 jQuery 创建<select> 列表,并在.select() 上将其值复制到隐藏字段。然后,在页面加载时,您的 jQuery 代码可以获取隐藏字段的值,并在填充后使用它在 <select> 元素中选择正确的项目。

这里的关键概念是状态弹出菜单是完全用 jQuery 创建的,而不是 Django 表单的一部分。这使您可以完全控制它,同时让所有其他字段正常工作。

编辑:还有另一种方法,但它不使用 Django 的表单类。

在视图中:

context = 'state': None, 'countries': Country.objects.all().order_by('name')
if 'country' in request.POST:
    context['country'] = request.POST['country']
    context['states'] = State.objects.filter(
        country=context['country']).order_by('name')
    if 'state' in request.POST:
        context['state'] = request.POST['state']
else:
    context['states'] = []
    context['country'] = None
# ...Set the rest of the Context here...
return render_to_response("addressform.html", context)

然后在模板中:

<select name="country" id="select_country">
    % for c in countries %
    <option value=" c.val "% ifequal c.val country % selected="selected"% endifequal %> c.name </option>
    % endfor %
</select>

<select name="state" id="select_state">
    % for s in states %
    <option value=" s.val "% ifequal s.val state % selected="selected"% endifequal %> s.name </option>
    % endfor %
</select>

当国家/地区发生变化时,您还需要使用常用的 javascript 来重新加载州选择器。

我还没有测试过这个,所以它可能有几个漏洞,但它应该可以理解这个想法。

所以你的选择是:

在 Django 表单中使用隐藏字段作为实际值,并通过 AJAX 在客户端创建选择菜单,或者 放弃 Django 的表单内容并自己初始化菜单。 创建一个custom Django form widget,我还没有这样做,因此不会评论。我不知道这是否可行,但看起来您需要在MultiWidget 中添加一对Selects,后者在常规文档中没有记录,因此您必须阅读源代码。李>

【讨论】:

这是一个聪明的主意。看起来有点脏,但我可以忍受。 如果有正确的记录,它就不是脏的。 ^_- 我似乎不需要隐藏元素来解决 Django 的一些怪癖。 Django 并没有真正做变异表单。只要我有一个,我就自己做表格。 也不太喜欢你的编辑,虽然它是可行的:p 我现在会坚持使用隐藏输入/AJAX sol'n。【参考方案2】:

根据 Mike 的建议:

// the jQuery
$(function () 
        var $country = $('.country');
        var $provInput = $('.province');
        var $provSelect = $('<select/>').insertBefore($provInput).change(function() 
                $provInput.val($provSelect.val());      
        );
        $country.change(function() 
                $provSelect.empty().addClass('loading');
                $.getJSON('/get-provinces.json', 'country':$(this).val(), function(provinces) 
                        $provSelect.removeClass('loading');
                        for(i in provinces) 
                                $provSelect.append('<option value="'+provinces[i][0]+'">'+provinces[i][1]+'</option>');
                        
                        $provSelect.val($provInput.val()).trigger('change');
                );
        ).trigger('change');
);

# the form
country = CharField(initial='CA', widget=Select(choices=COUNTRIES, attrs='class':'country'))
province = CharField(initial='BC', widget=HiddenInput(attrs='class':'province'))

# the view
def get_provinces(request):
    from django.utils import simplejson
    data = 
        'CA': CA_PROVINCES,
        'US': US_STATES
    .get(request.GET.get('country', None), None)
    return HttpResponse(simplejson.dumps(data), mimetype='application/json')

【讨论】:

嗯...还没有将函数传递给 jQuery,所以我不确定那是什么。通常我看到(function($)...)(jQuery); 是因为您希望$ 不是jQuery。此外,在for (i in provinces) 循环中,我将使用$('&lt;option/&gt;').val(provinces[i][0]).text(provinces[i][1]).appendTo($provSelect);。我猜我的方式比较慢,但是当你把子句放在一行时更容易阅读。此外,IIRC,您可以使用$('#id_country') 而不是$('.country');省份小部件也是如此,它可以让您在表单中转储 class 内容。 @Mike:$(function()$(document).ready(function() 的简写。我故意使用一个类,因为一个页面上可能有多个国家/省选择器……并不是说这个脚本可以与多个原样一起使用。【参考方案3】:

这是我的解决方案。它使用未记录的 Form 方法 _raw_value() 来查看请求的数据。这也适用于具有前缀的表单。

class CascadeForm(forms.Form):
    parent=forms.ModelChoiceField(Parent.objects.all())
    child=forms.ModelChoiceField(Child.objects.none())

    def __init__(self, *args, **kwargs):
        forms.Form.__init__(self, *args, **kwargs)
        parents=Parent.objects.all()
        if len(parents)==1:
            self.fields['parent'].initial=parents[0].pk

        parent_id=self.fields['parent'].initial or self.initial.get('parent') \
                  or self._raw_value('parent')
        if parent_id:
            # parent is known. Now I can display the matching children.
            children=Child.objects.filter(parent__id=parent_id)
            self.fields['children'].queryset=children
            if len(children)==1:
                self.fields['children'].initial=children[0].pk

jquery 代码:

function json_to_select(url, select_selector) 
/*
 Fill a select input field with data from a getJSON call
 Inspired by: http://***.com/questions/1388302/create-option-on-the-fly-with-jquery
*/
    $.getJSON(url, function(data) 
    var opt=$(select_selector);
    var old_val=opt.val();
        opt.html('');
        $.each(data, function () 
            opt.append($('<option/>').val(this.id).text(this.value));
        );
        opt.val(old_val);
        opt.change();
    )



   $(function()
     $('#id_parent').change(function()
       json_to_select('PATH_TO/parent-to-children/?parent=' + $(this).val(), '#id_child');
     )  
    );

回调代码,返回 JSON:

def parent_to_children(request):
    parent=request.GET.get('parent')
    ret=[]
    if parent:
        for children in Child.objects.filter(parent__id=parent):
            ret.append(dict(id=child.id, value=unicode(child)))
    if len(ret)!=1:
        ret.insert(0, dict(id='', value='---'))
    return django.http.HttpResponse(simplejson.dumps(ret), 
              content_type='application/json')

【讨论】:

这对我帮助很大。但是 _raw_value() 从 1.9 开始被删除。我正在使用以下内容:link。有人知道捕获未提交值的更好方法吗?非常感谢。

以上是关于Django/jQuery 级联选择框?的主要内容,如果未能解决你的问题,请参考以下文章

elementui级联选择器一级选择框不显示

Ajax级联选择框

级联选择器 两级不在框中显示

组合框级联效应不适用于不同的行

element Ui的级联选择器 任意一级选中下拉框自动关闭

级联组合框不会显示更新的值