Jump to content

Admin: prompt user with a choice before saving page


da²
 Share

Recommended Posts

Hello,

When deleting a field from a template, PW prompts the user to confirm that this field must be deleted. I'd like to use this behavior in my own code:


1) User save the page,
2) In a save hook I process data in the page,
3) If necessary (depending of data in the page) I prompt the user for a choice between multiple, so I want to display checkboxes. At this point page is not saved.
4) User select a checkbox and click save again,
4) I read the selected checkbox and continue to process page data before page is saved.

How to achieve this? What PW module or functions allow to prompt user when he's saving a page and displaying a form to make some choices?

Link to comment
Share on other sites

  • da² changed the title to Admin: prompt user with a choice before saving page
On 10/29/2023 at 10:14 AM, da² said:

How to achieve this? What PW module or functions allow to prompt user when he's saving a page and displaying a form to make some choices?

There are no modules for that as far as I know.

On 10/29/2023 at 10:14 AM, da² said:

When deleting a field from a template, PW prompts the user to confirm that this field must be deleted. I'd like to use this behavior in my own code:


1) User save the page,
2) In a save hook I process data in the page,
3) If necessary (depending of data in the page) I prompt the user for a choice between multiple, so I want to display checkboxes. At this point page is not saved.
4) User select a checkbox and click save again,
4) I read the selected checkbox and continue to process page data before page is saved.

What you are looking for is not so easy. ProcessWire does that by redirecting to a dedicated form that is only built for that very specific action and takes all the arguments via GET parameters:

dFQxMMl.png

That way PW knows what to do after the confirmation was given. In that case it will remove field 102 from template 1. You see that very basic behaviour and its limitations if you try to remove fields and also try to rename the template. Then PW needs to show to confirmations and do two actions and just skips one with informing the user:

s91B9FK.png

That's some background to what you were asking. If you tell us more about your real use case it would be easier to think of the best solution. Details really matter here. Which fields do you want to check? Are we talking about 2? 10? 100? Can we simply wipe data if some conditions are not met and show a warning or do we need to restore old values or such?

  • Like 1
Link to comment
Share on other sites

15 hours ago, bernhard said:

ProcessWire does that by redirecting to a dedicated form that is only built for that very specific action and takes all the arguments via GET parameters

OK I see, thank you for the explanation.

15 hours ago, bernhard said:

If you tell us more about your real use case it would be easier to think of the best solution.

I'm building a site and set of tools to manage sim-racing events in a league (races, championships...).

The concerned page is a sim-racing race page where the user can upload a file containing race results and statistics.
Problem is that this file may sometimes contain several races, and only one must be processed, but I don't know which one and the user at this point may also not know, so my idea is:

1) User upload the file and click save,
2) I parse the file, and if I find several races I cancel the save and display to the user a summary of the races contained in the file (to help him make the good choice) with a field where he can enter the race number he wants to process,
3) User enter the race number and click save again,
4) I process the file (the chosen race) and fill the page with results.

One constraint is that I don't want to parse the file every time the page is saved, but only when the file has changed. I say that because I was thinking of using a hook on ProcessPageEdit::processInput(raceResultFile!="") but I failed to detect if the field has changed in this hook.

 

Actually I feel this is impossible to achieve with simple hooks, but I may miss something.

Link to comment
Share on other sites

21 minutes ago, da² said:

I parse the file, and if I find several races I cancel the save

This is where you make it more complicated than it needs to be 😉

You don't need to cancel the save at any stage. You just need to make sure that only the correct values are written to a dedicated field of your page. But there's nothing bad in having the file stored in one field, then process that file on save, then let the user choose the correct race and then on the next save you write the correct race to the dedicated field that only stores one race.

This would be fairly easy to achieve with a custom markup field. There you process the file and display results, for example with a radio list element. The markup field could show some information like "this field will list all races from the uploaded file. please upload a file and save the page."

After upload and save you present something like this:

Quote

We found 5 races in the file, please choose the correct one and save the page

( ) race 1
( ) race 2
( ) race 3
...

Once the user selects for example "race 2" and saves the page you take that information and save all the necessary data of race 2 to your page's dedicated fields and voila you always have the correct data in your pages and you don't need to revert anything or such.

28 minutes ago, da² said:

Actually I feel this is impossible to achieve with simple hooks

In general chances are high that this feeling is wrong 😄 

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

As @bernhardwrote, it is pretty doable.

In addition to the markup field, I'd also add two hidden fields to the template to:

  • store the parsed data (textarea)
  • indicate we need to make a choice (checkbox)

In a Pages::saveReady hook, I'd check if the file field has changed.
If yes, parse the contents, and either:
  a) assign the page fields if there is only one race present;
  b) or fill the textarea with the parsed data (probably json encoded) and set needsChoice to 1.
If the file field hasn't changed, needsChoice is 1 and a choice was sent, wake up the data from the textarea, pick the chosen race and fill your fields. Then set needsChoice to 0.

In a ProcessPageEdit::buildForm hook, I'd check if needsSelection is set, and if yes, I'd fill the markup field with the race selection radio input. (Instead of the markup field, one could even use a regular InputfieldRadios and insert that into the form).

It also has the charm that you can put "needsChoice!=1" in your selectors to see all unfinished multi-race pages.

Two hooks, three fields, done.

  • Like 3
Link to comment
Share on other sites

2 hours ago, BitPoet said:

In addition to the markup field, I'd also add two hidden fields to the template to:

  • store the parsed data (textarea)
  • indicate we need to make a choice (checkbox)

Thx for the additions 🙂 That would be a great use case for using $page->meta() ! It's already there without doing anything and it's also a lot easier to manipulate meta data from within a hook than updating a hidden field's content.

1 hour ago, BillH said:

Perhaps you could display data using the RuntimeMarkup module.

And for selecting a race, the Dynamic Options module or the Dynamic Selects (commercial) module might be useful.

Everything needed is doable with just a few lines of code without any modules. The modules do not help here. Quite the contrary, they make things more complicated and less robust.

To add a runtime markup field to page edit:

<?php
$wire->addHookAfter("ProcessPageEdit::buildForm", function (HookEvent $event) {
  /** @var InputfieldForm $form */
  $form = $event->return;
  $page = $event->process->getPage();

  // custom rules where to add the field
  // eg add it only to pages with template "home"
  // otherwise exit early and do nothing
  if ($page->template !== 'home') return;

  $form->insertAfter([
    'type' => 'markup',
    'name' => 'mymarkup', // we need that later ;)
    'value' => '<h1>I am a custom markup field</h1>',
  ], $form->get('title'));
});

In this markup field you can show any HTML/PHP markup you want.

Now we can also add dynamic selects - also just a single and quite simple hook:

<?php
$wire->addHookAfter("ProcessPageEdit::buildForm", function (HookEvent $event) {
  /** @var InputfieldForm $form */
  $form = $event->return;
  $page = $event->process->getPage();

  // custom rules where to add the field
  // eg add it only to pages with template "home"
  // otherwise exit early and do nothing
  if ($page->template != 'home') return;

  // data coming from your file
  $races = ['foo', 'bar', 'baz'];

  // add all options
  $f = new InputfieldRadios();
  $f->label = 'Choose your race';
  $f->name = 'myrace';
  foreach ($races as $race) {
    $f->addOption($race, "Use Race '$race' for something");
  }

  // now add it after our markup field:
  $form->insertAfter($f, $form->get('mymarkup'));
});

And finally we save the data: Another small hook 🙂 

<?php
$wire->addHookAfter("Pages::saveReady", function (HookEvent $event) {
  // get the selected race from the radio input
  // we sanitize the input to make sure it is a string
  $race = $this->wire->input->post('myrace', 'string');
  if (!$race) return;

  // save selected race to page meta data
  $page = $event->arguments(0);
  $page->meta('myrace', $race);
});

Of course we have now two hooks for the same process, so we can combine those two to one and we end up with two hooks which should all you need:

<?php

$wire->addHookAfter("ProcessPageEdit::buildForm", function (HookEvent $event) {
  /** @var InputfieldForm $form */
  $form = $event->return;
  $page = $event->process->getPage();

  // custom rules where to add the field
  // eg add it only to pages with template "home"
  // otherwise exit early and do nothing
  if ($page->template != 'home') return;

  $race = $page->meta('myrace');
  $time = $page->meta('chosenat') ?: '';
  if ($time) $time = date("Y-m-d H:i:s", $time);
  $form->insertAfter([
    'type' => 'markup',
    'name' => 'mymarkup',
    'value' => '<h1 class=uk-margin-remove>I am a custom markup field</h1>'
      . "<p>Last Chosen Race: $race (@$time)</p>",
  ], $form->get('title'));

  // data coming from your file
  $races = ['foo', 'bar', 'baz'];

  // add all options
  $f = new InputfieldRadios();
  $f->label = 'Choose your race';
  $f->name = 'myrace';
  foreach ($races as $race) {
    $f->addOption($race, "Use Race '$race' for something");
  }

  // now add it after our markup field:
  $form->insertAfter($f, $form->get('mymarkup'));
});

$wire->addHookAfter("Pages::saveReady", function (HookEvent $event) {
  // get the selected race from the radio input
  // we sanitize the input to make sure it is a string
  $race = $this->wire->input->post('myrace', 'string');
  if (!$race) return;

  // save selected race to page meta data
  $page = $event->arguments(0);
  $page->meta('myrace', $race);
  $page->meta('chosenat', time());
});

Kyetsqk.png

No modules, no rocket science. Just ProcessWire awesomeness 🙂 

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

Thanks for the answers, the idea of having two consecutive saves is acceptable. 👍 I didn't think about it.
I didn't know about page meta data too.

Just one question, is there a reason you are using "input" instead of "$page->myrace"?

$race = $this->wire->input->post('myrace', 'string');
Link to comment
Share on other sites

3 minutes ago, da² said:

Just one question, is there a reason you are using "input" instead of "$page->myrace"?

Yes. myrace is just an inputfield that we added during runtime. It is not a field of the page, so $page->myrace will not work. But it is an inputfield of the form that is submitted, so the input post data will have that information available and we can use it for our further processing.

  • Like 1
Link to comment
Share on other sites

I'd like to add a Fieldset Tab on this page where I put fields related to race results.

Is it possible to focus this tab (display its content) via API in a hook?

EDIT : forget this question, after saving a page it stays on the same tab it was before, so I don't need this finally.

Link to comment
Share on other sites

  • 3 weeks later...
On 10/31/2023 at 9:23 PM, bernhard said:

Of course we have now two hooks for the same process, so we can combine those two to one and we end up with two hooks which should all you need:

Be careful with $page->meta(), I'm using it for another purpose and it throws if page doesn't exist in DB, so at first page save (after giving a title): ProcessPageAdd: WireDataDB sourceID must be greater than 0

It's necessary to check ID before:

if ($page->id)
   $page->meta(....);

 

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