If you benefit from web2py hope you feel encouraged to pay it forward by contributing back to society in whatever form you choose!

So, you want to make cascading dropdowns, for example you select a 'make' of car (Ford, Chevrolet, GMC, etc..) and then you select a 'model' that is filtered by the 'make'. Here's how you do that.


First, you setup your database. In this example, it's a simple cars db... I populated mine with 'Ford Focus, Ford CMAX, Nissan Sentra, Nissan Altima, Chevrolet Corvette, Chevrolet Camaro' make = first word, model = second word.




Next, you need to create your page... I'm going to call mine "caroptions". Notice that I have a default option called '--select make--' that's because when the page is first loaded, the modelsDiv content is not loaded from the DB. I could have added an ajax call to load it with 'Chevrolet' since that's the first option in my list (it's alphabetically ordered by make as you'll see in the controller).  However, I left that out because I wanted you to see the most basic setup, and I didn't want to tie my view to the data tier. Separation of concerns and all that.


<select id="makers" onchange="changeMaker();">
    <option value='select one'>--select make--</option>
    {{for make in makers:}}
    <option value='{{=make.Make}}'>{{=make.Make}}</option>
<input type="hidden" id="maker" name="maker" value="">
<div id="modelsDiv">
models go here
<script language="javascript" src="../static/js/jQuery.js"></script>
<script language="javascript" src="../static/js/web2py.js"></script>
<script language="javascript">
    function changeMaker(){
    var makeropts = document.getElementById("makers");
    var maker = makeropts.options[makeropts.selectedIndex].value;
    var x = document.getElementById("maker");
        x.value = maker; // anyone know a better way?
        ajax('models', ['maker'], 'modelsDiv');

You'll notice that I am including 'jQuery.js' and 'web2py.js'. If you are using the standard layout, you don't need to do this, as it's automatic. Just remove those lines.


Now, you'll see that I am using the 'ajax' function included in web2py.js to call the 'models' function in the controller, with the value of the input 'maker'. There may be a better way to get the maker (you can see that I got it in the variable called 'maker') but I couldn't get it to work any other way. Perhaps someone can comment and clue me in. Regardless, it's still easy to do. I just made a hidden field to store that value.


So, now let's setup the controller... in my case, I called it 'cars.py' so your subdirectory for the above file would be 'cars/caroptions.html'. If you named it 'carmakes.py' then your html above would have to be 'carmakes/caroptions.html'. You could just call it 'index.html' if you wish, but whatever... it's up to you. Once you know this technique you can customize it any way you want.


This is the controller cars.py...

# coding: utf8

def index(): return dict(message="hello from cars.py")

def caroptions():
    makers = db().select(db.cars.Make,distinct=True,orderby=db.cars.Make)
    return dict(makers=makers)

def models():
    models = db(db.cars.Make==request.vars.maker).select(db.cars.Model)
    return dict(models=models)

As you see, the function 'caroptions' returns a dictionary called 'makers' and the 'caroptions.html' file loops over the makers and puts them in the html. You want clear separation of Model / View / Controller just like we have here. Don't make your controller write the html, that's bad separation of concerns! Also note that I am calling 'session.forget(response)' here since I am not using the session at all. It's a speed hack, you can remove that line if you need to use the session. The way this was written though, you don't need to store anything in the session.


Now let's build the 'models.html' page since it's a function in the controller...

<select id="models">
    {{for model in models:}}
    <option value='{{=model.Model}}'>{{=model.Model}}</option>

Pretty simple. The output of this page is just going to replace the contents of the "modelsDiv" as defined in the 'caroptions.html' file. The ajax() call does this for you.


Any questions?


Related slices

Comments (1)

  • Login to post

  • 0
    pydev 5 years ago

    Hi Derek,

    just tested it and it worked, thanks for sharing!

    Here is some code to fill the db:

    def fillData():
        if db(db.cars.id>0).count() == 0:
            db.cars.insert(Make='Ford',Model='Ford Focus')
            db.cars.insert(Make='Ford',Model='Ford Focus')
            db.cars.insert(Make='Nissan',Model='Nissan Sentra')
            db.cars.insert(Make='Nissan',Model='Nissan Altima')
            db.cars.insert(Make='Chevrolet',Model='Chevrolet Corvette')
            db.cars.insert(Make='Chevrolet',Model='Chevrolet Camaro')



Hosting graciously provided by:
Python Anywhere