2
votes

I have a many-to-many relationship and am building a form to enable updating this.

The relationship is set using a series of checkboxes.

The models are Post and Category and have conventional mtm relationships defined.

In my edit post form I get a list of categories as well as eager loading the currently selected categories into my post model. The form looks like this:

{!! Form::model($post, ['route' => ['admin.posts.update', $post->id], 'method' => 'PATCH']) !!}

// .. some fields

@foreach ($categoryList as $categoryId => $category)
    <div class="checkbox">
    <label>
    {!! Form::checkbox('category[]', $categoryId, in_array($categoryId, $selected_categories)) !!} {{ $category }}
    </label>
    </div>
@endforeach

Selected categories is defined like this:

$selected_categories = $post->categories->lists('id');

...which gives a simple array of the ids of only the related categories.

This results in all category checkboxes being checked by default, despite the in_array($categoryId, $selected_categories)) statement, which only evaluates to true when a category id is related. If I change the first (name) parameter of the checkboxes to be something other than category[] they are checked correctly.

Why is the form model binding checking all checkboxes, and why, even if I try and override it by passing the 3rd argument to the checkbox does it have no effect?

1

1 Answers

1
votes

There seems to be a bug in the generate checkbox method of Illuminate's FormBuilder. When you pass a multi choice element (category[]) to the method, it looks for the word "on" in that array, and of course, it will find it for every checkbox that it generates, even if you only checked and posted one checkbox.

protected function getCheckboxCheckedState($name, $value, $checked) {
    if (isset($this->session) && ! $this->oldInputIsEmpty() && is_null($this->old($name))) return false;
    if ($this->missingOldAndModel($name)) return $checked;
    $posted = $this->getValueAttribute($name);

    return is_array($posted) ? in_array($value, $posted) : (bool) $posted;
}

Let's say you only checked one checkbox. The posted checkbox array:

checkbox[0]
checkbox[1]
checkbox[2]
checkbox[3] = 'on'     // only value that is ever posted
checkbox[4]
checkbox[5]

Array looks like this: array('checkbox' => [3 => 'on'])

Thus in_array('on', [3 => 'on']) will return true for all checkboxes.