Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

States of GRID checkboxes aren't saved for further use #2081

Open
bedengler opened this issue Jul 8, 2023 · 5 comments
Open

States of GRID checkboxes aren't saved for further use #2081

bedengler opened this issue Jul 8, 2023 · 5 comments

Comments

@bedengler
Copy link
Contributor

bedengler commented Jul 8, 2023

The issue

As per discussion on Discord, there's currently no implemented way of saving the state of checkboxes in a GRID. That means, you add checkboxes via "addSelection();", then you get checkboxes. That's it.

If you check some checkboxes and click on another page in paginator (below the grid), all selections get lost.
It's currently impossible to go through all items of a grid and make selections on several pages. Yes, you could increase the IPP, but that's not user friendly or convenient.

My current workaround is to call an AJAX file every time when a checkbox is clicked. I save the state of the checkbox in a session array then.

When the grid reloads, it reads the selected IDs from the session variable and pre-selects them.
That's working fine so far (code below) until it comes to pagination.
As soon as you click on another page, the selections get lost again (although the previously selected checkboxes are still in the session variable - so they still can be used, but aren't there visually which is bad for the UX).

(Partial) Workaround

Now let's discuss ways of how we can achieve the following:

  1. Save the state of checkboxes the atk4 way (e.g. using memorize) - so that we can use the selection for actions.
  2. Check previously checked checkboxes on reload of the GRID (also page reload)
  3. Make sure that selections are maintained when paginating

I added the following method into my extended GRID:

public function setupGrid() {

   $sel = $this->addSelection();

    // Load the selected checkboxes
    // Session variable name is too specific currently for general use - must be something like "$this->id->checkboxes" or "$this->memorize"
    if(isset($_SESSION['checkboxesChecked']))
      $session = $_SESSION[checkboxesChecked];
    else
      $session = NULL;

    // pre-select checkboxes, that have been selected before and are saved in the session
    if (isset($session)) {
      $jsCode = [];
    
      foreach ($session as $id) {
        $lineID = "#".$this->table->name." [data-id=".$id."] .checkbox";
        $jsCode[] = "$('" . $lineID . "').checkbox('check');";
      }
    }

    if (!empty($jsCode)) {
      $this->js(true, new \Atk4\Ui\JsExpression(implode("\n", $jsCode)));

      // This should maintain selections over pagination, but doesn't work currently
      $this->paginator->on('click', '.item', new \Atk4\Ui\JsExpression(implode("\n", $jsCode)));

    // Save clicked checkboxes into session as soon as clicked
    $this->on('click', '.checkbox', new \Atk4\Ui\JsExpression('
      var id = $(this).closest("tr").data("id");
      var checked = $(this).is(".checked");

      $.ajax({
        url: "ajax/saveToSession.php",
        method: "POST",
        data: { 
          idToChange: id, 
          saveWfSelection: 1,
          active: checked,
        }
      });
    '));
  }

use it like this:

$grid = myGrid::addTo($app);
$gridModel = new myModel($app->db);
$grid->setModel($gridModel);
$grid->setupGrid();

The saveToSession.php looks like this:

/** 
 * Function that gets a value and an array. If the value is in the array, it removes it from the array. If the value isn't present in the array, it adds it
 * Function is placed in an external file - I'm just adding it here for the sake of logic
 * Returns the new array
 */
function addRemoveValueFromArray($value, $array, $status = NULL) {
	// $status determines, if the given value should be activated or deactivated
	// Must be true or false, otherwise it will be ignored


	if (in_array($value, $array)) {
	    // If $value exists in the array, remove it
	    $array = array_filter($array, function($item) use ($value) {
	        return $item !== $value;
	    });
	    //$arrayNew = array_values($array);  // Re-index the array
	} 
	else{
	    // If $value does not exist in the array, add it
	    $array[] = $value;
	}

	// Now force the action according to the status
	if ($status == "false") {
		$array = array_filter($array, function($item) use ($value) {
	        return $item !== $value;
	    });
	}
	elseif($status == "true" AND !in_array($value, $array)) {
		$array[] = $value;
	}

	return $array;
}


// Save divers data into session variables
session_start();

$idToChange = $_POST['idToChange'];
	
if(!isset($_SESSION['checkboxesChecked']))
		$_SESSION['checkboxesChecked'] = [];
	
$selectionOld = $_SESSION['checkboxesChecked'];

$selectionNew = addRemoveValueFromArray($idToChange, $selectionOld, $_POST['active']);

$_SESSION['checkboxesChecked'] = $selectionNew;

This should be ment as a basis for discussion on how to implement the functionalities described above but in a more atk4 way.

Let's brainstorm first here and then do it.
I am happy to support and implement it, but I'm not so deep in atk4 core development, so I need your support / ideas on how to bring it into the atk4 core and create a pr please 🙏

@bedengler bedengler changed the title State of GRID checkboxes isn't saved currently for further use State of GRID checkboxes isn't saved for further use Jul 8, 2023
@bedengler bedengler changed the title State of GRID checkboxes isn't saved for further use States of GRID checkboxes aren't saved for further use Jul 8, 2023
@mvorisek
Copy link
Member

mvorisek commented Jul 8, 2023

So if you have a table with 100 entries (20 per page) and you want to go through all of them and select some of them, that's currently impossible.

This will lead to a hidden selection. This is not wanted nor standard. We do not want to allow selection (and actions) on non-displayed entities. The solution is however easy, use larger IPP (items per page) and optimionally narrow the entities by search.

Am I right is issue is solely about persisting the selection? Across what? Grid reload? page reload? Search? -> please comment and edit the description a) to be shorter :), b) to narrow it's scope.

@bedengler
Copy link
Contributor Author

bedengler commented Jul 9, 2023

More details on the issues:

1) Usability

  • In terms of usability, it can make sense to maintain the selection over pagination.
  • Switching to higher IPP forces the user to scroll. Whereas having everything on the whole screen without the need to scroll gives you a better overview.
  • Imagine, there are 115 entries and IPP is at 100 and the user realises that after making all selections. Paginating now would mean losing the selection. Not paginating means he needs to do the action twice.

I understand it's a matter of taste and learning / habits.
Nevertheless I believe the more natural and convenient way is to keep selection over page switching.

Probably we can add an option to addSelected that keeps this flexible?
Something like addSelection($preserveSelectionOnAllPages = false);
If set to true, checkboxes remain saved in the state, even if user switches to another page.

2) Persisting The Selection

  • At least for pagination, but ideally within the grid (using $grid->memorize())
  • When selecting checkboxes on page 1, going to page 2 and coming back to page 1, previous checkboxes should remain checked.
  • A way could be to provide an array to addSelection(), which checks all entries that are matching that array.

Example:

$arr = (0 => "1", 1 => "5");
$grid-> addSelection($array);

Expected behaviour:

  • Checkboxes get added to the grid
  • Checkbox which's ID match 1 and 5 are checked automagically

In my case I need to keep the state of checked checkboxes over a 4 step wizard.
So whenever the user jumps back to the step with the grid, where he made some selections before, these selections should remain checked.
addSelection($array); could solve that as well...

Example:
$grid->addSelection($wizard->recall($checkedCheckboxes));

3) Save The Selection

  • Something like $grid->getSelection(); that returns all checked checkboxes.
  • If previously mentioned "$preserveSelectionOnAllPages == false;" it returns just the selection of the page.
  • Otherwise it returns the whole selection. Use $grid->memorize(); to save the current state

4) Reset The Selection

A button to reset selection. If "$preserveSelectionOnAllPages == true;" just unsetting the variable that contains the selection.

@bedengler
Copy link
Contributor Author

bedengler commented Jul 9, 2023

Steps to do:

  • Enhance addSelection()
/**
  * @param $checkedArray contains an array with IDs that should be checked
  * @param $preserveSelectionOnAllPages if set to true, the checked checkboxes remain checked when switching to another page
  */
public function addSelection($checkedArray, $preserveSelectionOnAllPages = false) {}
  • Memorize selection
    Don't know the best way to do this currently. I believe we could use $grid->memorize(); as soon as a checkbox is clicked (see the initial thread for code examples).

  • Make selection recallable for further use. Something like
    $grid->getSelection();

  • Maintain selection over pagination

  • Reset selection - if $preserveSelectionOnAllPages == true -> delete memorized array
    Something like
    $grid->deleteSelection();

@mvorisek
Copy link
Member

mvorisek commented Jul 9, 2023

Imagine, there are 115 entries and IPP is at 100 and the user realises that after making all selections. Paginating now would mean losing the selection. Not paginating means he needs to do the action twice.

This is legit point. IPP change should not reset the selection.

Memorizing the selection across (whole) page load is however probably not wanted, as there can be multiple actions, so the memorized selection can be for another/previous action and can no longer make sense.

Also I am strongly againts any hidden selection, so page (in sense of paginator) change should probably not hold the selection from another no longer visible page.

API for setting an array of selection/getting the current selection might be good.

What are your usecases? Maybe you might want to store the checkebox state within entity and use inline/in-cell edit.

@bedengler
Copy link
Contributor Author

bedengler commented Jul 9, 2023

This is legit point. IPP change should not reset the selection.

Not only that, what, if the max. selectable IPP is 100 and you have 115 entries. No chance to get that done in a user friendly way...

Memorizing the selection across (whole) page load is however probably not wanted, as there can be multiple actions, so the memorized selection can be for another/previous action and can no longer make sense.

Probably yes. Although in some cases, it makes things easier. So we should be flexible (that's what I love about atk4: it's flexibility)...

What are your usecases? Maybe you might want to store the checkebox state within entity and use inline/in-cell edit.

I have a client who has 4.000 adspaces.
In 2006 I have written for them an entire management system including invoicing etc.
We adapted this system continuously.
Nevertheless this system is a bit outdated now, so we decided to relaunch it, put in all the experience we gathered within the last 15 years about their workflow and what they want, which processes work best for them etc.
I decided to go with atk4.

To create offers for their clients, they want a list of adspaces that's filterable and searchable.
atk4's wizard is awesome for that process.
In the first step they get a list (grid) of all adspaces. They filter / search e.g. the location, make some selections of adspaces that should be part of the offer.
Then they change the filters (probably another city) and continue making selections.

When they believe, they are ready for the next step, they go to the next step in the wizard.
There they can make adaptions to each of the selected entries (e.g. change the price, date etc.).

It happens, that they miss an entry, so they go one step back to the list again, search for another adspace and add it to the selection.

If the selection is reset every time they search, switch page or go to the next step, it would be a productivity and usability nightmare.

And the other way: if they could just go seamlessly back and forth that would make things easy and user friendly (in this case).

I hope you see my points now 😊

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants