Jump to content

Problem with PageReference field and hooks


verdeandrea
 Share

Recommended Posts

I’m working on a website with the following structure:

|-- Academy (template = academy)
|   |-- Courses (template = courses)
|   |   |-- Course 1 (template = course)
|   |   |-- Course 2 (template = course)
|   |   |-- Course [...] (template = course)
|   |-- Masters (template = courses)
|   |   |-- Master 1 (template = course)
|   |   |-- Master 2 (template = course)
|   |   |-- Master [...] (template = course)
|   |-- Workshops (template = courses)
|   |   |-- Workshop 1 (template = course)
|   |   |-- Workshop 2 (template = course)
|   |   |-- Workshop [...] (template = course)
|-- Course Status (template = course_status)
|   |-- Status A (template = status)
|   |-- Status B (template = status)
|   |-- Status [...] (template = status)

The course template includes a PageReference field called course_status, which connects to the pages under the “Course Status” section. Editor users cannot directly access “Course Status” pages but can create new statuses via the PageReference field (using the “Allow new pages to be created from field” feature).

Objective

I need to filter the status options by course groups. For example:

  • Courses under “Courses” should share the same statuses.
  • Courses under “Masters” should have a different set of statuses.

If a new status is created under “Course 1,” it should automatically appear for all “Courses” but not for “Masters” or “Workshops.”

Current Solution

I save a reference to the parent page (e.g., “Courses”) within the newly created status page. This allows me to filter status options based on the parent group. Here’s how I implemented it:

$wire->addHookAfter('InputfieldPage::getSelectablePages', function($event) {
    if ($event->object->hasField == 'course_status') {
        $page = $event->arguments('page');
        $parent = $page->parent;
        $statusPages = $this->pages->get(1028)->children("related_courses={$parent->id}");
        $event->return = $statusPages;
    }
});

$wire->addHookAfter('InputfieldPage::processInputAddPages', function(HookEvent $event) {
    $field = $event->object;
    if ($field->hasField && $field->hasField->name === 'course_status') {
        $coursePage = $field->hasPage;
        if ($coursePage) {
            $parentPage = $coursePage->parent;
            if ($parentPage && $parentPage->template->name === 'courses') {
                $selectedStatusPage = $field->value;
                if ($selectedStatusPage && $selectedStatusPage->template->name === 'status') {
                    $selectedStatusPage->of(false);
                    if (!$selectedStatusPage->related_courses->has($parentPage)) {
                        $selectedStatusPage->related_courses->add($parentPage);
                        $selectedStatusPage->save();
                    }
                }
            }
        }
    }
});

The Issue

The setup works well except in this scenario:

1. I create a new status (e.g., “Open”) under “Course 1.”

  • It appears for all “Courses” as expected but not for “Masters,” which is correct.

2. If I try to create a status called “Open” under “Master 1”:

  • A new page is not created (presumably because the title “Open” already exists).
  • The “Open” status becomes available but isn’t automatically selected.
  • I have to manually select it and save again to finalize the selection.

Questions

  1. How can I ensure that the existing status is automatically selected in this case?
  2. Is there a better, more efficient way to manage the entire status setup? I’m open to restructuring if it leads to a smarter solution.

Thanks in advance for your help!

Link to comment
Share on other sites

I'm wondering... Why does the status field only show statuses of one group and at the same time it is possible to create new statuses? Doesn't that make the filter obsolete and you could just skip it, which would also eliminate the problem that you are seeing?

Link to comment
Share on other sites

Hi @bernhard,

I’m not sure if I fully understood your question, but I’ll try to explain the situation in more detail, hoping to clarify things.

My client wants to have a section called “Academy,” where they can create an unlimited number of pages (e.g., “Courses,” “Masters,” and “Workshops”). These are just examples, but there could be more, each with different names.
Each of these pages has subpages (the ones I referred to as “Course 1,” “Master 1,” etc.). These subpages can also be unlimited in number, but they all share the same template.
The client wants to allow filtering of these pages (on the frontend) based on a property called “status.” However, the possible status values are not predefined, and they can vary for each group of pages. Additionally, the client wants to be free to add or remove statuses for each group of pages as needed.

For instance:

  • Pages under “Courses” might have statuses like “Open” and “Closed.”
  • Pages under “Masters” could have statuses like “In Preparation” or “Registrations Open.”

On the website’s “Courses” page, users should be able to select a filter by status, e.g., see only pages with the “Open” status or vice versa. Similarly, on the “Masters” page, users would see filters like “Registrations Open” or “In Preparation” and use those to filter all the master pages.

I hope this gives you a clearer idea of what I’m trying to achieve, but feel free to ask if you need more information.

Link to comment
Share on other sites

Hey @verdeandrea thx, I understood that. My question was why you only want to show "open / closed" to "courses" and "in prep / registration open" to "masters" and at the same time make it possible to add new statuses. Because that means anybody could just add "open" or "closed" which are only available under "courses" to any page under "masters" as well - which makes hiding them obsolete from a logical perspective. And if you didn't hide them, you would not have the problem that you observe.

My guess would be that you want to keep the list of available statuses smaller as the global list of statuses might grow large over time?

I'd probably add a custom <input> to that page reference field and hook into processInput to create the status page on my own, but only if it does not yet exist:

<?php
// append <input> to the page ref field
wire()->addHookAfter(
  'InputfieldPage::render',
  function (HookEvent $event) {
    $f = $event->object;
    
    // execute this hook only for the field in question
    if ($f->name !== 'your_page_ref_field') return;
    
    // append custom <input>
    $f->appendMarkup('<div class="uk-margin-top">
      ...or create a new status:<br>
      <input type="text" name="newstatus">
    </div>');
  }
);

// hook processInput to create new status
wire()->addHookAfter(
  'ProcessPageEdit::processInput',
  function (HookEvent $event) {
    // sanitize received status, adjust to your needs
    $status = trim(wire()->input->post('newstatus', 'string'));
    
    // no new status, no todos
    if (!$status) return;

    // try to find existing status page
    $statusPage = wire()->pages->get([
      'parent' => 123,
      'title' => $status,
    ]);

    // create new status page if it does not exist yet
    if (!$statusPage->id) {
      $statusPage = new Page();
      $statusPage->template = 'status';
      $statusPage->parent = 123;
      $statusPage->title = $status;
      $statusPage->save();
    }

    // set new statuspage as selected status
    $page = $event->object->getPage();
    $page->setAndSave('your_page_ref_field', $statusPage);
  }
);

 

Link to comment
Share on other sites

13 hours ago, verdeandrea said:

I’m working on a website with the following structure:

|-- Academy (template = academy)
|   |-- Courses (template = courses)
|   |   |-- Course 1 (template = course)
|   |   |-- Course 2 (template = course)
|   |   |-- Course [...] (template = course)
|   |-- Masters (template = courses)
|   |   |-- Master 1 (template = course)
|   |   |-- Master 2 (template = course)
|   |   |-- Master [...] (template = course)
|   |-- Workshops (template = courses)
|   |   |-- Workshop 1 (template = course)
|   |   |-- Workshop 2 (template = course)
|   |   |-- Workshop [...] (template = course)
|-- Course Status (template = course_status)
|   |-- Status A (template = status)
|   |-- Status B (template = status)
|   |-- Status [...] (template = status)

 To separate the status options for one group to the ones for others, why not have a separate Status Page (and status template) for each group:

|-- Course Status (template = course_status)
|   |-- Status A (template = status)
|   |-- Status B (template = status)
|   |-- Status [...] (template = status)
|-- Master Status (template = master_status)
|   |-- Status A (template = status)
|   |-- Status B (template = status)
|   |-- Status [...] (template = status)
|-- Workshop Status (template = workshop_status)
|   |-- Status A (template = status)
|   |-- Status B (template = status)
|   |-- Status [...] (template = status)

In the course template you would have PageReferenceFields to each of them, and you would hide the ones not matching to the Parent page of the actual course.

There's no conflict having the same status name in different groups. You could even adress different details (e.g. a status "open" could mean other details for a Master course than for a Workshop course).

Edited by ottogal
Typo
Link to comment
Share on other sites

Hey @bernhard, thank you for your response!

The issue is not necessarily that the list could grow large over time (although that wouldn’t be ideal from a UI perspective). The real problem is that I need to display filters on the frontend, even if no courses currently have that status. However, I only want to show the filters relevant to a specific group of pages.

Here’s an example to make it clearer:

  • The user creates a new “Course 1” and sets its status to “Open.”
  • Later, the status of “Course 1” is changed to “Closed.”

On the website, I need to display both filters, “Open” and “Closed,” even if no pages are currently assigned the “Open” status.

The challenge is figuring out which filters to show if there’s no explicit action by the user to associate a specific filter with the group of pages. Without that, there’s no way to determine that I should display “Open” but not, for example, “In Preparation.”

I considered adding a field to the parent page (e.g., “Courses”) to manually manage the available statuses for that group, but I’m trying to keep the backend workflow as simple as possible for my client.

That said, I’ll give your suggestion with the hook a try and will get back to you. Thank you very much for the idea!

Link to comment
Share on other sites

2 minutes ago, verdeandrea said:

I considered adding a field to the parent page (e.g., “Courses”) to manually manage the available statuses for that group, but I’m trying to keep the backend workflow as simple as possible for my client.

From what you wrote I think that would be the way to go. 🙂  How else could the system know to show the "open" status if no courses have that state at the moment?

I think you should not try to find another setup, I think you should try to fix the issues that you have with your workflow. But you didn't say what you don't like with your current workflow. Maybe there is a solution for that and then you have solved all problems without creating new ones 😉 

 

  • Like 1
Link to comment
Share on other sites

16 minutes ago, verdeandrea said:

The challenge is figuring out which filters to show if there’s no explicit action by the user to associate a specific filter with the group of pages. Without that, there’s no way to determine that I should display “Open” but not, for example, “In Preparation.”

I considered adding a field to the parent page (e.g., “Courses”) to manually manage the available statuses for that group, but I’m trying to keep the backend workflow as simple as possible for my client.

The message I deleted was answering this (I think so), but I was proposing the exact same solution as this:

23 hours ago, verdeandrea said:

I save a reference to the parent page (e.g., “Courses”) within the newly created status page.

So I don't get what is the problem with this solution? Then you juste have to edit the selector string of the status field, so that it searches only statuses that have the same "course parent" as the current course. Something like selector="template=status, course_parent.id=page.parent.id". I'm not sure about the selector, I didn't use PW since a few months.

Edited by da²
Link to comment
Share on other sites

Hi @ottogal, thank you for your response!

The challenge is that “Courses,” “Masters,” and “Workshops” are just examples. I have no way of knowing in advance what my client will create, so I can’t rely on static “Status” groups.

Were you suggesting dynamically creating a new status group every time a new “Courses” page is added? If so, I’ve experimented with something along those lines.

The issue I encountered is that the PageReference field requires a defined parent to allow the creation of new pages. Unfortunately, I haven’t found a way to make this work dynamically.

If you have ideas or workarounds for this, I’d be happy to explore them.

Link to comment
Share on other sites

Hey @da² 
this is the issue:

23 hours ago, verdeandrea said:

The Issue

The setup works well except in this scenario:

1. I create a new status (e.g., “Open”) under “Course 1.”

  • It appears for all “Courses” as expected but not for “Masters,” which is correct.

2. If I try to create a status called “Open” under “Master 1”:

  • A new page is not created (presumably because the title “Open” already exists).
  • The “Open” status becomes available but isn’t automatically selected.
  • I have to manually select it and save again to finalize the selection.

 

When the user creates a new status, but a status page with that title already exists, the new status does not get selected in the field automatically. The user has to select it manually a second time for it to be applied.

I think @bernhard’s proposed solution might fix the issue. I’ll give it a try and let you know how it goes.

  • Like 1
Link to comment
Share on other sites

12 minutes ago, verdeandrea said:

I think @bernhard’s proposed solution might fix the issue. I’ll give it a try and let you know how it goes.

I don't think so. This would still have the issue that if no course has the status "open" then the frontend would not know about that status and therefore would not show it.

Please read my previous message. I think it would be the best to save statuses to each group and then try to improve the workflow for the editor (whatever that means - you have to explain what you are missing) rather than changing fields so that they are easy to fill in but have the problem that they can't work on the frontend.

Link to comment
Share on other sites

@bernhard Yes, I tested the hooks, but they have some issues.

The main “problem” with that workflow—specifically, having a field to “enable” statuses on the parent (courses) template—is that if the user is creating a new course and needs a new status, they would have to:

  1. Save the course.
  2. Go back to the parent page to enable the status and save it.
  3. Return to the course page, select the newly enabled status, and save the course again.

This essentially turns the process into a 2-3 step task, whereas I’m aiming for everything to be achievable within the single course creation step.
At that point, it’s actually easier to create the new status page directly from the course page. Then, if it’s not automatically selected (because it already exists), the user can simply select it and save the course again in a second step. This would still be quicker overall compared to the alternative workflow.

I know it might seem like a small detail, but I believe this adjustment could improve the user experience and make the workflow smoother.
 

Link to comment
Share on other sites

1 hour ago, verdeandrea said:

I know it might seem like a small detail, but I believe this adjustment could improve the user experience and make the workflow smoother.

I totally agree. Those details matter a lot.

1 hour ago, verdeandrea said:
  • Save the course.
  • Go back to the parent page to enable the status and save it.
  • Return to the course page, select the newly enabled status, and save the course again.

I knew it 🙂 That's a totally different story now and the solution is different, obviously.

Actually with just a small change you can make it work well I think without any issues.

We just need to add another page reference field to the "courses" template and adjust our hook to also populate that field!

The idea is to use everything what I wrote in this post, but then, additionally populate a page reference field on the courses table so that the system remembers all statuses that have been used for that group.

An example:

  • Let's say we have three course groups: GroupA, GroupB, GroupC
  • Then we add a new course to GroupA, let's call it CourseAX.
  • We use the custom <input> to add the status "open"
    • this will create a new status page for us, /course-status/open
    • NEW: We add this status to the page reference field of GroupA, which means that field has status "open" at the moment.
  • Now we change the status of CourseAX from "open" to "closed" and do the same as above:
    • create a new status /course-status/closed
    • NEW: Add status "closed" to the GroupA page reference field

--> now GroupA has both "open" and "closed" in the page reference field even though we only have one event with one status. But we can use this page reference field to show the filter on the frontend.

The new hook to add this logic would be something like this:

<?php
// hook processInput to create new status
wire()->addHookAfter(
  'ProcessPageEdit::processInput',
  function (HookEvent $event) {
    // sanitize received status, adjust to your needs
    $status = trim(wire()->input->post('newstatus', 'string'));
    
    // no new status, no todos
    if (!$status) return;

    // try to find existing status page
    $statusPage = wire()->pages->get([
      'parent' => 123,
      'title' => $status,
    ]);

    // create new status page if it does not exist yet
    if (!$statusPage->id) {
      $statusPage = new Page();
      $statusPage->template = 'status';
      $statusPage->parent = 123;
      $statusPage->title = $status;
      $statusPage->save();
    }

    // set new statuspage as selected status
    $page = $event->object->getPage();
    $page->setAndSave('your_page_ref_field', $statusPage);
    
    // NEW: Also add status to parent page ref field
    $statuses = $page->parent->getUnformatted('your_statuses_field');
    $statuses->add($statusPage);
    $page->parent->setAndSave('your_statuses_field', $statuses);
  }
);

I think this should work 🙂 

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