diff --git a/libraries/joomla/form/fields/subform.php b/libraries/joomla/form/fields/subform.php
index 988fb6eb9dd8b..5204ed02de29a 100644
--- a/libraries/joomla/form/fields/subform.php
+++ b/libraries/joomla/form/fields/subform.php
@@ -232,7 +232,7 @@ protected function getInput()
try
{
// Prepare the form template
- $formname = 'subform' . ($this->group ? $this->group . '.' : '.') . $this->fieldname;
+ $formname = 'subform.' . str_replace(array('jform[', '[', ']'), array('', '.', ''), $control);
$tmplcontrol = !$this->multiple ? $control : $control . '[' . $this->fieldname . 'X]';
$tmpl = JForm::getInstance($formname, $this->formsource, array('control' => $tmplcontrol));
@@ -274,6 +274,14 @@ protected function getInput()
$data['fieldname'] = $this->fieldname;
$data['groupByFieldset'] = $this->groupByFieldset;
+ /**
+ * For each rendering process of a subform element, we want to have a
+ * separate unique subform id present to could distinguish the eventhandlers
+ * regarding adding/moving/removing rows from nested subforms from their parents.
+ */
+ static $unique_subform_id = 0;
+ $data['unique_subform_id'] = ('sr-' . ($unique_subform_id++));
+
// Prepare renderer
$renderer = $this->getRenderer($this->layout);
diff --git a/media/system/js/subform-repeatable-uncompressed.js b/media/system/js/subform-repeatable-uncompressed.js
index fb7d5dd0d8bfa..16f5f8070248f 100644
--- a/media/system/js/subform-repeatable-uncompressed.js
+++ b/media/system/js/subform-repeatable-uncompressed.js
@@ -29,7 +29,7 @@
this.$containerRows = this.options.rowsContainer ? this.$container.find(this.options.rowsContainer) : this.$container;
// last row number, help to avoid the name duplications
- this.lastRowNum = this.$containerRows.find(this.options.repeatableElement).length;
+ this.lastRowNum = this.$containerRows.find(this.options.repeatableElement).length;
// To avoid scope issues,
var self = this;
@@ -67,9 +67,9 @@
// prepare a template that we will use repeating
$.subformRepeatable.prototype.prepareTemplate = function(){
// create from template
- if(this.options.rowTemplateSelector){
- var tmplElement = this.$container.find(this.options.rowTemplateSelector)[0] || {};
- this.template = $.trim(tmplElement.text || tmplElement.textContent); //(text || textContent) is IE8 fix
+ if (this.options.rowTemplateSelector) {
+ // Find the template element and get its HTML content, this is our template.
+ this.template = $.trim(this.$container.find(this.options.rowTemplateSelector).last().html()) || '';
}
// create from existing rows
else {
@@ -142,13 +142,18 @@
$row.remove();
};
- // fix names ind id`s for field that in $row
- $.subformRepeatable.prototype.fixUniqueAttributes = function($row, count){
- var group = $row.attr('data-group'),// current group name
- basename = $row.attr('data-base-name'), // group base name, without count
- count = count || 0,
+ // fix names and id`s for fields in $row
+ $.subformRepeatable.prototype.fixUniqueAttributes = function(
+ $row, // the jQuery object to do fixes in
+ _count, // existing count of rows
+ _group, // current group name, e.g. 'optionsX'
+ _basename // group base name, without count, e.g. 'options'
+ ) {
+ var group = (typeof _group === 'undefined' ? $row.attr('data-group') : _group),
+ basename = (typeof _basename === 'undefined' ? $row.attr('data-base-name') : _basename),
+ count = (typeof _count === 'undefined' ? 0 : _count),
countnew = Math.max(this.lastRowNum, count),
- groupnew = basename + countnew; // new group name
+ groupnew = basename + countnew;
this.lastRowNum = countnew + 1;
$row.attr('data-group', groupnew);
@@ -203,6 +208,22 @@
// Guess there a label for this input
$row.find('label[for="' + forOldAttr + '"]').attr('for', idNew).attr('id', idNew + '-lbl');
}
+
+ /**
+ * Recursively replace our basename + old group with basename + new group
+ * inside of nested subform template elements. First we try to find such
+ * template elements, then we iterate through them and do the same replacements
+ * that we have made here inside of them.
+ */
+ var nestedTemplates = $row.find(this.options.rowTemplateSelector);
+ // If we found it, iterate over the found ones (might be more than one!)
+ for (var j = 0; j < nestedTemplates.length; j++) {
+ // Get the nested templates content (as DocumentFragment) and cast it
+ // to a jQuery object
+ var nestedTemplate = $($(nestedTemplates[j]).prop('content'));
+ // Fix the attributes for this nested template.
+ this.fixUniqueAttributes(nestedTemplate, count, group, basename);
+ }
};
// remove scripts attached to fields
@@ -247,18 +268,35 @@
if(window.SqueezeBox && window.SqueezeBox.assign){
SqueezeBox.assign($row.find('a.modal').get(), {parse: 'rel'});
}
+
+ // @TODO We need to do a lot more here. See e.g. administrator/templates/isis/js/template.js
+ // and all that it does with e.g. turning radios into btn groups with disabled/active/btn-danger classes.
+ // See also related issues #16695 and #16676, which could get fixed by this method being better.
+
+ // subforms in subforms
+ $row.find('div.subform-repeatable').subformRepeatable();
};
// defaults
$.subformRepeatable.defaults = {
- btAdd: ".group-add", // button selector for "add" action
- btRemove: ".group-remove",// button selector for "remove" action
- btMove: ".group-move",// button selector for "move" action
- minimum: 0, // minimum repeating
- maximum: 10, // maximum repeating
+ // button selector for "add" action, must be unique per nested subform!
+ btAdd: ".group-add",
+ // button selector for "remove" action, must be unique per nested subform!
+ btRemove: ".group-remove",
+ // button selector for "move" action, must be unique per nested subform!
+ btMove: ".group-move",
+ // minimum repeating
+ minimum: 0,
+ // maximum repeating
+ maximum: 10,
+ // selector for the repeatable element inside the main container,
+ // must be unique per nested subform!
repeatableElement: ".subform-repeatable-group",
- rowTemplateSelector: 'script.subform-repeatable-template-section', // selector for the row template
+