<select id="options" onchange="<% remote_function(update="options",
url=url(action='update_options')) %>">
<option value="0">Hello</option>
<option value="1">World</option>
</select>
Which resulted in a Myghty Template Error
Error: Error(NameError): global name 'url' is not defined
I thought the problem was: url=url(
so I replaced with: url=h.url
No more traceback just a browser crash. Browser is Firefox 1.5.
Any thoughts?
Any working demos I could look at?
When is the crash, after you change the selected item or before? If
after, what are you returning from your controller? One way of checking
your returned data is to go to the url directly, in your case
.../update_options.
>
> Any thoughts?
> Any working demos I could look at?
Have you looked at Ben's app that he mentioned a while ago?
Ben Bangert wrote:
> Here's a Pylons application I wrote that utilizes AJAX to have an auto-refreshing side-bar, a queue screen thats a draggable list, and a few AJAX replacement bits.
> http://hellanzb.com/trac/hellanzb/browser/hellahella/trunk
Robert
>
> I'm trying to build a demo app to get familiar with and demostrate
> Ajax in Pylons and used the following syntax in an autohandler
> which came from
> http://pylonshq.com/WebHelpers/module-webhelpers.rails.prototype.html
> in the remote_function section.
>
> <select id="options" onchange="<% remote_function(update="options",
> url=url(action='update_options')) %>">
> <option value="0">Hello</option>
> <option value="1">World</option>
> </select>
>
> Which resulted in a Myghty Template Error
>
> Error: Error(NameError): global name 'url' is not defined
>
> I thought the problem was: url=url( so I replaced with: url=h.url
Did you also call change the remote_function to be h.remote_function?
All the helper functions need to be prefixed by h. as Pylons doesn't
dump them into the globals.
Assuming you did prefix them all with h., can you turn off Javascript
in the browser and see what its making?
HTH,
Ben
I've looked at Ben's code for pointers and they shed light on the
missing h. in the docs samples but I wanted to put together a complete
demo to illustrate to myself how all the prototype functions worked with
the remote python bits as well. I just can't seem to work out what to do
at the python end.
Take the following example which is in an autohandler
<select id="options" onchange="<% h.remote_function(update="options",
url=h.url(action='update_options')) %>">
<option value="0">Hello</option>
<option value="1">World</option>
</select>
in the controller I have
def update_options(self):
# what goes here to access the value of options?
# can options be changed from here? If so how?
Thanks for all the help.
If you are wanting to change the browser ui then the helper
update_element_function() is the way to go. It generates in-line javascript that
can insert/replace/append html snippets on the client using Prototype's dom
manipulation functions. Ben's hellahella app has a few instances of this, e.g.
in the components directory there is enqueue_failure.myt and an
enqueue_success.myt both of which use the update_element_function() to firstly,
empty the html element with id 'enqueue' (using action-'empty') and then to set
the contents of that html element (using content='<p>Sent id to hellanzb</p>').
You can return any number of update_element_function() calls to change various
bits of the html page as required. The controller calls the component in order
to return the generated javascript as follows (where m.comp() calls the
specified component and writes the result to the output stream to be returned to
the browser (see the Myghty docs
http://www.myghty.org/docs/components.myt#components_programmatic_comp)
def enqueue_nzb(self, **params):
nzbid = m.request_args.get('newzbinid')
if nzbid and re.match(r'^\d{4,10}$', nzbid):
nzbserver.enqueuenewzbin(nzbid)
m.comp('/enqueue_success.myt')
else:
m.comp('/enqueue_failure.myt')
The key to using update_element_function() is that the calling function (i.e. js
in the browser) needs to use the evaluate_remote_response() helper which
actually executes the returned javascript rather than displaying it. In the
example above this is triggered in the autohandler using a remote form, i.e.
<% h.form_remote_tag(url=h.url(controller="/live",action="enqueue_nzb"),
complete=h.evaluate_remote_response(),
html=dict(id="enqueue_form")) %>
<p>newzbin ID: <% h.text_field("newzbinid", None, size='8') %> </p>
<% h.submit("Q ID") %>
<% h.end_form() %>
Note the action 'enqueue_nzb' and the 'complete=h.evaluate_remote_response()'
which says to the Prototype js code, when the call to the URL /enqueue_nzb
completes pass the returned text to the evaluate_remote_response() function to
be run as javascript.
> Take the following example which is in an autohandler
>
> <select id="options" onchange="<% h.remote_function(update="options",
> url=h.url(action='update_options')) %>">
> <option value="0">Hello</option>
> <option value="1">World</option>
> </select>
>
> in the controller I have
>
> def update_options(self):
> # what goes here to access the value of options?
> # can options be changed from here? If so how?
>
The remote_function() behaves similarly to the
update_element_function()/evaluate_remote_response() described above except that
it automatically inserts the returned data into the DOM element with the id
specified in the update='' param w/o going through the whole js evaluation
process. When updating container elements like div or span you can just return
the required html snippet from the controller, e.g.
def update_status(self):
m.write('<b>Status is updated</b>')
which will work with <div id="options"></div>
But updating the the actual option elements using this approach requires you
replace the entire <select> so you would need to wrap the select in a div or
span and return the entire select with the changed options, e.g.
def update_options(self):
m.write('''<select id="options" onchange="<%
h.remote_function(update="select_wrapper",url=h.url(action='update_options'),
script=True) %>"><option value="activity">activity</option><option
value="activity2">activity2</option></select>''')
with original html of
<div id="select_wrapper">
<select id="options" onchange="<% h.remote_function(update="options",
url=h.url(action='update_options')) %>">
<option value="0">Hello</option>
<option value="1">World</option>
</select>
</div>
HTH
Robert
Ben Bangert wrote:
> Did you also call change the remote_function to be h.remote_function?
> All the helper functions need to be prefixed by h. as Pylons doesn't
> dump them into the globals.
Yep.
>
> Assuming you did prefix them all with h., can you turn off Javascript
> in the browser and see what its making?
Turned it off and no crash. Not sure what you mean by what its making?
I changed the python code to call the debugger but it appears to be
crashing in the browser before getting back to python.
> Turned it off and no crash. Not sure what you mean by what its
> making?
I meant checking the page source to see what Javascript the function
is generating, maybe something weird is sneaking into it that's not
properly escaped, etc.
> I changed the python code to call the debugger but it appears to be
> crashing in the browser before getting back to python.
Perhaps you can paste the generated Javascript here, maybe one of the
others on the list can see why it wouldn't work, unfortunately I'm
not too proficient in Javascript.
- Ben
> I meant checking the page source to see what Javascript the function
> is generating, maybe something weird is sneaking into it that's not
> properly escaped, etc.
Thought that's what you meant. Here it is.
<select id="options" onchange="new Ajax.Updater('options', '/hello/update_options', {asynchronous:true, evalScripts:true})">
What is the page source if you go to /hello/update_options ? i.e. what is your
controller returning for the Ajax.Updater to use?
Robert
> What is the page source if you go to /hello/update_options ? i.e. what
> is your controller returning for the Ajax.Updater to use?
The controller isn't returning anything at present it's just trying to
invoke the debugger but the browsers has gone away by the time
update_options is called.
class HelloController(BaseController):
def index(self):
m.subexec('/index.myt')
def update_options(self):
self._attach_locals()
raise "hi"
Uwe.
On your setup if you type the url
http://localhost:5000/hello/update_options isn't the Pylons error page
displayed with a traceback ending in your 'raise "hi" statement? That is
what is happening in my test setup and if that is the case then that
entire page is being returned to the client for the js code to attempt
to add to the original <select> using innerHTML. To verify this in the
lib/prototype.js file add an alert() to show what is being returned, at
around line 776 it should say :
updateContent: function() {
var receiver = this.responseIsSuccess() ?
this.containers.success : this.containers.failure;
var response = this.transport.responseText;
...
if you add alert(this.transport.responseText); where the dots are,
reload the page with a Ctrl-F5 and change the selected option, you
should see a giant message box with html in it that is the Pylons error
page. This is what is causing your browser to fail, it just can't handle
trying to use that as innerHTML.
Have you tried putting "m.write('Hi") as the ony code in your
controller's update_options (assuming you read my earlier mail and
enclosed the <select> in a div. It should replace your <select> with the
text "hi", is that the case?
Robert
>On your setup if you type the url
>http://localhost:5000/hello/update_options isn't the Pylons error page
>displayed with a traceback ending in your 'raise "hi" statement? That is
>what is happening in my test setup and if that is the case then that
>entire page is being returned to the client for the js code to attempt
>to add to the original <select> using innerHTML. To verify this in the
>lib/prototype.js file add an alert() to show what is being returned, at
>around line 776 it should say :
>
> updateContent: function() {
> var receiver = this.responseIsSuccess() ?
> this.containers.success : this.containers.failure;
> var response = this.transport.responseText;
>...
>
>if you add alert(this.transport.responseText); where the dots are,
>reload the page with a Ctrl-F5 and change the selected option, you
>should see a giant message box with html in it that is the Pylons error
>page. This is what is causing your browser to fail, it just can't handle
>trying to use that as innerHTML.
>
>
Did that and nothing showed. I ended up changing the alert to
alert(receiver ==this.container.success);
which at least told me it was returning ok.
Am I correct in assuming that this select control is expecting a
complete replacement of itself as the return response? How bizzare. I
would have thought it would have been happy with a success or failure
return. Ideally it would be great is all we had to return was the
select.id.value and nothing else.
>Have you tried putting "m.write('Hi") as the ony code in your
>controller's update_options (assuming you read my earlier mail and
>enclosed the <select> in a div. It should replace your <select> with the
>text "hi", is that the case?
>
>
Did that and you are correct. When wrapped in <div id="options"> the
return text replaces the select box.
Here are general question for everyone. Is it reasonable for the whole
control to have to be resent as a response to an updater function? Even
if based on a Myghty template control this behaviour is a bit heavy handed.
Uwe.
Was that with the controller still raising an exception, as I'm getting the
described behaviour in that scenario? Are you using the latest svn (I'm a few
revisions behind atm)?
>
> Am I correct in assuming that this select control is expecting a
> complete replacement of itself as the return response? How bizzare. I
> would have thought it would have been happy with a success or failure
> return. Ideally it would be great is all we had to return was the
> select.id.value and nothing else.
>
Perhaps you're misunderstanding the role of remote_function() with update, it is
meant to be returning the new value for the content of some html element, that's
what it does. It doesn't have to be the select (and is probably most often used
to update another element).
What exactly are you trying to achieve, there is probably an alternative helper
better suited?
>
> Here are general question for everyone. Is it reasonable for the whole
> control to have to be resent as a response to an updater function? Even
> if based on a Myghty template control this behaviour is a bit heavy handed.
>
Well, it depends on what you are doing and which html elements you are updating.
The required behaviour (updating everything or not) is sometimes determined
by the browsers implementation or limitations of the DOM. In the case of select,
IE has had a long-standing bug where it will not work when updating the
innerHTML with <options>, the entire select *must* be replaced (and it looks
like Firefox requires the same). IIRC, updating one aspect of tables (I can't
recall if it was <tr> or <td> elements or something else?) is explicitly not
permitted by the DOM.
Robert
Robert Leftwich wrote:
> Was that with the controller still raising an exception, as I'm
> getting the
> described behaviour in that scenario? Are you using the latest svn
> (I'm a few
> revisions behind atm)?
With the exception I got result ok but no return info. With
m.write('hi') I got the control replaced. I'm using latest svn.
> Perhaps you're misunderstanding the role of remote_function() with
> update, it is
> meant to be returning the new value for the content of some html
> element, that's
> what it does. It doesn't have to be the select (and is probably most
> often used
> to update another element).
>
> What exactly are you trying to achieve, there is probably an
> alternative helper
> better suited?
I just wanted to return the current choice to the controller at time of
change nothing more no jumps no replacements of data or div's. I'm still
looking for how to get that value in the controller. It would have been
nice to be able to set the value of the select field in the return but
that is not necessary and probably a dubious practice unless the value
== the users selection.
> Well, it depends on what you are doing and which html elements you are
> updating.
> The required behaviour (updating everything or not) is sometimes
> determined
> by the browsers implementation or limitations of the DOM. In the case
> of select,
> IE has had a long-standing bug where it will not work when updating the
> innerHTML with <options>, the entire select *must* be replaced (and it
> looks
> like Firefox requires the same). IIRC, updating one aspect of tables
> (I can't
> recall if it was <tr> or <td> elements or something else?) is
> explicitly not
> permitted by the DOM.
So I'm finding - again browser diffs get in the way. Oh how far we have
yet to come. I'm wondering if js could be used to switch the values.
Anyway back to my original goal. Get the select.value from the browser
to the controller and return nothing other than success or failure. As
you say there may be a better helper.
Thanks for clarifying.
Uwe.
I'm just updating now, interested to see if behaviour has changed, no, I still
get the message box
>> What exactly are you trying to achieve, there is probably an
>> alternative helper
>> better suited?
>
>
> I just wanted to return the current choice to the controller at time of
> change nothing more no jumps no replacements of data or div's. I'm still
> looking for how to get that value in the controller. It would have been
> nice to be able to set the value of the select field in the return but
> that is not necessary and probably a dubious practice unless the value
> == the users selection.
Unfortunately, remote_function() is a static calling mechanism, i.e. it does not
include the dynamically selected values of the element it is associated with.
Depending on what else you have on the page (i.e. other form elements) you might
be better off using the observe_field() or observe_form() helper if you have
more than one element you want to trap the values of.
For a standalone select (i.e. without the enclosing div), you can use
<% h.observe_field('options', url=h.url(action="update_options"), with="'v=' +
value") %>
which will call the url action with an ARG of v=selected value, so modifying the
controller to add the ARGS is required, e.g.
def update_options(self, ARGS):
val = ARGS['v']
...
Ben, if you read this - is it possible to include the 'with' parameter name in
the method dec? e.g.
def update_options(self, v):
...
This doesn't work, when either get or post is used - am I being dense because
I'm too tired?
HTH
Robert
> [snip]
> For a standalone select (i.e. without the enclosing div), you can use
>
> <% h.observe_field('options', url=h.url(action="update_options"),
> with="'v=' +
> value") %>
>
> which will call the url action with an ARG of v=selected value, so
> modifying the
> controller to add the ARGS is required, e.g.
>
> def update_options(self, ARGS):
> val = ARGS['v']
> ...
>
Haven't tried the above aproach yet as I think the demo below will catch
the unwary/uninitiated like me.
> Ben, if you read this - is it possible to include the 'with' parameter
> name in
> the method dec? e.g.
>
> def update_options(self, v):
> ...
>
> This doesn't work, when either get or post is used - am I being dense
> because
> I'm too tired?
>
> HTH
Well it helped and it didn't so I'm enclosing what I've written just to
illustrate the symptoms and my interpretation of all that has gone before.
The page renders fine - but requires a refresh after a crash to revert
to defaults.
If you change either control from the default it crashes with a
combination of my page and a Pylons page.
Is this getting confusing?
Uwe.
# controllers/hello.py
from jsdemo.lib.base import *
class HelloController(BaseController):
def index(self):
m.subexec('/index.myt')
def update_options(self):
m.scomp("/selectcontrol.myt", cid="options", \
action="update_options", olist=["Hello","World"], default="Hello")
def update_2(self, ARGS):
temp = ARGS["default"]
m.scomp("/selectcontrol.myt", cid="options", \
action="update_options", olist=["First","Second","Third"],
default="%s" % temp)
# components/selectcontrol.myt
<%args>
cid
act
olist
default
</%args>
<div id="<% cid %>">
<select id='<% cid %>' onchange="new Ajax.Updater('<% cid %>', '<% act
%>', {asyncronous:true, evalScripts:true})">
<%python>
j = 0
for i in olist:
if i == default:
m.write("<option selected value='" + str(j) + "'>" + i +
"</option>\n")
else:
m.write("<option value='" + str(j) + "'>" + i + "</option>\n")
j = j + 1
</%python>
</select>
</div>
# templates/index.myt
<h2>It all ends in tears</h2>
# templates/autohandler
<html>
<head>
<title>Pylons jsdemo</title>
<script src="/js/prototype.js" type="text/javascript"></script>
</head>
<body>
<h2>Pylons jsdemo</h2>
<hr/>
<form name="testform">
<& selectcontrol.myt, cid="opt1", act="update_options",
olist=["Hello","World"], default="Hello" &>
<hr />
<& selectcontrol.myt, cid="opt2", act="update_2", olist=["First",
"Second", "Third"], default="Second" &>
</form>
<hr />
<div id="pagecontent">
% m.call_next()
</div>
</body>
</html>
> For a standalone select (i.e. without the enclosing div), you can use
>
> <% h.observe_field('options', url=h.url(action="update_options"),
> with="'v=' +
> value") %>
>
> which will call the url action with an ARG of v=selected value, so
> modifying the
> controller to add the ARGS is required, e.g.
>
> def update_options(self, ARGS):
> val = ARGS['v']
> ...
>
> Ben, if you read this - is it possible to include the 'with'
> parameter name in
> the method dec? e.g.
>
> def update_options(self, v):
> ...
Only variables defined in the Route will come through to the function
def. This is mainly because either the form/query args can go in
there, or the Route args. While I could push them all into the def,
I'm not sure what the desired behavior would be in the case that
there's form/query args with the same name as variables from the
Route match.
> This doesn't work, when either get or post is used - am I being
> dense because
> I'm too tired?
The first example, or the second?
- Ben
Yes I *was*...
>
> The first example, or the second?
>
The second.
Robert
PS I haven't checked but it is worth emphasising in the doco the direct
relationship between Routes and vars in the function def and the related
behaviour wrt form/query args.
No, there's a number of errors that need correcting...
>
> # controllers/hello.py
>
> from jsdemo.lib.base import *
>
> class HelloController(BaseController):
> def index(self):
> m.subexec('/index.myt')
>
> def update_options(self):
> m.scomp("/selectcontrol.myt", cid="options", \
> action="update_options", olist=["Hello","World"], default="Hello")
>
m.scomp writes the resulting data to a string, but you are not using the
returned results, you need to use m.comp()
> def update_2(self, ARGS):
> temp = ARGS["default"]
> m.scomp("/selectcontrol.myt", cid="options", \
> action="update_options", olist=["First","Second","Third"],
> default="%s" % temp)
>
'default' is a variable name in the /selectcontrol.myt file that you use to pick
the selected entry in a list, I'm not sure why you are expecting the browser to
return it. Removing the temp = ARGS["default"] line or replacing it with temp =
ARGS.get("default", 'Third') to return 'Third' if it does not exist in ARGS
would fix this problem.
Note that to test each action you can type the url of the controller into the
browser, i.e. http://localhost:5000/tasks/update_2 would bring up the Pylons
error page telling you that ARGS doesn't have 'default' in it. Adding a value
for default, e.g. http://localhost:5000/tasks/update_2?default=Third will fix
that (although it won't fix the underlying problem, which is there is nothing in
the html/js telling it to return an arg called 'default'). Also, as I mentioned
previously, using Ajax.Updater or remote_function() is a static thing, it will
*not* return the selected value from a list or data entered into form fields -
for that you can use observe_field/form as described previously (using ARGS).
Also, using the LiveHttpHeaders extension for Firefox is useful for seeing
exactly what form/query args the browser is sending to Pylons and it also allows
you to copy all the sent args so you can manually add them to the url in the
browser for testing.
> # components/selectcontrol.myt
>
> <%args>
> cid
> act
> olist
> default
> </%args>
>
> <div id="<% cid %>">
> <select id='<% cid %>' onchange="new Ajax.Updater('<% cid %>', '<% act
> %>', {asyncronous:true, evalScripts:true})">
> <%python>
> j = 0
> for i in olist:
> if i == default:
> m.write("<option selected value='" + str(j) + "'>" + i +
> "</option>\n")
> else:
> m.write("<option value='" + str(j) + "'>" + i + "</option>\n")
> j = j + 1
> </%python>
> </select>
> </div>
>
You've defined 2 elements with the same id (the div and the select) - this is
invalid html (the browser is too lax, it should report this sort of error).
Changing the select to: <select id='<% '%s_sel' %cid %>' .... will fix that (or
you could change the div id).
With all the above changes, it is working as expected in my test setup, using
rev 633.
HTH
Robert
The original example came from here...
http://pylonshq.com/WebHelpers/module-webhelpers.rails.prototype.html
in the remote_function section.
<select id="options" onchange="<% remote_function(update="options",
url=url(action='update_options')) %>">
<option value="0">Hello</option>
<option value="1">World</option>
</select>
which I missinterpreted - missread - brain fade.
My intent was to build a form control and seed it with data from a db
and bring it back if altered.
Perhaps the "remote_function" call should be used in a different context
- Ben?
Thanks for clearing it all up for me.
Uwe.
Robert Leftwich wrote:
>> # controllers/hello.py
>>
>> from jsdemo.lib.base import *
>>
>> class HelloController(BaseController):
>> def index(self):
>> m.subexec('/index.myt')
>>
>> def update_options(self):
>> m.scomp("/selectcontrol.myt", cid="options", \
>> action="update_options", olist=["Hello","World"],
>> default="Hello")
>>
>
> m.scomp writes the resulting data to a string, but you are not using
> the returned results, you need to use m.comp()
oops.