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)

  • Login to post



  • 0
    naleenyadav 7 years ago

    I am not getting the Add New Button, rather getting  Add New & nothing happens on its click. I am using web2py 2.0 release.

     


  • 0
    lzacchetti-15312 7 years ago

    just use following code to use supported jquery version:

     

        response.files.append("http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.23/jquery-ui.js")   
        response.files.append("http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.23/themes/smoothness/jquery-ui.css")


  • 0
    lzacchetti-15312 7 years ago

    The widget won't work properly in new web2py 2.0 release.

    Trying to understand the reason...


  • 0
    johannspies 8 years ago
    I want to use this with amounts of records that is to large to use the 'select' widget. In such cases it is beter to use an autocomplete widget like the suggest-widget. What would be necessary to combine this add-option with something like the suggest-widget? (The standard web2py-autocomplete-widget does not work well in IE).

  • 0
    brianm 8 years ago
    Go for it Scottianw! Stick it in your own github account and I'll edit this slice to point to it.

  • 0
    monotasker 8 years ago
    Thanks for the great widget. I'm adapting it to be packaged as a plugin using web2py's new plugin system. I'm happy to share that packaged version back. What's your preference: do you want me to host it myself from my github account, or would you like me to send you the code so that you can make it available? I'm happy either way--just don't want to step on your toes!

  • 0
    simakwm 9 years ago
    Wow, thanks! Another question. I used it in a simple SQLFORM, without CRUD and it works fine, except that I need to reload the page when the "add new" window closes to update the

  • 0
    brianm 9 years ago
    Simakwm, can't say for sure without seeing your controller & view. I'd recommend using the Firebug add-on for FireFox and seeing if it shows you a javascript error.

  • 0
    simakwm 9 years ago
    It's not working when the main form comes from a CRUD.

  • 0
    brianm 9 years ago
    Simakwm, it'll work just fine with CRUD forms - I've added a sample controller function to the slice.

  • 0
    richard 9 years ago
    Under Web2py 1.97.1 product/create.load is needed (generic.load not working) so if use the app (https://bitbucket.org/bmeredyk/web2py-select_or_add_option-widget/src) take care. Richard

Hosting graciously provided by:
Python Anywhere