web/data/cubicweb.ajax.js
changeset 0 b97547f5f1fa
child 12 95bc1d15349b
equal deleted inserted replaced
-1:000000000000 0:b97547f5f1fa
       
     1 /*
       
     2  *  :organization: Logilab
       
     3  *  :copyright: 2003-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
       
     4  *  :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
       
     5  */
       
     6 
       
     7 CubicWeb.require('python.js');
       
     8 CubicWeb.require('htmlhelpers.js');
       
     9 
       
    10 var JSON_BASE_URL = baseuri() + 'json?';
       
    11 
       
    12 // cubicweb loadxhtml plugin to make jquery handle xhtml response
       
    13 jQuery.fn.loadxhtml = function(url, data, reqtype, mode) {
       
    14     var ajax = null;
       
    15     if (reqtype == 'post') {
       
    16 	ajax = jQuery.post;
       
    17     } else {
       
    18 	ajax = jQuery.get;
       
    19     }
       
    20     if (this.size() > 1) {
       
    21 	log('loadxhtml was called with more than one element');
       
    22     }
       
    23     mode = mode || 'replace';
       
    24     var callback = null;
       
    25     if (data && data.callback) {
       
    26 	callback = data.callback;
       
    27 	delete data.callback;
       
    28     }
       
    29     var node = this.get(0); // only consider the first element
       
    30     ajax(url, data, function(response) {
       
    31 	var domnode = getDomFromResponse(response);
       
    32 	if (mode == 'swap') {
       
    33 	    var origId = node.id;
       
    34 	    node = swapDOM(node, domnode);
       
    35 	    if (!node.id) {
       
    36 		node.id = origId;
       
    37 	    }
       
    38 	} else if (mode == 'replace') {
       
    39 	    jQuery(node).empty().append(domnode);
       
    40 	} else if (mode == 'append') {
       
    41 	    jQuery(node).append(domnode);
       
    42 	}
       
    43 	// find sortable tables if there are some
       
    44 	if (typeof(Sortable) != 'undefined') {
       
    45 	    Sortable.sortTables(node);
       
    46 	}
       
    47 	// find textareas and wrap them if there are some
       
    48 	if (typeof(FCKeditor) != 'undefined') {
       
    49 	    buildWysiwygEditors(node);
       
    50 	}
       
    51 
       
    52 	if (typeof initFacetBoxEvents != 'undefined') {
       
    53 	    initFacetBoxEvents(node);
       
    54 	}
       
    55 
       
    56 	if (typeof buildWidgets != 'undefined') {
       
    57 	    buildWidgets(node);
       
    58 	}
       
    59 
       
    60 	while (jQuery.isFunction(callback)) {
       
    61 	    callback = callback.apply(this, [domnode]);
       
    62 	}
       
    63     });
       
    64 }
       
    65 
       
    66 
       
    67 
       
    68 /* finds each dynamic fragment in the page and executes the
       
    69  * the associated RQL to build them (Async call)
       
    70  */
       
    71 function loadDynamicFragments() {
       
    72     var fragments = getElementsByTagAndClassName('div', 'dynamicFragment');
       
    73     if (fragments.length == 0) {
       
    74 	return;
       
    75     }
       
    76     if (typeof LOADING_MSG == 'undefined') {
       
    77 	LOADING_MSG = 'loading'; // this is only a safety belt, it should not happen
       
    78     }
       
    79     for(var i=0; i<fragments.length; i++) {
       
    80 	var fragment = fragments[i];
       
    81 	fragment.innerHTML = '<h3>' + LOADING_MSG + ' ... <img src="data/loading.gif" /></h3>';
       
    82 	var rql = getNodeAttribute(fragment, 'cubicweb:rql');
       
    83 	var vid = getNodeAttribute(fragment, 'cubicweb:vid');
       
    84         var extraparams = {};
       
    85 	var actrql = getNodeAttribute(fragment, 'cubicweb:actualrql');
       
    86 	if (actrql) { extraparams['actualrql'] = actrql; }
       
    87 	var fbvid = getNodeAttribute(fragment, 'cubicweb:fallbackvid');
       
    88 	if (fbvid) { extraparams['fallbackvid'] = fbvid; }
       
    89 
       
    90 	replacePageChunk(fragment.id, rql, vid, extraparams);
       
    91     }
       
    92 }
       
    93 
       
    94 jQuery(document).ready(loadDynamicFragments);
       
    95 
       
    96 //============= base AJAX functions to make remote calls =====================//
       
    97 
       
    98 
       
    99 /*
       
   100  * This function will call **synchronously** a remote method on the cubicweb server
       
   101  * @param fname: the function name to call (as exposed by the JSONController)
       
   102  * @param args: the list of arguments to pass the function
       
   103  */
       
   104 function remote_exec(fname) {
       
   105     setProgressCursor();
       
   106     var props = {'mode' : "remote", 'fname' : fname, 'pageid' : pageid,
       
   107      		 'arg': map(jQuery.toJSON, sliceList(arguments, 1))};
       
   108     var result  = jQuery.ajax({url: JSON_BASE_URL, data: props, async: false}).responseText;
       
   109     result = evalJSON(result);
       
   110     resetCursor();
       
   111     return result;
       
   112 }
       
   113 
       
   114 function remoteCallFailed(err, req) {
       
   115     if (req.status == 500) {
       
   116 	updateMessage(err);
       
   117     } else {
       
   118 	updateMessage(_("an error occured while processing your request"));
       
   119     }
       
   120 }
       
   121 
       
   122 /*
       
   123  * This function is the equivalent of MochiKit's loadJSONDoc but
       
   124  * uses POST instead of GET
       
   125  */
       
   126 function loadJSONDocUsingPOST(url, queryargs, mode) {
       
   127     mode = mode || 'remote';
       
   128     setProgressCursor();
       
   129     var dataType = (mode == 'remote') ? "json":null;
       
   130     var deferred = loadJSON(url, queryargs, 'POST', dataType);
       
   131     deferred = deferred.addErrback(remoteCallFailed);
       
   132 //     if (mode == 'remote') {
       
   133 // 	deferred = deferred.addCallbacks(evalJSONRequest);
       
   134 //     }
       
   135     deferred = deferred.addCallback(resetCursor);
       
   136     return deferred;
       
   137 }
       
   138 
       
   139 
       
   140 function _buildRemoteArgs(fname) {
       
   141     return  {'mode' : "remote", 'fname' : fname, 'pageid' : pageid,
       
   142      	     'arg': map(jQuery.toJSON, sliceList(arguments, 1))};
       
   143 }
       
   144 
       
   145 /*
       
   146  * This function will call **asynchronously** a remote method on the cubicweb server
       
   147  * This function is a low level one. You should use `async_remote_exec` or
       
   148  * `async_rawremote_exec` instead.
       
   149  *
       
   150  * @param fname: the function name to call (as exposed by the JSONController)
       
   151  * @param funcargs: the function's arguments
       
   152  * @param mode: rawremote or remote
       
   153  */
       
   154 function _async_exec(fname, funcargs, mode) {
       
   155     setProgressCursor();
       
   156     var props = {'mode' : mode, 'fname' : fname, 'pageid' : pageid};
       
   157     var args = map(urlEncode, map(jQuery.toJSON, funcargs));
       
   158     args.unshift(''); // this is to be able to use join() directly
       
   159     var queryargs = as_url(props) + args.join('&arg=');
       
   160     return loadJSONDocUsingPOST(JSON_BASE_URL, queryargs, mode);
       
   161 }
       
   162 
       
   163 /*
       
   164  * This function will call **asynchronously** a remote method on the cubicweb server
       
   165  * @param fname: the function name to call (as exposed by the JSONController)
       
   166  * additional arguments will be directly passed to the specified function
       
   167  * Expected response type is Json.
       
   168  */
       
   169 function async_remote_exec(fname /* ... */) {
       
   170     return _async_exec(fname, sliceList(arguments, 1), 'remote');
       
   171 }
       
   172 
       
   173 /*
       
   174  * This version of _async_exec doesn't expect a json response.
       
   175  * It looks at http headers to guess the response type.
       
   176  */
       
   177 function async_rawremote_exec(fname /* ... */) {
       
   178     return _async_exec(fname, sliceList(arguments, 1), 'rawremote');
       
   179 }
       
   180 
       
   181 /*
       
   182  * This function will call **asynchronously** a remote method on the cubicweb server
       
   183  * @param fname: the function name to call (as exposed by the JSONController)
       
   184  * @param varargs: the list of arguments to pass to the function
       
   185  * This is an alternative form of `async_remote_exec` provided for convenience
       
   186  */
       
   187 function async_remote_exec_varargs(fname, varargs) {
       
   188     return _async_exec(fname, varargs, 'remote');
       
   189 }
       
   190 
       
   191 /* emulation of gettext's _ shortcut
       
   192  */
       
   193 function _(message) {
       
   194     return remote_exec('i18n', [message])[0];
       
   195 }
       
   196 
       
   197 function rqlexec(rql) {
       
   198     return async_remote_exec('rql', rql);
       
   199 }
       
   200 
       
   201 function userCallback(cbname) {
       
   202     async_remote_exec('user_callback', cbname);
       
   203 }
       
   204 
       
   205 function unloadPageData() {
       
   206     // NOTE: do not make async calls on unload if you want to avoid
       
   207     //       strange bugs
       
   208     remote_exec('unload_page_data');
       
   209 }
       
   210 
       
   211 function openHash() {
       
   212     if (document.location.hash) {
       
   213 	var nid = document.location.hash.replace('#', '');
       
   214 	var node = jQuery('#' + nid);
       
   215 	if (node) { removeElementClass(node, "hidden"); }
       
   216     };
       
   217 }
       
   218 jQuery(document).ready(openHash);
       
   219 
       
   220 function reloadComponent(compid, rql, registry, nodeid, extraargs) {
       
   221     registry = registry || 'components';
       
   222     rql = rql || '';
       
   223     nodeid = nodeid || (compid + 'Component');
       
   224     extraargs = extraargs || {};
       
   225   log('extraargs =', extraargs);
       
   226     var node = getNode(nodeid);
       
   227     var d = async_rawremote_exec('component', compid, rql, registry, extraargs);
       
   228     d.addCallback(function(result, req) {
       
   229 	var domnode = getDomFromResponse(result);
       
   230 	if (node) {
       
   231 	    // make sure the component is visible
       
   232 	    removeElementClass(node, "hidden");
       
   233 	    swapDOM(node, domnode);
       
   234 	}
       
   235     });
       
   236     d.addCallback(resetCursor);
       
   237     d.addErrback(function(xxx) {
       
   238 	updateMessage(_("an error occured"));
       
   239 	log(xxx);
       
   240     });
       
   241   return d;
       
   242 }
       
   243 
       
   244 /* XXX: HTML architecture of cubicweb boxes is a bit strange */
       
   245 function reloadBox(boxid, rql) {
       
   246     reloadComponent(boxid, rql, 'boxes', boxid);
       
   247 }
       
   248 
       
   249 function userCallbackThenUpdateUI(cbname, compid, rql, msg, registry, nodeid) {
       
   250     var d = async_remote_exec('user_callback', cbname);
       
   251     d.addCallback(function() {
       
   252 	reloadComponent(compid, rql, registry, nodeid);
       
   253 	if (msg) { updateMessage(msg); }
       
   254     });
       
   255     d.addCallback(resetCursor);
       
   256     d.addErrback(function(xxx) {
       
   257 	updateMessage(_("an error occured"));
       
   258 	log(xxx);
       
   259 	return resetCursor();
       
   260     });
       
   261 }
       
   262 
       
   263 function userCallbackThenReloadPage(cbname, msg) {
       
   264     var d = async_remote_exec('user_callback', cbname);
       
   265     d.addCallback(function() {
       
   266 	window.location.reload();
       
   267 	if (msg) { updateMessage(msg); }
       
   268     });
       
   269     d.addCallback(resetCursor);
       
   270     d.addErrback(function(xxx) {
       
   271 	updateMessage(_("an error occured"));
       
   272 	log(xxx);
       
   273 	return resetCursor();
       
   274     });
       
   275 }
       
   276 
       
   277 /*
       
   278  * unregisters the python function registered on the server's side
       
   279  * while the page was generated.
       
   280  */
       
   281 function unregisterUserCallback(cbname) {
       
   282     d = async_remote_exec('unregister_user_callback', cbname);
       
   283     d.addCallback(function() {resetCursor();});
       
   284     d.addErrback(function(xxx) {
       
   285 	updateMessage(_("an error occured"));
       
   286 	log(xxx);
       
   287 	return resetCursor();
       
   288     });
       
   289 }
       
   290 
       
   291 
       
   292 /* executes an async query to the server and replaces a node's
       
   293  * content with the query result
       
   294  *
       
   295  * @param nodeId the placeholder node's id
       
   296  * @param rql the RQL query
       
   297  * @param vid the vid to apply to the RQL selection (default if not specified)
       
   298  * @param extraparmas table of additional query parameters
       
   299  */
       
   300 function replacePageChunk(nodeId, rql, vid, extraparams, /* ... */ swap, callback) {
       
   301     var params = null;
       
   302     if (callback) {
       
   303 	params = {callback: callback};
       
   304     }
       
   305 
       
   306     var node = jQuery('#' + nodeId)[0];
       
   307     var props = {};
       
   308     if (node) {
       
   309 	props['rql'] = rql;
       
   310 	props['pageid'] = pageid;
       
   311 	if (vid) { props['vid'] = vid; }
       
   312 	if (extraparams) { jQuery.extend(props, extraparams); }
       
   313 	// FIXME we need to do as_url(props) manually instead of
       
   314 	// passing `props` directly to loadxml because replacePageChunk
       
   315 	// is sometimes called (abusively) with some extra parameters in `vid`
       
   316 	var mode = swap?'swap':'replace';
       
   317 	var url = JSON_BASE_URL + as_url(props);
       
   318 	jQuery(node).loadxhtml(url, params, 'get', mode);
       
   319     } else {
       
   320 	log('Node', nodeId, 'not found');
       
   321     }
       
   322 }
       
   323 
       
   324 /* XXX: this function should go in edition.js but as for now, htmlReplace
       
   325  * references it.
       
   326  *
       
   327  * replace all textareas with fckeditors.
       
   328  */
       
   329 function buildWysiwygEditors(parent) {
       
   330     jQuery('textarea').each(function () {
       
   331 	if (this.getAttribute('cubicweb:type', 'wysiwyg')) {
       
   332 	    if (typeof FCKeditor != "undefined") {
       
   333 		var fck = new FCKeditor(this.id);
       
   334 		fck.Config['CustomConfigurationsPath'] = fckconfigpath;
       
   335 		fck.Config['DefaultLanguage'] = fcklang;
       
   336 		fck.BasePath = "fckeditor/";
       
   337 		fck.ReplaceTextarea();
       
   338 	    } else {
       
   339 		log('fckeditor could not be found.');
       
   340 	    }
       
   341 	}
       
   342     });
       
   343 }
       
   344 
       
   345 jQuery(document).ready(buildWysiwygEditors);
       
   346 
       
   347 
       
   348 /* convenience function that returns a DOM node based on req's result. */
       
   349 function getDomFromResponse(response) {
       
   350     if (typeof(response) == 'string') {
       
   351 	return html2dom(response);
       
   352     }
       
   353     var doc = response.documentElement;
       
   354     var children = doc.childNodes;
       
   355     if (!children.length) {
       
   356 	// no child (error cases) => return the whole document
       
   357 	return doc.cloneNode(true);
       
   358     }
       
   359     if (children.length == 1) {
       
   360 	// only one child => return it
       
   361 	return children[0].cloneNode(true);
       
   362     }
       
   363     // several children => wrap them in a single node and return the wrap
       
   364     return DIV(null, map(methodcaller('cloneNode', true), children));
       
   365 }
       
   366 
       
   367 function postJSON(url, data, callback) {
       
   368     return jQuery.post(url, data, callback, 'json');
       
   369 }
       
   370 
       
   371 function getJSON(url, data, callback){
       
   372     return jQuery.get(url, data, callback, 'json');
       
   373 }
       
   374 
       
   375 CubicWeb.provide('ajax.js');