Jump to content

[solved] How to add action buttons fieldtype for each repeater


Federico
 Share

Recommended Posts

Hi PW community,

struggling to find a way to add custom action button fieldtype to each duplicates inside a repeater fieldtype, so for all repeater there will be a dedicated button to submit and post-elaborate related pages selection.

This repeater fieldtype will be used in custom module, I thought Hooks will be my friends for this, but maybe I am wrong. Couldn't find any post for this, maybe someone could suggest a way to go 

Much appreciated, thank you vey much

 

Untitled-1-01.png

Link to comment
Share on other sites

Just tried it: it works

Just follow the RuntimeMarkup instructions, and make sure you choose the wireRenderFile method. Add your new RM field to your repeater, and include something like

<li
	class="Inputfield InputfieldSubmit Inputfield_submitSelection uk-width-auto uk-margin-top uk-grid-margin uk-first-column reloaded InputfieldRowLast"
	id="make-this-id-unique">
	<div class="InputfieldContent uk-form-controls">
		<button id="make-this-unique-too"
			class="ui-button ui-widget ui-corner-all ui-state-default"
			name="ditto"
			value="Submit / Send x"
			type="submit">
			<span class="ui-button-text">Submit / Send x</span>
		</button>
	</div>
</li>

I guess you'll want to edit the IDs, and add data-attributes or hidden fields to do whatever you've got to do :-)

 

  • Like 1
Link to comment
Share on other sites

hm... @dragan i guess the runtimemarkup field only has to return the "<button>" element (without the <li>, <div.InputfieldContent> and so on...

3 hours ago, Federico said:

I thought Hooks will be my friends for this, but maybe I am wrong

you are right ;) see this post for example (and the following): 

and also this: https://processwire.com/blog/posts/building-custom-admin-pages-with-process-modules/#using-internal-components-modules (scroll down to the buttons section)

  • Like 2
Link to comment
Share on other sites

thanks both!!

@dragan I've created a php file with that html code and included as wireRenderFile in RuntimeMarkup - it works as expected, so thanks for the hint. Perhaps the html code needs to be integrated with php variable to host related IDs of each buttons, so eventually all buttons IDs and other attributes reflect the related repeater field. What might be the drawback of this solution, if any?

 

@bernhard, great tutorial there, hat off! as of the button, can you please tell how the option 2 could work in the case that:

- repeater is a field associated to a template (so far so standard)

- the edit to the repeaters field will be made from a custom module

- then, how to implement the following in such scenario? 

$button = $this->modules->get('InputfieldButton');
$button->value = 'Open Page in Panel';
$button->attr('data-href', './my-info');
$button->addClass('pw-panel');
$out .= $button->render();

I mean, how to properly hook every repeaters in a module and add button accordingly? 

Feel like I miss the basis...

Link to comment
Share on other sites

Great Obi-Wan @bernhard: Yes, of course. I was just quickly copy-and-pasting from Chrome inspector... wanting to do a quick "proof of concept".

@Federico As the RM module docs mention, "The field's value is accessible from the ProcessWire API in the frontend like any other field, i.e. it has access to $page and $pages.". I also made hints in my example code where IDs are used, that this should be populated by your custom PHP code (make-this-id-unique). 

  • Like 1
Link to comment
Share on other sites

Thanks @dragan!

as of the hook option, still I cannot find a way to hook each repeater, in order to append to them a button. I've tried this within "init()"

$this->pages->addHookAfter('ProcessPageEdit::buildForm', $this, 'addButtons');

then something like this

    public function addButtons($event) {
        $page = $event->object->getPage();

        if($page->template == "book2pdf"){
            $form = $event->return; 

            $accept = $this->modules->get('InputfieldSubmit');
            $accept->attr('id+name', 'hook_accept_application');
            $accept->class .= ' ui-priority-secondary head_button_clone';
            $accept->attr('value', $this->_('Accept Application'));

            $form->insertBefore($accept, $form->get("id"));
        }
    }

but it doesn't hook/output anything. Do you know what is the proper hook for getting each repeater one by one (then append a button to them)?

this is my execute() code:

    public function ___execute(){

        $editpage = $this->pages->get(2308);
        $ignorefields = array("title");
        $form = $this->modules->get("InputfieldForm");
        $form->method = 'post';
        $form->action = './';

        $fields = $editpage->fieldgroup;
        foreach($fields as $field) {
            if(in_array($field->name, $ignorefields)) continue;
            $inputfield = $fields->{$field->name}->getInputfield($editpage);
            $form->append($inputfield);
        }

        // the inputfields don't already have a submit button, so we'll add one.
        $submit = $this->modules->get("InputfieldSubmit");
        $submit->name = "submit";
        $submit->value = 'Save book';
        // add the submit button the the form
        $form->add($submit);
        $out = '';
        // process the form
        if($this->input->post->submit) {
            // now we assume the form has been submitted.
            // tell the form to process input frmo the post vars.
            $form->processInput($this->input->post);
            // see if any errors occurred
            if( count( $form->getErrors() )) {
                // re-render the form, it will include the error messages
                $out .= $form->render();
            } else {
                // successful form submission, so populate the page with the new values.
                $editpage->of(false); // turn off output formatting before setting values
                foreach($form as $field) {
                    //$editpage->set($field->name, $field->value);
                    $editpage->set($field->name, $form->get($field->name)->value);
                }
                // save it
                $editpage->save();
                $this->message("Book saved!");
                $out .= $form->render();
            }
        }/*
        if($this->input->post->ditto) {
            // now we assume the form has been submitted.
            // tell the form to process input frmo the post vars.
            $form->processInput($this->input->ditto);
            // see if any errors occurred
            if( count( $form->getErrors() )) {
                // re-render the form, it will include the error messages
                $out .= $form->render();
            } else {
                // successful form submission, so populate the page with the new values.
                $editpage->of(false); // turn off output formatting before setting values
                foreach($form as $field) {
                    $editpage->set($field->name, $form->get($field->name)->value);
                }
                $this->message("DITTO");
                $out .= $form->render();
            }
        }*/
         else {
            $out .= $form->render();
        }
        return $out;
    }

Maybe the hook method itself is wrong...

Link to comment
Share on other sites

hey @Federico

45 minutes ago, Federico said:

but it doesn't hook/output anything. Do you know what is the proper hook for getting each repeater one by one (then append a button to them)?

i didn't read all your code but I would hook the inputfield's render method of the field that is inside the repeater. or you create a runtime markup field with your code. that might be easier.

the only thing you have to take care of when hooking yourinputfield::render() is that the $field->name is not the field's name only but it has some appended or prepended string (repeater name or id, not sure at the moment... you can easily check this with tracy). always start without any additional checks/ifs/returns and then continue step by step until you get where you want, for example this in your /site/ready.php:

$wire->addHookAfter('Inputfield::render', function($event) {
  $field = $event->object;
  bd($field->name);
});

then you can modify the hook to only execute when needed (like hooking InputfieldText instead of the Inputfield base class, doing a return when the field name does not match...).

i think @Robin S came up with a good solution how to check for the right field name inside repeaters but i can't find where he mentioned it in the forum. this is another way of checking that i used, but maybe robin can show us the better way ;)

Link to comment
Share on other sites

Thanks @bernhard,

we will see if any better implementation could fit this scenario. In the meantime, I've been playing around the runtime fieldtype as suggested by @dragan, and I think for now this is the quickest solution. btw, to get unique IDs to each button (within the Details tab), this worked for me

$idUnique = $this->name;
return ProcessWire\wireRenderFile('runtimeButton.php', array('idUnique' => "$idUnique"));

 

Link to comment
Share on other sites

$wire->addHookAfter('Inputfield::render', function (HookEvent $event) {

	$field = $event->object;

	if (substr($field->name, 0, 13) === 'repeater_item') {
		$page = $event->arguments(0);

		$id = str_replace('repeater_item_', '', $field->name);
		$submitID = "submitRepeaterItem_$id";

		$form = wire('modules')->get("InputfieldForm");
		$submit = wire('modules')->get("InputfieldButton");
		$submit->attr("value", "Edit this repeater item as page");
		$submit->attr('data-href', wire('config')->urls->admin . "page/edit/?id=$id");
		$submit->addClass('pw-panel');
		$submit->addClass('uk-margin-left');
		$submit->attr("id+name", $submitID);
		$form->append($submit);
		$myForm = $form->render();
		$event->return .= $myForm;
	}

});

I've got this in ready.php... seems to work.

 

  • Like 2
  • Thanks 1
Link to comment
Share on other sites

Hi @dragan, late here just to say that after some play around hooks, I couldn't find any other way (better) than your nice snippet above. Indeed, your seems to be the most appropriate way to append buttons to each repeater (my case is in custom admin module but it involves just few changes). I will mark this tread as solved :) but other solution are still welcomed

Beside this, I've found that the InputfieldButton doesn't have any clean option to manage the html target="_blank", as these are the options the module allows

$repeaterBtn = $this->modules->get("InputfieldButton");
$repeaterBtn->attr("value", "Generate something");
$repeaterBtn->attr("type", $submitID);
$repeaterBtn->attr("id+name", "btn");
$repeaterBtn->attr("data-href", "customData");
$repeaterBtn->addClass("customClass");

Maybe someone has quick n' dirt solution, probably like this one?

$this->pages->addHookAfter('Inputfield::render', function (HookEvent $event) {
	$field = $event->object;
	
	if ($field->name === 'btn') {
		// add target blank
	}
});

 

Link to comment
Share on other sites

$wire->addHookAfter('InputfieldText::render', function($event) {
  $field = $event->object;
  if($field->name != 'title') return;

  $repeaterBtn = $this->modules->get("InputfieldButton");
  $repeaterBtn->attr("value", "Generate something");
  $repeaterBtn->attr("type", $submitID);
  $repeaterBtn->attr("id+name", "btn");
  $repeaterBtn->attr("href", "/admin/page/edit/?id=1");
  $repeaterBtn->attr("target", "_blank");
  $repeaterBtn->addClass("customClass");

  $event->return .= $repeaterBtn->render();
});

;)

btw: panels are a great alternative for new browser tabs: https://processwire.com/blog/posts/building-custom-admin-pages-with-process-modules/#using-internal-components-modules

  • Like 1
Link to comment
Share on other sites

@bernhard thanks that's awesome! I was trying that attr() but eventually I discovered that if the button type="if differs from just button here, the _blank will not work"

$repeaterBtn = $this->modules->get("InputfieldButton");
$repeaterBtn->attr("value", "Generate something");
$repeaterBtn->attr("type", "button"); // IF DIFFERS FROM JUST "button", IT WILL NOT OPEN NEW BROWSER TAB!
$repeaterBtn->attr("id+name", "btn");
$repeaterBtn->attr("href", "/admin/page/edit/?id=1");
$repeaterBtn->attr("target", "_blank");
$repeaterBtn->addClass("customClass");

$event->return .= $repeaterBtn->render();

Thanks again both!

  • Like 1
Link to comment
Share on other sites

  • 1 month later...

Hi there, sorry for raising this up, but I think it is not entirely correct to leave this as "solved", because I've just discovered that all the hook code above will actually add new <form> tag for each repeater, leading to a code mess and nested forms..

$this->pages->addHookAfter('Inputfield::render', function (HookEvent $event) {
    //$page = $event->arguments(0);
    $field = $event->object;
    //substr takes the first 13 letters to match the repeater_item
    if (substr($field->name, 0, 13) === 'repeater_item' && $this->page->name === 'book') {
        $id = str_replace('repeater_item_', '', $field->name);
        $form = wire('modules')->get("InputfieldForm");

        $repeaterBtn = wire('modules')->get("InputfieldButton");
        $repeaterBtn->attr("value", "Generate Brochure in panel");
        $repeaterBtn->attr("type", "button");
        $repeaterBtn->attr("id+name", "button_form_$field->name");
        $repeaterBtn->attr("data-href", "./testa/");
        $repeaterBtn->addClass("ui-priority-secondary ");
        $repeaterBtn->addClass('pw-panel button_export2pdf');
        $form->append($repeaterBtn);

        $myForm = $form->render();
        $event->return .= $myForm;
    }
});

by calling Inputfieldform here, eventually the module will load these new button in a new form tag, the latter nested into the main form tag

room for improvement?

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

  • Recently Browsing   0 members

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