Jump to content

How to copy field values of a repeater from the parent page to a child page?


Recommended Posts

Posted

Hello @ all,

does anyone know how to copy all the field values of a repeater from a parent page to a child page via the API. I know how to do it with standard fields (fe textfields) but repeaters are more complicated.

Scenario:

I have a parent page (fe a page about an event) and via a hook I create several child pages (fe all the dates for this event). During the creation process I copy several field values from the parent page to the new created child pages.

For standardfields I copy the value like this:

$k = new Page();
$k->setOutputFormatting(false);
$k->participantmaxnumber = $page->participantmaxnumber; //copy the field participantmaxnumber from the parent to the child page
$k->save()

But how can I achive the same by copying a repeater with several fields in it. This doesnt seem to work:

$k = new Page();
$k->setOutputFormatting(false);
  foreach($page->eventpricerepeater as $repeateritem) {
     $repeateritem->earlybookingdeadline = ...;
  }
$k->save()

I have also tried

$k->eventpricerepeater = $page->eventpricerepeater;

but without luck.

Has anyone done this in the past and knows how to copy repeaters and its items?

Best regards

Posted

If earlybookingdeadline is not an array itself, then this should work

$k->earlybookingdeadline = $repeateritem->earlybookingdeadline;// earlybookingdeadline NOT an array; it just returns a value

 

  • Like 1
Posted

Thanks @kongondo, but the repeateritem of the childpage contains no values with your code after creating the child page(s). It seems that the code doesnt add the value to a field INSIDE the repater (maybe if the field is outside). I got no errors, so the syntax is ok. :undecided:

Posted

Are you adding to a repeater field or a normal field? My code was for a normal field. If copying from REPEATER to REPEATER...then check out this thread

 

  • Like 2
Posted

I want to add fields (values) from a repeater to fields of another repeater: So the repaters are the same (one in the parent and one in the child page). I want to copy the repeater of the parent page with all its values to the childpage.

Posted (edited)

Edit @see post below...Use import instead! (duh!, early morning!)

-------------------------------------------

The process then would be:

  1. Create the child page
  2. The child page's template should have the repeater field ready to receive items
  3. Add a repeater item(s) to the child page. See this (using API to add repeater items)....getting values from the parent page

Maybe there's a different way of doing this....not sure...yes...import!

Edited by kongondo
better answer
Posted

This works for me

$c = $page->child;// page here is the parent page
//$repeaterItems = $page->repeater_test;// repeater with 6 fields (email, file, integer, text, single image, multi-images) x 2 repeater items
//$c->repeater_test->import($repeaterItems);// alternative import
$c->repeater_test->import($page->repeater_test);// import (into an existing repeater field in the child page)
$c->of(false);
$c->save('repeater_test');

 

  • Like 1
Posted

Hello @kongondo

your code works. I only get a notice from Tracy:

Screenshot_3.jpg

but thats not a big problem. I am running Tracy in strict mode.

A new problem occurs: Everytime I start to create the child pages with the API the "old" child pages including the repeater must be deleted. The child pages will be deleted but not the pages created by the repeater. These are the pieces of code that I have tried but without success.

foreach ($page->children("include=all") as $child) {
    $child->delete(); //delete all children first
}

OR

foreach ($page->children("include=all") as $child) {
   foreach($child->eventpricerepeater as $r) {
     $r->delete();
   }
   $child->delete(); //delete all children first
}

The undeleted repeater pages lead to  the SQL-error for duplicate entry.

Screenshot_4.jpg

Posted (edited)

Repeaters pages need to be deleted a bit differently

// delete repeater page: admin/repeaters/for-field-xxx
$repeaterID = $fields->get('name_of_repeater_field')->id;
// make sure you get the right repeater page
$repeaterPage = $pages->get("parent.name=repeaters, name=for-field-$repeaterID");
if($repeaterPage->id) $pages->delete($repeaterPage, true);

Regarding the error, not sure why you are getting it, but you can check like this:

if($parent && $parent->id) {
}

 

Edited by kongondo
add more code
  • Like 1
Posted

Ok, my child pages need to be called in a foreach, cause there are more than one:

                foreach ($page->children("include=all") as $child) {
                    $repeaterID = $child->get('eventpricerepeater')->id;
                    $repeaterPage = wire('pages')->get("parent.name=repeaters, name=for-field-$repeaterID");
                    if($repeaterPage->id) $child->delete($repeaterPage, true);
                    $child->delete(); 
                }

I run the code from a module, therefore I use "wire('pages')" instead of $pages. Unfortunately the code doesnt delete the repeater pages.

Posted

I have re-read your post and I see where we went wrong. Each field has only one repeater page. Its child pages are the parents of the repeater items.  Using true in the delete deletes the repeater parent page and its children. That's what my code would do. However, you want to delete only the children. So...

Repeaters Hierarchy

admin
	Repeaters
		repeater_field_name
			name_of_page_with_repeater
				1234567890// repeater item #1 on "name_of_page_with_repeater"
				1346798941// repeater item #2

   You want to delete 'name_of_page_with_repeater' AND NOT the page 'repeater_field_name'

The below (untested) should work (@see amended code in post below...$child has to be deleted first)

$page = $this->wire('pages')->get(1234);
$pages = $this->wire('pages');
$repeaterID = $this->wire('fields')->get('eventpricerepeater')->id;
// this is the 'repeater_field_name' page
$repeaterPage = $pages->get("parent.name=repeaters, name=for-field-$repeaterID");

foreach ($page->children("include=all") as $child) {
	// this is the 'name_of_page_with_repeater'	
	$childRepeaterPage = $repeaterPage->child("name=for-page-{$child->id}");
	// will also delete the repeate item pages, e.g. the '1234567890'	
	if($childRepeaterPage->id) $pages->delete($childRepeaterPage, true);
	$child->delete();
}

 

  • Like 1
Posted

You are right with your logic, but the code doesnt work :undecided:. Deleting repeaters is more than tricky via the API :huh:. Maybe I should find another solution.

Posted

My bad...The child page has to be deleted first:

$page = $this->wire('pages')->get(1234);
$pages = $this->wire('pages');
$repeaterID = $this->wire('fields')->get('eventpricerepeater')->id;
// this is the 'repeater_field_name' page
$repeaterPage = $pages->get("parent.name=repeaters, name=for-field-$repeaterID");

foreach ($page->children("include=all") as $child) {
	$id = $child->id;
	// first delete the child page	
	$child->delete();
	// this is the 'name_of_page_with_repeater'	
	$childRepeaterPage = $repeaterPage->child("name=for-page-$id");
	// will also delete the repeate item pages, e.g. the '1234567890'	
	if($childRepeaterPage->id) $pages->delete($childRepeaterPage, true);

}

 

  • Like 1
Posted (edited)

Something funky going on there. I have tested the code and it definitely works. Any errors? Does a visual check reveal anything? e.g. is that child page there? Is the repeater page there? Does it work outside a module context? Do you have Tracy/Debug on?

Edit

Also...did you take note of my code changes?

Edited by kongondo
Stuff...
Posted

This sounds like a handy AdminAction ;) 

Not specifically from the parent to child page, but a general one for copying/moving from one page to another. Not sure if it would be best as a new action, or incorporated into the logic of the "Copy Field Content To Other Page" action which currently only handles basic text based fields. I would like to extend this to support all field types. 

  • Like 2
Posted
1 minute ago, kongondo said:

@adrian....was just thinking about that :-)

If you're keen, I'd love a PR!

I think that adding it to that existing action would be the best approach, so I think I need to revisit the "Author" column so that everyone gets credit if there are multiple authors for an action.

  • Like 1
Posted

Getting a little OT here, but @Juergen - if this is incorporated into Admin Actions, you could call it via the API like this:

$options = array(
   'field' => 99, 
   'sourcePage' => 1131, 
   'destinationPage' => 1132
);
$modules->get("ProcessAdminActions")->CopyFieldContentToOtherPage($options);

Yes, that action should be improved to also handle field names (not just IDs), but you get the idea.

  • Like 2
Posted

Away from the deletion of the children repeater pages - it seems that there is still a problem by copying the repeater to the children pages

$k->eventpricerepeater->import($page->eventpricerepeater);

It seems that the repeater page will not be copied (created) to the children pages, because the page name is the same as in the parent (Duplicate-Entry).

repeaterpage.jpg

There exists only one page from the parent page (1482132297-8942-1) and it seems that the same page name will be copied to the children pages and therefore I always get the duplicate entry error.

As a result the repater pages will not be created - so the deletion process will not take place.

Screenshot_4.jpg

Here is the beginning of the code which creates the children pages

foreach ($periods as $item) {
                    // create new page
                    $k           = new Page();
                    $k->template = 'single-event'; // set template
                    $k->parent   = wire('pages')->get($page->id); // set the parent       
                    $k->setOutputFormatting(false);
                    // Copy page fields from parent page to newly created child pages
                    $k->date_start = $item;
                    $k->eventpricerepeater->import($page->eventpricerepeater);
                    $k->eventstatus          = $page->eventstatus;
                    ..............
                    ..............

This piece of code works because all fields except the repeater fields will be copied to the children pages.

Posted (edited)

Quick BTWs...

  1. In a module context, output formatting is always off (i.e. false), so no need to set it yourself.
  2. No need to 'get' the same parent over and over in the loop. Do it outside the loop :)

 

  • What is $page in your case?
  • What PW version are you working on?

 

The following code works for me as advertised, in ProcessWire 2.7 and 3.0.42. All parent repeater fields contents are copied over to the children repeater fields

$children = array('What We Do', 'Our Mission', 'Our History', 'Our Staff');
$parent = $pages->get('/about-us/');
foreach ($children as $title) {
	$k = new Page();
	$k->template = 'basic-page';
	$k->parent = $parent;
	$k->title = $title;
	$k->repeater_test->import($parent->repeater_test);
	$k->save();
}

The code above results in this tree under Home

about-us-tree.png

 

And this tree under repeaters. The crossed out repeater items are the unpublished 'ready to edit repeater items'

repeaters-tree.png

As you can see, names only have to be unique under the same parent (not just for repeaters but throughout ProcessWire)

 

Edited by kongondo
Posted

Mmmhh! It works in your case.

I run the latest dev version of PW (3.0.45)

$page is the parent page. I run a hook to create the child pages.

This is how $page is defined inside the hook function:

$page = $event->arguments[0];

So $page is always the page that you are working on (the parent page).

Only for better explanation:

It is a complex children creating function that is running with the hook. It creates children with different start dates depending on the settings. So the start date array is called $periods in my case. It is responsible for creating the children. If fe 5 dates should be created with an interval of 1 week than the period array consists of the five calculated start dates.

Here is an example:

$periods = array('21.12.2016', '28.12.2016', '04.01.2017', '11.01.2016');

This is why my foreach loop doesnt look like

foreach ($children as $title) {

Instead of this it looks like

foreach ($periods as $item) {

Thats the only difference.

This is the complete foreach loop which is responsible for creating the child pages and filling their fields with values from the parent page.

                foreach ($periods as $item) {
                    // create new page
                    $k           = new Page();
                    $k->template = 'single-event'; // set template
                    $k->parent   = wire('pages')->get($page->id); // set the parent       
                    // Copy page fields from parent page to newly created child pages
                    $k->date_start = $item;
                    $k->eventpricerepeater->import($page->eventpricerepeater);
                    $k->publish_until        = $item + $diff;                    
                    $k->eventstatus          = $page->eventstatus;
                    $k->participantmaxnumber = $page->participantmaxnumber;
                    $k->eventmaxstatus       = $page->eventmaxstatus;
                    $k->eventmaxstatus       = "1";
                    //get start date and create path name
                    $eventstartmultiple      = date("Y-m-d", $item); //get date for URL
                    $k->title                = $page->title;
                    //check for other languages
                    foreach ($this->languages as $lang) {
                        $lname   = $lang->isDefault() ? '' : $lang->id;
                        $default = $this->languages->get("default");
                        if ($page->title->getLanguageValue($lang)) {
                            $k->set("name$lname", $page->title->getLanguageValue($lang) . '-' . $eventstartmultiple);
                        } else {
                            $k->set("name$lname", $page->title->getLanguageValue($default) . '-' . $eventstartmultiple);
                        }
                        $k->set("status$lang", 1);
                        //$k->of(false);
                        $k->save();
                    }
                }

I will try to adapt it to your code and post the result here.

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...