--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/cubicweb/web/data/cubicweb.edition.js Sat Jan 16 13:48:51 2016 +0100
@@ -0,0 +1,579 @@
+/**
+ * Functions dedicated to edition.
+ *
+ * :organization: Logilab
+ * :copyright: 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+ * :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+ *
+ */
+
+//============= Eproperty form functions =====================================//
+/**
+ * .. function:: setPropValueWidget(varname, tabindex)
+ *
+ * called on CWProperty 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
+ *
+ * * `varname`, the name of the variable as used in the original creation form
+ * * `tabindex`, the tabindex that should be set on the widget
+ */
+
+function setPropValueWidget(varname, tabindex) {
+ var key = firstSelected(document.getElementById('pkey-subject:' + varname));
+ if (key) {
+ var args = {
+ fname: 'prop_widget',
+ pageid: pageid,
+ arg: $.map([key.value, varname, tabindex], JSON.stringify)
+ };
+ cw.jqNode('div:value-subject:' + varname).loadxhtml(AJAX_BASE_URL, args, 'post');
+ }
+}
+
+// *** EDITION FUNCTIONS ****************************************** //
+/**
+ * .. function:: reorderTabindex(start, formid)
+ *
+ * this function is called when an AJAX form was generated to
+ * make sure tabindex remains consistent
+ */
+function reorderTabindex(start, formid) {
+ var form = cw.getNode(formid || 'entityForm');
+ var inputTypes = ['INPUT', 'SELECT', 'TEXTAREA'];
+ var tabindex = (start == null) ? 15: start;
+ cw.utils.nodeWalkDepthFirst(form, function(elem) {
+ var tagName = elem.tagName.toUpperCase();
+ if (jQuery.inArray(tagName, inputTypes) != -1) {
+ if (jQuery(elem).attr('tabindex') != null) {
+ tabindex += 1;
+ jQuery(elem).attr('tabindex', tabindex);
+ }
+ return null;
+ }
+ return jQuery.grep(elem.childNodes, isElementNode);
+ });
+}
+
+function showMatchingSelect(selectedValue, eid) {
+ if (selectedValue) {
+ var divId = 'div' + selectedValue + '_' + eid;
+ var divNode = jQuery('#' + divId);
+ if (!divNode.length) {
+ var args = {
+ vid: 'unrelateddivs',
+ relation: selectedValue,
+ rql: rql_for_eid(eid),
+ '__notemplate': 1
+ };
+ var d = jQuery('#unrelatedDivs_' + eid).loadxhtml(BASE_URL + 'view', args, 'post', 'append');
+ d.addCallback(function() {
+ _showMatchingSelect(eid, jQuery('#' + divId));
+ });
+ } else {
+ _showMatchingSelect(eid, divNode);
+ }
+ } else {
+ _showMatchingSelect(eid, null);
+ }
+}
+
+/**
+ * .. function:: _showMatchingSelect(eid, divNode)
+ *
+ * * `divNode`, 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();
+ }
+}
+
+/**
+ * .. function:: buildPendingInsertHandle(elementId, element_name, selectNodeId, eid)
+ *
+ * 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 = cw.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 = jQuery(optionNode).attr('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"
+ loadRemote(AJAX_BASE_URL, ajaxFuncArgs('add_pending_inserts', null,
+ [oid.split(':')]), 'POST', true);
+ 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 = cw.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 = cw.jqNode('a' + elementId).text();
+ cw.jqNode(element_name + elementId).remove();
+ if (comboId) {
+ // re-insert option in combobox if it was taken from there
+ var selectNode = cw.getNode(comboId);
+ // XXX what on object relation
+ if (selectNode) {
+ var options = selectNode.options;
+ var node_id = elementId.substring(0, elementId.indexOf(':'));
+ options[options.length] = OPTION({
+ 'id': elementId,
+ 'value': node_id
+ },
+ entityView);
+ }
+ }
+ elementId = elementId.substring(2, elementId.length);
+ loadRemote(AJAX_BASE_URL, ajaxFuncArgs('remove_pending_insert', null,
+ elementId.split(':')), 'GET', true);
+}
+
+/**
+ * .. function:: buildPendingDeleteHandle(elementId, eid)
+ *
+ * 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]');
+}
+
+/**
+ * .. function:: addPendingDelete(nodeId, eid)
+ *
+ * * `nodeId`, eid_from:r_type:eid_to
+ */
+function addPendingDelete(nodeId, eid) {
+ var d = loadRemote(AJAX_BASE_URL, ajaxFuncArgs('add_pending_delete', null, nodeId.split(':')));
+ d.addCallback(function() {
+ // and strike entity view
+ cw.jqNode('span' + nodeId).addClass('pendingDelete');
+ // replace handle text
+ cw.jqNode('handle' + nodeId).text('+');
+ });
+}
+
+/**
+ * .. function:: cancelPendingDelete(nodeId, eid)
+ *
+ * * `nodeId`, eid_from:r_type:eid_to
+ */
+function cancelPendingDelete(nodeId, eid) {
+ var d = loadRemote(AJAX_BASE_URL, ajaxFuncArgs('remove_pending_delete', null, nodeId.split(':')));
+ d.addCallback(function() {
+ // reset link's CSS class
+ cw.jqNode('span' + nodeId).removeClass('pendingDelete');
+ // replace handle text
+ cw.jqNode('handle' + nodeId).text('x');
+ });
+}
+
+/**
+ * .. function:: togglePendingDelete(nodeId, eid)
+ *
+ * * `nodeId`, eid_from:r_type:eid_to
+ */
+function togglePendingDelete(nodeId, eid) {
+ // node found means we should cancel deletion
+ if (jQuery(cw.getNode('span' + nodeId)).hasClass('pendingDelete')) {
+ cancelPendingDelete(nodeId, eid);
+ } else {
+ addPendingDelete(nodeId, eid);
+ }
+}
+
+function selectForAssociation(tripletIdsString, originalEid) {
+ var tripletlist = $.map(tripletIdsString.split('-'),
+ function(x) { return [x.split(':')] ;});
+ var d = loadRemote(AJAX_BASE_URL, ajaxFuncArgs('add_pending_inserts', null, tripletlist));
+ d.addCallback(function() {
+ var args = {
+ vid: 'edition',
+ __mode: 'normal',
+ rql: rql_for_eid(originalEid)
+ };
+ document.location = 'view?' + asURL(args);
+ });
+
+}
+
+function updateInlinedEntitiesCounters(rtype, role) {
+ jQuery('div.inline-' + rtype + '-' + role + '-slot span.icounter').each(function(i) {
+ this.innerHTML = i + 1;
+ });
+}
+
+/**
+ * .. function:: addInlineCreationForm(peid, petype, ttype, rtype, role, i18nctx, insertBefore)
+ *
+ * makes an AJAX request to get an inline-creation view's content
+ * * `peid`, the parent entity eid
+ *
+ * * `petype`, the parent entity type
+ *
+ * * `ttype`, the target (inlined) entity type
+ *
+ * * `rtype`, the relation type between both entities
+ */
+function addInlineCreationForm(peid, petype, ttype, rtype, role, i18nctx, insertBefore) {
+ insertBefore = insertBefore || cw.getNode('add' + rtype + ':' + peid + 'link').parentNode;
+ var args = ajaxFuncArgs('inline_creation_form', null, peid, petype, ttype, rtype, role, i18nctx);
+ var d = loadRemote(AJAX_BASE_URL, args);
+ d.addCallback(function(response) {
+ var dom = getDomFromResponse(response);
+ loadAjaxHtmlHead(dom);
+ var form = jQuery(dom);
+ form.css('display', 'none');
+ form.insertBefore(insertBefore).slideDown('fast');
+ updateInlinedEntitiesCounters(rtype, role);
+ reorderTabindex(null, $(insertBefore).closest('form')[0]);
+ jQuery(cw).trigger('inlinedform-added', form);
+ // if the inlined form contains a file input, we must force
+ // the form enctype to multipart/form-data
+ if (form.find('input:file').length) {
+ // NOTE: IE doesn't support dynamic enctype modification, we have
+ // to set encoding too.
+ form.closest('form').attr('enctype', 'multipart/form-data').attr('encoding', 'multipart/form-data');
+ }
+ _postAjaxLoad(dom);
+ });
+ d.addErrback(function(xxx) {
+ cw.log('xxx =', xxx);
+ });
+}
+
+/**
+ * .. function:: removeInlineForm(peid, rtype, role, eid, showaddnewlink)
+ *
+ * removes the part of the form used to edit an inlined entity
+ */
+function removeInlineForm(peid, rtype, role, eid, showaddnewlink) {
+ cw.jqNode(['div', peid, rtype, eid].join('-')).slideUp('fast', function() {
+ $(this).remove();
+ updateInlinedEntitiesCounters(rtype, role);
+ });
+ if (showaddnewlink) {
+ toggleVisibility(showaddnewlink);
+ }
+}
+
+/**
+ * .. function:: removeInlinedEntity(peid, rtype, eid)
+ *
+ * alternatively adds or removes the hidden input that make the
+ * edition of the relation `rtype` possible between `peid` and `eid`
+ * * `peid`, the parent entity eid
+ *
+ * * `rtype`, the relation type between both entities
+ *
+ * * `eid`, the inlined entity eid
+ */
+function removeInlinedEntity(peid, rtype, eid) {
+ // XXX work around the eid_param thing (eid + ':' + eid) for #471746
+ var nodeid = ['rel', peid, rtype, eid + ':' + eid].join('-');
+ var node = cw.jqNode(nodeid);
+ if (!node.attr('cubicweb:type')) {
+ node.attr('cubicweb:type', node.val());
+ node.val('');
+ var divid = ['div', peid, rtype, eid].join('-');
+ cw.jqNode(divid).fadeTo('fast', 0.5);
+ var noticeid = ['notice', peid, rtype, eid].join('-');
+ cw.jqNode(noticeid).fadeIn('fast');
+ }
+}
+
+function restoreInlinedEntity(peid, rtype, eid) {
+ // XXX work around the eid_param thing (eid + ':' + eid) for #471746
+ var nodeid = ['rel', peid, rtype, eid + ':' + eid].join('-');
+ var node = cw.jqNode(nodeid);
+ if (node.attr('cubicweb:type')) {
+ node.val(node.attr('cubicweb:type'));
+ node.attr('cubicweb:type', '');
+ cw.jqNode(['fs', peid, rtype, eid].join('-')).append(node);
+ var divid = ['div', peid, rtype, eid].join('-');
+ cw.jqNode(divid).fadeTo('fast', 1);
+ var noticeid = ['notice', peid, rtype, eid].join('-');
+ cw.jqNode(noticeid).hide();
+ }
+}
+
+function _clearPreviousErrors(formid) {
+ // on some case (eg max request size exceeded, we don't know the formid
+ if (formid) {
+ jQuery('#' + formid + 'ErrorMessage').remove();
+ jQuery('#' + formid + ' span.errorMsg').remove();
+ jQuery('#' + formid + ' .error').removeClass('error');
+ } else {
+ jQuery('span.errorMsg').remove();
+ jQuery('.error').removeClass('error');
+ }
+}
+
+function _displayValidationerrors(formid, eid, errors) {
+ var globalerrors = [];
+ var firsterrfield = null;
+ for (fieldname in errors) {
+ var errmsg = errors[fieldname];
+ if (!fieldname) {
+ globalerrors.push(errmsg);
+ } else {
+ var fieldid = fieldname + ':' + eid;
+ var suffixes = ['', '-subject', '-object'];
+ var found = false;
+ // XXX remove suffixes at some point
+ for (var i = 0, length = suffixes.length; i < length; i++) {
+ var field = cw.jqNode(fieldname + suffixes[i] + ':' + eid);
+ if (field && jQuery(field).attr('type') != 'hidden') {
+ if (!firsterrfield) {
+ firsterrfield = 'err-' + fieldid;
+ }
+ jQuery(field).addClass('error');
+ var span = SPAN({
+ 'id': 'err-' + fieldid,
+ 'class': "errorMsg"
+ },
+ errmsg);
+ field.before(span);
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ firsterrfield = formid;
+ globalerrors.push(_(fieldname) + ' : ' + errmsg);
+ }
+ }
+ }
+ if (globalerrors.length) {
+ if (globalerrors.length == 1) {
+ var innernode = SPAN(null, globalerrors[0]);
+ } else {
+ var linodes =[];
+ for(var i=0; i<globalerrors.length; i++){
+ linodes.push(LI(null, globalerrors[i]));
+ }
+ var innernode = UL(null, linodes);
+ }
+ // insert DIV and innernode before the form
+ var div = DIV({
+ 'class': "errorMessage",
+ 'id': formid + 'ErrorMessage'
+ });
+ div.appendChild(innernode);
+ jQuery('#' + formid).before(div);
+ }
+ return firsterrfield || formid;
+}
+
+function handleFormValidationResponse(formid, onsuccess, onfailure, result, cbargs) {
+ // Success
+ if (result[0]) {
+ if (onsuccess) {
+ onsuccess(result, formid, cbargs);
+ } else {
+ document.location.href = result[1];
+ }
+ return true;
+ }
+ if (onfailure && ! onfailure(result, formid, cbargs)) {
+ return false;
+ }
+ unfreezeFormButtons(formid);
+ // Failures
+ _clearPreviousErrors(formid);
+ var descr = result[1];
+ var errmsg;
+ // Unknown structure
+ if ( !cw.utils.isArrayLike(descr) || descr.length != 2 ) {
+ errmsg = descr;
+ } else {
+ _displayValidationerrors(formid, descr[0], descr[1]);
+ errmsg = _("please correct errors below");
+ }
+ updateMessage(errmsg);
+ // ensure the browser does not scroll down
+ document.location.hash = '#header';
+ return false;
+}
+
+/**
+ * .. function:: unfreezeFormButtons(formid)
+ *
+ * unfreeze form buttons when the validation process is over
+ */
+function unfreezeFormButtons(formid) {
+ jQuery('#progress').hide();
+ // on some case (eg max request size exceeded, we don't know the formid
+ if (formid) {
+ jQuery('#' + formid + ' .validateButton').removeAttr('disabled');
+ } else {
+ jQuery('.validateButton').removeAttr('disabled');
+ }
+ return true;
+}
+
+/**
+ * .. function:: freezeFormButtons(formid)
+ *
+ * disable form buttons while the validation is being done
+ */
+function freezeFormButtons(formid) {
+ jQuery('#progress').show();
+ jQuery('#' + formid + ' .validateButton').attr('disabled', 'disabled');
+ return true;
+}
+
+/**
+ * .. function:: postForm(bname, bvalue, formid)
+ *
+ * used by additional submit buttons to remember which button was clicked
+ */
+function postForm(bname, bvalue, formid) {
+ var form = cw.getNode(formid);
+ if (bname) {
+ var child = form.appendChild(INPUT({
+ type: 'hidden',
+ name: bname,
+ value: bvalue
+ }));
+ }
+ var onsubmit = form.onsubmit;
+ if (!onsubmit || (onsubmit && onsubmit())) {
+ form.submit();
+ }
+ if (bname) {
+ jQuery(child).remove();
+ }
+}
+
+/**
+ * Cancel the operations done on the given form.
+ *
+ */
+$(function () {
+ $(document).on('click', '.cwjs-edition-cancel', function (evt) {
+ var $mynode = $(evt.currentTarget),
+ $form = $mynode.closest('form'),
+ $error = $form.find(':input[name="__errorurl"]'),
+ errorurl = $error.attr('value'),
+ args = ajaxFuncArgs('cancel_edition', null, errorurl);
+ loadRemote(AJAX_BASE_URL, args, 'POST', true);
+ history.back();
+ return false;
+ });
+});
+
+
+/**
+ * .. function:: validateForm(formid, action, onsuccess, onfailure)
+ *
+ * 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, onfailure) {
+ freezeFormButtons(formid);
+ try {
+ var zipped = cw.utils.formContents(formid);
+ var args = ajaxFuncArgs('validate_form', null, action, zipped[0], zipped[1]);
+ var d = loadRemote(AJAX_BASE_URL, args, 'POST');
+ } catch(ex) {
+ cw.log('got exception', ex);
+ return false;
+ }
+ d.addCallback(function(result, req) {
+ handleFormValidationResponse(formid, onsuccess, onfailure, result);
+ });
+ return false;
+}