0

Using CakePHP 3.5.3

Hi,

// WHAT I HAVE

I have a users index view which displays all the relevant users in the database. At the top of the page I have a select list which allows the user to select the deactivate option and on each users row I have a checkbox.

// WHAT I'M TRYING TO DO

The user can check the checkboxes they want then select the deactivate option from the select list and click submit. The submit sends the script to my actions method in my users controller. In the actions method I check to see if the deactivate option has been selected and if it hasn't I send a message back to the user to that effect. After this I check to see if a checkbox has been checked and if none have I send a message back to the user to that effect. If at least one checkbox has been checked and the deactivate option has been selected I then identify which checkboxes have been checked and update the database to deactivate for those choosen users.

// ON USERS INDEX VIEW I DECLARE MY CHECKBOX AS BELOW

<?= $this->Form->checkbox("checkboxes[]", ['value' => $user->id]); ?>

// ACTIONS METHOD

public function actions()
{  
    if (empty($this->request->getData('action'))) {
        $this->Flash->sys_error('', [
            'key' => 'message',
            'params' => [
                'p1' => __('Please select an action!')
            ]
        ]);    

        // Send to index action for page load
        $this->setAction('index');
    }
    else {

        $checkboxArray = $this->request->getData('checkboxes');
        foreach($checkboxArray as $key => $val):

            echo 'key is ' . $key . "<br />";
            echo 'val is ' . $val . "<br />";

            if (empty($val)) {
               unset($checkboxArray[$key]);
            }
        endforeach;

        if (empty($checkboxArray)) {
            $this->Flash->sys_error('', [
                'key' => 'message',
                'params' => [
                    'p1' => __('Please select a user!')
                ]
            ]);

            // Send to index action for page load
            $this->setAction('index'); 
        }
        else {

            foreach($this->request->getData('checkboxes') as $userID):
                if ($userID != 0) {
                    // UPDATE THE DATABSE
                    echo 'in update the database ' . '<hr />';
                }
            endforeach;

            // Send to index action for page load
            $this->setAction('index');
        }
    }   
}

Even though I'm not 100% sure if this the best or correct way to do this it does work except for the following error which occurs on the checkbox in the users index view each time I invoke $this->setAction('index'); The stacktrace from the log is as follows:

Notice (8): Array to string conversion in [C:\xampp\htdocs\app\vendor \cakephp\cakephp\src\View\Widget\CheckboxWidget.php, line 81] Request URL: /users/actions Referer URL: https://localhost/app/users Trace: Cake\Error\BaseErrorHandler::handleError() - CORE\src\Error\BaseErrorHandler.php, line 153 Cake\View\Widget\CheckboxWidget::_isChecked() - CORE\src\View\Widget\CheckboxWidget.php, line 81 Cake\View\Widget\CheckboxWidget::render() - CORE\src\View\Widget\CheckboxWidget.php, line 51 Cake\View\Helper\FormHelper::widget() - CORE\src\View\Helper\FormHelper.php, line 2775 Cake\View\Helper\FormHelper::checkbox() - CORE\src\View\Helper\FormHelper.php, line 1570 include - APP/Template\Users\index.ctp, line 193 Cake\View\View::_evaluate() - CORE\src\View\View.php, line 1196 Cake\View\View::_render() - CORE\src\View\View.php, line 1157 Cake\View\View::render() - CORE\src\View\View.php, line 765 Cake\Controller\Controller::render() - CORE\src\Controller\Controller.php, line 623 Cake\Http\ActionDispatcher::_invoke() - CORE\src\Http\ActionDispatcher.php, line 125 Cake\Http\ActionDispatcher::dispatch() - CORE\src\Http\ActionDispatcher.php, line 93 Cake\Http\BaseApplication::__invoke() - CORE\src\Http\BaseApplication.php, line 103 Cake\Http\Runner::__invoke() - CORE\src\Http\Runner.php, line 65 Cake\Http\Middleware\CsrfProtectionMiddleware::__invoke() - CORE\src\Http\Middleware\CsrfProtectionMiddleware.php, line 106 Cake\Http\Runner::__invoke() - CORE\src\Http\Runner.php, line 65 Cake\I18n\Middleware\LocaleSelectorMiddleware::__invoke() - CORE\src\I18n\Middleware\LocaleSelectorMiddleware.php, line 62 Cake\Http\Runner::__invoke() - CORE\src\Http\Runner.php, line 65 Cake\Routing\Middleware\RoutingMiddleware::__invoke() - CORE\src\Routing\Middleware\RoutingMiddleware.php, line 107 Cake\Http\Runner::__invoke() - CORE\src\Http\Runner.php, line 65 Cake\Routing\Middleware\AssetMiddleware::__invoke() - CORE\src\Routing\Middleware\AssetMiddleware.php, line 88 Cake\Http\Runner::__invoke() - CORE\src\Http\Runner.php, line 65 Cake\Error\Middleware\ErrorHandlerMiddleware::__invoke() - CORE\src\Error\Middleware\ErrorHandlerMiddleware.php, line 95 Cake\Http\Runner::__invoke() - CORE\src\Http\Runner.php, line 65 DebugKit\Middleware\DebugKitMiddleware::__invoke() - ROOT\vendor\cakephp\debug_kit\src\Middleware\DebugKitMiddleware.php, line 52 Cake\Http\Runner::__invoke() - CORE\src\Http\Runner.php, line 65 Cake\Http\Runner::run() - CORE\src\Http\Runner.php, line 51 Cake\Http\Server::run() - CORE\src\Http\Server.php, line 81 [main] - ROOT\webroot\index.php, line 40

// SOLUTION

I have managed to eliminate this error if I declare my checkbox as below:

// FROM
<?= $this->Form->checkbox("checkboxes[]", ['value' => $user->id]); ?>

// TO
<?= $this->Form->checkbox("checkboxes[$user->id]", ['value' => user->id]); ?>

// MY QUESTION

But this means I'm replacing the keys in the array with the user id and I don't know if this is considered as OK or if there's a better more professional way of doing it.

Any help would be greatly appreciated. Z.

@SamHecquet - Thanks for the edit, I'll know for next time how to format the stacktrace.

@Derek - As requested see below - I won't include the whole page as a lot of it I believe is unnecessary but if you want more than I have posted just let me know.

// Select list from the element

<!-- Start form -->
<?php
// Declare individual page forms
if ($page === 'usersPage') {
    // Users form
    echo $this->Form->create(null, [
        'url' => ['controller' => 'Users', 'action' => 'actions'],
        'novalidate' => true
     ]); 
}

<?php
// Declare individual page select lists
if ($page === 'usersPage') {
    // Users form
    $options = [
        '' => '--Select Action--', 
        'a' => 'Deactivate User/s'
    ];
}                    
else{
    header ("Location: ".$this->Url->build(["controller" => "Pages", "action" => "display", 'blank']));
    exit(0);    
}

$this->Form->label('action', '');
echo $this->Form->input('action', [
    'type' => 'select',
    'options' => $options, 
    'class' => 'ns-typ-2 ix-fa',
    'label' => false
]);                    
?>

<?= $this->Form->submit(__('Go'), [
    'class' => 'submit-ix-go'
]); 
?> 

// INDEX USERS

<!-- Headings -->
<div class="grid-ix-1 ix-r-top">

    <!-- Checkbox -->
    <div class="ix-usr-checkbox">
         <div class="ix-h">
             <div class="ix-h-cb">                    
                <input type="checkbox" onClick="toggle(this)" >
             </div>
         </div> 
     </div>

     <!-- Role -->
     <div class="ix-usr-role">
         <div class="ix-h">       
             <h2 class="ix-h2"><?= $this->Paginator->sort('Users.role', __('Role')) ?></h2>
         </div>
     </div>
</div>


<!-- Cells-->
foreach ($users as $user): ?>

    <div class="grid-ix-1">

        <!-- Checkbox -->
        <div class="ix-usr-checkbox">                       
            <div class="ix-c-cb">
                <?= $this->Form->checkbox('checkboxes[]', ['value' => $user->id]); ?>
            </div>
        </div>

        <!-- Role -->
        <div class="ix-usr-role">
            <div class="ix-usr-role-text"><?= h($user->role) ?></div>
        </div>

    </div>

<?php endforeach; ?>

<!-- Form ends -->              
<?= $this->Form->end() ?> 

@Derek - Please see the multiple checkbox issue.

When I used the solution from Dave the multiple checkbox selection worked on page load but not after I invoked the below:

// Send to index action for page load
$this->setAction('index'); 

I fixed this by redirecting instead of using setAction as below:

// Redirect to index page
return $this->redirect(['action' => 'index']);

But with your solution the multiple checkbox selection functionality did not work on page load or after any of the redirects. I use the following code to achieve this.

// Javascript 
// Check and uncheck all check boxes on index pages
function toggle(source) {
    checkboxes = document.getElementsByName('checkboxes[]');
    for(var i=0, n=checkboxes.length;i<n;i++) {
        checkboxes[i].checked = source.checked;                     
    }
} 

// And then the heading checkbox is:
<input type="checkbox" onClick="toggle(this)" >

// And of course the cells checkbox
<?= $this->Form->checkbox('checkboxes.' . $k, ['value' => $user->id]); ?>

If you help with the javascript great but if not if you advise that a solution can be found I'm happy to post in the stackoverflow javascript section for a solution which would mean I could go with your solution and not edit the framework code.

Thanks in advance for any help or advice. Z

Zenzs
  • 138
  • 13

2 Answers2

0

It's providing a pretty clear answer right there in the top of the error:

Array to string conversion in [C:\xampp\htdocs\app\vendor \cakephp\cakephp\src\View\Widget\CheckboxWidget.php, line 81

Simply find line 81 in your CheckboxWidget, and fix the specified problem.

If you're not sure how, simply searching "php array to string conversion error", provides many results, one of which describes your problem very specifically regarding inputs named checkboxes[].

Dave
  • 28,833
  • 23
  • 113
  • 183
  • Hi Dave, thanks for the help. I never would of imagined I'd ever of had to edit something in the framework but we live and learn. I edited line 81 which was: return (string)$data['val'] === (string)$data['value']; which I've edited to: return $data['val'] === $data['value']; My error is eliminated. – Zenzs Oct 23 '17 at 14:38
  • @Zenzs, you probably shouldn't edit the framework. You should look at line 81, and find out what's causing the issue, then modify YOUR code to make it work as it's expected by the framework. – Dave Oct 23 '17 at 17:49
  • Hi Dave, Understood, I live and learn some more. If I'm honest looking at the answer Derek has provided incuding the js checkbox fix I know I would of never been able to sort this out even after looking at the CheckboxWidget.php code. I hope you can understand that I'll have to tick Dereks as the answer. Thank you for your help anyway, at least I've learnt some more. Z. – Zenzs Oct 23 '17 at 18:46
  • It's no problem. Check any answer that is the best in your opinion! At a glance, I think you're making it more difficult by repeating through each option to create the checkboxes. The online CakePHP book should show you how to do this automatically. But glad to hear you have a working solution. – Dave Oct 23 '17 at 22:50
  • Hi Dave, Thanks for the heads up but unfortunately I'll have to save trying to learn that for another day as I've got to crack on. – Zenzs Oct 24 '17 at 11:36
0

Try changing the foreach to:

<?php foreach ($users as $k => $user): ?>

    <div class="grid-ix-1">

        <!-- Checkbox -->
        <div class="ix-usr-checkbox">
            <div class="ix-c-cb">
                <?= $this->Form->checkbox('checkboxes.' . $k, ['value' => $user->id, 'id' => 'checkbox' . $k, 'class' => 'userCheckbox']); ?>
            </div>
        </div>

        <!-- Role -->
        <div class="ix-usr-role">
            <div class="ix-usr-role-text"><?= h($user->role) ?></div>
        </div>

    </div>

<?php endforeach; ?>

Javascript function

function toggle(source) {
    checkboxes = document.getElementsByClassName('userCheckbox');
    for(var i=0, n=checkboxes.length;i<n;i++) {
        checkboxes[i].checked = source.checked;                     
    }
} 
Derek
  • 850
  • 9
  • 19
  • Hi Derek, That did work and without having to change the CheckboxWidget.php framework code which I think is a neater solution but the toggleing of all the checkboxes does not work. I've explained this in my original post. If you or anyone can help on this that would be great and I can go with your solution. – Zenzs Oct 23 '17 at 17:09
  • Hi Derek, I tried your edit but unfortunately the multiple checkbox selection still does not work. I've included the js I'm using in my first post which will hopefully help but if not even if you advise posting this issue in the stsckoverflow javascript section I'd be happy to do that. Many thanks. Z. – Zenzs Oct 23 '17 at 17:32
  • Set a class for them and getElemenstByClass for the javascript – Derek Oct 23 '17 at 17:37
  • Hi Derek, I noticed your edit has an id of checkbox so I changed my js to: checkboxes = document.getElementById('checkbox[]'); but I still can't get it to work. – Zenzs Oct 23 '17 at 17:39
  • Hi Derek, I changed checkbox in the view to: = $this->Form->checkbox('checkboxes.' . $k, ['value' => $user->id, 'class' => 'cb' . $k]); ?> and the js to: checkboxes = document.getElementByClass('cb'); but unfortunately still not working. – Zenzs Oct 23 '17 at 17:46
  • Hi Derek - Beautiful, all the checkboxes can now be selected. For other people I did have to change: getElementsByClass to: getElementsByClassName. A minor detail which at least I was able to sort out. Thank you very much for all your help. Z – Zenzs Oct 23 '17 at 18:34