Suggested changes to CF8 Repeater Area with initial clones

CraigH 21 Oct, 2025
1 Likes

Firstly, many thanks for all the hard work on Chronforms which continues to be a great tool.

I have migrated most of my forms from CF7 to CF8 but there is one issue that is stopping me from migrating the last two forms which I describe below. The issue is on the Repeater loop, and in particular the creation of initial instances of a loop.

I have created a simple form to describe the issue and the workaround I have found.

In this simple form, I have set

  • The repeater name in Repeater Area's wizard setting to "craft_loop". 
  • The key name to "nnnn" with a start count of 0 and max of 10.
  • I have a single field inside the repeater with field name "craft"

I insert a PHP script that sets up two initial repeater instances (these would normally be read from a database):

$this->data['craft_loop'][1]="1";
$this->data['craft'][1] = "Laser";
$this->data['craft_loop'][2]="1";
$this->data['craft'][2] = "Phantom";

On inspection of the html created when the form is loaded, I see the form has two loop instances with the field with names:

<input type="text" name="craft[1]" id="craft_nnnn" placeholder="" value="" fdprocessedid="glouoa">
<input type="text" name="craft[2]" id="craft_nnnn" placeholder="" value="" fdprocessedid="jqznwx">

If I then add a third instance manually within the form using the an "add clone" button  it gets named

<input type="text" id="craft_nnnn-3" placeholder="" value="" oname="craft[nnnn]" name="craft[3]" fdprocessedid="d6ubm">

Note the structure of the ID field is different,  where the manually created id has "-3" appended for the 3rd instance.

In addition there is a hidden field that, for the initial instances, has the structure

<input type="hidden" name="craft_loop[nnnn]" value="1">
<input type="hidden" name="craft_loop[nnnn]" value="1">

and for instances created within the form looks like this:

<input type="hidden" value="1" oname="craft_loop[nnnn]" name="craft_loop[1]">

This difference in names is causing problems for listeners and other javascript I have. To make them consistent I located file

~/administrator/components/com_chronoforms8/pages/chronoforms/views/area_repeater/output.php 

 and made a couple of changes:

line 29 from

	<input type="hidden" name="<?php echo CF8::getname($element); ?>[<?php echo $key; ?>]" value="1">

to

	<input type="hidden" name="<?php echo CF8::getname($element); ?>[<?php echo $k; ?>]" value="1">

line 24 from

	echo str_replace("[".$key."]", "[".$k."]", $content);

to

	if($key!=$k)
	{
		//replace all occurances of loop [loopkey]  with [loopindex]
		$content = str_replace("[".$key."]", "[".$k."]", $content);
		// then replace all occurances of _loopkey with _loopkey-loopindex - (this probably needs more rigor)
		$content = str_replace("_".$key, "_".$key."-".$k, $content);
		//dd($content);
	}
	echo  $content;

This brings about more consistency in the html generated, and my listeners and javascript work on both initial and manually added repeats.

Can these changes or more thorough changes be included in a future release of CF8?

There were some other issues that I encountered but found acceptable work arounds:

  1. I found that setting values for fields in the initial repeats in the php action do not get copied into the fields.
    1. I have got round this by writing some javascript to copy the values across to the fields from the data structure.
  2. For the pre-created repeats, none of the listeners set up in the wizard designer worked.  
    1. I got round this by adding javascript code to add the listeners for the repeats.

Environment is

  • CF8 8.0.49
  • Joomla: 8.4.0
  • PHP 8.3.26
Max_admin 05 Nov, 2025
Answer
1 Likes

Hi Craig

Thanks for sharing the fixes.

Fix 1 has been applied and fix 2 was replaced by a different code in order to support settings values of initial repeats.

Max, ChronoForms developer
ChronoMyAdmin: Database administration within Joomla, no phpMyAdmin needed.
ChronoMails simplifies Joomla email: newsletters, logging, and custom templates.
CraigH 25d ago

Many thanks for the fixes in 8.0.50 that I have been testing. Initial values for predefined repeater clones is working well.

I did spot there is still a difference in the naming convention of the ID attribute for initial clones verses those created from the clone button.As of 8.0.50 the initial clones have the structure:

<input type="text" name="craft[1]" id="craft_1" placeholder="" value="Laser" fdprocessedid="2r8itg">
<input type="text" name="craft[2]" id="craft_2" placeholder="" value="Phantom" fdprocessedid="es562a">

compared with one added later (where nnnn is the repeater key)

<input type="text" name="craft[3]" id="craft_nnnn-3" placeholder="" value="" oname="craft[nnnn]"  fdprocessedid="v7idjk">

However, when I migrate my CF7 forms I can change the javascript I use to work with the input element "name" attribute rather than the "id" attribute to avoid this.

Some other observations:

  1. The initial repeats do not have any of the event listeners in place that might be attached to fields within the repeater.
    1. I can work around this with some onLoad javascript to add them.
  2. There doesnt seem to be a way of naming the repeater and hence javascript has to work with the auto generated names, ie "repeater-1" for the first repeater.*
  3. In CF7 there was a handy attribute "data-lastindex" in the main repeater div that held the highest index of clones. This was useful when you need to loop round clones that exist.   I have got round this by assuming it will be the key of the last clone in a nodeList returned in a querySelectorAll():
myClones = document.querySelectorAll('.repeater-1');
lastIndex = myClones[myClones.length- 1].getAttribute('data-key');

I think i can now start on my remaining CF7 form migrations.

Max_admin 20d ago

Hi Craig

I have a repeater here and Start Count setting is set to 1, the created field id is set to "select_year_n-1", when I add a new one the id is set to "select_year_n-2", so I think it works fine ?

Regarding your other observations:

1- Listeners do not work at the moment

2- You can change the name using the Wizard settings behavior, this changes the name in the Data variable.

3- For now each repeater instance has a "data-key" attribute, you can get the max one.

Max, ChronoForms developer
ChronoMyAdmin: Database administration within Joomla, no phpMyAdmin needed.
ChronoMails simplifies Joomla email: newsletters, logging, and custom templates.
CraigH 20d ago

Oh this is weird. I had the repeater Start Count = 0, but I had some PHP that created and populated two clones.

$this->data['craft_loop'][1]="1";
$this->data['craft'][1] = "Laser";
$this->data['craft_loop'][2]="1";
$this->data['craft'][2] = "Phantom";

resulting in these fields, as expected:

<input type="text" name="craft[1]" id="craft_1" placeholder="" value="Laser" fdprocessedid="2r8itg">
<input type="text" name="craft[2]" id="craft_2" placeholder="" value="Phantom" fdprocessedid="es562a">

I just tried setting "Start Count" = 1, and I get 5 clones 😳:

<input type="text" name="craft[1]" id="craft_1" placeholder="" value="Laser" fdprocessedid="zxifh">
<input type="text" name="craft[2]" id="craft_2" placeholder="" value="Phantom" fdprocessedid="kgyu2h">
<input type="text" id="craft_nnnn-3" placeholder="" value="" oname="craft[nnnn]" name="craft[3]" fdprocessedid="yvkfb5">
<input type="text" name="craft[4]" id="craft_1-4" placeholder="" value="Laser" fdprocessedid="c5wm5m">
<input type="text" name="craft[5]" id="craft_2-5" placeholder="" value="Phantom" fdprocessedid="9blhxjf">

So that is two populated clones, followed by an unpopulated one with the ID attribute like you see, followed by a repeat of the first two clones.

With debug on, I see this in the data:

    [craft_loop] => Array
        (
            [1] => 1
            [2] => 1
        )
    [craft] => Array
        (
            [1] => Laser
            [2] => Phantom
        )

CraigH 20d ago

Hi Max, many thanks for the responses. Just to clarify on your response to my second bullet

      2- You can change the name using the Wizard settings behavior, this changes the name in the Data variable.

I do have the wizzard setting that works in the $data variable in my php, but I was referring to the clone elements in the generated HTML, like:

<div class="nui form clonable repeater-1" data-selector=".clonable.repeater-1" data-cloner=".repeater-1-cloner" data-key="4" data-rkey="nnnn" data-startcount="1" data-maxcount="10">

Some of my javascript is locating such elements using this repeater name, but I am fine with this I can use the auto generated name.

Max_admin 20d ago
1 Likes

Hi Craig

I have updated this for the next update, you can update it for yourself too, line 12 has been changed:

$id = CF8::getname($element);//"repeater-".$element["id"];
Max, ChronoForms developer
ChronoMyAdmin: Database administration within Joomla, no phpMyAdmin needed.
ChronoMails simplifies Joomla email: newsletters, logging, and custom templates.
CraigH 19d ago

I menitioned some odd behaviour I was seeing on this topic on update #p407512 when having both initial clone values declared in $data and a non-zero "start count" in the repeater designer setup. I have tracked this down to interference between the creation of clones from $data in php file ~/administrator/components/com_chronoforms8/pages/chronoforms/views/area_repeater/output.php  and subsequent execution of javascript contained in file ~/libraries/chrono_lib/assets/nui.min.js

In the javascript code it is grabbing the data-startcount attribute and creating that number of clones for both the hidden clone and any already existing clones (ie clone of a clone).So if you start with two clones created with initial data and a start count of 1, you will get 5 clones in total as I discovered.

I have made a suggested change in the un-minified nui JS code that:

  1. Checks the number of clones that already exist and only creates more if that is less than the start count.
  2. Does not clone a clone

The original code fragment is this:

	this._element.hasAttribute("data-startcount"))
    ) {
	let startcount = parseInt(this._element.getAttribute("data-startcount"));
	for (let i = 1; i <= startcount; i++)
	    this.Clone(
		this._element,
		document.querySelectorAll(this.selector)[document.querySelectorAll(this.selector).length - 1]
	    );
    }
}

is changed to this:

	this._element.hasAttribute("data-startcount"))
    ) {
	let startcount = parseInt(this._element.getAttribute("data-startcount"));
			let clonerName =  this._element.getAttribute("data-cloner");
			let numClones = document.querySelectorAll('[data-cloner="' + clonerName + '"]').length - 1;
			let datarkey = this._element.getAttribute("data-rkey");
			let datakey = this._element.getAttribute("data-key");
			if(numClones<startcount && datarkey == datakey)  // dont clone a clone
			{
				for (let i = numClones ; i < startcount; i++)
				{
					this.Clone(
						this._element,
						document.querySelectorAll(this.selector)[document.querySelectorAll(this.selector).length - 1]
					);
				}
			}
    }
}

This works better in my test cases but still leaves the difference in naming convention of the id attributes for initial clones created in php verses those created in javascript.

Max_admin 14d ago
1 Likes

Hi Craig

Thank you for posting the fix, I have changed it a bit, here is the full "init" function code now:

init() {
		this.selector = this._element.getAttribute("data-selector")
		let key = this._element.hasAttribute("data-rkey") ? this._element.getAttribute("data-rkey") : "n";

		if (this._element.matches("[data-key='"+key+"']")) {
			this._element.classList.add("hidden")
			this._element.querySelectorAll("*[name]").forEach(input => {
				input.setAttribute("name2", input.getAttribute("name"))
				input.setAttribute("oname", input.getAttribute("name"))
				input.removeAttribute("name")
			})
		}else{
			return
		}

		if (this._element.hasAttribute("data-cloner") && document.querySelector(this.selector) === this._element) {
			document.querySelector(this._element.getAttribute("data-cloner")).addEventListener("click", e => {
				this.Clone(this._element, document.querySelectorAll(this.selector)[document.querySelectorAll(this.selector).length - 1])
			})
		}

		if (this._element.querySelector(".add-clone") != null) {
			this._element.querySelector(".add-clone").addEventListener("click", e => {
				this.Clone(this._element)
			})
		}

		if (this._element.querySelector(".remove-clone") != null) {
			this._element.querySelector(".remove-clone").addEventListener("click", e => {
				this._element.dispatchEvent(new CustomEvent('cloneRemoved', { detail: this._element, bubbles:true }))
				this._element.remove()
			})
		}

		if(this._element.hasAttribute("data-startcount")){
			let startcount = parseInt(this._element.getAttribute("data-startcount"))
			for(let i = 1; i <= startcount; i++){
				if(document.querySelectorAll(this.selector+"[data-key='"+i+"']").length == 0){
					this.Clone(this._element, document.querySelectorAll(this.selector)[document.querySelectorAll(this.selector).length - 1])
				}
			}
		}
	}

Please test it and let me know if it works correctly in your scenario ?

Max, ChronoForms developer
ChronoMyAdmin: Database administration within Joomla, no phpMyAdmin needed.
ChronoMails simplifies Joomla email: newsletters, logging, and custom templates.
CraigH 13d ago
1 Likes

Hi Max, yes your fix works well in my test cases.

Max_admin 13d ago

Great, thank you for the confirmation.

Max, ChronoForms developer
ChronoMyAdmin: Database administration within Joomla, no phpMyAdmin needed.
ChronoMails simplifies Joomla email: newsletters, logging, and custom templates.
CraigH 13d ago

Sorry Max, after some more testing with your fix I have found that it stops the "remove clone" button from working for the clones that are setup with initial values.

CraigH 12d ago
1 Likes

Hi Max,  I made a small edit to your Clone init() function that you provided in this topic. It adds the remove-clone listener before the return call in the else block, copied from lower down. It seems to fix the remove clone button not working for clones created with initial values in the php action that was introduced with your fix.

		init() {
			this.selector = this._element.getAttribute("data-selector")
			let key = this._element.hasAttribute("data-rkey") ? this._element.getAttribute("data-rkey") : "n";
			if (this._element.matches("[data-key='"+key+"']")) {
				this._element.classList.add("hidden")
				this._element.querySelectorAll("*[name]").forEach(input => {
					input.setAttribute("name2", input.getAttribute("name"))
					input.setAttribute("oname", input.getAttribute("name"))
					input.removeAttribute("name")
				})
			}else{
				if (this._element.querySelector(".remove-clone") != null) {
					this._element.querySelector(".remove-clone").addEventListener("click", e => {
						this._element.dispatchEvent(new CustomEvent('cloneRemoved', { detail: this._element, bubbles:true }))
						this._element.remove()
					})
				}				
				return
			}
Max_admin 10d ago
1 Likes

Hi Craig

Thanks for the fix, this will be available in the next update.

Max, ChronoForms developer
ChronoMyAdmin: Database administration within Joomla, no phpMyAdmin needed.
ChronoMails simplifies Joomla email: newsletters, logging, and custom templates.
CraigH 10d ago

Great, many thanks

You need to login to be able to post a reply.