--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/web/data/cubicweb.edition.js Wed Nov 05 15:52:50 2008 +0100
@@ -0,0 +1,530 @@
+/*
+ * :organization: Logilab
+ * :copyright: 2003-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+ * :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+ */
+
+CubicWeb.require('python.js');
+CubicWeb.require('htmlhelpers.js');
+CubicWeb.require('ajax.js');
+
+
+//============= Eproperty form functions =====================================//
+
+/* called on Eproperty key selection:
+ * - get the selected value
+ * - get a widget according to the key by a sync query to the server
+ * - fill associated div with the returned html
+ *
+ * @param varname the name of the variable as used in the original creation form
+ * @param tabindex the tabindex that should be set on the widget
+ */
+function setPropValueWidget(varname, tabindex) {
+ var key = firstSelected(jQuery('#pkey:'+varname));
+ if (key) {
+ var args = _buildRemoteArgs('prop_widget', key, varname, tabindex);
+ jQuery('#div:value:'+varname).loadxhtml(JSON_BASE_URL, args, 'post');
+ }
+}
+
+
+// *** EDITION FUNCTIONS ****************************************** //
+
+/*
+ * this function is called when an AJAX form was generated to
+ * make sure tabindex remains consistent
+ */
+function reorderTabindex(start) {
+ var form = getNode('entityForm');
+ var inputTypes = ['INPUT', 'SELECT', 'TEXTAREA'];
+ var tabindex = (start==null)?15:start;
+ nodeWalkDepthFirst(form, function(elem) {
+ var tagName = elem.tagName.toUpperCase();
+ if (inputTypes.contains(tagName)) {
+ if (getNodeAttribute(elem, 'tabindex') != null) {
+ tabindex += 1;
+ elem.setAttribute('tabindex', tabindex);
+ }
+ return null;
+ }
+ return filter(isElementNode, elem.childNodes);
+ });
+}
+
+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);
+ } else {
+ _showMatchingSelect(eid, divNode);
+ }
+ }
+ 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
+ // (would actually be better to directly hide the displayed one)
+ jQuery('#unrelatedDivs_' + eid).children().hide();
+ // divNode not found means 'no relation selected' (i.e. first blank item)
+ if (divNode && divNode.length) {
+ divNode.show();
+ }
+}
+
+// this function builds a Handle to cancel pending insertion
+function buildPendingInsertHandle(elementId, element_name, selectNodeId, eid) {
+ jscall = "javascript: cancelPendingInsert('" + [elementId, element_name, selectNodeId, eid].join("', '") + "')";
+ return A({'class' : 'handle', 'href' : jscall,
+ 'title' : _("cancel this insert")}, '[x]');
+}
+
+function buildEntityLine(relationName, selectedOptionNode, comboId, eid) {
+ // textContent doesn't seem to work on selectedOptionNode
+ var content = selectedOptionNode.firstChild.nodeValue;
+ var handle = buildPendingInsertHandle(selectedOptionNode.id, 'tr', comboId, eid);
+ var link = A({'href' : 'view?rql=' + selectedOptionNode.value,
+ 'class' : 'editionPending', 'id' : 'a' + selectedOptionNode.id},
+ content);
+ var tr = TR({'id' : 'tr' + selectedOptionNode.id}, [ TH(null, relationName),
+ TD(null, [handle, link])
+ ]);
+ try {
+ var separator = getNode('relationSelectorRow_' + eid);
+ //dump('relationSelectorRow_' + eid) XXX warn dump is not implemented in konqueror (at least)
+ // XXX Warning: separator.parentNode is not (always ?) the
+ // table itself, but an intermediate node (TableSectionElement)
+ var tableBody = separator.parentNode;
+ tableBody.insertBefore(tr, separator);
+ } catch(ex) {
+ log("got exception(2)!" + ex);
+ }
+}
+
+function buildEntityCell(relationName, selectedOptionNode, comboId, eid) {
+ var handle = buildPendingInsertHandle(selectedOptionNode.id, 'div_insert_', comboId, eid);
+ var link = A({'href' : 'view?rql=' + selectedOptionNode.value,
+ 'class' : 'editionPending', 'id' : 'a' + selectedOptionNode.id},
+ content);
+ var div = DIV({'id' : 'div_insert_' + selectedOptionNode.id}, [handle, link]);
+ try {
+ var td = jQuery('#cell'+ relationName +'_'+eid);
+ td.appendChild(div);
+ } catch(ex) {
+ alert("got exception(3)!" + ex);
+ }
+}
+
+function addPendingInsert(optionNode, eid, cell, relname) {
+ var value = getNodeAttribute(optionNode, 'value');
+ if (!value) {
+ // occurs when the first element in the box is selected (which is not
+ // an entity but the combobox title)
+ return;
+ }
+ // 2nd special case
+ if (value.indexOf('http') == 0) {
+ document.location = value;
+ return;
+ }
+ // 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(':'));
+ var selectNode = optionNode.parentNode;
+ // remove option node
+ selectNode.removeChild(optionNode);
+ // add line in table
+ if (cell) {
+ // new relation as a cell in multiple edit
+ // var relation_name = relationSelected.getAttribute('value');
+ // relation_name = relation_name.slice(0, relation_name.lastIndexOf('_'));
+ buildEntityCell(relname, optionNode, selectNode.id, eid);
+ }
+ else {
+ var relationSelector = getNode('relationSelector_'+eid);
+ var relationSelected = relationSelector.options[relationSelector.selectedIndex];
+ // new relation as a line in simple edit
+ buildEntityLine(relationSelected.text, optionNode, selectNode.id, eid);
+ }
+}
+
+function cancelPendingInsert(elementId, element_name, comboId, eid) {
+ // remove matching insert element
+ var entityView = jqNode('a' + elementId).text();
+ jqNode(element_name + elementId).remove();
+ if (comboId) {
+ // re-insert option in combobox if it was taken from there
+ var selectNode = getNode(comboId);
+ if (selectNode){
+ var options = selectNode.options;
+ var node_id = elementId.substring(0, elementId.indexOf(':'));
+ options[options.length] = OPTION({'id' : elementId, 'value' : node_id}, entityView);
+ }
+ }
+ remote_exec('remove_pending_insert', elementId.split(':'));
+}
+
+// this function builds a Handle to cancel pending insertion
+function buildPendingDeleteHandle(elementId, eid) {
+ var jscall = "javascript: addPendingDelete('" + elementId + ', ' + eid + "');";
+ return A({'href' : jscall, 'class' : 'pendingDeleteHandle',
+ 'title' : _("delete this relation")}, '[x]');
+}
+
+// @param nodeId eid_from:r_type:eid_to
+function addPendingDelete(nodeId, eid) {
+ var d = async_remote_exec('add_pending_delete', nodeId.split(':'));
+ d.addCallback(function () {
+ // and strike entity view
+ jqNode('span' + nodeId).addClass('pendingDelete');
+ // replace handle text
+ jqNode('handle' + nodeId).text('+');
+ });
+}
+
+// @param nodeId eid_from:r_type:eid_to
+function cancelPendingDelete(nodeId, eid) {
+ var d = async_remote_exec('remove_pending_delete', nodeId.split(':'));
+ d.addCallback(function () {
+ // reset link's CSS class
+ jqNode('span' + nodeId).removeClass('pendingDelete');
+ // replace handle text
+ jqNode('handle' + nodeId).text('x');
+ });
+}
+
+// @param nodeId eid_from:r_type:eid_to
+function togglePendingDelete(nodeId, eid) {
+ // node found means we should cancel deletion
+ if ( hasElementClass(getNode('span' + nodeId), 'pendingDelete') ) {
+ cancelPendingDelete(nodeId, eid);
+ } else {
+ addPendingDelete(nodeId, eid);
+ }
+}
+
+
+function selectForAssociation(tripletIdsString, originalEid) {
+ var tripletlist = map(function (x) { return x.split(':'); },
+ tripletIdsString.split('-'));
+ var d = async_remote_exec('add_pending_inserts', tripletlist);
+ d.addCallback(function () {
+ var args = {vid: 'edition', __mode: 'normal',
+ rql: rql_for_eid(originalEid)};
+ document.location = 'view?' + as_url(args);
+ });
+
+}
+
+
+function updateInlinedEntitiesCounters(rtype) {
+ 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
+ * @param ptype : the parent entity type
+ * @param ttype : the target (inlined) entity type
+ * @param rtype : the relation type between both entities
+ */
+function addInlineCreationForm(peid, ptype, ttype, rtype, role) {
+ var d = async_rawremote_exec('inline_creation_form', peid, ptype, ttype, rtype, role);
+ d.addCallback(function (response) {
+ var linknode = getNode('add' + rtype + ':' + peid + 'link');
+ var form = jQuery(getDomFromResponse(response));
+ 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');
+ // MochiKit.Signal.signal(CubicWeb, 'inlinedform-added', form);
+ });
+ d.addErrback(function (xxx) {
+ log('xxx =', xxx);
+ });
+}
+
+/*
+ * removes the part of the form used to edit an inlined entity
+ */
+function removeInlineForm(peid, rtype, eid) {
+ jqNode(['div', peid, rtype, eid].join('-')).slideUp('fast', function() {
+ $(this).remove();
+ updateInlinedEntitiesCounters(rtype);
+ });
+}
+
+/*
+ * alternatively adds or removes the hidden input that make the
+ * edition of the relation `rtype` possible between `peid` and `eid`
+ * @param peid : the parent entity eid
+ * @param rtype : the relation type between both entities
+ * @param eid : the inlined entity eid
+ */
+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();
+ jqNode(divid).fadeTo('fast', 0.5);
+ // setOpacity(divid, 0.4);
+ jqNode(noticeid).fadeIn('fast');
+ // appear(jQuery('#' + noticeid), {'duration': 0.5});
+ }
+}
+
+function restoreInlinedEntity(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 = 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();
+ }
+}
+
+function _clearPreviousErrors(formid) {
+ jQuery('#' + formid + ' span.error').remove();
+}
+
+function _displayValidationerrors(formid, eid, errors) {
+ var globalerrors = [];
+ var firsterrfield = null;
+ for (fieldname in errors) {
+ var errmsg = errors[fieldname];
+ var fieldid = fieldname + ':' + eid;
+ var field = jqNode(fieldname + ':' + eid);
+ if (field && getNodeAttribute(field, 'type') != 'hidden') {
+ if ( !firsterrfield ) {
+ firsterrfield = 'err-' + fieldid;
+ }
+ addElementClass(field, 'error');
+ var span = SPAN({'id': 'err-' + fieldid, 'class': "error"}, errmsg);
+ field.before(span);
+ } else {
+ firsterrfield = formid;
+ globalerrors.push(fieldname + ': ' + errmsg);
+ }
+ }
+ if (globalerrors.length) {
+ if (globalerrors.length == 1) {
+ var innernode = SPAN(null, globalerrors[0]);
+ } else {
+ var innernode = UL(null, map(LI, globalerrors));
+ }
+ // insert DIV and innernode before the form
+ var div = DIV({'class' : "errorMessage"});
+ div.appendChild(innernode);
+ jQuery('#' + formid).before(div);
+ }
+ return firsterrfield || formid;
+}
+
+
+function handleFormValidationResponse(formid, onsuccess, result) {
+ // Success
+ if (result[0]) {
+ if (onsuccess) {
+ return onsuccess(result[1]);
+ } else {
+ document.location.href = result[1];
+ return ;
+ }
+ }
+ unfreezeFormButtons(formid);
+ // Failures
+ _clearPreviousErrors(formid);
+ var descr = result[1];
+ // Unknown structure
+ if ( !isArrayLike(descr) || descr.length != 2 ) {
+ log('got strange error :', descr);
+ updateMessage(descr);
+ return ;
+ }
+ _displayValidationerrors(formid, descr[0], descr[1]);
+ updateMessage(_("please correct errors below"));
+ document.location.hash = '#header';
+ return false;
+}
+
+
+/* unfreeze form buttons when the validation process is over*/
+function unfreezeFormButtons(formid) {
+ jQuery('#progress').hide();
+ jQuery('#' + formid + ' input.validateButton').removeAttr('disabled');
+ return true;
+}
+
+/* disable form buttons while the validation is being done */
+function freezeFormButtons(formid) {
+ var formbuttons = jQuery(formid + ' input.validateButton');
+ jQuery('#progress').show();
+ jQuery(formid + ' input.validateButton').attr('disabled', 'disabled');
+ return true;
+}
+
+/* used by additional submit buttons to remember which button was clicked */
+function postForm(bname, bvalue, formid) {
+ var form = getNode(formid);
+ if (bname) {
+ form.appendChild(INPUT({type: 'hidden', name: bname, value: bvalue}));
+ }
+ var onsubmit = form.onsubmit;
+ if (!onsubmit || (onsubmit && onsubmit())) {
+ form.submit();
+ }
+}
+
+
+/* called on load to set target and iframeso object.
+ * NOTE: this is a hack to make the XHTML compliant.
+ * NOTE2: `object` nodes might be a potential replacement for iframes
+ * NOTE3: there is a XHTML module allowing iframe elements but there
+ * is still the problem of the form's `target` attribute
+ */
+function setFormsTarget() {
+ jQuery('form.entityForm').each(function () {
+ var form = jQuery(this);
+ 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 */
+ form.append(IFRAME({name: target, id: target,
+ src: 'javascript: void(0)',
+ width: '0px', height: '0px'}));
+ }
+ });
+}
+
+$(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
+ * to post the form. If the post is successful, `validateForm` redirects
+ * to the appropriate URL. Otherwise, the validation errors are displayed
+ * around the corresponding input fields.
+ */
+function validateForm(formid, action, onsuccess) {
+ try {
+ var d = _sendForm(formid, action);
+ } catch (ex) {
+ log('got exception', ex);
+ return false;
+ }
+ 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
+ * @param rtype : the attribute being edited
+ * @param eid : the eid of the entity being edited
+ * @param reload: boolean to reload page if true (when changing URL dependant data)
+ */
+function inlineValidateForm(formid, rtype, eid, divid, reload) {
+ try {
+ var form = getNode(formid);
+ if (typeof FCKeditorAPI != "undefined") {
+ for ( var name in FCKeditorAPI.__Instances ) {
+ var oEditor = FCKeditorAPI.__Instances[name] ;
+ if ( oEditor.GetParentForm() == form ) {
+ oEditor.UpdateLinkedField();
+ }
+ }
+ }
+ var zipped = formContents(form);
+ var d = async_remote_exec('edit_field', 'apply', zipped[0], zipped[1], rtype, eid);
+ } catch (ex) {
+ log('got exception', ex);
+ return false;
+ }
+ d.addCallback(function (result, req) {
+ handleFormValidationResponse(formid, noop, result);
+ if (reload) {
+ document.location.href = result[1];
+ } else {
+ var fieldview = getNode(divid);
+ // XXX using innerHTML is very fragile and won't work if
+ // we mix XHTML and HTML
+ fieldview.innerHTML = result[2];
+ // switch inline form off only if no error
+ if (result[0]) {
+ // hide global error messages
+ jQuery('div.errorMessage').remove();
+ jQuery('#appMsg').hide();
+ cancelInlineEdit(eid, rtype, divid);
+ }
+ }
+ return false;
+ });
+ return false;
+}
+
+/**** inline edition ****/
+function showInlineEditionForm(eid, rtype, divid) {
+ jQuery('#' + divid).hide();
+ jQuery('#' + divid + '-form').show();
+}
+
+function cancelInlineEdit(eid, rtype, divid) {
+ jQuery('#' + divid).show();
+ jQuery('#' + divid + '-form').hide();
+}
+
+CubicWeb.provide('edition.js');