I've been working on these and I've added some stuff. It's still needs a
lot of work (I'm going to add callback and options as well as more
widgets and fix some inconsitencies on how the widgets are created and
inserted into the temapltes), I don't think this is ready for inclusion
on the SVN trunk. The patch is provided so other programmers can check
the code from problems or improvements.
You can test the functions added on (as long as the IP lasts):
http://200.117.30.210:8888/rpc
http://200.117.30.210:8888/remoteform
(try senao on the searchfield, tested on opera 8.5, IE 6 and FireFox 1.5)
The controllers for these tests are:
@turbogears.expose(template="pruebatg.templates.rpc")
def rpc(self):
mostrarArticulosSenao = widgets.LinkRemoteFunction(
target = turbogears.url('/buscarArticulo'),
data = dict(searchString = 'senao')
)
mostrarArticulosModem = widgets.LinkRemoteFunction(
target = turbogears.url('/buscarArticulo'),
data = dict(searchString = 'modem')
)
return dict(mostrarArticulosSenao=mostrarArticulosSenao,
mostrarArticulosModem=mostrarArticulosModem)
@turbogears.expose(template="pruebatg.templates.remoteform")
def remoteform(self):
formBusquedaArticulo = widgets.RemoteForm(
name = "BusquedaArticulo",
widgets = [widgets.TextField("searchString", default=None,
validator=validators.String())],
target_dom = 'articulos'
)
return dict(formBusquedaArticulo=formBusquedaArticulo)
And the templates:
for rpc:
<span py:replace="mostrarArticulosSenao.insert('Find
*senao*',update='articulos')"></span><br/>
<span py:replace="mostrarArticulosModem.insert('Find
*modem*',update='articulos')"></span>
<div id="articulos"></div>
for remoteform:
<span
py:replace="formBusquedaArticulo.insert(action='/buscarArticulo')"></span>
<div id="articulos"></div>
Before continuing on this I wanted an orientation about the MVC
paradigm, I've been working for a long time on a framework of my own so
there are many stuff that I still have to get used to. For example, I'm
pretty confused on this particular point, why the action url for
TableForms is specificed on the views and not in the controllers, you
will see that RemoteForm behaves just like TableForm because it's
inherited from it but in LinkRemoteFunction I've put the target url to
be specified when the widget is created in the controller, what you
specifiy on the view is the DOM target that will cointain the answer
from the POST done. I'd like to know is this is correct or if I have to
change it, also in my patch the dictionary containing the POST data for
the LinkRemoteFunction widget has to be specfied on the controller. I'm
not sure if this is ok or not either.
After getting those things staight I'll try to get both widgets to
follow the MVC correctly
--
Claudio
Index: turbogears/widgets/static/ajax.js
===================================================================
--- turbogears/widgets/static/ajax.js (revision 0)
+++ turbogears/widgets/static/ajax.js (revision 0)
@@ -0,0 +1,58 @@
+function remoteFormRequest(form, target) {
+ var query = Array();
+ var target = getElement(target);
+ fields = form.getElementsByTagName('INPUT');
+ for( var i=0; i<fields.length; i++) {
+ element = getElement(fields[i].name);
+ if (element) {
+ query[element.name] = element.value;
+ }
+ }
+ query["tg_random"] = new Date().getTime();
+ makePOSTRequest(form.action, target, queryString(query));
+ return true;
+}
+
+function remoteRequest(target_url, target_dom, data, options) {
+ makePOSTRequest(target_url, getElement(target_dom), queryString(data));
+ return true;
+}
+
+function makePOSTRequest(url, target, parameters) {
+ var http_request = false;
+ if (window.XMLHttpRequest) { // Mozilla, Safari,...
+ http_request = new XMLHttpRequest();
+ if (http_request.overrideMimeType) {
+ http_request.overrideMimeType('text/xml');
+ }
+ } else if (window.ActiveXObject) { // IE
+ try {
+ http_request = new ActiveXObject("Msxml2.XMLHTTP");
+ } catch (e) {
+ try {
+ http_request = new ActiveXObject("Microsoft.XMLHTTP");
+ } catch (e) {}
+ }
+ }
+ if (!http_request) {
+ alert('Cannot create XMLHTTP instance');
+ return false;
+ }
+
+ var insertContents = function () {
+ if (http_request.readyState == 4) {
+ if (http_request.status == 200) {
+ target.innerHTML = http_request.responseText;
+ } else {
+ alert('There was a problem with the request.
Status('+http_request.status+')');
+ }
+ }
+ }
+
+ http_request.onreadystatechange = insertContents;
+ http_request.open('POST', url, true);
+ http_request.setRequestHeader("Content-type",
"application/x-www-form-urlencoded");
+ http_request.setRequestHeader("Content-length", parameters.length);
+ http_request.setRequestHeader("Connection", "close");
+ http_request.send(parameters);
+}
Index: turbogears/widgets/forms.py
===================================================================
--- turbogears/widgets/forms.py (revision 440)
+++ turbogears/widgets/forms.py (working copy)
@@ -1,8 +1,9 @@
-from turbogears.widgets import Widget, SubmitButton, TextField
+from turbogears.widgets import Widget, SubmitButton, TextField, JSLink,
static, mochikit
from turbogears import validators
from turbogears.util import setlike
import cherrypy
from formencode import declarative
+from turbogears import jsonify
class attrdictwrapper(object):
def __init__(self, d):
@@ -132,7 +133,8 @@
template = """
<form xmlns:py="http://purl.org/kid/ns#" name="${widget.name}"
action="${getattr(self, 'action', None)}" method="${getattr(self,
'method', 'post')}"
- enctype="${getattr(self, 'enctype', None)}">
+ enctype="${getattr(self, 'enctype', None)}"
+ py:attrs="getattr(widget, 'attrs', dict())">
<div py:for="widget in widgets" py:if="widget.hidden" py:strip="True">
${widget.insert(getattr(self.widget_value, widget.name, None),
input_values, widget_error.get(widget.name, None))}
</div>
@@ -152,3 +154,71 @@
</table>
</form>
"""
+
+class RemoteForm(TableForm):
+ def __init__(self, **kw):
+ super(RemoteForm, self).__init__(**kw)
+ self.attrs["onSubmit"] = "return
!remoteFormRequest(this, '%s');" % self.target_dom
+
+ def retrieve_javascript(self, **kw):
+ scripts = super(RemoteForm,
self).retrieve_javascript(**kw)
+ scripts.add(JSLink(static,'ajax.js'))
+ scripts.add(mochikit)
+ return scripts
+
+class RPC(object):
+ """ RPC base object """
+
+ def __init__(self, target=None, data=None, **kw):
+ assert target is not None and data is not None,
"Missing keyword argument"
+
+ # this will invoke the widget constructors
(multi-inheritance)
+ super(RPC, self).__init__(**kw)
+
+ self.target_url = target
+ #self.target_dom = target_dom
+ self.data = data
+
+ # TODO: callbacks
+ """
+ The callbacks that may be specified are (in
order):
+ :loading: Called when the remote document
is being loaded with data by the browser.
+ :loaded: Called when the browser has
finished loading the remote document.
+ :interactive: Called when the user can
interact with the remote document, even though it has not finished loading.
+ :success: Called when the XMLHttpRequest
is completed, and the HTTP status code is in the 2XX range.
+ :failure: Called when the XMLHttpRequest
is completed, and the HTTP status code is not in the 2XX range.
+ :complete: Called when the XMLHttpRequest
is complete (fires after success/failure if they are present).,
+ """
+
+ # TODO: options
+ """
+ You can customize further browser side call
logic by passing in JavaScript code snippets via some optional parameters. In
their order of use these are:
+ :confirm: Adds confirmation dialog.
+ :condition: Perform remote request
conditionally by this expression. Use this to describe browser-side conditions
when request should not be initiated.
+ :before: Called before request is
initiated.
+ :after: Called immediately after request was
initiated and before :loading.
+ :submit: Specifies the DOM element ID
that's used as the parent of the form elements. By default this is the current
form, but it could just as well be the ID of a table row or any other DOM
element.
+ """
+
+ def insertjs(self, target_dom):
+ return "return ! remoteRequest('%s', '%s', %s,
%s)" % (self.target_url, target_dom, jsonify.encode(self.data), dict())
+
+ def retrieve_javascript(self, **kw):
+ scripts = super(RPC,
self).retrieve_javascript(**kw)
+ scripts.add(JSLink(static,'ajax.js'))
+ scripts.add(mochikit)
+ return scripts
+
+class LinkRemoteFunction(RPC, Widget):
+ """ Returns a link that executes a POST asynchronously
+ and updates a DOM Object with the result of it """
+
+ template = """
+<div xmlns:py="http://purl.org/kid/ns#" py:strip="True">
+<a name="${widget.name}" py:attrs="widget.attrs"
onclick="${widget.insertjs(target_dom=self.update)}"
href="#">${widget_value}</a>
+</div>
+"""
+
+ def __init__(self, description=None, **kw):
+ super(LinkRemoteFunction, self).__init__(**kw)
+ self.description = description
\ No newline at end of file