Jump to content
thetuningspoon

Assigning Fields to Repeaters with the API

Recommended Posts

Hi!

I have been struggling with this for the last few hours and could not find a solution for it anywhere on the forums. I want to create a repeater field using the API and then add fields to it programatically. I can create the field and assign it to a template alright, but I can't assign any fields to the repeater itself.

I've tried using $field->repeaterFields when creating the field but cannot figure out what it's supposed to accept. I tried passing it an array of IDs as well as a fieldgroup, but I couldn't get either to work.

Any ideas?

Share this post


Link to post
Share on other sites

Sorry, I don't have much time, and maybe someone else will provide a simpler answer, but I went through the process of figuring this out for a module. It was more complicated because I was creating the repeater fields from JSON, but if you search through this code:

https://github.com/adrianbj/ProcessPageTreeMigrator/blob/master/ProcessPageTreeMigrator.module for "repeater" you should find what you need in there.

Sorry I can't be of more help right now.

Share this post


Link to post
Share on other sites

Thanks adrian, but I'm having trouble following along with what's happening in your code.

I discovered that the system is automatically creating a system template/fieldgroup for the repeater with the repeater_xxx naming convention, but it's not created until after I've gone into the admin and gone in to edit the repeater field. So I tried grabbing the fieldgroup after creating the field in the code and adding to it right away, but it seems that the fieldgroup doesn't actually exist yet?

So yeah, I think I'm going to need a simpler answer :)

Share this post


Link to post
Share on other sites

Adrian, I just found your post here: http://processwire.com/talk/topic/4736-add-inputfieldrepeater-to-module-configuration-page/

I tried replicating your process for setting up the fieldgroup, field, and template, but PW keeps creating a filedgroup & template with a different name (adding a 1 to the end of it) instead of using the one I created.

EDIT:

Here is my code:

$repeaterName = "sc_promos";
$f = new Field();
			$f->type = $this->modules->get("FieldtypeRepeater");
			$f->name = $repeaterName;
		
			$repeaterFg = new Fieldgroup();
			$repeaterFg->name = "repeater_$repeaterName";
			
			//Add fields to fieldgroup - add others as necessary
			$repeaterFg->append($this->fields->get("sc_promo_active"));
			
			$repeaterFg->save();
			
			$repeaterT = new Template();
			$repeaterT->name = "repeater_$repeaterName";
			$repeaterT->flags = 8;
			$repeaterT->noChildren = 1;
			$repeaterT->noParents = 1;
			$repeaterT->noGlobal = 1;
			$repeaterT->slashUrls = 1;
			$repeaterT->fieldgroup = $repeaterFg;
			
			$repeaterT->save();
			
			//Now, add the fields directly to the repeater
			$f->repeaterFields = $this->fields->get("sc_promo_active");
			
			$f->save();
			
			//Add the sc_promos repeater field to the sc-promo-codes fieldgroup
			$fg->add($f);
			$fg->save();

Share this post


Link to post
Share on other sites

Good find - I had forgotten about that post :)

I just used the code from that other post of mine on a fresh PW install (latest dev, although I think it should work on 2.3 stable as well) and it works perfectly.

I know that when I was playing around with writing that code I often messed up the PW database to the point where I was getting errors and found the best way was to do a fresh install again.

BUT, I just noticed that you are missing these key lines in your version:

$repeater_page = "for-field-{$f->id}";
$f->parent_id = $this->pages->get("name=$repeater_page")->id;
$f->template_id = $repeater_template->id;
$f->repeaterReadyItems = 3;

This is critical to make things work.

Let me know how you go.

  • Like 2

Share this post


Link to post
Share on other sites

YESYESYESYESYES!!!!  That was it! I thought that that particular bit of code wasn't necessary because I only wanted to create the repeater and not any associated pages. I'm not sure why it works but it does :)

Yes, I've had to go in and manually hack the database due to errors along the way, but all is working well, so I don't think I've messed up anything too bad :)

I do have to say... this is way complicated for ProcessWire since most things in PW tend to be surprisingly easy. But I don't think Ryan foresaw repeaters being created this way. I'm sure this is something that could be simplified in the future.

  • Like 1

Share this post


Link to post
Share on other sites

I agree it does seem complicated, but so long as it works for you :)

On mobile at the moment, but I might try to clean up that codea little and comment it better so people know what's going on.

Glad it worked for you!

Share this post


Link to post
Share on other sites

Maybe we could put together a function that would take the repeater name and desired fields as inputs and do all the hard work of creating the fieldgroup, template, and page... If I get a few extra minutes I may give that a try.

Share this post


Link to post
Share on other sites

Exactly what I was thinking. If you don't get to it, I'll do it when I am back at my computer.

Share this post


Link to post
Share on other sites

Well, I'm no pro at this and you could probably improve it, but here's my attempt which serves my current needs pretty well:

	/**
	 * Creates a repeater field with associated fieldgroup, template, and page
	 *
	 * @param string $repeaterName The name of your repeater field
	 * @param string $repeaterFields List of field names to add to the repeater, separated by spaces
	 * @param string $repeaterLabel The label for your repeater
	 * @param string $repeaterTags Tags for the repeater field
	 * @return Returns the new Repeater field
	 *
	 */
	public function createRepeater($repeaterName,$repeaterFields,$repeaterLabel,$repeaterTags)
	{
		$fieldsArray = explode(' ',$repeaterFields);
		
		$f = new Field();
		$f->type = $this->modules->get("FieldtypeRepeater");
		$f->name = $repeaterName;
		$f->label = $repeaterLabel;
		$f->tags = $repeaterTags;
		$f->repeaterReadyItems = 3;
		
		//Create fieldgroup
		$repeaterFg = new Fieldgroup();
		$repeaterFg->name = "repeater_$repeaterName";
		
		//Add fields to fieldgroup
		foreach($fieldsArray as $field) {
			$repeaterFg->append($this->fields->get($field));
		}
		
		$repeaterFg->save();
		
		//Create template
		$repeaterT = new Template();
		$repeaterT->name = "repeater_$repeaterName";
		$repeaterT->flags = 8;
		$repeaterT->noChildren = 1;
		$repeaterT->noParents = 1;
		$repeaterT->noGlobal = 1;
		$repeaterT->slashUrls = 1;
		$repeaterT->fieldgroup = $repeaterFg;
		
		$repeaterT->save();
		
		//Setup page for the repeater - Very important
		$repeaterPage = "for-field-{$f->id}";
		$f->parent_id = $this->pages->get("name=$repeaterPage")->id;
		$f->template_id = $repeaterT->id;
		$f->repeaterReadyItems = 3;
		
		//Now, add the fields directly to the repeater field
		foreach($fieldsArray as $field) {
			$f->repeaterFields = $this->fields->get($field);
		}
		
		$f->save();
		
		return $f;
	}

And here's an example of calling it:

$f = $this->createRepeater("sc_promos","sc_promo_active sc_promo_code sc_promo_discount","Promotional Offer","shoppingCart");

You can then use $f to add your new repeater field to a fieldgroup/template.

  • Like 11

Share this post


Link to post
Share on other sites

Nice work - that's pretty much exactly what I was thinking.

I am not sure about setting the flags to system and permanent though as the defaults for a function. I think there should at least be a comment on that line that users should adjust to their needs as I don't think this is a typical use case.

Glad to see that code of mine is getting some use - I have to say it was quite an effort to figure out and as you said, very un-PW in its complexity. I would love to hear from Ryan as to whether there is an easier way to achieve this.

Also, for the sake of putting all the information about creating and working with repeaters from the API in one thread, I think it is worth mentioning the special getNew() method for creating repeater items. It is used in the Page Tree Migrator module and described in detail on this page: http://processwire.com/api/fieldtypes/repeaters/

  • Like 3

Share this post


Link to post
Share on other sites

Nice work - that's pretty much exactly what I was thinking.

Thanks!

I am not sure about setting the flags to system and permanent though as the defaults for a function. I think there should at least be a comment on that line that users should adjust to their needs as I don't think this is a typical use case.

Good point. I removed that line from the code altogether.

Share this post


Link to post
Share on other sites

Thanks everfreecreative for that convenient function, I was fiddling around with that myself atm.

Is is save to delete the template, fieldgroup and field to delete the whole thing or will there be any unused relational pairings left in the database?

Share this post


Link to post
Share on other sites

Is is save to delete the template, fieldgroup and field to delete the whole thing or will there be any unused relational pairings left in the database?

I think that will do it.

Share this post


Link to post
Share on other sites
Is is save to delete the template, fieldgroup and field to delete the whole thing or will there be any unused relational pairings left in the database?

I know this is old and I am not sure if this is still the case, but when I last looked into this I actually had to do the following to fully clean up a repeater field:

$current_field_id = $this->fields->get("field_name")->id;

$forfieldid = "for-field-$current_field_id";
$sql = "DELETE FROM pages WHERE name=:forfieldid";
$query = $this->wire('database')->prepare($sql);
$query->bindValue(':forfieldid', $forfieldid);
$query->execute();

Here's a related Github issue (https://github.com/ryancramerdesign/ProcessWire/issues/368) and forum post (https://processwire.com/talk/topic/4440-deletepage-true-doesnt-delete-repeated-fields-on-230/). Note that Post #10 by yours truly describes the issue which the above fix deals with. I think this is different to the other things described.

I know this has become a little off-topic now, but wanted to respond to owzim's question with some details and potential issues.

  • Like 1

Share this post


Link to post
Share on other sites

I know this is a pretty old thread, but every time it comes to creating a Repeater through the API, I get LOST.

@thetuningspoon's function illuminates a LOT of what happens behind the scenes when a new Repeater is made through the GUI - but I just keep wishing it was as simple as:

$f = new FieldtypeRepeater();
$f->set('name', 'repeaterTest');
$f->add($repeaterField1);
$f->add($repeaterField2);
$f->save();

Because that isn't technically possible without setting the Fieldgroup or Template that will be used by the Repeater first, right?

 

At the end of the day, I think we should improve the docs for Repeaters (https://processwire.com/api/fieldtypes/repeaters/) to include a section on creating a Repeater from scratch through the API so that this process is more easily understood. I am always referencing several Forum posts and parts of the docs in order to get through the spaghetti - having everything in one place would be great.

Otherwise it would be awesome to have some functions as a part of the FieldtypeRepeater Class that cut down on the amount of API calls one needs to make in order to create one of these.

 

Just my 2 cents.

  • Like 6

Share this post


Link to post
Share on other sites

Has anyone have any experience adding fields to Repeater Matrix items (through the API of course)? Like, add a new field to certain repeater matrix type.

Share this post


Link to post
Share on other sites
7 hours ago, elabx said:

Has anyone have any experience adding fields to Repeater Matrix items (through the API of course)? Like, add a new field to certain repeater matrix type.

Probably a question best answered by Ryan in the pro support board, but this worked for me...

// Name of repeater matrix field
$matrix_field_name = 'test_matrix';

// Name of matrix type you want to add field to
$matrix_type_name = 'type_1';

// Name of field you want to add to matrix type
$add_field_name = 'images';

// Get field objects
$matrix_field = $fields->get($matrix_field_name);
$add_field = $fields->get($add_field_name);

// Add field to repeater matrix fieldgroup
$matrix_template = $templates->get("repeater_$matrix_field_name");
$matrix_fieldgroup = $matrix_template->fieldgroup;
$matrix_fieldgroup->add($add_field);
$matrix_fieldgroup->save();

// Associate field with matrix type
$matrix_types = $matrix_field->type->getMatrixTypes($matrix_field);
$matrix_type_integer = $matrix_types[$matrix_type_name];
$property = "matrix{$matrix_type_integer}_fields";
$field_ids_in_type = $matrix_field->$property;
$field_ids_in_type[] = $add_field->id;
$matrix_field->$property = $field_ids_in_type;
$matrix_field->save();

 

  • Like 4

Share this post


Link to post
Share on other sites

Is this thread still alive? Has something changed so far, so that repeater fields could be added in a more elegant way (I mean, when added programmatically)? 🙂

Share this post


Link to post
Share on other sites

  For me, it looks like, adding to property repeaterFields does not work correctly. Changed it to the following:

$repeaterFieldIds = [];

foreach($fieldsArray as $field) {
  $repeaterFieldIds[] = $this->fields->get($field)->id;
}

$f->repeaterFields = $repeaterFieldIds;
    
$f->save();

 

Share this post


Link to post
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

  • Recently Browsing   0 members

    No registered users viewing this page.

  • Similar Content

    • By Spyros
      Hello
      I'm having a strange issue with the $page->find(), for some reason I'm missing some of the pages from the results. I found then that I was missing all the pages with the same "PAGE NAME". Is it a bug or am I missing something?
      PS 
      If I change the "PAGE NAME" of one of the missing ones then I'm retrieving the page without any problem.
      Thank you
    • By t0b1
      Hello there, and thanks for ProcessWire!
      I'm getting to know ProcessWire while doing my first project using it and I really like it so far.
      The challenge I'm facing right now is the following:
      I have a One-Pager using fullpage.js, realized as a single PW-Page containing a Repeater Field where each Repeater Item is one Section.
      Some Sections are supposed to have a little menu at the Top which references/links to different Sections of the Website, so I wanted to use a Checkbox Field "Top Menu" to decide if a Section gets a Menu and a Page Reference Field to choose the different Sections (Repeater Items) it should contain.
      I've already accomplished this by pasting the following code into /site/ready.php:
       
      $wire->addHookAfter('InputfieldPage::getSelectablePages', function($event) { if($event->object->hasField == 'top_menu_entries') { $page = $event->arguments('page'); if($page instanceof RepeaterPage) $page = $page->getForPage(); $event->return = $page->; } });  
      The only problem that still remains is that when I select the Entry of the Repeater Item itself it doesnt save the selection, meaning after I saved it's unselected again.
      On some Sections I do want a Menu-Entry for the Section itself though (which would be styled differently and not link anywhere) for Continuity-Reasons, any ideas on how to achieve that?
    • By Guy Incognito
      This short script loops through some images from an XML feed and pushes new ones to an image field. It all works perfectly, except for some reason the last image (only) in the loop each time doesn't receive the image description... can everyone spot why? TIA! 🙂 
      foreach ($propertyImages as $img) { $fileName = trim($img[0]); if ( !empty($fileName) ) { $imgPath = '../property_data/'.$fileName; if(file_exists($imgPath) && !in_array(strtolower($fileName),$currentImages)) { $p->property_images->add($imgPath); $p->save(); $newImg = $p->property_images->last(); $newImg->description = $img[1]; $p->save(); } } }  
    • By MateThemes
      Hello everyone!
      I am working with Processwire since some time. But some topics are quite hard for me.
      I have a Portfolio (Gallery) Page.
      I am build a template with Portfolio Index and pages with portfolio entries.
      Structure:
      Portfolio Index
      -- Portfolio Entry
      -- Portfolio Entry
      and so on.
      Portfolio Entry has an Image field with max 12 images and are accessible Templates. 
      Now I want to display the single Portfolio Entry on the Portfolio Index and Paginate them. In the index page all images of a single Entry page should be displayed (I should not be organized as albums, where a random image of the portfolio entry should be displayed). 
      I have no clue to achieve this. May someone could give me an advice.
      Thank you in advance!
    • By ngrmm
      I have a page with a table. Each table row has a page-reference field and a checkbox.
      The Page sends emails to all users (page-refrence->email-field) and change the value of the checkbox in a row to 1.
      It works with this:
      <?php // event ID fron url query $eventID = $input->get('eventID','int'); // get event-page $event = $pages->get($eventID); // config $fromEmail = $event->event_mail_from; $fromName = $event->event_mail_from_name; $emailSubject = $event->event_subject; // email html body ob_start(); include('./_inc/emailbody.inc'); $emailBody = ob_get_clean(); // make event-page editable $event->of(false); // loop through table and send out emails foreach($event->event_clients_list as $event_table_row) { // get client page $clientPage = $event_table_row->client_name; // get client email $clientEmail = $clientPage->email; // if client isn't invited yet (checkbox not checked) if($event_table_row->client_invited == '') { // send email $m = new WireMail(); $m->to($clientEmail); $m->from($fromEmail, $fromName); $m->subject($emailSubject); $m->bodyHTML($emailBody); $m->send(); // mark client as invited $event_table_row->client_invited = 1; $event->save('event_clients_list'); } } ?> But i have to use a variable in my emailbody.inc which i'm able to get in the table-loop.
      So i do the including of the body inside my loop. But this doesn't work anymore. Page sends out the emails but is unable to change the value of the checkbox.
      I get no errors!
      I'm using ProTable
      <?php // event ID fron url query $eventID = $input->get('eventID','int'); // get event-page $event = $pages->get($eventID); // config $fromEmail = $event->event_mail_from; $fromName = $event->event_mail_from_name; $emailSubject = $event->event_subject; // loop through table and send out emails foreach($event->event_clients_list as $event_table_row) { // get client page $clientPage = $event_table_row->client_name; // get client email $clientEmail = $clientPage->email; // email html body ob_start(); include('./_inc/emailbody.inc'); $emailBody = ob_get_clean(); // make event-page editable $event->of(false); // if client isn't invited yet (checkbox not checked) if($event_table_row->client_invited == '') { // send email $m = new WireMail(); $m->to($clientEmail); $m->from($fromEmail, $fromName); $m->subject($emailSubject); $m->bodyHTML($emailBody); $m->send(); // mark client as invited $event_table_row->client_invited = 1; $event->save('event_clients_list'); } } ?>  
×
×
  • Create New...