Jump to content

Repeater won't save repeater items added programmatically


efiguerola
 Share

Recommended Posts

Hello,

I am developing a module that lets a user create, modify and save a "TV Grid" (the schedule for a TV channel).

Here you can view some images that illustrate the concept: https://www.google.com/search?q=tv+grid&tbm=isch

Of course, those are just examples and do not represent my specific implementation, but with that I hope you get the idea.

The module installs a template for a "channel" (among other things). The "channel" has a "Repeater" field, where I store the complete grid for that channel. The repeater (grid) contains repeater items (grid items) that represent a scheduled show, e.g. ["Friends", Monday, 1st show of the day, lasts 60 minutes].

The repeater field is hidden from the user if he/she tries to edit the channel page via the admin's page edition interface. The intended way to edit this TV grid is by using the module's page that I've developed, which provides a custom interface for creating, manipulating and saving the grid. This of course means that I'm doing many things programmatically.

The problem is that the repeater items I'm creating via code using the user's input just WON'T save. I've made all the code changes I could think of, trying different stuff, without positive results.

Looking at the database, I notice the following when I run my code:

- The corresponding row for the repeater field in its table is updated/saved.

- However, no new rows are created for the repeater item fields (e.g. TV Show, duration, day, etc.).

Would you please help me out here? I'm becoming insane :frantics: ... I've included some code below.

/*
 * More code before this, ommited because it isn't relevant.
 */

// Get the channel object (a Page).
$channel = wire("pages")->get($channelId);
if(!$channel->id) {
	$this->error("Channel does not exist. Grid can't be processed.");
	return "";
}

// We need to clean this channel's grid before working with it.
// "channel" is the Page
// "tvChannelGrid" is the Repeater
$channel->tvChannelGrid->removeAll();
$channel->tvChannelGrid->save();
$channel->save(); // Save the page.*/

// Add the grid items received from the module's form to the channel's grid.
foreach($tvGridData as $i => $item) { // "tvGridData" is an associative array with data received from a form.
	
	$showId = intval($item["tv_show_id"]);
	$duration = intval($item["duration"]);
	$day = intval($item["col"]);
	$row = intval($item["row"]);
	
	$gridItem = $channel->tvChannelGrid->getNew(); // this must be done according to the documentation.
	$gridItem->save(); // Save the newly built item.
	// Assign values to repeater fields.
	$gridItem->tvChannelGridShow = wire("pages")->get($showId); // FieldtypePage
	$gridItem->tvChannelGridShowDuration = $duration; // FieldtypeInteger
	$gridItem->tvChannelGridShowDay = $day; // FieldtypeInteger
	$gridItem->tvChannelGridShowRow = $row; // FieldtypeInteger
	$gridItem->save(); // Save the newly built item.
	
	$channel->tvChannelGrid->add($gridItem); // Add the new item to the repeater.
}
$channel->tvChannelGrid->save(); // Save the repeater.
$channel->save(); // Save the page.
Link to comment
Share on other sites

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

[WARNING] This is a long post. Sorry about that.

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

I solved this, so might as well post the details here. There *is* some weird ProcessWire behavior going on, but first things first.

1. I didn't create the repeater field correctly (oops).

As a part of the installation process for my module, I had to programmatically create a template which had a repeater as one of its fields. Now, I had a hard time finding documentation on how to do this exactly, and eventually I found a forum post that kinda helped me (here), but it would seem like it was missing some key steps needed in order to properly create a repeater field programmatically (perhaps due to it being an old post). I learned about those extra steps in another forum post (specifically, this one).

2. The repeater field table was receiving duplicate values for the ID of the first repeater item (a bug, perhaps?)

So I fix the installation code, do a clean PW install, then install my module again. Everything works, beautiful. Except... I was checking out the table PW created for my repeater field, and I noticed something weird going on. Initially, I create a page using the template that has this repeater field. A new row is created on the repeater field table in the database. At this point there are no existing repeater items added to the repeater, so the row looks like this:

post-3681-0-52167100-1442073584_thumb.pn

The "data" column stores a comma-separated list of the IDs of every single repeater item owned by the repeater. The "count" column stores the amount of repeater items that the repeater owns. At this point, zero.

However, when I added some items to the repeater using the interface I built for my module (a form receives the user input and stores it using the API, as per the Repeaters documentation), the ID of the first repeater item (and only the first one) appeared twice on the "data" column of the repeater field table. This also meant the "count" column had a value of [real amount of items + 1]. So, for example:

post-3681-0-70208100-1442074157_thumb.pn

In order to solve this issue, I had to do something in my code that I think shouldn't be necessary, but I needed it to work well, so...

Regarding the code that saves the repeater/repeater items, I had to change it from this:

public function processForm() {
	
	// Get the data sent with the form and sanitize it before using it.
	/*-OMMITED FOR BREVITY-*/
	// Channel ID validations.
	/*-OMMITED FOR BREVITY-*/
	// Get the channel object (a page).
	/*-OMMITED FOR BREVITY-*/
	// TV Grid data validations.
	/*-OMMITED FOR BREVITY-*/
	
	// We need to clean this channel's grid before working with it.
	/*
		channel = Page
		tvChannelGrid = Repeater field
		tvChannelGridShow, tvChannelGridShowDay, tvChannelGridShowDuration, tvChannelGridShowRow = Repeater fields
	*/
	if(count($channel->tvChannelGrid) > 0) {
		$channel->tvChannelGrid->removeAll(); // Remove all repeater items.
		$channel->save("tvChannelGrid"); // Save the repeater.
		$channel->save(); // Save the page.
	}
	
	// Add the grid items received from the module's form to the channel's grid.
	foreach($tvGridData as $i => $item) {
		
		$showId = intval($item["tv_show_id"]);
		$duration = intval($item["duration"]);
		$day = intval($item["col"]);
		$row = intval($item["row"]);
		
		// Create a new item and add it to the repeater.
		$gridItem = $channel->tvChannelGrid->getNew(); // New repeater item
		// Assign values to repeater item fields.
		$gridItem->tvChannelGridShow = wire("pages")->get($showId);
		$gridItem->tvChannelGridShowDuration = $duration;
		$gridItem->tvChannelGridShowDay = $day;
		$gridItem->tvChannelGridShowRow = $row;
		$gridItem->save(); // Save the newly built repeater item.
		$gridItem->of(false); // Recommended in a forum post while trying to solve the issue.
		$channel->tvChannelGrid->add($gridItem); // Add repeater item to repeater.
	}
	
	if(count($tvGridData) > 0) { // We clearly received some new items, so let's save our work.
		$channel->save("tvChannelGrid"); // Save the repeater.
		$channel->save(); // Save the page.
	}
	
	return "<h2>TV Grid processed.</h2><p><a href='" . $this->page->url . "?channelId=$channelId'>Go back to the TV Grid Editor.</a></p>";
}

...to this:

public function processForm() {
	
	// Get the data sent with the form and sanitize it before using it.
	/*-OMMITED FOR BREVITY-*/
	// Channel ID validations.
	/*-OMMITED FOR BREVITY-*/
	// Get the channel object (a page).
	/*-OMMITED FOR BREVITY-*/
	// TV Grid data validations.
	/*-OMMITED FOR BREVITY-*/
	
	// We need to clean this channel's grid before working with it.
	/*
		channel = Page
		tvChannelGrid = Repeater field
		tvChannelGridShow, tvChannelGridShowDay, tvChannelGridShowDuration, tvChannelGridShowRow = Repeater fields
	*/
	if(count($channel->tvChannelGrid) > 0) {
		$channel->tvChannelGrid->removeAll(); // Remove all repeater items.
		$channel->save("tvChannelGrid"); // Save the repeater.
		$channel->save(); // Save the page.
	}
	
	// Add the grid items received from the module's form to the channel's grid.
	foreach($tvGridData as $i => $item) {
		
		$showId = intval($item["tv_show_id"]);
		$duration = intval($item["duration"]);
		$day = intval($item["col"]);
		$row = intval($item["row"]);
		
		// Create a new item and add it to the repeater.
		// FOR SOME REASON (), creating a variable here, storing the new grid item
		// inside said variable, and then passing the variable to the repeater's "add"
		// function as a parameter, causes the first repeater item's ID to be duplicated
		// in the repeater field's table (repeater field row, "data" column; also, wrong
		// repeater item count in "count" column).
		$channel->tvChannelGrid->add($this->createGridItem(array(
			"showId" => $showId,
			"duration" => $duration,
			"day" => $day,
			"row" => $row,
		), $channel->tvChannelGrid));
	}
	
	if(count($tvGridData) > 0) {
		$channel->save("tvChannelGrid"); // Save the repeater.
		$channel->save(); // Save the page.
	}
	
	return "<h2>TV Grid processed.</h2><p><a href='" . $this->page->url . "?channelId=$channelId'>Go back to the TV Grid Editor.</a></p>";
}

function createGridItem($data, $grid) {
	
	$gridItem = $grid->getNew();
	$gridItem->tvChannelGridShow = wire("pages")->get($data["showId"]);
	$gridItem->tvChannelGridShowDuration = $data["duration"];
	$gridItem->tvChannelGridShowDay = $data["day"];
	$gridItem->tvChannelGridShowRow = $data["row"];
	$gridItem->save(); // Save the newly built item.
	$gridItem->of(false);
	
	return $gridItem;
}

At first I thought: "Okay, the problem seems to be that I need to isolate the piece of code that creates the new repeater item inside a separate function", but then I realized that's not quite it. That is because, even if I isolated that piece of code inside a different function, the problem still occurred if I created a variable ($gridItem) inside the foreach loop that stored the output of the createGridItem function.

In the end, I had to pass the output of the createGridItem function directly to the repeater's add function, without storing it inside a variable first.

Hmm, so I guess there is a tl;dr:

  • (*) We need rich, readily available documentation for backend development / module development / doing things with code and not via GUI, in the same way we have awesome documentation for the frontend (pages, templates, API usage) aspect of PW. I think forum posts are great for solving specific issues, or maybe expanding on lesser known aspects of something, but they shouldn't be the primary source of information, there should be documentation pages for that.  :)
  • (**) Apparently, I cannot have a variable that stores a new repeater item inside a foreach loop, or PW does weird stuff.  :o

(*) Do note that I am not in any way demanding this happens, I'm merely making a suggestion.

(**) If you notice I did some dumb mistake, point it out... I don't think so, but just in case... hahahaha.

Link to comment
Share on other sites

  • 4 weeks later...

Hi, I'm trying to fill a repeater using a foreach and even using you'r Function approach

Did your code fill all the elements of the repeater and saved them?

I'm still getting this error :

Error: Uncaught exception 'WireException' with message 'Can't save page 0: /1444384917-1926-1/: It has no parent assigned' in C:\wamp2.5\www\eelst\wire\core\Pages.php:1045

My initial code is posted here

Link to comment
Share on other sites

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
 Share

×
×
  • Create New...