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)
Comments (7)
- Login to post
order by: newest oldest upvoted downvoted
very interesting.
Have you used this in a non-trivial app? My concern is it would clutter my controllers with what is usually defined in the view.
Not sure if this site qualifies but I use it here. Its main strength is creating event handler ajax callbacks. This allows you to separate the presentation layer from its behavior even more. The view can be pure markup and the behavior is dictated by the controller. Just my personal design preference more than anything.
Excellent work! I'm gonna be using this in my latest project. Thanks a lot. =)
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?
show more comments0
richard 14 years ago
0
mrfreeze 14 years ago
0
insolsence 14 years ago
0
frank 12 years ago
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 --