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

It's very simple and fast because the data query is done by server side. I call it shadow clone jutsu like the one in Naruto. There is an actual list name='maker_name' in view and it's switching to ajax one when first selection is made, then switch back to actual one when page is submitted.

SCREENSHOT

alt text

WORKING SAMPLE

Visit demo site here

MODEL - db.py

# -*- coding: utf-8 -*-
db = DAL('sqlite://storage.sqlite') 

response.generic_patterns = ['*'] if request.is_local else []

# Foreign key is used for good practice but not required to use ajax casading drop down
db.define_table('Category',
    Field('Name'))

db.define_table('Maker',
    Field('Name'),
    Field('Category_ID', db.Category),
    Field('Note', 'text'))

db.define_table('Product',
    Field('Part_Number'),
    Field('Maker_ID', db.Maker),
    Field('List_Price', 'decimal(13,2)'),
    Field('Special_Price', 'decimal(13,2)'))

db.Category.Name.requires = IS_NOT_EMPTY()
db.Maker.Name.requires = IS_NOT_EMPTY()
db.Maker.Category_ID.requires = IS_IN_DB(db, db.Category.id, '%(Name)s')
db.Product.Part_Number.requires = IS_NOT_EMPTY()
db.Product.Maker_ID.requires = IS_IN_DB(db, db.Maker.id, '%(Name)s')

# Insert test data - you can delete the line below if it's not necessary

if db(db.Category.id>0).count() == 0:
    db.Category.insert(Name='PC')
    db.Category.insert(Name='Smart Phone')

    db.Maker.insert(Name='Toshiba', Category_ID=1, Note='Good Maker')
    db.Maker.insert(Name='HP', Category_ID=1, Note='Good Maker')
    db.Maker.insert(Name='Dell', Category_ID=1, Note='Good Maker')
    db.Maker.insert(Name='Apple', Category_ID=2, Note='Good Maker')
    db.Maker.insert(Name='Samsung', Category_ID=2, Note='Good Maker')

    db.Product.insert(Part_Number='Toshiba Product A', Maker_ID=1, List_Price=1000, Special_Price=500)
    db.Product.insert(Part_Number='Toshiba Product B', Maker_ID=1, List_Price=1500, Special_Price=1000)
    db.Product.insert(Part_Number='Toshiba Product C', Maker_ID=1, List_Price=2000, Special_Price=1500)
    db.Product.insert(Part_Number='Toshiba Product D', Maker_ID=1, List_Price=2500, Special_Price=2000)
    db.Product.insert(Part_Number='Toshiba Product E', Maker_ID=1, List_Price=3000, Special_Price=2500)
    db.Product.insert(Part_Number='Toshiba Product F', Maker_ID=1, List_Price=3500, Special_Price=3500)

    db.Product.insert(Part_Number='HP Product A', Maker_ID=2, List_Price=1000, Special_Price=500)
    db.Product.insert(Part_Number='HP Product B', Maker_ID=2, List_Price=1500, Special_Price=1000)
    db.Product.insert(Part_Number='HP Product C', Maker_ID=2, List_Price=2000, Special_Price=1500)
    db.Product.insert(Part_Number='HP Product D', Maker_ID=2, List_Price=2500, Special_Price=2000)
    db.Product.insert(Part_Number='HP Product E', Maker_ID=2, List_Price=3000, Special_Price=2500)
    db.Product.insert(Part_Number='HP Product F', Maker_ID=2, List_Price=3500, Special_Price=3500)

    db.Product.insert(Part_Number='Dell Product A', Maker_ID=3, List_Price=1000, Special_Price=500)
    db.Product.insert(Part_Number='Dell Product B', Maker_ID=3, List_Price=1500, Special_Price=1000)
    db.Product.insert(Part_Number='Dell Product C', Maker_ID=3, List_Price=2000, Special_Price=1500)
    db.Product.insert(Part_Number='Dell Product D', Maker_ID=3, List_Price=2500, Special_Price=2000)
    db.Product.insert(Part_Number='Dell Product E', Maker_ID=3, List_Price=3000, Special_Price=2500)
    db.Product.insert(Part_Number='Dell Product F', Maker_ID=3, List_Price=3500, Special_Price=3500)

    db.Product.insert(Part_Number='Apple Product A', Maker_ID=4, List_Price=1000, Special_Price=500)
    db.Product.insert(Part_Number='Apple Product B', Maker_ID=4, List_Price=1500, Special_Price=1000)
    db.Product.insert(Part_Number='Apple Product C', Maker_ID=4, List_Price=2000, Special_Price=1500)
    db.Product.insert(Part_Number='Apple Product D', Maker_ID=4, List_Price=2500, Special_Price=2000)
    db.Product.insert(Part_Number='Apple Product E', Maker_ID=4, List_Price=3000, Special_Price=2500)
    db.Product.insert(Part_Number='Apple Product F', Maker_ID=4, List_Price=3500, Special_Price=3500)

    db.Product.insert(Part_Number='Samsung Product A', Maker_ID=5, List_Price=1000, Special_Price=500)
    db.Product.insert(Part_Number='Samsung Product B', Maker_ID=5, List_Price=1500, Special_Price=1000)
    db.Product.insert(Part_Number='Samsung Product C', Maker_ID=5, List_Price=2000, Special_Price=1500)
    db.Product.insert(Part_Number='Samsung Product D', Maker_ID=5, List_Price=2500, Special_Price=2000)
    db.Product.insert(Part_Number='Samsung Product E', Maker_ID=5, List_Price=3000, Special_Price=2500)
    db.Product.insert(Part_Number='Samsung Product F', Maker_ID=5, List_Price=3500, Special_Price=3500)

CONTROLLER - default.py

# -*- coding: utf-8 -*-

def index():
    if request.vars.maker_name:
        lists = db(db.Product.Maker_ID==request.vars.maker_name).select(db.Product.ALL)
        themakers = db(db.Maker.id==request.vars.maker_name).select(db.Maker.ALL)      

    else:
        lists = db(db.Product.Maker_ID==1).select(db.Product.ALL)
        themakers = db(db.Maker.id==1).select(db.Maker.ALL)

    categories = db().select(db.Category.ALL)

    if request.vars.category_name:
        makers = db(db.Maker.Category_ID==request.vars.category_name).select(db.Maker.ALL)
    else:
        makers = db(db.Maker.Category_ID==1).select(db.Maker.ALL)
    return dict(lists=lists, categories=categories, makers=makers, themakers=themakers)

def maker():
    makers = db(db.Maker.Category_ID==request.vars.category_name).select(db.Maker.ALL)
    result = "<select name='maker_name'>"
    for maker in makers:
        result += "<option value='" + str(maker.id) + "'>" + maker.Name + "</option>"  
    result += "</select>"
    return XML(result)

VIEW - default/index.html

{{extend 'layout.html'}}

<form enctype="multipart/form-data" action="{{URL()}}" method="post">
    <select name='category_name' 
        onchange="jQuery(maker_name).remove();
        ajax('maker', ['category_name'], 'shadow_clone');">
        {{for category in categories:}}
            <option value="{{=category.id}}" 
                {{=" selected='selected'" if str(category.id)==request.vars.category_name else ""}}>
            {{=category.Name}}
            </option>
        {{pass}}
    </select>

    <span id='shadow_clone'></span>

    <select name='maker_name' >
        {{for maker in makers:}}
            <option value="{{=maker.id}}" 
                {{=XML(" selected='selected'") if str(maker.id)==request.vars.maker_name else ""}}>
            {{=maker.Name}}</option>
        {{pass}}
    </select>
    <input type="submit" value='Submit'>  
</form>

<hr>

<div id="left_price">

    <div id="effective_price">
        {{for themaker in themakers:}}
            <p>{{=themaker.Note}}</p>
        {{pass}}
    </div>


    <table class="pricetable">
        <tr>
            <th class="priceleft">Part Number</th>
            <th class="priceright">List ($)</th>
            <th class="priceright">Special ($)</th>
        </tr>
    </table>

    <div id="table_price">
        <table  class="pricetable">
            {{for list in lists:}}
            <tr>
                <td class="priceleft">{{=list.Part_Number}}</td>
                <td class="priceright">{{=list.List_Price}}</td>
                <td class="priceright">{{=list.Special_Price}}</td>
            </tr>
            {{pass}}
        </table>
    </div>

</div>

<div id="right_price">

</div>

CSS - base.css (Not required) This is not required for the function. I just add it for my view.

/*For Ajax Cascading Drop Down - Omi Chiba */
#left_price {
    width: 55%;
    float:left;
}

#right_price {
    width: 40%;
    float:left;
    margin-left:5%;
}

#table_price {
    width: 500px;
    height: 420px;
    overflow: auto;
}

#effective_price {
    width: 475px;
    text-align: right;
}

.pricetable {
    width: 480px;
}

.priceleft {
    width: 160px;
    text-align: left;
}

.priceright {
    width: 80px;
    text-align: right;
}

Related slices

Comments (2)

  • Login to post



  • 0
    ochiba77 12 years ago
    Kostas, Thank you, I fixed the XML part in the view. For controller, I confirmed it worked great, too and I will leave it as option for people who want to use this code.

  • 0
    kmouts 12 years ago
    My small contributions: 1. In CONTROLLER
    
    def maker():
        makers = db(db.Maker.Category_ID==request.vars.category_name).select(db.Maker.ALL)
        opts=[OPTION(maker.Name,_value=str(maker.id)) for maker in makers]
        s = SELECT(opts,_name='maker_name')
        s[1].update(_selected="_selected")
        result = s.xml()
        return XML(result)
    
    This way the select tag is constructed in a more "web2pythonic" way IMHO. In addition, the
    "selected" option is easy to be chosen in the initial form. Otherwise the "for" loop must be broken,
    to add an

Hosting graciously provided by:
Python Anywhere