2

There are many many questions here on SO about how to properly persist checkbox fields in Laravel after form submission ( example, example, example ), this question is not about that.

Please note I am also well aware of Laravel's old() method, and that it accepts a default value. My question is about a particular case where old() does not seem to work for a checkbox.

I'm working on an edit form, updating data already in the DB. The form will initially be populated with the values from the DB.

The standard approach to re-populating form inputs after failed validation is to use old(). For most input types, something like the following works:

<input type='text' name='unicorns' value='{{ old('unicorns', $model->unicorns) }}'>

On initial page load (before the form is submitted) this works fine. As nothing has been submitted yet, old('unicorns') is null, so the default of $model->unicorns will be used, and the text field's value reflects the current DB state.

After submitting a new value, which fails validation, old('unicorns') will be the new value, even if the new value was an empty string, and again the text field's value reflects the submitted state, as we want.

However this breaks down for checkboxes, when the unchecked checkbox does not appear in the request at all. Consider:

<input type='checkbox' name='unicorns' value='1' @if old('unicorns', $model->unicorns) checked @endif>

On initial page load this works fine, just like for text fields. But after failed validation, for the 2 cases where the checkbox is changed from its initial state:

  • Case 1: DB state 0 (unchecked), submitted state checked. old('unicorns') will be 1, so the test evaluates true, the checkbox is checked - OK!

  • Case 2: DB state 1 (checked), submitted state unchecked. Since old('unicorns') is null, old() falls back to the default value $model->unicorns (1), so the test evaluates true, and the checkbox is checked - but we just unchecked it! FAIL!

(There is no problem in the other 2 cases, where the checkbox is not changed from the state in the DB).

How to solve this?

I initially thought the best way around this would be to test if there has been a validation error - if so, use the old() value, with no default fallback; if not, use old() with default fallback. One way to test for failed validation is to check $errors. This seems to work, but it only works within a view AFAICT, as (from the Laravel docs):

Note: The $errors variable is bound to the view ...

I'd like to create a global helper for this, and $errors won't work there. Anyway, this approach feels ... clunky and too complicated.

Am I missing something? Is there a neat Laravel way of solving this? It seems odd that old() simply does not work for this particular case.

This is not a new problem, how do others handle this?

Don't Panic
  • 13,965
  • 5
  • 32
  • 51
  • What kind of form you are using? Is it a edit form or a new empty form? – Shubham Pokhriyal Dec 19 '17 at 07:30
  • @Shubhampokhriyal It's an edit form; I thought the question made this clear (the existing value `$model->unicorns` is in the mix etc), but I've updated it to make it more so. – Don't Panic Dec 19 '17 at 07:44
  • @Shubhampokhriyal OK - can you explain? How would I re-populate the checkbox with a new state after failed validation? – Don't Panic Dec 19 '17 at 07:48
  • Ok I found a link may be this will help https://stackoverflow.com/questions/23762841/laravel-4-showing-edit-form-with-old-data-input-as-well-as-db-information – Shubham Pokhriyal Dec 19 '17 at 07:51
  • @Shubhampokhriyal That describes using `old()` on text fields, using form model binding. I'm not using either of those things. – Don't Panic Dec 19 '17 at 08:06

6 Answers6

1

This is the solution I referred to in my question. It works well, and 6 months later, it does not seem as clunky as when I posted the question. I'd be happy to hear how others are doing this though.

I have an Html helper, created as described in this question, and aliased in config/app.php as described in the first part of this answer to that question. I added a static method to it:

namespace App\Helpers;
use Illuminate\Support\ViewErrorBag;

class Html {
    /**
     * Shortcut for populating checkbox state on forms.
     *
     * If there are failed validation errors, we should use the old() state
     * of the checkbox, ignoring the current DB state of the field; otherwise
     * just use the DB value.
     *
     * @param string $name The input field name
     * @param string $value The current DB value
     * @return string 'checked' or null;
     */
    public static function checked($name, $value) {
        $errors = session('errors');

        if (isset($errors) && $errors instanceof ViewErrorBag && $errors->any()) {
            return old($name) ? 'checked' : '';
        }

        return ($value) ? 'checked' : '';
    }
}

Now in my views I can use it like so:

<input type='checkbox' name='unicorns' value='1' {{ \Html::checked('unicorns', $model->unicorns) }}>

This correctly handles all combinations of DB and submitted state, on initial load and after failed validation.

Don't Panic
  • 13,965
  • 5
  • 32
  • 51
1

I came across the same problem and found this old post. Thought to share my solution:

<input type="checkbox" name="unicorns" class="form-check-input" id="verified" {{ !old()?$model->unicorns:old('unicorns')?' checked':'' }}>

{{ !old()?$model->unicorns:old('unicorns')?' checked':'' }}

Nested ternaries: The first ternary provides the condition for the second:

When there is no old() array, there is no post, thus the DB value $model->unicorns will be used as the condition like so:

{{ $model->unicorns?' checked':'' }} will correctly evaluate on a value of either 0 or 1

If however old() array is present, we can use the existence of the old('unicorns') variable as the condition like so:

{{ old('unicorns')?' checked':'' }} will correctly evaluate on a value of on or no value.

It's a bit of type juggle and you have the nested ternary, but it works well.

0

I had a similar situation and my solution was to add a hidden field just before the checkbox field. So, in your case it would be something like:

<input type='hidden' name='unicorns' value='0'>
<input type='checkbox' name='unicorns' value='1' {!! old('unicorns', $model->unicorns) ? ' checked' : '' !!}>

That way, when the checkbox is not ticked, a value of 0 for that field will still be present in the post request. When it is ticked then the value of the checkbox will overwrite the value in the hidden field, so the order is important. You shouldn't need to use your custom class with this approach.

Does that work for you?

Jose B
  • 2,030
  • 2
  • 19
  • 17
  • Thanks for trying, but no, this does not answer the question at all. The **very first sentence** of the question describes that I am not asking the question you answered. The question is about correctly maintaining checkbox state on the front end, this answer does not address that. – Don't Panic Jan 10 '18 at 00:48
  • Hmmm, fair enough, sorry I couldn't be of more help :-) – Jose B Jan 10 '18 at 09:04
0

From Don't Panic 's answer, you could do it like this in blade:

<input type="checkbox" class="form-check-input" value='true'
@if ($errors->any())
{{ old('is_private') ? 'checked' : '' }}
@else
{{ isset($post) && $post->is_private ? 'checked' : '' }}
@endif
>

Hope it helps.

Ricky Li
  • 191
  • 1
  • 1
  • 6
0

I do this

 <input id="main" class="form-check-inline"
 type="checkbox" name="position" {{old('position',$position->status)?'checked':''}}>
Rohallah Hatami
  • 525
  • 6
  • 12
  • AFAICT this fails in exactly the same way as Case 2 I describe in the question. If the DB state is `1` (checked), and you *uncheck* the checkbox, then submit, if validation fails and you return to the form, this code will have the checkbox *checked*, which is not correct - you just unchecked it. This does not work? – Don't Panic Mar 07 '19 at 23:06
-1

I actually do a simple ternary operator when persisting checkbox states:

<input name='remember' type='checkbox' value='1' {{ old('remember') ? 'checked' : '' }}>

Edit:

<input name='remember' type='checkbox' value='yes' {{ old('remember') == 'yes' ? 'checked' : '' }}>
plmrlnsnts
  • 1,644
  • 12
  • 10
  • But this won't set the initial state, whatever the value is currently in the DB, right? – Don't Panic Dec 19 '17 at 07:38
  • Of course you have to tweak it a bit when you're dealing with a value stored in a DB. I provided you an example that is usually used in a `create` form. If you would like to retrieve a record then display an `edit ` form you just have change it to `old('remember', YOUR_VALUE)` – plmrlnsnts Dec 19 '17 at 07:44
  • Thanks for the input, but I am not sure you read or understood the question fully. I describe why using the default as you describe won't work. I'd love to be shown I'm wrong, and if you can show me pls do, that's why I'm here! :-) . As it stands, your answer and comment suggest you haven't understood the problem. – Don't Panic Dec 19 '17 at 07:47
  • I think the best way for you to do this is to replace the value with a string. Something like `` – plmrlnsnts Dec 19 '17 at 07:49
  • Whether the value is a string or an int makes no difference. It will still be missing entirely from the request when unchecked, which is the root of the problem. – Don't Panic Dec 19 '17 at 07:52
  • I downvoted; this answer missed the point of the question entirely. – Don't Panic Dec 27 '17 at 05:22
  • Hi @Don'tPanic, I checked few full calendar url's you answered, you provided valuable points there,please take a look on https://stackoverflow.com/questions/62105331/fullcalendar-daygrid-from-and-to-ranges. – Prabha Jun 02 '20 at 15:02
  • @Prabha I'll have a look - but I think this comment is on the wrong question? This is not about Fullcalendar. – Don't Panic Jun 02 '20 at 23:28