# HG changeset patch # User Sylvain Thénault # Date 1270562677 -7200 # Node ID 834269261ae406f175eff30ff89196d34918877a # Parent 3684ccae5cdc300957399e5a0ded04bcd429ce43# Parent 35e6878e2fd07cf6eca2ca3acfd2dcfdd3462aae merge diff -r 3684ccae5cdc -r 834269261ae4 doc/book/en/development/devweb/js.rst --- a/doc/book/en/development/devweb/js.rst Tue Apr 06 16:04:50 2010 +0200 +++ b/doc/book/en/development/devweb/js.rst Tue Apr 06 16:04:37 2010 +0200 @@ -54,12 +54,224 @@ ajax request, otherwise the document itself for standard HTTP requests. +Important AJAX APIS +~~~~~~~~~~~~~~~~~~~ -Overview of what's available +* `jQuery.fn.loadxhtml` is an important extension to jQuery which + allow proper loading and in-place DOM update of xhtml views. It is + suitably augmented to trigger necessary events, and process CubicWeb + specific elements such as the facet system, fckeditor, etc. + +* `asyncRemoteExec` and `remoteExec` are the base building blocks for + doing arbitrary async (resp. sync) communications with the server + +A simple example with asyncRemoteExec +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +In the python side, we have to extend the BaseController class. The +@jsonize decorator ensures that the `return value` of the method is +encoded as JSON data. By construction, the JSonController inputs +everything in JSON format. + +.. sourcecode: python + + from cubicweb.web.views.basecontrollers import JSonController, jsonize + + @monkeypatch(JSonController) + @jsonize + def js_say_hello(self, name): + return u'hello %s' % name + +In the javascript side, we do the asynchronous call. Notice how it +creates a `deferred` object. Proper treatment of the return value or +error handling has to be done through the addCallback and addErrback +methods. + +.. sourcecode: javascript + + function async_hello(name) { + var deferred = asyncRemoteExec('say_hello', name); + deferred.addCallback(function (response) { + alert(response); + }); + deferred.addErrback(function () { + alert('something fishy happened'); + }); + } + + function sync_hello(name) { + alert( remoteExec('say_hello', name) ); + } + +A simple example with loadxhtml +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Here we are concerned with the retrieval of a specific view to be +injected in the live DOM. The view will be of course selected +server-side using an entity eid provided by the client side. + +.. sourcecode: python + + from cubicweb import typed_eid + from cubicweb.web.views.basecontrollers import JSonController, xhtmlize + + @monkeypatch(JSonController) + @xhtmlize + def js_frob_status(self, eid, frobname): + entity = self._cw.entity_from_eid(typed_eid(eid)) + return entity.view('frob', name=frobname) + +.. sourcecode: javascript + + function update_some_div(divid, eid, frobname) { + var params = {fname:'frob_status', eid: eid, frobname:frobname}; + jQuery('#'+divid).loadxhtml(JSON_BASE_URL, params, 'post'); + } + +In this example, the url argument is the base json url of a cube +instance (it should contain something like +`http://myinstance/json?`). The actual JSonController method name is +encoded in the `params` dictionnary using the `fname` key. + +A more real-life example from CubicWeb +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +A frequent use case of Web 2 applications is the delayed (or +on-demand) loading of pieces of the DOM. This is typically achieved +using some preparation of the initial DOM nodes, jQuery event handling +and proper use of loadxhtml. + +We present here a skeletal version of the mecanism used in CubicWeb +and available in web/views/tabs.py, in the `LazyViewMixin` class. + +.. sourcecode: python + + def lazyview(self, vid, rql=None): + """ a lazy version of wview """ + w = self.w + self._cw.add_js('cubicweb.lazy.js') + urlparams = {'vid' : vid, 'fname' : 'view'} + if rql is not None: + urlparams['rql'] = rql + w(u'
' % ( + vid, xml_escape(self._cw.build_url('json', **urlparams)))) + w(u'
') + self._cw.add_onload(u""" + jQuery('#lazy-%(vid)s').bind('%(event)s', function() { + load_now('#lazy-%(vid)s');});""" + % {'event': 'load_%s' % vid, 'vid': vid}) + +This creates a `div` with an specific event associated to it. + +The full version deals with: + +* optional parameters such as an entity eid, an rset + +* the ability to further reload the fragment + +* the ability to display a spinning wheel while the fragment is still + not loaded + +* handling of browsers that do not support ajax (search engines, + text-based browsers such as lynx, etc.) + +The javascript side is quite simple, due to loadxhtml awesomeness. + +.. sourcecode: javascript + + function load_now(eltsel) { + var lazydiv = jQuery(eltsel); + lazydiv.loadxhtml(lazydiv.attr('cubicweb:loadurl')); + } + +This is all significantly different of the previous `simple example` +(albeit this example actually comes from real-life code). + +Notice how the `cubicweb:loadurl` is used to convey the url +information. The base of this url is similar to the global javascript +JSON_BASE_URL. According to the pattern described earlier, +the `fname` parameter refers to the standard `js_view` method of the +JSonController. This method renders an arbitrary view provided a view +id (or `vid`) is provided, and most likely an rql expression yielding +a result set against which a proper view instance will be selected. + +The `cubicweb:loadurl` is one of the 29 attributes extensions to XHTML +in a specific cubicweb namespace. It is a means to pass information +without breaking HTML nor XHTML compliance and without resorting to +ungodly hacks. + +Given all this, it is easy to add a small nevertheless useful feature +to force the loading of a lazy view (for instance, a very +computation-intensive web page could be scinded into one fast-loading +part and a delayed part). + +In the server side, a simple call to a javascript function is +sufficient. + +.. sourcecode: python + + def forceview(self, vid): + """trigger an event that will force immediate loading of the view + on dom readyness + """ + self._cw.add_onload("trigger_load('%s');" % vid) + +The browser-side definition follows. + +.. sourcecode: javascript + + function trigger_load(divid) { + jQuery('#lazy-' + divd).trigger('load_' + divid); + } + + +Anatomy of a lodxhtml call +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The loadxhtml extension to jQuery accept many parameters with rich +semantics. Let us detail these. + +* `url` (mandatory) should be a complete url, typically based on the + JSonController, but this is not strictly mandatory + +* `data` (optional) is a dictionnary of values given to the + controller specified through an `url` argument; some keys may have a + special meaning depending on the choosen controller (such as `fname` + for the JSonController); the `callback` key, if present, must refer + to a function to be called at the end of loadxhtml (more on this + below) + +* `reqtype` (optional) specifies the request method to be used (get or + post); if the argument is 'post', then the post method is used, + otherwise the get method is used + +* `mode` (optional) is one of `replace` (the default) which means the + loaded node will replace the current node content, `swap` to replace + the current node with the loaded node, and `append` which will + append the loaded node to the current node content + + +About the `callback` option: + +* it is called with two parameters: the current node, and a list + containing the loaded (and post-processed node) + +* whenever is returns another function, this function is called in + turn with the same parameters as above + +This mecanism allows callback chaining. + + +Javascript library: overview ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * jquery.* : jquery and jquery UI library +* cubicweb.ajax.js : concentrates all ajax related facilities (it + extends jQuery with the loahxhtml function, provides a handfull of + high-level ajaxy operations like asyncRemoteExec, reloadComponent, + replacePageChunk, getDomFromResponse) + * cubicweb.python.js : adds a number of practical extension to stdanrd javascript objects (on Date, Array, String, some list and dictionary operations), and a pythonesque way to build classes. Defines a @@ -69,11 +281,6 @@ in various other cubicweb javascript resources (baseuri, progress cursor handling, popup login box, html2dom function, etc.) -* cubicweb.ajax.js : concentrates all ajax related facilities (it - extends jQuery with the loahxhtml function, provides a handfull of - high-level ajaxy operations like asyncRemoteExec, reloadComponent, - replacePageChunk, getDomFromResponse) - * cubicweb.widgets.js : provides a widget namespace and constructors and helpers for various widgets (mainly facets and timeline) @@ -83,5 +290,6 @@ * cubicweb.facets.js : used by the facets mechanism -xxx massmailing, gmap, fckcwconfig, timeline-bundle, timeline-ext, -calendar, goa, flotn tazy, tabs, bookmarks +There is also javascript support for massmailing, gmap (google maps), +fckcwconfig (fck editor), timeline, calendar, goa (CubicWeb over +AppEngine), flot (charts drawing), tabs and bookmarks. diff -r 3684ccae5cdc -r 834269261ae4 doc/book/en/development/entityclasses/application-logic.rst --- a/doc/book/en/development/entityclasses/application-logic.rst Tue Apr 06 16:04:50 2010 +0200 +++ b/doc/book/en/development/entityclasses/application-logic.rst Tue Apr 06 16:04:37 2010 +0200 @@ -34,14 +34,19 @@ entity objects as messengers between these components of an application. It means that an attribute set as in `obj.x = 42`, whether or not x is actually an entity schema attribute, has a short -life span, limited to the hook, operation or view within the object -was built. +life span, limited to the hook, operation or view within which the +object was built. -Setting an attribute value should always be done in the context of a +Setting an attribute or relation value can be done in the context of a Hook/Operation, using the obj.set_attributes(x=42) notation or a plain RQL SET expression. -That still leaves for entity objects an essential role: it's where an +In views, it would be preferable to encapsulate the necessary logic in +a method of the concerned entity class(es). But of course, this advice +is also reasonnable for Hooks/Operations, though the separation of +concerns here is less stringent than in the case of views. + +This leads to the practical role of entity objects: it's where an important part of the application logic lie (the other part being located in the Hook/Operations). @@ -72,14 +77,17 @@ implementation. The attributes `tree_attribute`, `parent_target` and `children_target` are used by the TreeMixIn code. This is typically used in views concerned with the representation of tree-like -structures (CubicWeb provides several such views). It is important -that the views themselves try not to implement this logic, not only -because such views would be hardly applyable to other tree-like -relations, but also because it is perfectly fine and useful to use -such an interface in Hooks. In fact, Tree nature is a property of the -data model that cannot be fully and portably expressed at the level of -database entities (think about the transitive closure of the child -relation). +structures (CubicWeb provides several such views). + +It is important that the views themselves try not to implement this +logic, not only because such views would be hardly applyable to other +tree-like relations, but also because it is perfectly fine and useful +to use such an interface in Hooks. + +In fact, Tree nature is a property of the data model that cannot be +fully and portably expressed at the level of database entities (think +about the transitive closure of the child relation). This is a further +argument to implement it at entity class level. The `dc_title` method provides a (unicode string) value likely to be consummed by views, but note that here we do not care about output @@ -92,8 +100,8 @@ The fetch_attrs, fetch_order class attributes are parameters of the `ORM`_ layer. They tell which attributes should be loaded at once on entity object instantiation (by default, only the eid is known, other -attributes are loaded on demand), and which attribute is to be used -when SORTing in an RQL expression concerned with many such entities. +attributes are loaded on demand), and which attribute is to be used to +order the .related() and .unrelated() methods output. Finally, we can observe the big TICKET_DEFAULT_STATE_RESTR is a pure application domain piece of data. There is, of course, no limitation diff -r 3684ccae5cdc -r 834269261ae4 web/views/basecontrollers.py --- a/web/views/basecontrollers.py Tue Apr 06 16:04:50 2010 +0200 +++ b/web/views/basecontrollers.py Tue Apr 06 16:04:37 2010 +0200 @@ -445,6 +445,7 @@ view = req.vreg['views'].select('doreledit', req, rset=rset, rtype=args['rtype']) stream = view.set_stream() view.render(**args) + # XXX why not _call_view ? extresources = req.html_headers.getvalue(skiphead=True) if extresources: stream.write(u'
\n') diff -r 3684ccae5cdc -r 834269261ae4 web/views/tabs.py --- a/web/views/tabs.py Tue Apr 06 16:04:50 2010 +0200 +++ b/web/views/tabs.py Tue Apr 06 16:04:37 2010 +0200 @@ -32,9 +32,7 @@ def lazyview(self, vid, rql=None, eid=None, rset=None, tabid=None, reloadable=False, show_spinbox=True, w=None): - """a lazy version of wview - first version only support lazy viewing for an entity at a time - """ + """ a lazy version of wview """ w = w or self.w self._cw.add_js('cubicweb.lazy.js') urlparams = {'vid' : vid, 'fname' : 'view'}