Jump to content

Dependent selects with repeater items (not inside)


Zeka
 Share

Recommended Posts

Hi.

I have this setup:

1. Template 'shop'. This template has two fields 'title' and repeater 'product_shop_categories_list'

2. Repeater field 'product_shop_categories_list' also has two fields: 'title' and the second is repeater field with name 'product_shop_use_list'.

3. 'product_shop_use_list' field has only title field. 

4. Template 'product' with two Page reference fields 'product_shop_categories' and 'product_shop_uses'.

5. Field 'product_shop_categories' is set to use 'repeater_product_shop_categories_list' template as a selector. So I get all repeater items/pages from 'product_shop_categories_list' field.

6. 'product_shop_uses' field is set to use 'Custom find' with selector 'template=repeater_product_shop_use_list, parent.title%=page.product_shop_categories.name 

Here is screenshot of repeaters part of the page tree which explains why I use this selector. 

5a8c9cc0cd983_2018-02-2023_35_29.thumb.jpg.0eb6a34e60ea7e03a0e2b14d51e39b9d.jpg

But it doesn't work. On initial load 'product_shop_uses' field is empty and when I change 'product_shop_categories' field I get all  page with repeater 'repeater_shop_use_list'.

Is dependent selects functionality intended to work with such setup? 

@Robin S what do you think? 

 

Link to comment
Share on other sites

9 minutes ago, Zeka said:

6. 'product_shop_uses' field is set to use 'Custom find' with selector 'template=repeater_product_shop_use_list, parent.title%=page.product_shop_categories.name 

I'm pretty sure this part won't work. The value of the input in a Page Reference inputfield is an ID (or IDs for a multiple Page Reference field). That is what gets passed to the selector to find selectable pages for the dependent Page Reference field. So "page.product_shop_categories.name" is going to translate to something like "1234.name" which isn't going to work.

If you want to debug this some more you can hook ProcessPageSearch::executeFor, which is what the dependent selects JS calls via AJAX. For example:

$wire->addHookAfter('ProcessPageSearch::executeFor', function(HookEvent $event) {
    $get_vars = $this->input->get;
    $return = $event->return;
    $decoded = wireDecodeJSON($return);
    bd($get_vars, 'get_vars');
    bd($decoded, 'decoded');
});

 

  • Like 1
Link to comment
Share on other sites

22 minutes ago, Robin S said:

you can hook ProcessPageSearch::executeFor, which is what the dependent selects JS calls via AJAX

It's just occurred to me that this opens up a lot of possibilities for getting dependent selects to work in situations where they otherwise wouldn't. Inside repeater items, or doing things like what you are trying to do @Zeka. You can hook before ProcessPageSearch::executeFor and manipulate the values in $input->get to make up whatever selector you want.

Or alternatively hook after and manipulate what is returned in the matches array.

To only affect requests coming from a dependent select you could look for the presence of limit=999 - not sure if that's used in other calls to the method though.

  • Like 1
Link to comment
Share on other sites

@Robin S Thanks one again. Got it working.

The first hook is to 'InputfieldPage::getSelectablePages' so on initial load or when a page is being edited not for the first time I get a desirable result.

	$wire->addHookAfter('InputfieldPage::getSelectablePages', function($event) {
		if($event->object->hasField == 'product_shop_types') {
			$page = $event->arguments('page');
			$items = new PageArray();
			$categories = $page->product_shop_categories;
			foreach ($categories as $cat) {
				$items->import($cat->product_shop_use_list);
			}

			$event->return = $items;
		}
	});

As dependent selects functionality relies on '=page.' pattern in the selector and I had to somehow identify this calls to ProcessPageSearch I made a hook to 'InputfieldPage::render' where I set id=page.product_shop_categoreis to get id values for further usage and custom_search=product_shop_types to identify the request. 

	$wire->addHookBefore('InputfieldPage::render', function($event) {
		if($event->object->hasField == 'product_shop_types') {
			$event->object->findPagesSelect = "id=page.product_shop_categories, custom_search=product_shop_types";
		}
	});

Third hook to ProcessPageSearch::executeFor

$wire->addHookAfter('ProcessPageSearch::executeFor', function(HookEvent $event) {
	$get_vars = $this->input->get;
	if ($get_vars->custom_search === 'product_shop_types') {
		$categories = $this->pages->find("id=$get_vars->id");
		$items = new PageArray();
		foreach ($categories as $cat) {
			$items->import($cat->product_shop_use_list);
		}
		$return = array(
			'selector' => strval($categories->getSelectors()),
			'total' => $items->getTotal(),
			'limit' => $items->getLimit(),
			'start' => $items->getStart(),
			'matches' => $items->explode(function($item) {
				return array(
					'id' => $item->id,
					'title' => $item->title->getLanguageValue($this->wire('user')->language),
					'name' => $item->name
				);
				}),
			);
			$event->return = wireEncodeJson($return);
		}
	});

For now, there is only one issue that I get one empty item before other results in AsmSelect and I can't find out why.

Link to comment
Share on other sites

8 minutes ago, Zeka said:

For now, there is only one issue that I get an empty item in AsmSelect and I can't find out why.

That's added by InputfieldPage.js here. It's so that there isn't an option pre-selected in the inputfield, which is a good thing normally. If you want to remind users that they need to select an option for this field you can make the field required. I guess you could add some custom JS to remove the empty option if you really don't want it.

  • Like 1
Link to comment
Share on other sites

@Robin S

I faced an issue:

When I change first Page reference field I get new dynamically added options in the second field and if I choose these newly added options and save a page they disappear and not saved in a field. Is there some validation mechanism that I missed? 

Link to comment
Share on other sites

42 minutes ago, Zeka said:

When I change first Page reference field I get new dynamically added options in the second field and if I choose these newly added options and save a page they disappear and not saved in a field. Is there some validation mechanism that I missed? 

There is validation done in InputfieldPage::isValidPage - this is done using whatever settings you have defined for the selectable pages in the Page Reference field settings. So you would want to make sure whatever settings you have there are broad enough to allow for any of the pages you are showing in the inputfield via your hooks.

If the settings need to be so broad that a very large number of pages would be matched then you might want to consider making the hook to ProcessPageSearch::executeFor a "replace" hook so the method isn't needlessly returning a huge number of pages before you replace its return value. Likewise consider replacing InputfieldPage::getSelectablePages for the field.

  • Like 1
Link to comment
Share on other sites

@Robin S While trying to debug "isValidPage", I tracked down that second Page reference field is not recognized as changed and looks like it's not processed. I get a notification that only first "parent" field was changed. Also, there are calls of 'InputfieldPage::isValidPage' only for "parent" field. Before/after hook to Pages::saveReady also tells that field is not changed.

Any thoughts where should I look? 

I'm very appreciative of your help.

Link to comment
Share on other sites

2 hours ago, Zeka said:

Any thoughts where should I look? 

Besides the validation inside InputfieldPage there is also validation done by the specific inputfield type used in the field (e.g. AsmSelect, SelectMultiple, etc). I forgot about this. Many of these extend InputfieldSelect and so use the processInput method there. So whatever pages you are showing in the Page Reference field have to be one of the $options for that inputfield (e.g. see InputfieldSelect::isOption method).

So I think your problem is coming from this part...

On 2/22/2018 at 9:55 AM, Zeka said:

The first hook is to 'InputfieldPage::getSelectablePages' so on initial load or when a page is being edited not for the first time I get a desirable result.

Whatever pages you restrict to here are the only ones that will pass validation. So rather that limiting the options with this hook you might need to come up with other strategy for hiding the options. For example, hide unwanted options with JS, or trigger a change on the source Page Reference field on load so your dependent select kicks in right from the start.

  • Like 2
Link to comment
Share on other sites

@Robin S Thank you once again for your explanations and all help. You were right, the issue was that all dynamically added options were not inside valid options of Input field. Got it working by putting all possible options before input processing. 

	$wire->addHookBefore('InputfieldSelect::processInput', function($event) {
		$field = $event->object;
		if ($field->name === 'product_uses') {
			$items = $this->pages->find('template=repeater_product_uses_list')->explode('title', ['key' => 'id']);
			$field->addOptions($items);
		}
	});

 

  • Like 1
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...