[facets] Correctly look for inputs of type "hidden" (closes #4502768)
jQuery ':hidden' selector looks at CSS visual properties (eg, 'display',
'visibility'). The intent here was probably to look for inputs of type
"hidden", which many facets use to store user selection data (eg,
FacetRangeWidget).
The problem is that regular text inputs (eg the "has_text" facet which
has a '<input type="text"/>') will be picked up by this selector if they
are inside a folded facet. Chaos and destruction ensue.
/* copyright 2003-2014 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
* contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
*
* This file is part of CubicWeb.
*
* CubicWeb is free software: you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free
* Software Foundation, either version 2.1 of the License, or (at your option)
* any later version.
*
* CubicWeb is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
* details.
*
* You should have received a copy of the GNU Lesser General Public License along
* with CubicWeb. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* .. function:: Deferred
*
* dummy ultra minimalist implementation of deferred for jQuery
*/
cw.ajax = new Namespace('cw.ajax');
function Deferred() {
this.__init__(this);
}
jQuery.extend(Deferred.prototype, {
__init__: function() {
this._onSuccess = [];
this._onFailure = [];
this._req = null;
this._result = null;
this._error = null;
},
addCallback: function(callback) {
if ((this._req.readyState == 4) && this._result) {
var args = [this._result, this._req];
jQuery.merge(args, cw.utils.sliceList(arguments, 1));
callback.apply(null, args);
}
else {
this._onSuccess.push([callback, cw.utils.sliceList(arguments, 1)]);
}
return this;
},
addErrback: function(callback) {
if (this._req.readyState == 4 && this._error) {
callback.apply(null, [this._error, this._req]);
}
else {
this._onFailure.push([callback, cw.utils.sliceList(arguments, 1)]);
}
return this;
},
success: function(result) {
this._result = result;
try {
for (var i = 0; i < this._onSuccess.length; i++) {
var callback = this._onSuccess[i][0];
var args = [result, this._req];
jQuery.merge(args, this._onSuccess[i][1]);
callback.apply(null, args);
}
} catch(error) {
this.error(this._req, null, error);
}
},
error: function(xhr, status, error) {
this._error = error;
for (var i = 0; i < this._onFailure.length; i++) {
var callback = this._onFailure[i][0];
var args = [error, this._req];
jQuery.merge(args, this._onFailure[i][1]);
if (callback !== undefined)
callback.apply(null, args);
}
}
});
var AJAX_PREFIX_URL = 'ajax';
var JSON_BASE_URL = baseuri() + 'json?';
var AJAX_BASE_URL = baseuri() + AJAX_PREFIX_URL + '?';
jQuery.extend(cw.ajax, {
/* variant of jquery evalScript with cache: true in ajax call */
_evalscript: function ( i, elem ) {
var src = elem.getAttribute('src');
if (src) {
jQuery.ajax({
url: src,
async: false,
cache: true,
dataType: "script"
});
} else {
jQuery.globalEval( elem.text || elem.textContent || elem.innerHTML || "" );
}
if ( elem.parentNode ) {
elem.parentNode.removeChild( elem );
}
},
evalscripts: function ( scripts ) {
if ( scripts.length ) {
jQuery.each(scripts, cw.ajax._evalscript);
}
},
/**
* returns true if `url` is a mod_concat-like url
* (e.g. http://..../data??resource1.js,resource2.js)
*/
_modconcatLikeUrl: function(url) {
var base = baseuri();
if (!base.endswith('/')) { base += '/'; }
var modconcat_rgx = new RegExp('(' + base + 'data/([a-z0-9]+/)?)\\?\\?(.+)');
return modconcat_rgx.exec(url);
},
/**
* decomposes a mod_concat-like url into its corresponding list of
* resources' urls
* >>> _listResources('http://foo.com/data/??a.js,b.js,c.js')
* ['http://foo.com/data/a.js', 'http://foo.com/data/b.js', 'http://foo.com/data/c.js']
*/
_listResources: function(src) {
var resources = [];
var groups = cw.ajax._modconcatLikeUrl(src);
if (groups == null) {
resources.push(src);
} else {
var dataurl = groups[1];
$.each(cw.utils.lastOf(groups).split(','),
function() {
resources.push(dataurl + this);
}
);
}
return resources;
},
_buildMissingResourcesUrl: function(url, loadedResources) {
var resources = cw.ajax._listResources(url);
var missingResources = $.grep(resources, function(resource) {
return $.inArray(resource, loadedResources) == -1;
});
cw.utils.extend(loadedResources, missingResources);
var missingResourceUrl = null;
if (missingResources.length == 1) {
// only one resource missing: build a node with a single resource url
// (maybe the browser has it in cache already)
missingResourceUrl = missingResources[0];
} else if (missingResources.length > 1) {
// several resources missing: build a node with a concatenated
// resources url
var dataurl = cw.ajax._modconcatLikeUrl(url)[1];
var missing_path = $.map(missingResources, function(resource) {
return resource.substring(dataurl.length);
});
missingResourceUrl = dataurl + '??' + missing_path.join(',');
}
return missingResourceUrl;
},
_loadAjaxStylesheets: function($responseHead, $head) {
$responseHead.find('link[href]').each(function(i) {
var $srcnode = $(this);
var url = $srcnode.attr('href');
if (url) {
var missingStylesheetsUrl = cw.ajax._buildMissingResourcesUrl(url, cw.loaded_links);
// compute concat-like url for missing resources and append <link>
// element to $head
if (missingStylesheetsUrl) {
// IE has problems with dynamic CSS insertions. One symptom (among others)
// is a "1 item remaining" message in the status bar. (cf. #2356261)
// document.createStyleSheet needs to be used for this, although it seems
// that IE can't create more than 31 additional stylesheets with
// document.createStyleSheet.
if ($.browser.msie) {
document.createStyleSheet(missingStylesheetsUrl);
} else {
$srcnode.attr('href', missingStylesheetsUrl);
$srcnode.appendTo($head);
}
}
}
});
$responseHead.find('link[href]').remove();
},
_loadAjaxScripts: function($responseHead, $head) {
$responseHead.find('cubicweb\\:script').each(function(i) {
var $srcnode = $(this);
var url = $srcnode.attr('src');
if (url) {
var missingScriptsUrl = cw.ajax._buildMissingResourcesUrl(url, cw.loaded_scripts);
if (missingScriptsUrl) {
$srcnode.attr('src', missingScriptsUrl);
/* special handling of <script> tags: script nodes appended by jquery
* use uncached ajax calls and do not appear in the DOM
* (See comments in response to Syt on // http://api.jquery.com/append/),
* which cause undesired duplicated load in our case. We now handle
* a list of already loaded resources, since bare DOM api gives bugs with the
* server-response event, and we lose control on when the
* script is loaded (jQuery loads it immediately). */
cw.ajax.evalscripts($srcnode);
}
} else {
// <script> contains inlined javascript code, node content
// must be evaluated
jQuery.globalEval($srcnode.text());
}
});
$responseHead.find('cubicweb\\:script').remove();
}
});
//============= utility function handling remote calls responses. ==============//
/**
* .. function:: function loadAjaxHtmlHead(response)
*
* inspect dom response (as returned by getDomFromResponse), search for
* a <div class="ajaxHtmlHead"> node and put its content into the real
* document's head.
* This enables dynamic css and js loading and is used by replacePageChunk
*/
function loadAjaxHtmlHead(response) {
var $head = jQuery('head');
var $responseHead = jQuery(response).find('div.ajaxHtmlHead');
// no ajaxHtmlHead found, no processing required
if (!$responseHead.length) {
return response;
}
cw.ajax._loadAjaxStylesheets($responseHead, $head);
cw.ajax._loadAjaxScripts($responseHead, $head);
// add any remaining children (e.g. meta)
$responseHead.children().appendTo($head);
// remove original container, which is now empty
$responseHead.remove();
// if there was only one actual node in the reponse besides
// the ajaxHtmlHead, then remove the wrapper created by
// getDomFromResponse() and return this single element
// For instance :
// 1/ CW returned the following content :
// <div>the-actual-content</div><div class="ajaxHtmlHead">...</div>
// 2/ getDomFromReponse() wrapped this into a single DIV to hold everything
// in one, unique, dom element
// 3/ now that we've removed the ajaxHtmlHead div, the only
// node left in the wrapper if the 'real' node built by the view,
// we can safely return this node. Otherwise, the view itself
// returned several 'root' nodes and we need to keep the wrapper
// created by getDomFromResponse()
if (response.childNodes.length == 1 && response.getAttribute('cubicweb:type') == 'cwResponseWrapper') {
return response.firstChild;
}
return response;
}
function _postAjaxLoad(node) {
// find textareas and wrap them if there are some
if (typeof(FCKeditor) != 'undefined') {
buildWysiwygEditors();
}
if (typeof initFacetBoxEvents != 'undefined') {
initFacetBoxEvents(node);
}
if (typeof buildWidgets != 'undefined') {
buildWidgets(node);
}
if (typeof roundedCorners != 'undefined') {
roundedCorners(node);
}
if (typeof setFormsTarget != 'undefined') {
setFormsTarget(node);
}
_loadDynamicFragments(node);
// XXX [3.7] jQuery.one is now used instead jQuery.bind,
// jquery.treeview.js can be unpatched accordingly.
jQuery(cw).trigger('server-response', [true, node]);
jQuery(node).trigger('server-response', [true, node]);
}
function remoteCallFailed(err, req) {
cw.log(err);
if (req.status == 500) {
updateMessage(err);
} else {
updateMessage(_("an error occurred while processing your request"));
}
}
//============= base AJAX functions to make remote calls =====================//
/**
* .. function:: ajaxFuncArgs(fname, form, *args)
*
* extend `form` parameters to call the js_`fname` function of the json
* controller with `args` arguments.
*/
function ajaxFuncArgs(fname, form /* ... */) {
form = form || {};
$.extend(form, {
'fname': fname,
'pageid': pageid,
'arg': $.map(cw.utils.sliceList(arguments, 2), JSON.stringify)
});
return form;
}
/**
* .. function:: loadxhtml(url, form, reqtype='get', mode='replace', cursor=false)
*
* build url given by absolute or relative `url` and `form` parameters
* (dictionary), fetch it using `reqtype` method, then evaluate the
* returned XHTML and insert it according to `mode` in the
* document. Possible modes are :
*
* - 'replace' to replace the node's content with the generated HTML
* - 'swap' to replace the node itself with the generated HTML
* - 'append' to append the generated HTML to the node's content
*
* If `cursor`, turn mouse cursor into 'progress' cursor until the remote call
* is back.
*/
jQuery.fn.loadxhtml = function(url, form, reqtype, mode, cursor) {
if (this.size() > 1) {
cw.log('loadxhtml called with more than one element');
} else if (this.size() < 1) {
cw.log('loadxhtml called without an element');
}
var node = this.get(0); // only consider the first element
if (cursor) {
setProgressCursor();
}
var d = loadRemote(url, form, reqtype);
d.addCallback(function(response) {
var domnode = getDomFromResponse(response);
domnode = loadAjaxHtmlHead(domnode);
mode = mode || 'replace';
// make sure the component is visible
$(node).removeClass("hidden");
if (mode == 'swap') {
var origId = node.id;
node = cw.swapDOM(node, domnode);
if (!node.id) {
node.id = origId;
}
} else if (mode == 'replace') {
jQuery(node).empty().append(domnode);
} else if (mode == 'append') {
jQuery(node).append(domnode);
}
_postAjaxLoad(node);
});
d.addErrback(remoteCallFailed);
if (cursor) {
d.addCallback(resetCursor);
d.addErrback(resetCursor);
}
return d;
}
/**
* .. function:: loadRemote(url, form, reqtype='GET', sync=false)
*
* Asynchronously (unless `sync` argument is set to true) load a URL or path
* and return a deferred whose callbacks args are decoded according to the
* Content-Type response header. `form` should be additional form params
* dictionary, `reqtype` the HTTP request type (get 'GET' or 'POST').
*/
function loadRemote(url, form, reqtype, sync) {
if (!url.toLowerCase().startswith(baseuri().toLowerCase())) {
url = baseuri() + url;
}
if (!sync) {
var deferred = new Deferred();
jQuery.ajax({
url: url,
type: (reqtype || 'POST').toUpperCase(),
data: form,
traditional: true,
async: true,
beforeSend: function(xhr) {
deferred._req = xhr;
},
success: function(data, status) {
deferred.success(data);
},
error: function(xhr, status, error) {
try {
if (xhr.status == 500) {
var reason_dict = cw.evalJSON(xhr.responseText);
deferred.error(xhr, status, reason_dict['reason']);
return;
}
} catch(exc) {
cw.log('error with server side error report:' + exc);
}
deferred.error(xhr, status, null);
}
});
return deferred;
} else {
var result;
// jQuery.ajax returns the XHR object, even for synchronous requests,
// but in that case, the success callback will be called before
// jQuery.ajax returns. The first argument of the callback will be
// the server result, interpreted by jQuery according to the reponse's
// content-type (i.e. json or xml)
jQuery.ajax({
url: url,
type: (reqtype || 'GET').toUpperCase(),
data: form,
traditional: true,
async: false,
success: function(res) {
result = res;
}
});
return result;
}
}
//============= higher level AJAX functions using remote calls ===============//
var _i18ncache = {};
/**
* .. function:: _(message)
*
* emulation of gettext's _ shortcut
*/
function _(message) {
var form;
if (!(message in _i18ncache)) {
form = ajaxFuncArgs('i18n', null, [message]);
_i18ncache[message] = loadRemote(AJAX_BASE_URL, form, 'GET', true)[0];
}
return _i18ncache[message];
}
/**
* .. function:: _loadDynamicFragments(node)
*
* finds each dynamic fragment in the page and executes the
* the associated RQL to build them (Async call)
*/
function _loadDynamicFragments(node) {
if (node) {
var fragments = jQuery(node).find('div.dynamicFragment');
} else {
var fragments = jQuery('div.dynamicFragment');
}
if (fragments.length == 0) {
return;
}
if (typeof LOADING_MSG == 'undefined') {
LOADING_MSG = 'loading'; // this is only a safety belt, it should not happen
}
for (var i = 0; i < fragments.length; i++) {
var fragment = fragments[i];
fragment.innerHTML = '<h3>' + LOADING_MSG + ' ... <img src="data/loading.gif" /></h3>';
var $fragment = jQuery(fragment);
// if cubicweb:loadurl is set, just pick the url et send it to loadxhtml
var url = $fragment.attr('cubicweb:loadurl');
if (url) {
$fragment.loadxhtml(url);
continue;
}
// else: rebuild full url by fetching cubicweb:rql, cubicweb:vid, etc.
var rql = $fragment.attr('cubicweb:rql');
var items = $fragment.attr('cubicweb:vid').split('&');
var vid = items[0];
var extraparams = {};
// case where vid='myvid¶m1=val1¶m2=val2': this is a deprecated abuse-case
if (items.length > 1) {
cw.log("[3.5] you're using extraargs in cubicweb:vid " +
"attribute, this is deprecated, consider using " +
"loadurl instead");
for (var j = 1; j < items.length; j++) {
var keyvalue = items[j].split('=');
extraparams[keyvalue[0]] = keyvalue[1];
}
}
var actrql = $fragment.attr('cubicweb:actualrql');
if (actrql) {
extraparams['actualrql'] = actrql;
}
var fbvid = $fragment.attr('cubicweb:fallbackvid');
if (fbvid) {
extraparams['fallbackvid'] = fbvid;
}
extraparams['rql'] = rql;
extraparams['vid'] = vid;
$fragment.loadxhtml(AJAX_BASE_URL, ajaxFuncArgs('view', extraparams));
}
}
function unloadPageData() {
// NOTE: do not make async calls on unload if you want to avoid
// strange bugs
loadRemote(AJAX_BASE_URL, ajaxFuncArgs('unload_page_data'), 'GET', true);
}
function removeBookmark(beid) {
var d = loadRemote(AJAX_BASE_URL, ajaxFuncArgs('delete_bookmark', null, beid));
d.addCallback(function(boxcontent) {
$('#bookmarks_box').loadxhtml(AJAX_BASE_URL,
ajaxFuncArgs('render', null, 'ctxcomponents',
'bookmarks_box'));
document.location.hash = '#header';
updateMessage(_("bookmark has been removed"));
});
}
userCallback = cw.utils.deprecatedFunction(
'[3.19] use a plain ajaxfunc instead of user callbacks',
function userCallback(cbname) {
setProgressCursor();
var d = loadRemote(AJAX_BASE_URL, ajaxFuncArgs('user_callback', null, cbname));
d.addCallback(resetCursor);
d.addErrback(resetCursor);
d.addErrback(remoteCallFailed);
return d;
});
userCallbackThenUpdateUI = cw.utils.deprecatedFunction(
'[3.19] use a plain ajaxfunc instead of user callbacks',
function userCallbackThenUpdateUI(cbname, compid, rql, msg, registry, nodeid) {
var d = userCallback(cbname);
d.addCallback(function() {
$('#' + nodeid).loadxhtml(AJAX_BASE_URL, ajaxFuncArgs('render', {'rql': rql},
registry, compid));
if (msg) {
updateMessage(msg);
}
});
});
userCallbackThenReloadPage = cw.utils.deprecatedFunction(
'[3.19] use a plain ajaxfunc instead of user callbacks',
function userCallbackThenReloadPage(cbname, msg) {
var d = userCallback(cbname);
d.addCallback(function() {
window.location.reload();
if (msg) {
updateMessage(msg);
}
});
});
/**
* .. function:: unregisterUserCallback(cbname)
*
* unregisters the python function registered on the server's side
* while the page was generated.
*/
unregisterUserCallback = cw.utils.deprecatedFunction(
'[3.19] use a plain ajaxfunc instead of user callbacks',
function unregisterUserCallback(cbname) {
setProgressCursor();
var d = loadRemote(AJAX_BASE_URL, ajaxFuncArgs('unregister_user_callback',
null, cbname));
d.addCallback(resetCursor);
d.addErrback(resetCursor);
d.addErrback(remoteCallFailed);
});
//============= XXX move those functions? ====================================//
function openHash() {
if (document.location.hash) {
var nid = document.location.hash.replace('#', '');
var node = jQuery('#' + nid);
if (node) {
$(node).removeClass("hidden");
}
};
}
jQuery(document).ready(openHash);
/**
* .. function:: buildWysiwygEditors(parent)
*
*XXX: this function should go in edition.js but as for now, htmlReplace
* references it.
*
* replace all textareas with fckeditors.
*/
function buildWysiwygEditors(parent) {
jQuery('textarea').each(function() {
if (this.getAttribute('cubicweb:type') == 'wysiwyg') {
// mark editor as instanciated, we may be called a number of times
// (see _postAjaxLoad)
this.setAttribute('cubicweb:type', 'fckeditor');
if (typeof FCKeditor != "undefined") {
var fck = new FCKeditor(this.id);
fck.Config['CustomConfigurationsPath'] = fckconfigpath;
fck.Config['DefaultLanguage'] = fcklang;
fck.BasePath = baseuri() + "fckeditor/";
fck.ReplaceTextarea();
} else {
cw.log('fckeditor could not be found.');
}
}
});
}
jQuery(document).ready(buildWysiwygEditors);
/**
* .. function:: stripEmptyTextNodes(nodelist)
*
* takes a list of DOM nodes and removes all empty text nodes
*/
function stripEmptyTextNodes(nodelist) {
/* this DROPS empty text nodes */
var stripped = [];
for (var i = 0; i < nodelist.length; i++) {
var node = nodelist[i];
if (isTextNode(node)) {
/* all browsers but FF -> innerText, FF -> textContent */
var text = node.innerText || node.textContent;
if (text && ! text.strip()) {
continue;
}
} else {
stripped.push(node);
}
}
return stripped;
}
/**
* .. function:: getDomFromResponse(response)
*
* convenience function that returns a DOM node based on req's result.
* XXX clarify the need to clone
* */
function getDomFromResponse(response) {
if (typeof(response) == 'string') {
var doc = html2dom(response);
} else {
var doc = response.documentElement;
}
var children = doc.childNodes;
if (!children.length) {
// no child (error cases) => return the whole document
return jQuery(doc).clone().context;
}
children = stripEmptyTextNodes(children);
if (children.length == 1) {
// only one child => return it
return jQuery(children[0]).clone().context;
}
// several children => wrap them in a single node and return the wrap
return DIV({'cubicweb:type': "cwResponseWrapper"},
$.map(children, function(node) {
return jQuery(node).clone().context;})
);
}
/* High-level functions *******************************************************/
/**
* .. function:: reloadCtxComponentsSection(context, actualEid, creationEid=None)
*
* reload all components in the section for a given `context`.
*
* This is necessary for cases where the parent entity (on which the section
* apply) has been created during post, hence the section has to be reloaded to
* consider its new eid, hence the two additional arguments `actualEid` and
* `creationEid`: `actualEid` is the eid of newly created top level entity and
* `creationEid` the fake eid that was given as form creation marker (e.g. A).
*
* You can still call this function with only the actual eid if you're not in
* such creation case.
*/
function reloadCtxComponentsSection(context, actualEid, creationEid) {
// in this case, actualEid is the eid of newly created top level entity and
// creationEid the fake eid given as form creation marker (e.g. A)
if (!creationEid) { creationEid = actualEid ; }
var $compsholder = $('#' + context + creationEid);
// reload the whole components section
$compsholder.children().each(function (index) {
// XXX this.id[:-len(eid)]
var compid = this.id.replace("_", ".").rstrip(creationEid);
var params = ajaxFuncArgs('render', null, 'ctxcomponents',
compid, actualEid);
$(this).loadxhtml(AJAX_BASE_URL, params, null, 'swap', true);
});
$compsholder.attr('id', context + actualEid);
}
/**
* .. function:: reload(domid, compid, registry, formparams, *render_args)
*
* `js_render` based reloading of views and components.
*/
function reload(domid, compid, registry, formparams /* ... */) {
var ajaxArgs = ['render', formparams, registry, compid];
ajaxArgs = ajaxArgs.concat(cw.utils.sliceList(arguments, 4));
var params = ajaxFuncArgs.apply(null, ajaxArgs);
return $('#'+domid).loadxhtml(AJAX_BASE_URL, params, null, 'swap', true);
}
/* ajax tabs ******************************************************************/
function setTab(tabname, cookiename) {
// set appropriate cookie
jQuery.cookie(cookiename, tabname, {path: '/'});
// trigger show + tabname event
triggerLoad(tabname);
}
function loadNow(eltsel, holesel, reloadable) {
var lazydiv = jQuery(eltsel);
var hole = lazydiv.children(holesel);
hole.show();
if ((hole.length == 0) && ! reloadable) {
/* the hole is already filed */
return;
}
lazydiv.loadxhtml(lazydiv.attr('cubicweb:loadurl'), {'pageid': pageid});
}
function triggerLoad(divid) {
jQuery('#lazy-' + divid).trigger('load_' + divid);
}
/* DEPRECATED *****************************************************************/
// still used in cwo and keyword cubes at least
reloadComponent = cw.utils.deprecatedFunction(
'[3.9] reloadComponent() is deprecated, use loadxhtml instead',
function(compid, rql, registry, nodeid, extraargs) {
registry = registry || 'components';
rql = rql || '';
nodeid = nodeid || (compid + 'Component');
extraargs = extraargs || {};
var node = cw.jqNode(nodeid);
return node.loadxhtml(AJAX_BASE_URL, ajaxFuncArgs('component', null, compid,
rql, registry, extraargs));
}
);
function remoteExec(fname /* ... */) {
setProgressCursor();
var props = {
fname: fname,
pageid: pageid,
arg: $.map(cw.utils.sliceList(arguments, 1), JSON.stringify)
};
var result = jQuery.ajax({
url: AJAX_BASE_URL,
data: props,
async: false,
traditional: true
}).responseText;
if (result) {
result = cw.evalJSON(result);
}
resetCursor();
return result;
}
function asyncRemoteExec(fname /* ... */) {
setProgressCursor();
var props = {
fname: fname,
pageid: pageid,
arg: $.map(cw.utils.sliceList(arguments, 1), JSON.stringify)
};
// XXX we should inline the content of loadRemote here
var deferred = loadRemote(AJAX_BASE_URL, props, 'POST');
deferred = deferred.addErrback(remoteCallFailed);
deferred = deferred.addErrback(resetCursor);
deferred = deferred.addCallback(resetCursor);
return deferred;
}
jQuery(document).ready(function() {
_loadDynamicFragments();
// build loaded_scripts / loaded_links lists
cw.loaded_scripts = [];
jQuery('head script[src]').each(function(i) {
cw.utils.extend(cw.loaded_scripts, cw.ajax._listResources(this.getAttribute('src')));
});
cw.loaded_links = [];
jQuery('head link[href]').each(function(i) {
cw.utils.extend(cw.loaded_links, cw.ajax._listResources(this.getAttribute('href')));
});
});