Interdependent drop-down menus

139 views
Skip to first unread message

E. B.

unread,
Oct 28, 2024, 2:44:33 PM10/28/24
to py4web
Hi,

how can I create interdependent drop-down menus. I have tried the following:

I have two tables (daten, items_1). The second references the first.

In the controller:

@action("get_items", method=['GET', 'POST'])
@action.uses(db)
def get_items():
daten_n_id = request.get_vars.get('daten_id') or request.post_vars.get('daten_id')
items_list = db(db.items_1.daten_id == daten_n_id).select().as_list()
return dict(items=items_list)

On my html page:

<div class="dropdown-wrapper">
<label for="daten_id">Auswahl Daten/label>
[[=form.custom.daten_id]]
</div>

<div class="dropdown-wrapper">
<label for="items_1_id">Auswahl Items</label>
<select id="items_1_id" name="items_1_id" disabled>
<option value="">Bitte das erste Dropdown nutzen</option>
</select>
</div>

There is another JavaScript that ensures that the second drop-down menu (for selecting Items) is dynamically updated based on the selection in the first drop-down menu (for Daten).

The following error message always appears:
py4web\apps\example_app\templates\add.html", line 40, in template
[[=form.custom.daten_id]]
AttributeError: 'Param' object has no attribute 'daten_id'

Can someone help me?

Rob Paire

unread,
Nov 1, 2024, 6:34:04 PM11/1/24
to py4web
Hi E.B.,

Great to see you in the Py4Web community!

If you're planning to write your project entirely from scratch, you'll definitely need to pay close attention to the manual. However, it's often much easier to start with a working example and then make small, incremental changes as you go. This approach can help you get a better feel for how things work step by step.

Give it a try by pasting the code below and see if you can get it running. Let me know if this helps, and if you run into any issues.

### models.py ###
from .common import db, Field
from pydal.validators import *

# Define two sample tables: options and choices
db.define_table("options",
                Field("option_name", "string"))

db.define_table("choices",
                Field("choice_name", "string"),
                Field("option_id", "reference options"))

# Insert sample records in the options table for red, green, blue
if not db(db.options).count():
    db.options.insert(option_name="red")
    db.options.insert(option_name="green")
    db.options.insert(option_name="blue")

# Commit once to persist all the changes
db.commit()

### controllers.py  ###
from py4web import action, redirect, URL, Field
from pydal.validators import IS_IN_DB
from py4web.utils.form import Form
from .common import db

# Define the sample controller action
@action('welcome', method=['GET', 'POST'])
@action.uses('welcome.html', db)
def welcome():
    # Create a dropdown field using records from the options table
    form = Form([
        Field("name", "string"),
        Field("color", "reference options",  requires=IS_IN_DB(db, 'options.id', 'options.option_name'))
    ], csrf_session=None)
    name = None
    color = None
   
    # If the form is successfully submitted, get the user input
    if form.accepted:
        name = form.vars.get("name")
        color = form.vars.get("color")
   
    # Pass the form, name, and selected color to the template for rendering
    return dict(form=form, name=name, color=color)

### welcome.html ###
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Welcome Page</title>
</head>
<body>
    <h1>Welcome Page</h1>
   
    <!-- Render the form here -->
    [[=form]]



<!-- Results  Populated After the form has been Submitted -->
<hr>
[[=name]]
<br>
[[=color]]
   
</body>
</html>

Massimo DiPierro

unread,
Nov 4, 2024, 1:15:55 AM11/4/24
to Rob Paire, py4web
While two drop down menus dependent on each other is definitively possible with some JS (or better with Vue.js) I would like to understand the use case first and details of how it is supposed to work. It does not seem a good UI/UX but I may be misunderstanding.

--
You received this message because you are subscribed to the Google Groups "py4web" group.
To unsubscribe from this group and stop receiving emails from it, send an email to py4web+un...@googlegroups.com.
To view this discussion visit https://groups.google.com/d/msgid/py4web/2ce451c2-c2bf-4210-bf33-1665b64c1f8dn%40googlegroups.com.

Massimo

unread,
Nov 4, 2024, 1:22:03 AM11/4/24
to py4web
My apology. Ignore my previous response. I did not read the originally email properly because was scrambled. I will post a solution to the question...

Massimo

unread,
Nov 4, 2024, 2:13:04 AM11/4/24
to py4web
I would do:

models.py
from .common import db, Field, auth
from pydal.validators import *
from py4web.utils.populate import populate

db.define_table(
'book',
Field('title')
# auth.signature
)

db.define_table(
'review',
Field('book_id', 'reference book'),
Field('title'),
Field('body','text'),
# auth.signature
)

if db(db.book).count() == 0:
from py4web.utils.populate import populate
populate(db.book, 10)
populate(db.review, 100)

controllers.py
from py4web import action
from .common import db


@action("index")
@action.uses("index.html")
def index():
return {}

@action("books")
@action.uses(db)
def _():
return {"books": db(db.book).select().as_list()}

@action("reviews/<book_id:int>")
@action.uses(db)
def _(book_id):
return {"reviews": db(db.review.book_id==book_id).select().as_list()}


templates/index.html
[[extend 'layout.html']]

<div id="myapp">
<select v-model="selected_book" v-on:change="update_reviews">
<option v-for="book in books" v-bind:value="book" v-text="book.title"></option>
</select>

<select v-model="selected_review">
<option v-for="review in reviews" v-bind:value="review" v-text="review.title"></option>
</select>

<p v-if="selected_review" v-text="selected_review.body"></p>
</div>


<script>
var app = {};
// get the base URL to call APIs
app.base_url = window.location.href.split("/").slice(0,4).join("/");
// store the app configuration
app.config = {};
// this function sets the initial state of the app
app.config.setup = function() {
// returns variables needed
return {
books: Vue.ref([]), // the list of books (as returned by API)
reviews: Vue.ref([]), // the list of reviews (as returned by API)
selected_book: Vue.ref(null), // the selected book
selected_review: Vue.ref(null) // the selected review
};
};
// methods available to the app
app.config.methods = {};
app.config.methods.update_books = function(){
// get books and store them in app.vue.books
fetch(app.base_url + "/books").then(r=>r.json()).then(data=>{app.vue.books=data.books;});
};
app.config.methods.update_reviews = function(){
// get reviews for selected_book_id and store them in app.vue.reviews
app.vue.selected_review = null;
fetch(app.base_url + "/reviews/" + app.vue.selected_book.id).then(r=>r.json()).then(data=>{app.vue.reviews=data.reviews;});
};
// build the app and link it with div#myapp
app.vue = Vue.createApp(app.config).mount("#myapp");
// populate the list of books
app.config.methods.update_books();
</script>

It may be a bit overkill but should be easy to extend and generalize.
Hope this help and I understood the question.

Jorge Salvat

unread,
Nov 4, 2024, 5:31:11 AM11/4/24
to py4web
Very nice working example, thanks Massimo

E. B.

unread,
Nov 4, 2024, 10:23:17 AM11/4/24
to py4web
Hello everyone,

thank you very much for the great suggestions. I can learn a lot from them. I will implement them all with my forms, but it will take some time because I haven't been coding for that long. For starters, I've implemented a fairly simple implementation using Javascript. In which I use hide/show. It's cumbersome, but it works.
 
 if (selectedValue === “1”) {
   if (value === “1” || value === “2” || value === “”) {
   $(this).show();
    } else {
   $(this).hide();
    }

Best regards!

Rob Paire

unread,
Nov 4, 2024, 1:27:11 PM11/4/24
to py4web
This example is helpful - Thank you!
Reply all
Reply to author
Forward
0 new messages