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

How to use it

Place clienttools.py (download at the bottom) in your application's modules folder.

In your model:

clienttools = local_import('clienttools')
page = clienttools.PageManager(globals())
event = clienttools.EventManager(page)
js = clienttools.ScriptManager(page) # javascript helpers
jq = clienttools.JQuery # don't instantiate, just to shorten

To include a google hosted ajax API (http://code.google.com/apis/ajaxlibs/):

page.google_load("jqueryui", "1.7.2")

Or include any script or stylesheet dynamically (why load them if you don't need them?):

page.include("http://ajax.googleapis.com/ajax/libs/jqueryui/1.7.2/jquery-ui.js")
page.include(URL(r=request,c='static',f='myscript.js'))
page.include(URL(r=request,c='static',f='mystyle.css'))

You can also cache web resources locally:

page.include("http://ajax.googleapis.com/ajax/libs/jqueryui/1.7.2/jquery-ui.js", 
                      download=True, subfolder="jqueryui")

Not specifying a subfolder will put it in the 'static' folder.

In your view (place in web2py_ajax.html to include automatically):

{{=page.render()}}

What it does

You can do some interesting things in the controller.

This will subscribe the DIV to the click event which calls the specified controller function when triggered:

def index():
    d = DIV("Click Me", _id="clickme")
    event.listen("click", d, handle_it)
    return dict(d=d)

def handle_it():
    return("alert('Thanks for clicking');")

The default data selector is 'form:first' but you can specify another jQuery selector if you like. You can also pass web2py HTML helpers or a custom jQuery command (more on this below).

event.listen("click", d, handle_it,data="#another")
event.listen("click", d, handle_it,data="jQuery('#clickme').html()" )
event.listen("click", d, handle_it,data=jq(d).html() )

By default, the response is eval'd. You can also pass a web2py helper (must have an ID or name attribute) and it will be updated with the response. You can also pass another jQuery selector string.

def index(): 
     d = DIV("Click Me", _id="clickme")
     event.listen("click", d, handle_it,d)
     return dict(d=d)

def handle_it():
    return("Date and time: " + str(request.now))

You can also pass args and get information about the event by setting event_args=True.
Setting persist=True will use 'live' instead of 'bind' in the jQuery event declaration.
This will make the event persist even if the element is replaced by another matching element.
Here is the full signature of the listen function:

def listen(self, event, target, handler, success="eval(msg);", data='form:first', 
              args=None, on_error="" ,persist=False, event_args=False)

The ScriptManager (instantiated as js in these examples) is a collection of prebuilt scripts. This will add a timer with an on/off switch to the page. At each interval, the timer will call the handle_it function and get the date and time on the server.

d = DIV('Server time: ' + str(request.now),_id="servertime")

def index():
    form = SQLFORM.factory(Field('timer', requires=IS_IN_SET(['On','Off']),default="On", 
                                          widget=SQLFORM.widgets.radio.widget))   
    event.listen('change',"input[name='timer']",handle_it)
    page.ready(jq("input[type='submit']").hide()())
    callback = js.call_function(handle_it)
    page.ready(js.timer(callback,3000))
    return dict(d=d,form=form)

def handle_it():
    if request.vars.timer == "Off":
        return jq(d).css("color","red").html("Timer Stopped")() + \
               js.stop_timer(request.vars.timer_id)           
    if request.vars.timer == "On" and not request.vars.timer_id:
        callback = js.call_function(handle_it)
        return jq(d).css("color","black").html("Timer Started")() + \
               js.timer(callback,3000)
    return jq(d).html("Server Time: " + str(request.now))()

Notice the JQuery object (jq). It is a helper designed to reduce the amount of string substitutions when returning scripts.

It basically allows you to write jQuery in Python (with a few limitations).

jq("#servertime").html("Timer Stopped") # produces 'jQuery("#servertime").html("Timer Stopped")'

Adding () to the end of a JQuery object will add a semicolon:

jq("#servertime").css("color","red")() # produces 'jQuery("#servertime").css("color", "red");'

If the jQuery attribute is also a python keyword (such as attr), you must prepend it with an underscore:

jq("#test")._attr(dict(style="background:red;"))() #produces 'jQuery("#time").attr({"style": "background:red;"});'

You can also pass web2py html helpers:

d = DIV(_id="clickme")
jq(d).hide()() # produces 'jQuery("#clickme").hide();'
i = INPUT(_type="text",_name="test")
jq(i).html(d)() # produces 'jQuery("input[name=\'test\']").html("<div id=\'clickme\'></div>");'

Accessing the jQuery object itself:

jq().noConflict()() # produces 'jQuery.noConflict();'

More examples

This will add a confirmation to the submit and focus on the first input when the form is displayed:

def index():
    form = SQLFORM.factory(Field('first'),Field('last'))   
    if form.accepts(request.vars, session):        
        response.flash = "Got it!"
    event.listen('submit',"form","return confirm('Are you sure?');") # adds a confirmation to submit       
    page.ready(jq("input:first").focus()())
    return dict(form=form)

Custom form errors:

def index():    
    form = SQLFORM.factory(Field('first',requires=IS_NOT_EMPTY()),Field('last',requires=IS_NOT_EMPTY()))
    if form.accepts(request.vars,session):
        page.ready('alert("Got it!");')
    elif form.errors:
        for k in form.errors:
            page.ready(jq("#no_table_" + k).after(SPAN(form.errors[k],_style="color:red;"))())
    form.errors.clear()
    return dict(form=form)

Even more examples

This will call a server function once after 5 seconds:

def index():
    callback = js.call_function(handle_it)
    page.ready(js.delay(callback, 5000))
    return dict()
def handle_it():
    return js.alert("Gotcha!")

This will add a confirmation to a function:

def index():
    callback = js.call_function(handle_it)
    page.ready(js.confirm("Call handle_it?",callback,js.alert("Cancelled!")))
    return dict()
def handle_it():
    return js.alert("It has been handled!")

Let's create a dialog with jQueryUI:

def index():
    page.include("http://ajax.googleapis.com/ajax/libs/jqueryui/1.7.2/jquery-ui.min.js")
    # or  page.google_load("jqueryui", "1.7.2")
    page.include("http://ajax.googleapis.com/ajax/libs/jqueryui/1.7.2/themes/ui-darkness/jquery-ui.css")   
    d = DIV("Click Me",_id="clickme")
    event.listen("click",d,handle_it)
    return dict(d=d)

def handle_it():
    return jq("#clickme").append(DIV(_id="dialog"))() + \
           jq("#dialog").html("%s")() % str(request.now) + \
           jq("#dialog").dialog(dict(title="Server Time"))()

Now for a progress bar. It's almost too easy:

progress = DIV(_id="progress")
wrapper = DIV(progress,_style="width:400px;")

def index():
    page.include("http://ajax.googleapis.com/ajax/libs/jqueryui/1.7.2/jquery-ui.min.js")
    page.include("http://ajax.googleapis.com/ajax/libs/jqueryui/1.7.2/themes/ui-darkness/jquery-ui.css")        
    callback = js.call_function(get_progress)
    page.ready(jq(progress).progressbar( dict(value=request.now.second) )() )
    page.ready(js.timer(callback,2000))
    return dict(wrapper=wrapper)

def get_progress():
    return jq(progress).progressbar('option', 'value', request.now.second)()

And a slider with an unnecessary callback:

slider = DIV(_id="slider")
wrapper = DIV(slider, _style="width:400px;")

def index():
    page.include("http://ajax.googleapis.com/ajax/libs/jqueryui/1.7.2/jquery-ui.min.js")
    page.include("http://ajax.googleapis.com/ajax/libs/jqueryui/1.7.2/themes/ui-darkness/jquery-ui.css")        
    page.ready(jq(slider).slider()())
    callback = js.function("", ["e", "ui"], js.call_function(handle_it)) 
    page.ready(jq(slider).bind('slidestop', callback)())
    return dict(wrapper=wrapper)

def handle_it():
    return js.alert(jq(slider).slider('option', 'value'),unquote=True)

Let's get fancy and send the value of the progress bar to the server:

slider = DIV(_id="slider")
wrapper = DIV(slider, _style="width:400px;")

def index():
    page.include("http://ajax.googleapis.com/ajax/libs/jqueryui/1.7.2/jquery-ui.min.js")
    page.include("http://ajax.googleapis.com/ajax/libs/jqueryui/1.7.2/themes/ui-darkness/jquery-ui.css")        
    page.ready(jq(slider).slider()())
    data=jq("#slider > a").css("left")
    callback = js.function("", "e,ui", js.call_function(handle_it, data=data ) )
    page.ready(jq(slider).bind('slidestop', callback)())
    return dict(wrapper=wrapper)

def handle_it():
    if request.vars:
        k,v = request.vars.items()[0]
        return js.alert("Progress: " + k)

Here is a simple example of an ajax auto-suggest:

def autosuggest():
    state = INPUT(_type="text",_name="state")
    suggestions = DIV(_id="suggestions")
    event.listen("keyup",state,get_suggestions,suggestions,data=state)
    return dict(state=state,suggestions=suggestions)

def get_suggestions():
    if request.vars.state == '': return ''
    states = ['Alabama','Alaska','American Samoa','Arizona','Arkansas','California','Colorado',
              'Connecticut','Delaware','District of Columbia','Florida','Georgia','Guam','Hawaii',
               'Idaho','Illinois','Indiana','Iowa','Kansas','Kentucky','Louisiana','Maine','Maryland',
               'Massachusetts','Michigan','Minnesota','Mississippi','Missouri','Montana','Nebraska',
               'Nevada','New Hampshire','New Jersey','New Mexico','New York','North Carolina','North Dakota',
               'Northern Marianas Islands','Ohio','Oklahoma','Oregon','Pennsylvania','Puerto Rico',
               'Rhode Island','South Carolina','South Dakota','Tennessee','Texas','Utah','Vermont','Virginia',
               'Virgin Islands','Washington','West Virginia','Wisconsin','Wyoming']
    suggestions = []
    for state in states:
        if state.lower().startswith(request.vars.state.lower()):
            suggestions.append(state)
    if len(suggestions):
        return "<br />".join(suggestions)
    return ''

Building Widgets

Now that we have a mechanism to control the behavior of the client from the server, we can build reusable widgets more easily. Let's build an ajax upload control using a this jQuery plugin:

def AjaxUploadWidget(field, value, **attr):       
    filelist = UL(_id="filelist")
    filewrapper = DIV("Files: ", filelist) 
    upload = INPUT(_type="button",_value="    Add Files    ",_id="ajax_upload")
    control = DIV(upload, filewrapper)
    page.include("http://valums.com/wp-content/uploads/ajax-upload/ajaxupload.3.5.js", download=True)
    page.ready("new AjaxUpload('%s', {action: '%s',onChange: %s, onComplete: %s})" % \
                ("#ajax_upload",URL(r=request),
                 js.function("",['file','extension'], jq(upload).val("Uploading...")() ),
                 js.function("",['file','response'],jq(upload).val("    Add Files    ")() + \
                              jq(filelist).append("<li>'+file+'</li>") )) 
              )
    return control

def ajax_upload():    
    form = SQLFORM.factory(Field('file',type="upload", widget=AjaxUploadWidget))
    for r in request.vars:
        if r=="userfile":
            # process the file here
            return "Success"
    return dict(form=form)

We could, of course, turn this into a class in order to fully encapsulate the plugin's API. This is just to give an example of what can be done.


 

Download module here

Download demo application here (name it clienttools)

Related slices

Comments (7)

  • Login to post



  • 0
    icreator 10 years ago

    /views - is empty in demo ((


  • 0
    beefheart 12 years ago

    @cmj - It looks like there was a backward-incompatible change in web2py. You need to add this to your model: response.generic_patterns = ['*']


  • 1
    cmj 12 years ago

    I'm having a problem running the demo app ... I downloaded the .w2p file and used web2py admin to install it.

     

    The front page with the list of examples comes up fine, but when I click any of them I get an error like this --

     

    invalid view (default/jqueryui_slider_value.html)

     

    Have I configured something wrong?  Any suggestions?

     

    Thanks --


  • 0
    frank 12 years ago
    very useful slice,thanks you very much, I got one issue when I take use of same code with web2pyslice user module by using client tools, I used the use same user management code as web2pyslices in my program which work perfectly in 1. 91.6,when I upgrade to 1.99.4?there is login issue which say as following, 'registration_id' Traceback (most recent call last): File "D:\my project\web2py\gluon\restricted.py", line 204, in restricted exec ccode in environment File "D:/my project/web2py/applications/caicaifu/controllers/default.py", line 904, in File "D:\my project\web2py\gluon\globals.py", line 172, in self._caller = lambda f: f() File "D:/my project/web2py/applications/caicaifu/controllers/default.py", line 134, in user form=auth() File "D:\my project\web2py\gluon\tools.py", line 1141, in __call__ return getattr(self,args[0])() File "D:\my project\web2py\gluon\tools.py", line 1728, in login user = self.get_or_create_user(form.vars) File "D:\my project\web2py\gluon\tools.py", line 1454, in get_or_create_user if user and user.registration_id and user.registration_id!=keys.get('registration_id',None): File "D:\my project\web2py\gluon\dal.py", line 4440, in __getattr__ return self[key] File "D:\my project\web2py\gluon\dal.py", line 4431, in __getitem__ return dict.__getitem__(self, key) KeyError: 'registration_id' do you have same issue? I want to know which version of web2py using in this web2pyslices production site,have you try 1.99.4?

  • 0
    insolsence 14 years ago
    Excellent work! I'm gonna be using this in my latest project. Thanks a lot. =)
show more comments

Hosting graciously provided by:
Python Anywhere