# HG changeset patch # User sylvain.thenault@logilab.fr # Date 1240411846 -7200 # Node ID 7ff24154351d0a9fe5d094270b488d2274fc2490 # Parent 06af20e663f2bf0f143e31b2aa60d6a844a1a5da javascript + json refactoring diff -r 06af20e663f2 -r 7ff24154351d web/data/cubicweb.ajax.js --- a/web/data/cubicweb.ajax.js Tue Apr 21 19:20:56 2009 +0200 +++ b/web/data/cubicweb.ajax.js Wed Apr 22 16:50:46 2009 +0200 @@ -1,6 +1,6 @@ /* * :organization: Logilab - * :copyright: 2003-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved. + * :copyright: 2003-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved. * :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr */ @@ -37,6 +37,7 @@ if (typeof roundedCornersOnLoad != 'undefined') { roundedCornersOnLoad(); } + jQuery(CubicWeb).trigger('ajax-loaded'); } // cubicweb loadxhtml plugin to make jquery handle xhtml response @@ -109,22 +110,6 @@ //============= base AJAX functions to make remote calls =====================// - -/* - * This function will call **synchronously** a remote method on the cubicweb server - * @param fname: the function name to call (as exposed by the JSONController) - * @param args: the list of arguments to pass the function - */ -function remote_exec(fname) { - setProgressCursor(); - var props = {'mode' : "remote", 'fname' : fname, 'pageid' : pageid, - 'arg': map(jQuery.toJSON, sliceList(arguments, 1))}; - var result = jQuery.ajax({url: JSON_BASE_URL, data: props, async: false}).responseText; - result = evalJSON(result); - resetCursor(); - return result; -} - function remoteCallFailed(err, req) { if (req.status == 500) { updateMessage(err); @@ -137,88 +122,66 @@ * This function is the equivalent of MochiKit's loadJSONDoc but * uses POST instead of GET */ -function loadJSONDocUsingPOST(url, queryargs, mode) { - mode = mode || 'remote'; +function loadJSONDocUsingPOST(url, data) { setProgressCursor(); - var dataType = (mode == 'remote') ? "json":null; - var deferred = loadJSON(url, queryargs, 'POST', dataType); + var deferred = loadJSON(url, data, 'POST'); deferred = deferred.addErrback(remoteCallFailed); -// if (mode == 'remote') { -// deferred = deferred.addCallbacks(evalJSONRequest); -// } deferred = deferred.addCallback(resetCursor); return deferred; } -function _buildRemoteArgs(fname) { - return {'mode' : "remote", 'fname' : fname, 'pageid' : pageid, - 'arg': map(jQuery.toJSON, sliceList(arguments, 1))}; -} - /* - * This function will call **asynchronously** a remote method on the cubicweb server - * This function is a low level one. You should use `async_remote_exec` or - * `async_rawremote_exec` instead. + * This function will call **synchronously** a remote method on the cubicweb server + * @param fname: the function name to call (as exposed by the JSONController) + * + * additional arguments will be directly passed to the specified function * - * @param fname: the function name to call (as exposed by the JSONController) - * @param funcargs: the function's arguments - * @param mode: rawremote or remote + * It looks at http headers to guess the response type. */ -function _async_exec(fname, funcargs, mode) { - var props = {'mode' : mode, 'fname' : fname, 'pageid' : pageid}; - var args = map(urlEncode, map(jQuery.toJSON, funcargs)); - args.unshift(''); // this is to be able to use join() directly - var queryargs = as_url(props) + args.join('&arg='); - return loadJSONDocUsingPOST(JSON_BASE_URL, queryargs, mode); +function remoteExec(fname /* ... */) { + setProgressCursor(); + var props = {'fname' : fname, 'pageid' : pageid, + 'arg': map(jQuery.toJSON, sliceList(arguments, 1))}; + var result = jQuery.ajax({url: JSON_BASE_URL, data: props, async: false}).responseText; + if (result) { + result = evalJSON(result); + } + resetCursor(); + return result; } /* - * This function will call **asynchronously** a remote method on the cubicweb server + * This function will call **asynchronously** a remote method on the json + * controller of the cubicweb http server + * * @param fname: the function name to call (as exposed by the JSONController) + * * additional arguments will be directly passed to the specified function - * Expected response type is Json. - */ -function async_remote_exec(fname /* ... */) { - return _async_exec(fname, sliceList(arguments, 1), 'remote'); -} - -/* - * This version of _async_exec doesn't expect a json response. + * * It looks at http headers to guess the response type. */ -function async_rawremote_exec(fname /* ... */) { - return _async_exec(fname, sliceList(arguments, 1), 'rawremote'); +function asyncRemoteExec(fname /* ... */) { + var props = {'fname' : fname, 'pageid' : pageid, + 'arg': map(jQuery.toJSON, sliceList(arguments, 1))}; + return loadJSONDocUsingPOST(JSON_BASE_URL, props); } -/* - * This function will call **asynchronously** a remote method on the cubicweb server - * @param fname: the function name to call (as exposed by the JSONController) - * @param varargs: the list of arguments to pass to the function - * This is an alternative form of `async_remote_exec` provided for convenience - */ -function async_remote_exec_varargs(fname, varargs) { - return _async_exec(fname, varargs, 'remote'); -} /* emulation of gettext's _ shortcut */ function _(message) { - return remote_exec('i18n', [message])[0]; -} - -function rqlexec(rql) { - return async_remote_exec('rql', rql); + return remoteExec('i18n', [message])[0]; } function userCallback(cbname) { - async_remote_exec('user_callback', cbname); + asyncRemoteExec('user_callback', cbname); } function unloadPageData() { // NOTE: do not make async calls on unload if you want to avoid // strange bugs - remote_exec('unload_page_data'); + remoteExec('unload_page_data'); } function openHash() { @@ -236,7 +199,7 @@ nodeid = nodeid || (compid + 'Component'); extraargs = extraargs || {}; var node = getNode(nodeid); - var d = async_rawremote_exec('component', compid, rql, registry, extraargs); + var d = asyncRemoteExec('component', compid, rql, registry, extraargs); d.addCallback(function(result, req) { var domnode = getDomFromResponse(result); if (node) { @@ -259,7 +222,7 @@ } function userCallbackThenUpdateUI(cbname, compid, rql, msg, registry, nodeid) { - var d = async_remote_exec('user_callback', cbname); + var d = asyncRemoteExec('user_callback', cbname); d.addCallback(function() { reloadComponent(compid, rql, registry, nodeid); if (msg) { updateMessage(msg); } @@ -273,7 +236,7 @@ } function userCallbackThenReloadPage(cbname, msg) { - var d = async_remote_exec('user_callback', cbname); + var d = asyncRemoteExec('user_callback', cbname); d.addCallback(function() { window.location.reload(); if (msg) { updateMessage(msg); } @@ -291,7 +254,7 @@ * while the page was generated. */ function unregisterUserCallback(cbname) { - var d = async_remote_exec('unregister_user_callback', cbname); + var d = asyncRemoteExec('unregister_user_callback', cbname); d.addCallback(function() {resetCursor();}); d.addErrback(function(xxx) { updateMessage(_("an error occured")); @@ -322,11 +285,11 @@ props['pageid'] = pageid; if (vid) { props['vid'] = vid; } if (extraparams) { jQuery.extend(props, extraparams); } - // FIXME we need to do as_url(props) manually instead of + // FIXME we need to do asURL(props) manually instead of // passing `props` directly to loadxml because replacePageChunk // is sometimes called (abusively) with some extra parameters in `vid` var mode = swap?'swap':'replace'; - var url = JSON_BASE_URL + as_url(props); + var url = JSON_BASE_URL + asURL(props); jQuery(node).loadxhtml(url, params, 'get', mode); } else { log('Node', nodeId, 'not found'); @@ -357,6 +320,22 @@ jQuery(document).ready(buildWysiwygEditors); +/* + * takes a list of DOM nodes and removes all empty text nodes + */ +function stripEmptyTextNodes(nodelist) { + var stripped = []; + for (var i=0; i < nodelist.length; i++) { + var node = nodelist[i]; + if (isTextNode(node) && !node.textContent.strip()) { + continue; + } else { + stripped.push(node); + } + } + return stripped; +} + /* convenience function that returns a DOM node based on req's result. */ function getDomFromResponse(response) { if (typeof(response) == 'string') { @@ -368,6 +347,7 @@ // no child (error cases) => return the whole document return doc.cloneNode(true); } + children = stripEmptyTextNodes(children); if (children.length == 1) { // only one child => return it return children[0].cloneNode(true); diff -r 06af20e663f2 -r 7ff24154351d web/data/cubicweb.bookmarks.js --- a/web/data/cubicweb.bookmarks.js Tue Apr 21 19:20:56 2009 +0200 +++ b/web/data/cubicweb.bookmarks.js Wed Apr 22 16:50:46 2009 +0200 @@ -1,7 +1,7 @@ CubicWeb.require('ajax.js'); function removeBookmark(beid) { - d = async_remote_exec('delete_bookmark', beid); + d = asyncRemoteExec('delete_bookmark', beid); d.addCallback(function(boxcontent) { reloadComponent('bookmarks_box', '', 'boxes', 'bookmarks_box'); document.location.hash = '#header'; diff -r 06af20e663f2 -r 7ff24154351d web/data/cubicweb.calendar.js --- a/web/data/cubicweb.calendar.js Tue Apr 21 19:20:56 2009 +0200 +++ b/web/data/cubicweb.calendar.js Wed Apr 22 16:50:46 2009 +0200 @@ -235,7 +235,7 @@ // the only way understood by both IE and Mozilla. Otherwise, // IE accepts innerText and mozilla accepts textContent var selectedDate = new Date(cal.year, cal.month, cell.innerHTML, 12); - var xxx = remote_exec("format_date", toISOTimestamp(selectedDate)); + var xxx = remoteExec("format_date", toISOTimestamp(selectedDate)); input.value = xxx; cal.hide(); } diff -r 06af20e663f2 -r 7ff24154351d web/data/cubicweb.compat.js --- a/web/data/cubicweb.compat.js Tue Apr 21 19:20:56 2009 +0200 +++ b/web/data/cubicweb.compat.js Wed Apr 22 16:50:46 2009 +0200 @@ -365,13 +365,12 @@ }; -function loadJSON(url, data, type, dataType) { +function loadJSON(url, data, type) { var d = new Deferred(); jQuery.ajax({ url: url, type: type, data: data, - dataType: dataType, beforeSend: function(xhr) { d.req = xhr; diff -r 06af20e663f2 -r 7ff24154351d web/data/cubicweb.edition.js --- a/web/data/cubicweb.edition.js Tue Apr 21 19:20:56 2009 +0200 +++ b/web/data/cubicweb.edition.js Wed Apr 22 16:50:46 2009 +0200 @@ -1,6 +1,6 @@ /* * :organization: Logilab - * :copyright: 2003-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved. + * :copyright: 2003-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved. * :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr */ @@ -22,7 +22,8 @@ function setPropValueWidget(varname, tabindex) { var key = firstSelected(document.getElementById('pkey:'+varname)); if (key) { - var args = _buildRemoteArgs('prop_widget', key, varname, tabindex); + var args = {fname: 'prop_widget', pageid: pageid, + arg: map(jQuery.toJSON, [key, varname, tabindex])}; jqNode('div:value:'+varname).loadxhtml(JSON_BASE_URL, args, 'post'); } } @@ -51,40 +52,25 @@ }); } + function showMatchingSelect(selectedValue, eid) { if (selectedValue) { divId = 'div' + selectedValue + '_' + eid; var divNode = jQuery('#' + divId); if (!divNode.length) { var args = {vid: 'unrelateddivs', relation: selectedValue, - rql: rql_for_eid(eid), pageid: pageid, - '__notemplate': 1}; - jQuery.get(JSON_BASE_URL, args, function(response) { - // append generated HTML to the cell - jQuery('#unrelatedDivs_' + eid).append(getDomFromResponse(response)); - _showMatchingSelect(eid, jQuery('#' + divId)); - }); - // deferred = doXHR(JSON_BASE_URL + queryString(args)); - // deferred.addCallback(_buildAndShowMatchingSelect, eid, divId); + rql: rql_for_eid(eid), '__notemplate': 1, + callback: function() {_showMatchingSelect(eid, jQuery('#' + divId))}}; + jQuery('#unrelatedDivs_' + eid).loadxhtml(baseuri() + 'view', args, 'post', 'append'); } else { _showMatchingSelect(eid, divNode); } - } - else { + } else { _showMatchingSelect(eid, null); } } - -// @param divStr a HTML string returned by the server -// function _buildAndShowMatchingSelect(eid, divId, req) { -// var tdNode = jQuery('#unrelatedDivs_' + eid); -// // append generated HTML to the cell -// tdNode.appendChild(getDomFromRequest(req)); -// _showMatchingSelect(eid, jQuery('#' + divId)); -// } - // @param divNode is a jQuery selection function _showMatchingSelect(eid, divNode) { // hide all divs, and then show the matching one @@ -154,7 +140,7 @@ // add hidden parameter var entityForm = jQuery('#entityForm'); var oid = optionNode.id.substring(2); // option id is prefixed by "id" - remote_exec('add_pending_insert', oid.split(':')); + remoteExec('add_pending_inserts', [oid.split(':')]); var selectNode = optionNode.parentNode; // remove option node selectNode.removeChild(optionNode); @@ -186,7 +172,7 @@ options[options.length] = OPTION({'id' : elementId, 'value' : node_id}, entityView); } } - remote_exec('remove_pending_insert', elementId.split(':')); + remoteExec('remove_pending_insert', elementId.split(':')); } // this function builds a Handle to cancel pending insertion @@ -198,7 +184,7 @@ // @param nodeId eid_from:r_type:eid_to function addPendingDelete(nodeId, eid) { - var d = async_remote_exec('add_pending_delete', nodeId.split(':')); + var d = asyncRemoteExec('add_pending_delete', nodeId.split(':')); d.addCallback(function () { // and strike entity view jqNode('span' + nodeId).addClass('pendingDelete'); @@ -209,7 +195,7 @@ // @param nodeId eid_from:r_type:eid_to function cancelPendingDelete(nodeId, eid) { - var d = async_remote_exec('remove_pending_delete', nodeId.split(':')); + var d = asyncRemoteExec('remove_pending_delete', nodeId.split(':')); d.addCallback(function () { // reset link's CSS class jqNode('span' + nodeId).removeClass('pendingDelete'); @@ -232,11 +218,11 @@ function selectForAssociation(tripletIdsString, originalEid) { var tripletlist = map(function (x) { return x.split(':'); }, tripletIdsString.split('-')); - var d = async_remote_exec('add_pending_inserts', tripletlist); + var d = asyncRemoteExec('add_pending_inserts', tripletlist); d.addCallback(function () { var args = {vid: 'edition', __mode: 'normal', rql: rql_for_eid(originalEid)}; - document.location = 'view?' + as_url(args); + document.location = 'view?' + asURL(args); }); } @@ -246,13 +232,9 @@ jQuery('#inline' + rtype + 'slot span.icounter').each(function (i) { this.innerHTML = i+1; }); - // var divnode = jQuery('#inline' + rtype + 'slot'); - // var iforms = getElementsByTagAndClassName('span', 'icounter', divnode); - // for (var i=0; i>> as_url({'rql' : "RQL", 'x': [1, 2], 'itemvid' : "oneline"}) + * >>> asURL({'rql' : "RQL", 'x': [1, 2], 'itemvid' : "oneline"}) * rql=RQL&vid=list&itemvid=oneline&x=1&x=2 */ -function as_url(props) { +function asURL(props) { var chunks = []; for(key in props) { var value = props[key]; diff -r 06af20e663f2 -r 7ff24154351d web/data/cubicweb.preferences.js --- a/web/data/cubicweb.preferences.js Tue Apr 21 19:20:56 2009 +0200 +++ b/web/data/cubicweb.preferences.js Wed Apr 22 16:50:46 2009 +0200 @@ -5,6 +5,6 @@ */ function toggle_and_remember_visibility(elemId, cookiename) { jqNode(elemId).toggleClass('hidden'); - async_remote_exec('set_cookie', cookiename, + asyncRemoteExec('set_cookie', cookiename, jQuery('#' + elemId).attr('class')); } diff -r 06af20e663f2 -r 7ff24154351d web/data/cubicweb.tabs.js --- a/web/data/cubicweb.tabs.js Tue Apr 21 19:20:56 2009 +0200 +++ b/web/data/cubicweb.tabs.js Wed Apr 22 16:50:46 2009 +0200 @@ -1,6 +1,6 @@ function set_tab(tabname, cookiename) { // set appropriate cookie - async_remote_exec('set_cookie', cookiename, tabname); + asyncRemoteExec('set_cookie', cookiename, tabname); // trigger show + tabname event trigger_load(tabname); } diff -r 06af20e663f2 -r 7ff24154351d web/data/cubicweb.widgets.js --- a/web/data/cubicweb.widgets.js Tue Apr 21 19:20:56 2009 +0200 +++ b/web/data/cubicweb.widgets.js Wed Apr 22 16:50:46 2009 +0200 @@ -277,7 +277,7 @@ this.eid_to = name[1]; this.etype_to = wdgnode.getAttribute('cubicweb:etype_to'); this.etype_from = wdgnode.getAttribute('cubicweb:etype_from'); - var d = async_remote_exec('add_and_link_new_entity', this.etype_to, this.rel, this.eid_to, this.etype_from, 'new_val'); + var d = asyncRemoteExec('add_and_link_new_entity', this.etype_to, this.rel, this.eid_to, this.etype_from, 'new_val'); d.addCallback(function (eid) { jQuery(wdgnode).find("option[selected]").removeAttr("selected"); var new_option = OPTION({'value':eid, 'selected':'selected'}, value=new_val); diff -r 06af20e663f2 -r 7ff24154351d web/views/basecontrollers.py --- a/web/views/basecontrollers.py Tue Apr 21 19:20:56 2009 +0200 +++ b/web/views/basecontrollers.py Wed Apr 22 16:50:46 2009 +0200 @@ -18,7 +18,7 @@ from cubicweb import NoSelectableObject, ValidationError, ObjectNotFound, typed_eid from cubicweb.utils import strptime from cubicweb.selectors import yes, match_user_groups -from cubicweb.view import STRICT_DOCTYPE, CW_XHTML_EXTENSIONS +from cubicweb.view import STRICT_DOCTYPE from cubicweb.common.mail import format_mail from cubicweb.web import ExplicitLogin, Redirect, RemoteCallFailed from cubicweb.web.formrenderers import FormRenderer @@ -30,8 +30,42 @@ HAS_SEARCH_RESTRICTION = True except ImportError: # gae HAS_SEARCH_RESTRICTION = False - - + + +def xhtml_wrap(source): + head = u'\n' + STRICT_DOCTYPE + return head + u'
%s
' % source.strip() + +def jsonize(func): + """decorator to sets correct content_type and calls `simplejson.dumps` on + results + """ + def wrapper(self, *args, **kwargs): + self.req.set_content_type('application/json') + result = func(self, *args, **kwargs) + return simplejson.dumps(result) + return wrapper + +def xhtmlize(func): + """decorator to sets correct content_type and calls `xmlize` on results""" + def wrapper(self, *args, **kwargs): + self.req.set_content_type(self.req.html_content_type()) + result = func(self, *args, **kwargs) + return xhtml_wrap(result) + return wrapper + +def check_pageid(func): + """decorator which checks the given pageid is found in the + user's session data + """ + def wrapper(self, *args, **kwargs): + data = self.req.get_session_data(self.req.pageid) + if data is None: + raise RemoteCallFailed(self.req._('pageid-not-found')) + return func(self, *args, **kwargs) + return wrapper + + class LoginController(Controller): id = 'login' @@ -44,10 +78,10 @@ # Cookie authentication return self.appli.need_login_content(self.req) - + class LogoutController(Controller): id = 'logout' - + def publish(self, rset=None): """logout from the application""" return self.appli.session_handler.logout(self.req) @@ -60,7 +94,7 @@ """ id = 'view' template = 'main-template' - + def publish(self, rset=None): """publish a request, returning an encoded string""" view, rset = self._select_view_and_rset(rset) @@ -131,7 +165,7 @@ else: rql = 'SET Y %s X WHERE X eid %%(x)s, Y eid %%(y)s' % rtype for teid in eids: - req.execute(rql, {'x': eid, 'y': typed_eid(teid)}, ('x', 'y')) + req.execute(rql, {'x': eid, 'y': typed_eid(teid)}, ('x', 'y')) class FormValidatorController(Controller): @@ -178,56 +212,65 @@ except AttributeError: eid = err.entity return (False, (eid, err.errors)) - -def xmlize(source): - head = u'\n' + STRICT_DOCTYPE % CW_XHTML_EXTENSIONS - return head + u'
%s
' % source.strip() -def jsonize(func): - """sets correct content_type and calls `simplejson.dumps` on results - """ - def wrapper(self, *args, **kwargs): - self.req.set_content_type('application/json') - result = func(self, *args, **kwargs) - return simplejson.dumps(result) - return wrapper - - -def check_pageid(func): - """decorator which checks the given pageid is found in the - user's session data - """ - def wrapper(self, *args, **kwargs): - data = self.req.get_session_data(self.req.pageid) - if data is None: - raise RemoteCallFailed(self.req._('pageid-not-found')) - return func(self, *args, **kwargs) - return wrapper - class JSonController(Controller): id = 'json' - template = 'main' def publish(self, rset=None): - mode = self.req.form.get('mode', 'html') + """call js_* methods. Expected form keys: + + :fname: the method name without the js_ prefix + :args: arguments list (json) + + note: it's the responsability of js_* methods to set the correct + response content type + """ self.req.pageid = self.req.form.get('pageid') + fname = self.req.form['fname'] + try: + func = getattr(self, 'js_%s' % fname) + except AttributeError: + raise RemoteCallFailed('no %s method' % fname) + # no attribute means the callback takes no argument + args = self.req.form.get('arg', ()) + if not isinstance(args, (list, tuple)): + args = (args,) + args = [simplejson.loads(arg) for arg in args] try: - func = getattr(self, '%s_exec' % mode) - except AttributeError, ex: - self.error('json controller got an unknown mode %r', mode) - self.error('\t%s', ex) - result = u'' - else: - try: - result = func(rset) - except RemoteCallFailed: - raise - except Exception, ex: - self.exception('an exception occured on json request(rset=%s): %s', - rset, ex) - raise RemoteCallFailed(repr(ex)) - return result.encode(self.req.encoding) + result = func(*args) + except RemoteCallFailed: + raise + except Exception, ex: + self.exception('an exception occured while calling js_%s(%s): %s', + fname, args, ex) + raise RemoteCallFailed(repr(ex)) + if result is None: + return '' + # get unicode on @htmlize methods, encoded string on @jsonize methods + elif isinstance(result, unicode): + return result.encode(self.req.encoding) + return result + + def _rebuild_posted_form(self, names, values, action=None): + form = {} + for name, value in zip(names, values): + # remove possible __action_xxx inputs + if name.startswith('__action'): + continue + # form.setdefault(name, []).append(value) + if name in form: + curvalue = form[name] + if isinstance(curvalue, list): + curvalue.append(value) + else: + form[name] = [curvalue, value] + else: + form[name] = value + # simulate click on __action_%s button to help the controller + if action: + form['__action_%s' % action] = u'whatever' + return form def _exec(self, rql, args=None, eidkey=None, rocheck=True): """json mode: execute RQL and return resultset as json""" @@ -240,31 +283,15 @@ return None return None - @jsonize - def json_exec(self, rset=None): - """json mode: execute RQL and return resultset as json""" - rql = self.req.form.get('rql') - if rset is None and rql: - rset = self._exec(rql) - return rset and rset.rows or [] - - def _set_content_type(self, vobj, data): - """sets req's content type according to vobj's content type - (and xmlize data if needed) - """ - content_type = vobj.content_type - if content_type == 'application/xhtml+xml': - self.req.set_content_type(content_type) - return xmlize(data) - return data - - def html_exec(self, rset=None): + @xhtmlize + def js_view(self): # XXX try to use the page-content template req = self.req rql = req.form.get('rql') - if rset is None and rql: + if rql: rset = self._exec(rql) - + else: + rset = None vid = req.form.get('vid') or vid_from_rset(req, rset, self.schema) try: view = self.vreg.select_view(vid, req, rset) @@ -292,46 +319,46 @@ stream.write(u'\n') if req.form.get('paginate') and divid == 'pageContent': stream.write(u'') - source = stream.getvalue() - return self._set_content_type(view, source) + return stream.getvalue() - def rawremote_exec(self, rset=None): - """like remote_exec but doesn't change content type""" - # no attribute means the callback takes no argument - args = self.req.form.get('arg', ()) - if not isinstance(args, (list, tuple)): - args = (args,) - fname = self.req.form['fname'] - args = [simplejson.loads(arg) for arg in args] - try: - func = getattr(self, 'js_%s' % fname) - except AttributeError: - self.exception('rawremote_exec fname=%s', fname) - return u"" - return func(*args) + @xhtmlize + def js_prop_widget(self, propkey, varname, tabindex=None): + """specific method for CWProperty handling""" + entity = self.vreg.etype_class('CWProperty')(self.req, None, None) + entity.eid = varname + entity['pkey'] = propkey + form = self.vreg.select_object('forms', 'edition', self.req, None, + entity=entity) + form.form_build_context() + vfield = form.field_by_name('value') + renderer = FormRenderer() + return vfield.render(form, renderer, tabindex=tabindex) \ + + renderer.render_help(form, vfield) - remote_exec = jsonize(rawremote_exec) - - def _rebuild_posted_form(self, names, values, action=None): - form = {} - for name, value in zip(names, values): - # remove possible __action_xxx inputs - if name.startswith('__action'): - continue - # form.setdefault(name, []).append(value) - if name in form: - curvalue = form[name] - if isinstance(curvalue, list): - curvalue.append(value) - else: - form[name] = [curvalue, value] - else: - form[name] = value - # simulate click on __action_%s button to help the controller - if action: - form['__action_%s' % action] = u'whatever' - return form - + @xhtmlize + def js_component(self, compid, rql, registry='components', extraargs=None): + if rql: + rset = self._exec(rql) + else: + rset = None + comp = self.vreg.select_object(registry, compid, self.req, rset) + if extraargs is None: + extraargs = {} + else: # we receive unicode keys which is not supported by the **syntax + extraargs = dict((str(key), value) + for key, value in extraargs.items()) + extraargs = extraargs or {} + return comp.dispatch(**extraargs) + + @check_pageid + @xhtmlize + def js_inline_creation_form(self, peid, ttype, rtype, role): + view = self.vreg.select_view('inline-creation', self.req, None, + etype=ttype, peid=peid, rtype=rtype, + role=role) + return view.dispatch(etype=ttype, peid=peid, rtype=rtype, role=role) + + @jsonize def js_validate_form(self, action, names, values): # XXX this method (and correspoding js calls) should use the new # `RemoteCallFailed` mechansim @@ -359,6 +386,7 @@ return (False, self.req._(str(err))) return (False, '???') + @jsonize def js_edit_field(self, action, names, values, rtype, eid): success, args = self.js_validate_form(action, names, values) if success: @@ -368,52 +396,29 @@ return (success, args, entity.printable_value(rtype)) else: return (success, args, None) - - def js_rql(self, rql): - rset = self._exec(rql) - return rset and rset.rows or [] - + +# def js_rql(self, rql): +# rset = self._exec(rql) +# return rset and rset.rows or [] + + @jsonize def js_i18n(self, msgids): """returns the translation of `msgid`""" return [self.req._(msgid) for msgid in msgids] + @jsonize def js_format_date(self, strdate): """returns the formatted date for `msgid`""" date = strptime(strdate, '%Y-%m-%d %H:%M:%S') return self.format_date(date) + @jsonize def js_external_resource(self, resource): """returns the URL of the external resource named `resource`""" return self.req.external_resource(resource) - def js_prop_widget(self, propkey, varname, tabindex=None): - """specific method for CWProperty handling""" - entity = self.vreg.etype_class('CWProperty')(self.req, None, None) - entity.eid = varname - entity['pkey'] = propkey - form = self.vreg.select_object('forms', 'edition', self.req, None, - entity=entity) - form.form_build_context() - vfield = form.field_by_name('value') - renderer = FormRenderer() - return vfield.render(form, renderer, tabindex=tabindex) \ - + renderer.render_help(form, vfield) - - def js_component(self, compid, rql, registry='components', extraargs=None): - if rql: - rset = self._exec(rql) - else: - rset = None - comp = self.vreg.select_object(registry, compid, self.req, rset) - if extraargs is None: - extraargs = {} - else: # we receive unicode keys which is not supported by the **syntax - extraargs = dict((str(key), value) - for key, value in extraargs.items()) - extraargs = extraargs or {} - return self._set_content_type(comp, comp.dispatch(**extraargs)) - @check_pageid + @jsonize def js_user_callback(self, cbname): page_data = self.req.get_session_data(self.req.pageid, {}) try: @@ -421,53 +426,16 @@ except KeyError: return None return cb(self.req) - - def js_unregister_user_callback(self, cbname): - self.req.unregister_callback(self.req.pageid, cbname) - - def js_unload_page_data(self): - self.req.del_session_data(self.req.pageid) - - def js_cancel_edition(self, errorurl): - """cancelling edition from javascript - - We need to clear associated req's data : - - errorurl - - pending insertions / deletions - """ - self.req.cancel_edition(errorurl) - - @check_pageid - def js_inline_creation_form(self, peid, ttype, rtype, role): - view = self.vreg.select_view('inline-creation', self.req, None, - etype=ttype, peid=peid, rtype=rtype, - role=role) - source = view.dispatch(etype=ttype, peid=peid, rtype=rtype, role=role) - return self._set_content_type(view, source) - - def js_remove_pending_insert(self, (eidfrom, rel, eidto)): - self._remove_pending(eidfrom, rel, eidto, 'insert') - - def js_add_pending_insert(self, (eidfrom, rel, eidto)): - self._add_pending(eidfrom, rel, eidto, 'insert') - - def js_add_pending_inserts(self, tripletlist): - for eidfrom, rel, eidto in tripletlist: - self._add_pending(eidfrom, rel, eidto, 'insert') - - def js_remove_pending_delete(self, (eidfrom, rel, eidto)): - self._remove_pending(eidfrom, rel, eidto, 'delete') - - def js_add_pending_delete(self, (eidfrom, rel, eidto)): - self._add_pending(eidfrom, rel, eidto, 'delete') if HAS_SEARCH_RESTRICTION: + @jsonize def js_filter_build_rql(self, names, values): form = self._rebuild_posted_form(names, values) self.req.form = form builder = FilterRQLBuilder(self.req) return builder.build_rql() + @jsonize def js_filter_select_content(self, facetids, rql): rqlst = self.vreg.parse(self.req, rql) # XXX Union unsupported yet mainvar = prepare_facets_rqlst(rqlst)[0] @@ -477,13 +445,33 @@ update_map[facetid] = facet.possible_values() return update_map + def js_unregister_user_callback(self, cbname): + self.req.unregister_callback(self.req.pageid, cbname) + + def js_unload_page_data(self): + self.req.del_session_data(self.req.pageid) + + def js_cancel_edition(self, errorurl): + """cancelling edition from javascript + + We need to clear associated req's data : + - errorurl + - pending insertions / deletions + """ + self.req.cancel_edition(errorurl) + def js_delete_bookmark(self, beid): - try: - rql = 'DELETE B bookmarked_by U WHERE B eid %(b)s, U eid %(u)s' - self.req.execute(rql, {'b': typed_eid(beid), 'u' : self.req.user.eid}) - except Exception, ex: - self.exception(unicode(ex)) - return self.req._('Problem occured') + rql = 'DELETE B bookmarked_by U WHERE B eid %(b)s, U eid %(u)s' + self.req.execute(rql, {'b': typed_eid(beid), 'u' : self.req.user.eid}) + + def js_set_cookie(self, cookiename, cookievalue): + # XXX we should consider jQuery.Cookie + cookiename, cookievalue = str(cookiename), str(cookievalue) + cookies = self.req.get_cookie() + cookies[cookiename] = cookievalue + self.req.set_cookie(cookies, cookiename) + + # relations edition stuff ################################################## def _add_pending(self, eidfrom, rel, eidto, kind): key = 'pending_%s' % kind @@ -492,7 +480,7 @@ self.req.set_session_data(key, pendings) def _remove_pending(self, eidfrom, rel, eidto, kind): - key = 'pending_%s' % kind + key = 'pending_%s' % kind try: pendings = self.req.get_session_data(key) pendings.remove( (typed_eid(eidfrom), rel, typed_eid(eidto)) ) @@ -501,6 +489,21 @@ else: self.req.set_session_data(key, pendings) + def js_remove_pending_insert(self, (eidfrom, rel, eidto)): + self._remove_pending(eidfrom, rel, eidto, 'insert') + + def js_add_pending_inserts(self, tripletlist): + for eidfrom, rel, eidto in tripletlist: + self._add_pending(eidfrom, rel, eidto, 'insert') + + def js_remove_pending_delete(self, (eidfrom, rel, eidto)): + self._remove_pending(eidfrom, rel, eidto, 'delete') + + def js_add_pending_delete(self, (eidfrom, rel, eidto)): + self._add_pending(eidfrom, rel, eidto, 'delete') + + # XXX specific code. Kill me and my AddComboBox friend + @jsonize def js_add_and_link_new_entity(self, etype_to, rel, eid_to, etype_from, value_from): # create a new entity eid_from = self.req.execute('INSERT %s T : T name "%s"' % ( etype_from, value_from ))[0][0] @@ -508,12 +511,6 @@ rql = 'SET F %(rel)s T WHERE F eid %(eid_to)s, T eid %(eid_from)s' % {'rel' : rel, 'eid_to' : eid_to, 'eid_from' : eid_from} return eid_from - def js_set_cookie(self, cookiename, cookievalue): - # XXX we should consider jQuery.Cookie - cookiename, cookievalue = str(cookiename), str(cookievalue) - cookies = self.req.get_cookie() - cookies[cookiename] = cookievalue - self.req.set_cookie(cookies, cookiename) class SendMailController(Controller): id = 'sendmail' @@ -549,7 +546,7 @@ msg = format_mail({'email' : self.req.user.get_email(), 'name' : self.req.user.dc_title(),}, [recipient], body, subject) - self.smtp.sendmail(helo_addr, [recipient], msg.as_string()) + self.smtp.sendmail(helo_addr, [recipient], msg.as_string()) def publish(self, rset=None): # XXX this allow anybody with access to an cubicweb application to use it as a mail relay @@ -572,4 +569,4 @@ self.sendmail(self.config['submit-mail'], _('%s error report') % self.config.appid, body) url = self.build_url(__message=self.req._('bug report sent')) raise Redirect(url) - +