--- 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);
--- 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';
--- 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();
}
--- 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;
--- 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<iforms.length; i++) {
- // iforms[i].innerHTML = i+1;
- // }
}
+
/*
* makes an AJAX request to get an inline-creation view's content
* @param peid : the parent entity eid
@@ -260,21 +242,17 @@
* @param rtype : the relation type between both entities
*/
function addInlineCreationForm(peid, ttype, rtype, role) {
- var d = async_rawremote_exec('inline_creation_form', peid, ttype, rtype, role);
+ var d = asyncRemoteExec('inline_creation_form', peid, ttype, rtype, role);
d.addCallback(function (response) {
var linknode = getNode('add' + rtype + ':' + peid + 'link');
var dom = getDomFromResponse(response);
var form = jQuery(dom);
form.css('display', 'none');
form.insertBefore(linknode.parentNode).slideDown('fast');
- // setStyle(form, {display: 'none'});
- // insertSiblingNodesBefore(linknode.parentNode, form);
updateInlinedEntitiesCounters(rtype);
- // slideDown(form, {'duration':0.6});
reorderTabindex();
form.trigger('inlinedform-added');
postAjaxLoad(dom);
- // MochiKit.Signal.signal(CubicWeb, 'inlinedform-added', form);
});
d.addErrback(function (xxx) {
log('xxx =', xxx);
@@ -300,15 +278,13 @@
*/
function removeInlinedEntity(peid, rtype, eid) {
var nodeid = ['rel', peid, rtype, eid].join('-');
- var divid = ['div', peid, rtype, eid].join('-');
- var noticeid = ['notice', peid, rtype, eid].join('-');
var node = jqNode(nodeid);
if (node && node.length) {
node.remove();
+ var divid = ['div', peid, rtype, eid].join('-');
jqNode(divid).fadeTo('fast', 0.5);
- // setOpacity(divid, 0.4);
+ var noticeid = ['notice', peid, rtype, eid].join('-');
jqNode(noticeid).fadeIn('fast');
- // appear(jQuery('#' + noticeid), {'duration': 0.5});
}
}
@@ -321,11 +297,8 @@
node = INPUT({type: 'hidden', id: nodeid,
name: rtype+':'+peid, value: eid});
jqNode(['fs', peid, rtype, eid].join('-')).append(node);
- // appendChildNodes(fs, node);
jqNode(divid).fadeTo('fast', 1);
- // setOpacity(divid, 1);
jqNode(noticeid).hide();
- // jQuery('#' + noticeid).hide();
}
}
@@ -433,8 +406,8 @@
var target = form.attr('cubicweb:target');
if (target) {
form.attr('target', target);
- /* do not use display: none because some browser ignore iframe
- * with no display */
+ /* do not use display: none because some browsers ignore iframe
+ * with no display */
form.append(IFRAME({name: target, id: target,
src: 'javascript: void(0)',
width: '0px', height: '0px'}));
@@ -444,10 +417,6 @@
$(document).ready(setFormsTarget);
-function _sendForm(formid, action) {
- var zipped = formContents(formid);
- return async_remote_exec('validate_form', action, zipped[0], zipped[1]);
-}
/*
* called on traditionnal form submission : the idea is to try
@@ -457,7 +426,8 @@
*/
function validateForm(formid, action, onsuccess) {
try {
- var d = _sendForm(formid, action);
+ var zipped = formContents(formid);
+ var d = asyncRemoteExec('validate_form', action, zipped[0], zipped[1]);
} catch (ex) {
log('got exception', ex);
return false;
@@ -465,11 +435,11 @@
function _callback(result, req) {
handleFormValidationResponse(formid, onsuccess, result);
}
- // d.addCallback(handleFormValidationResponse, formid, onsuccess);
d.addCallback(_callback);
return false;
}
+
/*
* called by live-edit forms to submit changes
* @param formid : the dom id of the form used
@@ -489,7 +459,7 @@
}
}
var zipped = formContents(form);
- var d = async_remote_exec('edit_field', 'apply', zipped[0], zipped[1], rtype, eid);
+ var d = asyncRemoteExec('edit_field', 'apply', zipped[0], zipped[1], rtype, eid);
} catch (ex) {
log('got exception', ex);
return false;
--- a/web/data/cubicweb.formfilter.js Tue Apr 21 19:20:56 2009 +0200
+++ b/web/data/cubicweb.formfilter.js Wed Apr 22 16:50:46 2009 +0200
@@ -43,7 +43,7 @@
var zipped = facetFormContent(form);
zipped[0].push('facetargs');
zipped[1].push(vidargs);
- var d = async_remote_exec('filter_build_rql', zipped[0], zipped[1]);
+ var d = asyncRemoteExec('filter_build_rql', zipped[0], zipped[1]);
d.addCallback(function(result) {
var rql = result[0];
var $bkLink = jQuery('#facetBkLink');
@@ -80,7 +80,7 @@
reloadComponent('edit_box', rql, 'boxes', 'edit_box');
}
}
- var d = async_remote_exec('filter_select_content', toupdate, rql);
+ var d = asyncRemoteExec('filter_select_content', toupdate, rql);
d.addCallback(function(updateMap) {
for (facetId in updateMap) {
var values = updateMap[facetId];
--- a/web/data/cubicweb.htmlhelpers.js Tue Apr 21 19:20:56 2009 +0200
+++ b/web/data/cubicweb.htmlhelpers.js Wed Apr 22 16:50:46 2009 +0200
@@ -111,13 +111,13 @@
}
/* builds an url from an object (used as a dictionnary)
- * Notable difference with MochiKit's queryString: as_url does not
+ * Notable difference with MochiKit's queryString: asURL does not
* *url_quote* each value found in the dictionnary
*
- * >>> 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];
--- 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'));
}
--- 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);
}
--- 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);
--- 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'<?xml version="1.0"?>\n' + STRICT_DOCTYPE
+ return head + u'<div xmlns="http://www.w3.org/1999/xhtml" xmlns:cubicweb="http://www.logilab.org/2008/cubicweb">%s</div>' % 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'<?xml version="1.0"?>\n' + STRICT_DOCTYPE % CW_XHTML_EXTENSIONS
- return head + u'<div xmlns="http://www.w3.org/1999/xhtml" xmlns:cubicweb="http://www.logilab.org/2008/cubicweb">%s</div>' % 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 <arg> 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'</div>\n')
if req.form.get('paginate') and divid == 'pageContent':
stream.write(u'</div></div>')
- 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 <arg> 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)
-
+