Jump to content

Add custom options to InputfieldSelect via a hook


a-ok
 Share

Recommended Posts

As the title suggests I am wanting to add custom options to InputfieldSelect via a hook.

I have this in my ready.php

$wire->addHookBefore('InputfieldSelect::render', function($event) {
    if ($event->object->name != 'work_detail_videos_image') return;
    $page = $event->arguments('page');
    $items = array();
    foreach($page->work_detail_images as $image) {
        $items[] = $image->name;
    }
    $event->object->addOptions($items);
});

I think it's wrong, however. Mainly because the field is within a repeater? The repeater is set to non-AJAX.

Any thoughts?

Link to comment
Share on other sites

There's no "page" argument to Inputfield::render, but you can use the property $inputfield->hasPage to retrieve the page associated with the Inputfield. The returned page is the individual repeater item page, not the page being edited. If work_detail_images is in the same repeater, the following line should be enough to get things working:

	$page = $event->object->hasPage;

 

  • Like 1
Link to comment
Share on other sites

1 hour ago, BitPoet said:

There's no "page" argument to Inputfield::render, but you can use the property $inputfield->hasPage to retrieve the page associated with the Inputfield. The returned page is the individual repeater item page, not the page being edited. If work_detail_images is in the same repeater, the following line should be enough to get things working:


	$page = $event->object->hasPage;

 

Thanks so much. Okay... this makes sense.

Unfortunately 'work_detail_images' isn't in the repeater. I guess I'll need to hook into the repeater too?

Link to comment
Share on other sites

38 minutes ago, oma said:

Unfortunately 'work_detail_images' isn't in the repeater. I guess I'll need to hook into the repeater too?

Probably nothing that complicatied. You can use getForPage() on the repater item to retrieve the page it belongs to.

$repeaterPage = $event->object->hasPage;
$page = $repeaterPage->getForPage();
  • Like 1
Link to comment
Share on other sites

21 minutes ago, BitPoet said:

Probably nothing that complicatied. You can use getForPage() on the repater item to retrieve the page it belongs to.


$repeaterPage = $event->object->hasPage;
$page = $repeaterPage->getForPage();

Thanks! Got this working with your help. Only issue now is that it doesn't seem to 'save' the selections by the user when saved. Is that because it's 're-adding' them each time?

Link to comment
Share on other sites

I also tried the following from this topic https://processwire.com/talk/topic/14463-fieldtypeoptions-set-selectable-options-through-api/

 

$wire->addHookBefore('InputfieldSelect::render', function($event) {

	if ($event->object->hasField == 'work_detail_videos_image') {

		$repeaterPage = $event->object->hasPage;
		$page = $repeaterPage->getForPage();

		$options = $event->object->getOptions();

		foreach($page->work_detail_images as $image) {
			if ($image->ext == 'GIF' || $image->ext == 'gif') {
				$option = new SelectableOption();
				$option->title = $image->name;
				$options->add($option);
			}
    	}

		$event->object->setOptions($options);

	}

});

But it errors out with "Call to a member function add() on array"

Link to comment
Share on other sites

The not saving thing is actually something I encountered too a few days ago when populating select options in a hook. Unfortunately, I didn't find the time to dig deeper (and it isn't an urgent case). Come to think of it, it might be a validation issue, as when saving in ProcessPageEdit, the render hook isn't called before processInput is triggered. So running the same hook code before InputfieldSelect::processInput might solve it.

  • Like 1
Link to comment
Share on other sites

18 hours ago, BitPoet said:

The not saving thing is actually something I encountered too a few days ago when populating select options in a hook. Unfortunately, I didn't find the time to dig deeper (and it isn't an urgent case). Come to think of it, it might be a validation issue, as when saving in ProcessPageEdit, the render hook isn't called before processInput is triggered. So running the same hook code before InputfieldSelect::processInput might solve it.

Thanks for your help, @bitpoet. It seems like adding 'InputfieldSelect::processInput' breaks it (doesn't return any options).

Link to comment
Share on other sites

  • 3 years later...

If anyone's wondering how to get the data to save, you need to set the options again prior to processing the input. So here's my example - first hook adds my options to the field so they render, second hook makes the options available when processing the input:

wire->addHookBefore('InputfieldSelect::render', function($event) {
    if($event->object->name == 'my_select_field_name') {
        $countries = [1 => 'UK', 2 => 'USA', 3 => 'Etc etc'];
        $event->object->setOptions($countries);
    }
});

$wire->addHookBefore('InputfieldSelect::processInput', function(HookEvent $event) {
    // Get the object the event occurred on, if needed
    $InputfieldSelect = $event->object;
    if ($InputfieldSelect->name == 'my_select_field_name') {
        $countries = [1 => 'UK', 2 => 'USA', 3 => 'Etc etc'];
        $event->object->setOptions($countries);
    }
});

Using this in a complicated project where I've got custom tables for some data and it works nicely.

Possibly also worth noting this works for checkboxes and radios I think as they are all extended from InputfieldSelect

Edited by Pete
Typo - $InputfieldSelet->id should have been ->name
Link to comment
Share on other sites

I think you could also hook Field::getInputfield() instead of Inputfield::render() and set your options there. They should then also be available in processInput, but I have not tested what I just said so I might be wrong ? 

Link to comment
Share on other sites

@bernhard I can't quite work out what the code would look like there, because if I bd() the inputfields on the page, it only shows the "outer" combo field name, not the subfields.

Something else to note using my code is that if you output the field DATA (not the inputfield markup) on the frontend (which in my case is a country) using code like this:

echo $user->billing_details->country;

it won't show the data as it doesn't know what the key/value pairs are supposed to be when formatting the data. So I got around that by doing:

echo $user->getUnformatted('billing_details')->country;

Which simply outputs whatever you had stored in the DB.

I'm sure there's some way of covering all this in 2 hooks or less using your method bernhard but not sure what it would look like for a combo field. For now my 2 hooks and outputting the unformatted value gets me where I need to be. I just wish I'd remembered about getUnformatted 90 minutes ago ? 

  • Like 1
Link to comment
Share on other sites

  • 1 year later...
On 7/28/2022 at 11:32 AM, Pete said:
wire->addHookBefore('InputfieldSelect::render', function($event) {
    if($event->object->name == 'my_select_field_name') {
        $countries = [1 => 'UK', 2 => 'USA', 3 => 'Etc etc'];
        $event->object->setOptions($countries);
    }
});

$wire->addHookBefore('InputfieldSelect::processInput', function(HookEvent $event) {
    // Get the object the event occurred on, if needed
    $InputfieldSelect = $event->object;
    if ($InputfieldSelect->name == 'my_select_field_name') {
        $countries = [1 => 'UK', 2 => 'USA', 3 => 'Etc etc'];
        $event->object->setOptions($countries);
    }
});

Had the same need today and was not able to get it working properly. The quoted version did only work if I had some options set in the inputfield's config, which is not good. I came up with this solution (that only works for single selects):

<?php
$wire->addHookBefore('InputfieldInteger::render', function ($event) {
  $inputfield = $event->object;
  if ($inputfield->name != "your_fieldname") return;

  $f = new InputfieldSelect();
  $f->name = $inputfield->name;
  $f->value = $inputfield->hasPage->{$inputfield->hasField};
  $f->setOptions([1 => 'UK', 2 => 'USA', 3 => 'Etc etc']);
  
  $event->return = $f->render();
  $event->replace = true;
});

This works because it simply stores the option's id in the integer field so it does not try to sanitize for options that it does not have.

Link to comment
Share on other sites

Not sure if this is of any help, @bernhard, but I had a similar sort of need in my home-built pagebuilder module. The following code is called from the module init()

	protected function allowableLayouts($allowedLayoutFieldName) {
		// Create the options for motif_allowable_children/sublayouts being all the pro-forma layouts
		$layouts = $this->pages->find("template=MotifLayout");
		//bd($layouts, '$layouts');
		if($layouts && $layouts->count() > 0) {// Prevent deletion of options if pages not yet loaded
			$options = new selectableOptionArray();
			$i = 1;
			foreach($layouts as $layout) {
				$option = new SelectableOption();
				$option->set('id', $layout->id); // use page id as this should not change as components are added, changed and deleted
				$option->set('sort', $i);  // this assigns the correct value but seems to have no effect on the display order
				$option->set('title', $layout->title);
				$option->set('value', $layout->path);
				//bd($option, 'allowable child');
				$options->add($option);
				$i++;
			}

			$allowedLayoutField = $this->fields->get($allowedLayoutFieldName);
			if($allowedLayoutField && $allowedLayoutField->id > 0) {

				//bd($options, 'Allowable options for ' . $layoutField);
				// ToDo - Make this conditional on there being a change (better efficiency) after being satisfied that it works OK
				$allowedLayoutField->type->setOptions($allowedLayoutField, $options);
			}

		}
	}

The above is presented "as-is" but, if it is relevant, I'm sure you can adapt as required.

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...