/** * @author Rafael Ostertag <rafael.ostertag@math.uzh.ch> */ /* global $ */ /* @depend Utils.js */ var QfqNS = QfqNS || {}; (function (n) { 'use strict'; /** * @name addFields * @function * @global * @deprecated use appendTemplate(). * */ n.addFields = function (templateSelector, targetSelector, maxLines) { n.appendTemplate(templateSelector, targetSelector, maxLines); }; /** * Append a child created from a template to a target. All occurrences of `%d` in attribute values will be replaced * by the number of children of target. * * @name appendTemplate * @public * @global * @function * * @param {string} templateSelector jQuery selector uniquely identifying the template. * @param {string} targetSelector jQuery selector uniquely identifying the target container. * @param {number} maxChildren do not allow more than `maxChildren` of children in target. * */ n.appendTemplate = function (templateSelector, targetSelector, maxChildren) { var responsibleButton; var escapedTemplateSelector = n.escapeJqueryIdSelector(templateSelector); var escapedTargetSelector = n.escapeJqueryIdSelector(targetSelector); var $template = $(escapedTemplateSelector); var $target = $(escapedTargetSelector); var lines = n.countLines(escapedTargetSelector) + 1; if (lines >= maxChildren + 1) { return; } var deserializedTemplate = n.deserializeTemplateAndRetainPlaceholders($template.text()); n.expandRetainedPlaceholders(deserializedTemplate, lines); // Store the button object, so we can easily access it when this `line` is removed by the user responsibleButton = n.getResponsibleButtonFromTarget($target); if (responsibleButton) { deserializedTemplate.data('field.template.addButton', responsibleButton); } $target.append(deserializedTemplate); n.informFormOfChange($target); lines = n.countLines(escapedTargetSelector); if (lines >= maxChildren && responsibleButton) { n.disableButton(responsibleButton); } }; n.getResponsibleButtonFromTarget = function ($target) { var $button; var buttonSelector = $target.data('qfq-line-add-button'); if (!buttonSelector) { return null; } $button = $(buttonSelector); if ($button.length === 0) { return null; } return $button[0]; }; n.disableButton = function (button) { $(button).attr('disabled', 'disabled'); }; n.enableButton = function (button) { $(button).removeAttr('disabled'); }; n.informFormOfChange = function ($sourceOfChange) { var $enclosingForm = $sourceOfChange.closest("form"); $enclosingForm.change(); }; /** * @name initializeFields * @global * @function * @deprecated use initializeTemplateTarget() */ n.initializeFields = function (element, templateSelectorData) { n.initializeTemplateTarget(element, templateSelectorData); }; /** * When the template target contains no children, it initializes the target element by appending the first child * created from the template. * * @name initializeTemplateTarget * @global * @function * * @param {HTMLElement} element the target HTMLElement to be initialized * @param {string} [templateSelectorData=qfq-line-template] name of the `data-` attribute containing the jQuery * selector * selecting the template */ n.initializeTemplateTarget = function (element, templateSelectorData) { var responsibleButton; var $element = $(element); var templateSelector, escapedTemplateSelector, $template, deserializedTemplate; templateSelector = $element.data(templateSelectorData || 'qfq-line-template'); escapedTemplateSelector = n.escapeJqueryIdSelector(templateSelector); $template = $(escapedTemplateSelector); if ($element.children().length > 0) { n.setPlaceholderRetainers($template.text(), element); return; } deserializedTemplate = n.deserializeTemplateAndRetainPlaceholders($template.text()); n.expandRetainedPlaceholders(deserializedTemplate, 1); deserializedTemplate.find('.qfq-delete-button').remove(); $element.append(deserializedTemplate); }; /** * @name removeFields * @global * @function * @deprecated use removeThisChild() */ n.removeFields = function (target) { n.removeThisChild(target); }; /** * Remove the element having a class `qfq-line`. Uses `eventTarget` as start point for determining the closest * element. * * @name removeFields * @global * @function * @param {HTMLElement} eventTarget start point for determining the closest `.qfq-line`. */ n.removeThisChild = function (eventTarget) { var $line = $(eventTarget).closest('.qfq-line'); var $container = $line.parent(); var buttonToEnable = $line.data('field.template.addButton'); $line.remove(); n.reExpandLineByLine($container); n.informFormOfChange($container); if (buttonToEnable) { n.enableButton(buttonToEnable); } }; /** * Takes a template as string and deserializes it into DOM. Any attributes having a value containing `%d` will be * * @private * @param template * */ n.deserializeTemplateAndRetainPlaceholders = function (template) { var $deserializedTemplate = $(template); $deserializedTemplate.find("*").each(function () { var $element = $(this); $.each(this.attributes, function () { if (this.value.indexOf('%d') !== -1) { $element.data(this.name, this.value); } }); if (n.isTextRetainable($element)) { $element.data('element-text', $element.text()); } }); return $deserializedTemplate; }; /** * Set placeholder retainer on existing lines * @private * @param template * @param element container element */ n.setPlaceholderRetainers = function (template, element) { var responsibleButton; var $deserializedTemplate = $(template); var $flatTemplate = $deserializedTemplate.find('*'); var $element = $(element); var $childrenOfElement = $element.children(); responsibleButton = n.getResponsibleButtonFromTarget($element); $childrenOfElement.each(function () { var $this = $(this); // Add button reference $this.data('field.template.addButton', responsibleButton); // We use .find() to increase chances of $flatTemplate and $childrenOfChild having the same ordering. var $childrenOfChild = $this.find('*'); $childrenOfChild.each(function (childIndex) { var correspondingTemplateElement, $correspondingTemplateElement, $child; if (childIndex >= ($flatTemplate.length - 1)) { // the current child element has no corresponding element in the template, so no use of trying to // copy over retaining information. return; } $child = $(this); correspondingTemplateElement = $flatTemplate[childIndex]; $correspondingTemplateElement = $(correspondingTemplateElement); // Create the retainers on the child for each child $.each(correspondingTemplateElement.attributes, function () { if (this.value.indexOf('%d') !== -1) { $child.data(this.name, this.value); } }); if (n.isTextRetainable($correspondingTemplateElement)) { $child.data('element-text', $correspondingTemplateElement.text()); } }); }); }; /** * @private */ n.expandRetainedPlaceholders = function ($elements, value) { $elements.find('*').each(function () { var $element = $(this); $.each(this.attributes, function () { var retainedPlaceholder = $element.data(this.name); if (retainedPlaceholder) { this.value = n.replacePlaceholder(retainedPlaceholder, value); } }); if (n.hasRetainedText($element)) { $element.text(n.replacePlaceholder($element.data('element-text'), value)); } }); }; /** * @private * @param $element * @returns {*} */ n.isTextRetainable = function ($element) { return $element.is("label") && $element.text().indexOf('%d') !== -1 && $element.children().length === 0; }; /** * @private * @param $element * @returns {boolean} */ n.hasRetainedText = function ($element) { return $element.data('element-text') !== undefined; }; /** * @private * @param targetSelector * @returns {jQuery} */ n.countLines = function (targetSelector) { var escapedTargetSelector = n.escapeJqueryIdSelector(targetSelector); return $(targetSelector).children().length; }; /** * @private */ n.replacePlaceholder = function (haystack, by) { return haystack.replace(/%d/g, by); }; /** * @private * @param $container */ n.reExpandLineByLine = function ($container) { $container.children().each(function (index) { var $element = $(this); n.expandRetainedPlaceholders($element, index + 1); }); }; })(QfqNS);