In laravel, is there some way of nesting related resources in a form?
Say I have this:
class Person extends Eloquent {
public function addresses() {
return $this->hasMany("Address");
}
}
class Address extends Eloquent {
public function person() {
return $this->belongsTo("Person");
}
}
and I want a Person form to collect information about that Person's Addresses. Does laravel facilitate this in a way that is equivalent to Rails' accepts_nested_attributes_for :address and fields_for :address?
I'd just like something simple where I can include the Address fields with the results of the Person form, since the Address doesn't really exist apart from the Person. Does this make sense?
== EDIT ==
This is hypothetical code
What I'm looking for is something that would resemble this:
{{ Form::model(new Person, array("action" => "admin\PersonController#store", "method" => "POST")) }}
{{ Form::text("name", array(...)) // <input name='person[name]' ... /> }}
{{ Form::email("email", array(...)) // <input name='person[email]' ... /> }}
{{ Form::fields_for("addresses"/* Would be name of relation */) }}
{{ Form::text("street_address") // <input name='person[addresses][][street_address]' ... /> }}
{{ Form::close_fields() }}
{{ Form::close() }}
You are on the right track with the input names.
Form
// Form open, Person fields, etc...
<h2>Addresses</h2>
#foreach ($addresses as $address)
<fieldset>
{{ Input::text('addresses['.$address->id.'][address_1]', $address->address_1) }}
{{ Input::text('addresses['.$address->id.'][address_1]', $address->address_2) }}
{{ Input::text('addresses['.$address->id.'][city]', $address->city) }}
{{ Input::text('addresses['.$address->id.'][state]', $address->state) }}
{{ Input::text('addresses['.$address->id.'][zip]', $address->zip) }}
</fieldset>
#endforeach
// Form Close
If you want to add addresses you'll need to generate some random key to use instead of the address id. This will keep the fields grouped.
Controller Logic
This is how I would handle input, using 'fillable' to filter the data going into the models.
// Get the Person model, fill, save, etc...
$addressIds = array();
foreach (Input::get('addresses', array()) as $id => $addressData)
{
$address = Address::find($id) ?: new Address;
$address->fill($addressData);
$address->save();
$addressIds[] = $address->id;
}
$changes = $person->addresses()->sync($addressIds);
// Delete the unused addresses
foreach ($changes['detached'] as $detachedAddressId)
{
$address = Address::find($detachedAddressId);
if (!empty($address)) $address->delete();
}
Now you can use the package "modelform" to create a form for various models or even a set of forms for relations.
https://github.com/andersondanilo/modelform
You should manage all of this from your controller. Collect the data from the form and use them as you please:
// Controller Code
$person = new Person(array(
'field_1' => Input::get('field_1'),
'field_2' => Input::get('field_2'),
.............
));
$person->save();
$address = new Address(array(
'field_x' => Input::get('field_x'),
'field_y' => Input::get('field_y'),
.............
));
$person->addresses()->save($address);
See it in the docs
Related
I've been working on migrating several of our forms to Laravel, but there's one last step I'm not entirely sure on how to go about. I have a form that does an Insert into a database, but instead of just having 2 pages--the form and the submission page--I have 3: the form, a confirmation and a submission page.
Here is what I have at the moment:
Routes:
Route::any('application/housing-form', array('as'=>'application.form', 'uses'=>'ApplicationController#form'));
Route::post('application/confirmation', array('as'=>'application.confirmation', 'uses'=>'ApplicationController#confirmation'));
Route::post('application/submit', array('as'=>'application.submit', 'uses'=>'ApplicationController#submit'));
ApplicationController:
public function form()
{
$application = new Application;
return View::make('application/form')->with(array('application'=>$application));
}
public function confirmation()
{
$input = Input::all();
//More here?
return View::make('application/confirmation')->with(array('input'=>$input));
}
public function submit() {
$input = Input::all();
DB::table('application')->insert(
array(
<field1> => $input('field1')
...
)
);
return View::make('application/submit');
}
Views:
//form
{{ Form::model($application, array('route'=>'application.confirmation')
//inputs
{{ Form::submit('Continue') }}
{{ Form::close() }}
//confirmation
{{ Form::open(array('route'=>'application.form') }}
{{ Form::submit('Back to my information') }}
{{ Form::close() }}
{{ Form::open(array('route'=>'application.submit') }}
{{ Form::submit('Submit') }}
{{ Form::close() }}
//submission
<p>Thank you for your submission!</p>
What I am unsure about is how to persist the data from the form through the confirmation page and into the submission page. From what I can tell, I can see a few options:
Reflash all of the input
Use a hidden field (or fields) to send the information
Insert the information into the database in the confirmation page and just do an update with an in-between query with the information.
I'm pretty sure it would be the first one: reflashing the data. But if so, I'm not sure where you're actually supposed to call Session::flash or Session::reflash. Or how many times I need to do it to get it through all of the requests. Any suggestions on how to go about that, or how to streamline the rest of the form would be greatly appreciated.
One extra note as well is that this particular form deals with a large number of input fields (around 60). That's part of why I want to avoid having to request each individual field to a minimum.
What I would do is to flash the input to the session in order to repopulate the form. This can be achieved by using the Input::flash() method like so:
public function confirmation(){
Input::flash(); //this will store the input to the session
return View::make('application/confirmation');
}
Then in your view, use the Input::old() method to retrieve input data from the previous request:
{{ Form::text('fieldname', Input::old('fieldname')) }}
I have a edit form with some checkboxes that I am trying to make checked when the associated many to many relationship is established.
Distributors belong to many beers
Beers belong to many distributors
in my controller I have:
$breweries = Brewery::lists('name', 'id');
$all_dist = Distributor::all();
$beer = Beer::find($id);
$distributions = [];
foreach ($beer->distributors as $distributor)
{
$distributions[$distributor->id] = BeerDistribution::where('beer_id', '=', $beer->id)
->where('distributor_id', '=', $distributor->id)->first()->price;
}
return View::make('beers.edit', ['beer' => $beer, 'distributors' => $all_dist, 'distributions' => $distributions, 'breweries' => $breweries, 'styles' => $styles]);
and I in my edit form I have:
{{ Form::model($beer, ['route' => ['beers.update', $beer->id], 'method' => 'PATCH']) }}
#foreach ($distributors as $distributor)
<?php $carried = in_array($distributor->id, array_keys($distributions)) ? true : false ?>
{{ Form::checkbox('distributors[]', $distributor->id, $carried); }}
{{ Form::label($distributor->name) }}
{{ Form::label('price' . $distributor->id, 'Retail:') }}
<?php $price = $carried ? $distributions[$distributor->id] : null ?>
{{ Form::text('price' . $distributor->id, $price ) }}
#endforeach
{{ Form::submit('Save') }}
{{ Form::close() }}
Basically I am passing an associated array of each distributor_id => price. This array also tells me which distributors the beer already belongs to so that I can mark those checked off in my edit form.
Here's where things get wonky. When I load this form, all the checkboxes will be checked no matter what. If I change my controller loop to this:
foreach ($beer->distributors()->lists('distributor_id') as $distributor_id)
Then I can do create my array.
Why does calling $beer->distributors in the controller would result in all the checkboxes being checked?
The problem is with the last argument : $carried
{{ Form::checkbox('distributors[]', $distributor->id, [ "checked" => $carried ]); }}
Pass the last parameter as array, and tell it exactly which attribute to modify and the attribute value.
I am brand new to Laravel, and following a super basic tutorial.
However the tutorial did not come with an edit record section, which I am attempting to extend myself.
Route:
Route::controller('admin/products', 'ProductsController');
Controller:
class ProductsController extends BaseController
{
public function getUpdate($id)
{
$product = Product::find($id);
if ($product) {
$product->title = Input::get('title');
$product->save();
return Redirect::to('admin/products/index')->with('message', 'Product Updated');
}
return Redirect::to('admin/products/index')->with('message', 'Invalid Product');
}
..ECT...
I realise the controller is requesting an ID to use, but I cannot figure out how to pass it a product ID when the form is posted/get.
Form:
{{Form::open(array("url"=>"admin/products/update",'method' => 'get', 'files'=>true))}}
<ul>
<li>
{{ Form::label('title', 'Title:') }}
{{ Form::text('title') }}
{{ Form::hidden('id', $product->id) }}
..ECT...
{{ Form::close() }}
my initial idea was to pass the product id within the form URL like:
{{Form::open(array("url"=>"admin/products/update/{{product->id}}", 'files'=>true))}}
But no luck with that either.
The error I get is:
Missing argument 1 for ProductsController::postUpdate()
Interestingly if I type directly into the URL:
http://localhost/laravel/public/admin/products/update/3
It works and the item with id 3 is altered fine.
So can anyone help and inform me how to pass the id with a form?
Thanks very much
The first Problem here ist the following:
{{Form::open(array("url"=>"admin/products/update/{{product->id}}", 'files'=>true))}}
the {{product->id}} is wrong in two ways:
it should be {{$product->id}}
BUT it wouldn't work anyway because the inner {{..}} inside of the {{Form::...}} won't be recognized since it is inside a string and therefore part of the string itself.
You either have to write it this way:
{{Form::open(array("url"=>"admin/products/update/".$product->id, 'files'=>true))}}
or you give your route a name in your routes.php file and do it this way:
{{Form::open(array('route' => array('route.name', $product->id, 'files'=>true)))}}
I prefer the second way.
You also might want to look into Form Model Bingin
I want to use form model binding in Laravel. The following (simplified) example works fine:
{{ Form::model($user, array('class'=>'form-horizontal')) }}
{{ Form::token() }}
{{ Form::label('email', 'Email Address') }}
{{ Form::text('email') }}
{{ Form::close() }}
However, I want to use arrays in the name attributes, as is pretty standard nowadays. In other words, have user[email] as the field name, so that I get all the form elements in one array in the backend.
Is this possible with model binding? When I use {{ Form::text('user[email]') }} the email does not get filled in. I tried adding array('user'=>$user) in the Form::model function in case it needed a nested value, but no luck.
Form::model(array('user' => $user)) is the correct solution, BUT unfortunately the implementation of form model binding is pretty bad as it does not easily work on a nested, mixed set of arrays and objects. See https://github.com/laravel/framework/pull/5074
You could try Form::model(array('user' => $user->toArray())) or Form::model((object) array('user' => $user)).
You could do something like this assuming that you would have a single $user and multiple $types
Form::macro('userTypes', function($user,$types)
{
foreach ($types as $type) {
$concat = $user . "_" . $type;
return '<input type="{$type}" name="{$concat}">';
}
});
And customize the output with your form style, even adding more complexity to the function might be required.
And then simply calling it for example
$user = "johndoe";
$types = array("email","text");
Form::userTypes($user,$types);
That would result in
<input type="email" name="johndoe_email">
<input type="text" name="johndoe_phone">
If you want to do it in a single line and assuming that you would have a single $user and a single$type you could do something like
Form::macro('userType', function($user,$type)
{
return '<input type="{$type}" name="{$user[$type]}">';
});
And with the call
$user = [ "mail" => "some_mail_value" ];
Form::userType($user,"mail");
Would result in
<input type="mail" name="some_mail_value">
Or perhaps you'd like something that would work with a single $user key-value array as :
Form::macro('userType', function($user)
{
$keys = array_keys($user);
foreach ($keys as $key => $value) {
return '<input type="{$key}" name="{$value}">';
}
});
And with the call
$user = ["mail" => "mail_value" , "text" => "text_value"];
Form::userType($user);
That would result in
<input type="mail" name="mail_value">
<input type="text" name="text_value">
And finally I didn't find a direct way to do it with default form model binding, as It requires the field name to be the same as the model attribute, but you could do a workaround as
Form::macro('customBind', function($user_model,$type)
{
return '<input type={$type} name="user[{$type}]" value="{$user_model->$type}">';
});
And then
$user = new User();
$user->email = "johndoe#gmail.com";
Form::customBind($user,"email");
Which would produce something like
<input type="email" name="user[email]" value="johndoe#gmail.com">
The takeaway point is that basically the solution to your problem is creating a Macro, I have provided some workarounds on this but you will need to refine this to your specific needs.
I'm having a hard time figuring out how to repopulate a form for edit that has check boxes in it. I think the most confusing part is because they are coming from a pivot table.
I have users, permissions, and users_permissions tables.
For a quick demonstration of what the tables look like, I ran this query and included a screen clip of the results.
return $userPermissions = User::with('permissions')->find($id);
In my form I just have two permissions check boxes for now until I get the concept working, then I will add a foreach loop and grab them all from the database, but now I have the following:
<div class="form-group">
<label>
{{ Form::hidden('permissions[4]', '0', ['class' => 'checkbox-inline']) }}
{{ Form::checkbox('permissions[4]', '1', ['class' => 'checkbox-inline']) }}
Manage Content
</label>
</div>
<div class="form-group">
<label>
{{ Form::hidden('permissions[3]', '0', ['class' => 'checkbox-inline']) }}
{{ Form::checkbox('permissions[3]', '1', ['class' => 'checkbox-inline']) }}
Manage Users
</label>
</div>
I'm not sure if this is useful information, but when I first create the user, I create a permissions array to attach to the new user. Here is the code for that,
public function createUserPermissionsArray($input)
{
$permissionsArray = [];
$permissions = $input['permissions'];
foreach ($permissions as $id => $value)
{
if ($value == 1)
{
array_push($permissionsArray, $id);
}
}
$this->saveNewUserToDatabase($input, $permissionsArray);
}
I really need some direction here about how to solve this problem. Thanks
I recently worked on something similar, except I had groups with permissions assigned to them, which users were then assigned to. For example, for GroupController::edit() I had this code:
public function edit($id)
{
if(!permitted('group.edit')) {
return Redirect::route('user.dashboard');
}
$group = Group::find($id);
$permissions = Permission::all();
return View::make('admin.group.edit', [
'group' => $group,
'permissions' => $permissions,
'assigned' => $group->permissions->lists('id')
]);
}
Then within the form partial for the view (admin.group.partials.form), I had the following code to handle permissions:
#foreach($permissions as $permission)
<div class="row">
#if(isset($assigned))
{{ Form::checkbox(
'permissions[' . $permission->ident .']',
$permission->id,
in_array($permission->id, $assigned))
}}<label for="permissions">{{ $permission->ident }} - {{ $permission->description }}</label>
#else
{{ Form::checkbox(
'permissions[' . $permission->ident .']',
$permission->id)
}}<label for="group">{{ $permission->ident }} - {{ $permission->description }}</label>
#endif
</div>
#endforeach
I've formatted it the best I can, so that it's easy to read. Basically I had a create.blade.php and edit.blade.php which both include the form partial, but each file handles the opening and closing of the form (model binding for edit, normal open for create), hence the if statement.
Unfortunately I couldn't find a better method to achieve this. I hope this helps you.
P.S: As a side note, I've actually written a tutorial about creating a robust and simple ACL with Laravel, I can link if you'd like.