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

NOTE: A newer version of this widget has been created by Jurgis - please see http://www.web2pyslices.com/slice/show/1616/widget-select-or-add-option-ng.

 

This widget will create a <select> object with an "Add" button next to it, allowing users to add new catagories, etc on the fly without having to visit a different screen. Works with IS_IN_DB and uses web2py components and jqueryUI dialogs

This widget was inspired by the OPTION_WITH_ADD_LINK slice

The widget

Place the following code into a model file (models/A_widget.py)

from gluon.sqlhtml import *
class SELECT_OR_ADD_OPTION(object):

    def __init__(self, controller=None, function=None, form_title=None, button_text = None, dialog_width=450):
        if form_title == None:
            self.form_title = T('Add New')
        else:
            self.form_title = T(form_title)
        if button_text == None:
            self.button_text = T('Add')
        else:
            self.button_text = T(button_text)
        self.dialog_width = dialog_width

        self.controller = controller
        self.function = function
    def widget(self, field, value):
        #generate the standard widget for this field
        select_widget = OptionsWidget.widget(field, value)

        #get the widget's id (need to know later on so can tell receiving controller what to update)
        my_select_id = select_widget.attributes.get('_id', None) 
        add_args = [my_select_id] 
        #create a div that will load the specified controller via ajax
        form_loader_div = DIV(LOAD(c=self.controller, f=self.function, args=add_args,ajax=True), _id=my_select_id+"_dialog-form", _title=self.form_title)
        #generate the "add" button that will appear next the options widget and open our dialog
        activator_button = A(T(self.button_text), _id=my_select_id+"_option_add_trigger")
        #create javascript for creating and opening the dialog
        js = '$( "#%s_dialog-form" ).dialog({autoOpen: false, show: "blind", hide: "explode", width: %s});' % (my_select_id, self.dialog_width)
        js += '$( "#%s_option_add_trigger" ).click(function() { $( "#%s_dialog-form" ).dialog( "open" );return false;}); ' % (my_select_id, my_select_id)        #decorate our activator button for good measure
        js += '$(function() { $( "#%s_option_add_trigger" ).button({text: true, icons: { primary: "ui-icon-circle-plus"} }); });' % (my_select_id) 
        jq_script=SCRIPT(js, _type="text/javascript")

        wrapper = DIV(_id=my_select_id+"_adder_wrapper")
        wrapper.components.extend([select_widget, form_loader_div, activator_button, jq_script])
        return wrapper

You assign the widget to a field using

#Initialize the widget
add_option = SELECT_OR_ADD_OPTION(form_title="Add a new something", controller="product", function="add_category", button_text = "Add New", dialog_width=500)
#assign widget to field
db.product.category_id.widget = add_option.widget

The widget accepts the following arguments:

  • form_title: string. This will appear as the jQueryUI dialog's title. Default = "Add New"
  • controller: string. Name of the controller that will handle record creation.
  • function: string. Name of the function that will handle record creation. (Should both create form, accept it and be prepared to issue javascript to interact with widget - see below.)
  • button_text: string. The text that should appear on the button that will activate our form dialog. Default is "Add".
  • dialog_width: integer. The desired width in pixels of the dialog box. Default is 450

The Model

Define your database tables in models/db.py

db.define_table('category',
    Field('name', 'string', notnull=True, unique=True),
    Field('description', 'text')
)

db.define_table('product',
    Field('category_id', db.category, requires=IS_IN_DB(db, 'category.id', 'category.name')),
    Field('name', 'string', notnull=True),
    Field('description', 'text'),
    Field('price', 'decimal(10,2)', notnull=True)
    )

Create your controller functions

def create():
    #This is the main function, the one your users go to

    #Initialize the widget
    add_option = SELECT_OR_ADD_OPTION(form_title="Add new Product Category", controller="product", function="add_category", button_text = "Add New")
    #assign widget to field
    db.product.category_id.widget = add_option.widget

    form = SQLFORM(db.product)
    if form.accepts(request.vars, session):
        response.flash = "New product created"
    elif form.errors:
        response.flash = "Please fix errors in form"
    else:
        response.flash = "Please fill in the form"

    #you need jquery for the widget to work, include here or just put in your master layout.html
    response.files.append("http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.9/jquery-ui.js")
    response.files.append("http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.9/themes/smoothness/jquery-ui.css")
    return dict(message="Create your product",
            form = form)

def add_category():
    #this is the controller function that will appear in our dialog
    form = SQLFORM(db.category)

    if form.accepts(request.vars):
        #Successfully added new item
        #do whatever else you may want

        #Then let the user know adding via our widget worked        
        response.flash = T("Added")
        target= request.args[0]
        #close the widget's dialog box
        response.js = '$( "#%s_dialog-form" ).dialog( "close" ); ' %(target)
        #update the options they can select their new category in the main form                
        response.js += """$("#%s").append("<option value='%s'>%s</option>");""" \
                % (target, form.vars.id, form.vars.name)
        #and select the one they just added
        response.js += """$("#%s").val("%s");""" % (target, form.vars.id)
        #finally, return a blank form incase for some reason they wanted to add another option
        return form
    elif form.errors:
        #silly user, just send back the form and it'll still be in our dialog box complete with error messages
        return form
    else:
        #hasn't been submitted yet, just give them the fresh blank form        
        return form

Create Required Views

As of web2py 1.97.1, generic views have been disabled for security reason, so you'll need to explicitly create views for your create and add_category controller functions. (Thanks Richard for pointing this out!)

views/product/create.html

{{extend 'layout.html'}}
<h1>Create a New Product</h1>
{{=message}}
{{=form}}

views/product/add_category.load (I just created the new file via the admin interface & used the generic one web2py created for me, tweak as needed.)

{{if len(response._vars)==1:}}{{=response._vars.values()[0]}}{{else:}}{{=BEAUTIFY(response._vars)}}{{pass}}

Use with CRUD Forms

You can also use this widget with your crud forms, just remember to tell web2py to use our widget, also include the jqueryUI stuff. (Hint, if you're gonna use this lots just do the widget setup in the model & jqueryUI include in your main template.)

In your controller:

def create_crud():
    from gluon.tools import Crud
    crud = Crud(globals(), db)
    add_option = SELECT_OR_ADD_OPTION(form_title="Add new Product Category", controller="product", function="add_category", button_text = "Add New")
    #assign widget to field
    db.product.category_id.widget = add_option.widget
    form = crud.create(db.product)
    #you need jquery for the widget to work, include here or just put in your master layout.html
    response.files.append("http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.9/jquery-ui.js")
    response.files.append("http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.9/themes/smoothness/jquery-ui.css")
    return dict(form = form)

In Action

A form with the SELECT_OR_ADD_OPTION widget

alt text

Click on the "Add New" button and the dialog opens. (Hmm, can't type my own widget's name right!)

alt text

Submit and the new option is created and automatically selected in the main form.

alt text

Get It

Grab the source or a sample app from bitbucket

Related slices

Comments (12)

show more comments

Hosting graciously provided by:
Python Anywhere