I think you may have to use indexes as you said?
<input type="text" name="items[0][name]">
<input type="text" name="items[0][route]">
<input type="text" name="items[1][name]">
<input type="text" name="items[1][route]">
The Laravel Documentation explains how to Validate nested attributes, but it doesn't explain how to create them.
I'm doing something like this:
<input type="text" name="items[][name]">
<input type="text" name="items[][route]">
But that gives me the following structure:
array(
0 => array (
"name" => "Home"
),
1 => array(
"route" => "/"
),
2 => array(
"name" => "About"
),
3 => array(
"route" => "/about"
)
)
Instead of:
array(
0 => array (
"name" => "Home",
"route" => "/"
),
1 => array(
"name" => "About",
"route" => "/about"
)
)
Any ideas?
These items are part of a rearrangeable structure that dynamically generated, so hopefully I won't have to sort out indices.
I think you may have to use indexes as you said?
<input type="text" name="items[0][name]">
<input type="text" name="items[0][route]">
<input type="text" name="items[1][name]">
<input type="text" name="items[1][route]">
@bashy is right. If you just put an empty bracket then it creates a new array item for every input instead of for each set of inputs like you want.
When I need to dynamically add new inputs to a form like this, I clone the last set and then use something like this (jQuery)
clone.find('input').each(function(i, input) {
input.name = input.id = input.id.replace(/\d+/, function(n) { return ++n });
input.value = "";
});
Assuming the index is the only number in the input's name this will replace the number with n+1 and clear the value.
@bashy & @eheitman I figured as much. Right now, I have each input group in a .form-item div, and I'm using jQuery UI as my reordering tool.
So now I'm dealing with the following issues:
Has this problem been tackled in the past? What are the existing solutions?
Has this problem been tackled in the past? What are the existing solutions?
@tylernathanreed Honestly, I'm not sure. Usually if I need a few dynamic elements in a form I just reuse bits of script I have in other projects (like what I pasted above).
If your project's frontend is complex enough, you might want to look into a Javascript framework like Angular or Vue.
@eheitman I ended up doing something like this:
<form method="POST" action="{{ route('menus.store') }}">
{{ csrf_field() }}
{{ method_field(method('menus.store')) }}
<input type="text" name="title" placeholder="Enter Menu Title..." class="form-control" value="{{ old('title') }}">
<input type="text" name="class" placeholder="Enter Menu Class... (e.g. 'menu-header')" class="form-control" value="{{ old('class') }}">
<label for="items" class="form-label">Items</label>
<ul id="form-items" class="list-group">
@foreach((array) old('items') as $index => $item)
@include('models.menus.items.template', compact($index, $item))
@endforeach
</ul>
<a id="add-form-item" class="btn btn-primary form-control" href="#">
<i class="glyphicon glyphicon-plus"></i>
<span>Create Menu Item</span>
</a>
<button type="submit" class="btn btn-success form-control">Create Menu</button>
</form>
Where the template is the view contained the nested form attributes, and the #add-form-item button is picked up by my JavaScript to dynamically manage the form.
Here's my template:
<?php $name = 'items[' . (isset($index) ? $index : '{0}'); ?>
<li class="list-group-item form-group ui-state-default">
<i class="btn btn-bare btn-highlight glyphicon glyphicon-move"></i>
<label for="name" class="form-label">Name</label>
<input type="text" name="{{ $name }}[name]" placeholder="Enter Item Name..." class="form-control" value="{{ $item['name'] or '' }}">
<label for="route" class="form-label">Route</label>
<input type="text" name="{{ $name }}[route]" placeholder="Enter Item URL..." class="form-control" value="{{ $item['route'] or '' }}">
<label for="permissions" class="form-label">Permissions</label>
<select id="permissions" name="{{ $name }}[permissions][]" data-placeholder="Permissions..." multiple class="chosen-select form-control">
@foreach($permissions as $permission)
<?php
// Intialize the Selected Status
$selected = false;
// Make sure the Item has Permissions Set
if(isset($item['permissions']))
{
// Determine the Array of Permission Names
$array = isset($item['permissions'][0]['name']) ? array_pluck($item['permissions'], 'name') : $item['permissions'];
// Determine whether or not the Permission is Selected
$selected = in_array($permission->name, $array);
}
?>
<option value="{{ $permission->name }}"{{ $selected ? ' selected' : '' }}>{{ $permission->display }}</option>
@endforeach
</select>
<input type="hidden" name="{{ $name }}[order]" value="{{ $index or '{0}' }}" data-name="order">
<i class="btn btn-bare btn-highlight glyphicon glyphicon-remove" data-id="remove-form-item"></i>
</li>
And now for the actual JavaScript:
<script src="/media/js/forms.js"></script>
<script>
$('#form-items').listable({
template: '#form-item-template',
add: '#add-form-item',
remove: '[data-id=remove-form-item]'
});
</script>
Which pulls in my jQuery extension:
jQuery.fn.extend({
listable: function(options) {
return this.each(function() {
// Determine the Container for the Items
var container = $(this);
// Create the Update Function
var update = function() {
container.children().each(function(index) {
// Replace each Name Index
$('[name]', this).each(function() {
this.name = this.name.replace(/\[\{?\d+\}?\]/, '[' + index + ']');
});
// Replace the Hidden Order Inputs
$('input[data-name=order]', this).each(function() {
this.value = index;
});
});
};
// Make sure the Container is Sortable
container.sortable({
containment: 'parent',
update: update
});
// Disable Text Selection within the Container (Excludes Form Items)
container.disableSelection();
// Determine the Template for the Items
var template = $(options.template).html();
// Determine the Add Button for the Container
var add = $(options.add);
// Register the Add Buttons
add.click(function() {
// Determine the Number of Items
var count = container.children().length;
// Determine the Element being Appended
var element = $(template.replace(/\{0\}/g, count));
// Determine the Remove Button
var remove = $(options.remove, element[0]);
// Register the Remove Button
remove.click(function() {
// Remove the Element
element.remove();
// Update the Indices
update();
});
// Append the Element to the Container
container.append(element);
});
// Register existing Remove Buttons
container.children().each(function() {
// Determine the Element bing Appended
var element = $(this);
// Determine the Remove Button
var remove = $(options.remove, element[0]);
// Register the Remove Button
remove.click(function() {
// Remove the Element
element.remove();
// Update the Indices
update();
});
});
});
}
});
Where the $().sortable comes from jQuery UI.
I know that this is quite a bit (in fact, I've excluded several formatting elements). Is this something that Vue.js could shorten for me?
This is pretty hectic right now, and I'm about to make it worse, with infinite nesting (currently, this is just one layer).
Please or to participate in this conversation.