Edit and updating fails for joining table in Laravel 7 - eloquent

I have two tables (City and Area). The city has many areas. So I have successfully added and deleted the data. But I am facing multiple errors while editing and updating. I have just started learning Laravel. I used a resource controller but encountered a problem with declaring routes, so I tried the name route also.
This error I have encountered: Trying to get property 'id' of non-object
and indicates the form's route ID. which is $editArea->id. Here is my code.
web.php route:
Route::resource('area','Admin\AreaController');
Route::get('area/edit/{id}','Admin\AreaController#edit')->name('area.edit');
Route::post('area/update/{id}','Admin\AreaController#update')->name('area.update');
edit button: Edit
AreaController:
public function edit(Area $area, $id)
{
$editArea= Area::find($id);
$cityno= City::orderBy('city_name', 'asc')->get();
return view('admin.editArea', compact('editArea','cityno'));
}
public function update(Request $request, Area $area, $id)
{
$editArea=Area::find($id);
$editArea->city_id=$request->city_id;
$editArea->area_name=$request->area_name;
$editArea->save();
}
edit form:
<form method="POST" action="{{route('area.update', $editArea->id)}}">
#csrf
#method('put')
<select name="city_id">
#foreach($cityno as $row)
<option value="{{$row->id}}" >{{ucwords($row->city_name)}}</option>
#endforeach
</select>
<input type="text" name="area_name" value="{{$editArea->area_name}}">
<button type="submit">Update Area</button>
</form>
Area model:
public function city(){
return $this->belongsTo(City::class,'city_id');}
city model:
public function area(){
return $this->hasMany(Area::class); }
As I'm a new learner, I'm stuck in these files to find my wrong code.

Related

Blazor: How to use the onchange event in <select> when using #bind also?

I need to be able to run a function after a selection in made in a <select>. The issue is I'm also binding with #bind and I get a error when I try to use #onchange stating that it is already in use by the #bind. I tried using #onselectionchange, but that does nothing(doesn't run the function). I could forget the #bind and just assign #onchange to a function, but I'm not sure how to pass the selected value to the function.
I have the following code:
<select #bind="#SelectedCustID" # #onchange="#CustChanged" class="form-control">
#foreach (KeyGuidPair i in CustList)
{
<option value="#i.Value">#i.Text</option>
}
</select>
Thanks.
#bind is essentially equivalent to the having both value and #onchange, e.g.:
<input #bind="CurrentValue" />
Is equivalent to:
<input value="#CurrentValue" #onchange="#((ChangeEventArgs e) => CurrentValue = e.Value.ToString())" />
Since you've already defined #onchange, instead of also adding #bind, just add value to prevent the clash:
<select value="#SelectedCustID" #onchange="#CustChanged" class="form-control">
#foreach (KeyGuidPair i in CustList)
{
<option value="#i.Value">#i.Text</option>
}
</select>
Source: https://learn.microsoft.com/en-us/aspnet/core/blazor/components/data-binding?view=aspnetcore-3.1
<select #bind="MyProperty">
<option>Your Option<option>
</select>
#code {
private string myVar;
public string MyProperty
{
get { return myVar; }
set
{
myVar = value;
SomeMethod();
}
}
private void SomeMethod()
{
//Do something
}
}
Just add #bind-value and #bind-value:event="oninput" with #onchange as below. Note the lower case letters. This works for me without any issue.
<select #bind-value="variablenametokeepselectedvalue" #onchange="yourmethodname" #bind-value:event="oninput">
<option value="1">Test</option>
</select>
This seems to be a popular confusion. Firstly you cant use #onchange since it would internally be used by #bind. You should be able to access the selected value from the setter of your CustChanged property. Based on what you are trying to do with your CustChanged, you may not even need to manually check when this value is updated. For instance, if your intent is to use CustChanged in your UI directly or indirectly (within Linq or something), the UI would automatically update with CustChanged value when your <select> is changed. Hence, for most use cases I don't see the need to check when it was updated.
To use #onchange, you can bind it to a function something like this:
public void OnUpdated(ChangeEventArgs e)
{
var selected = e.Value;
}
You can avoid #bind altogether (if you're using a foreach):
<select #onchange=#(handleChange)>
#foreach (var option in _options){
<option value=#option.Id selected=#(SelectedId == option.Id)>#option.Name</option>
}
</select>
#code {
public record Person(int Id, string Name);
public int SelectedId { get; set; }
public List<Person> _options = new List<Person>() {
new Person(1,"A"),
new Person(2,"B"),
new Person(3,"C")
};
public void handleChange(ChangeEventArgs args) {
Console.WriteLine(args.Value);
SelectedId = Int32.Parse(args.Value.ToString());
}
}
Some sordid details: I was getting some weird behavior when trying to use F# with server-side Blazor. In short, setting the List of options to the result of an Entity Framework query (mapped to a list of records) wouldn't #bind properly, but using a dummy list of options that were C# classes and not F# records did work. It wasn't due to it being records though, because if I set the list to the EF query and then immediately set it to a dummy list of records, it still didn't #bind properly - but it did work if I commented out the EF line.
Please check this example. It is using #bind but upon setting the value it triggers #onchange event
<div class="form-group">
<label for="client">Client Name</label>
<select id="client" #bind="CheckSelected" class="form-control">
<option value="selected1">selected1</option>
<option value="selected2">selected2</option>
</select>
</div>
#code {
private string selectedItem {get; set;}
private string CheckSelected
{
get
{
return selectedItem;
}
set
{
ChangeEventArgs selectedEventArgs = new ChangeEventArgs();
selectedEventArgs.Value = value;
OnChangeSelected(selectedEventArgs);
}
}
private void OnChangeSelected(ChangeEventArgs e)
{
if (e.Value.ToString() != string.Empty)
{
selectedItem = e.Value.ToString();
}
}
}
Please check below code for reference how we use select tag with bind value and call function onselection value change in blazor
<InputSelect class="form-control mb-0" ValueExpression="#(()=>request.Id)" Value="request.Id"
ValueChanged="#((string value) => SelectedValueChange(value))">
#if (dropdownResponses != null)
{
#foreach (var item in dropdownResponses)
{
<option value="#item.Id.ToString()">#item.Name</option>
}
}
</InputSelect>
use <InputSelect> tag instead of <select> tag and use ValueChanged method for getting call on select value change
here is the code of ValueChanged function
internal void SelectedValueChange(string value)
{
string NewValue = value;
}
Starting from .NET 7 Preview 7, the recommended way to handle this issue is to use the bind:after modifier.
Here is a small example (partially borrowed from the docs):
<p>
<label>
Select one or more cities:
<select #bind="SelectedCities" multiple #bind:after="DoSomething">
<option value="bal">Baltimore</option>
<option value="la">Los Angeles</option>
<option value="pdx">Portland</option>
<option value="sf">San Francisco</option>
<option value="sea">Seattle</option>
</select>
</label>
</p>
<span>
Selected Cities: #string.Join(", ", SelectedCities)
</span>
#code {
public string[] SelectedCities { get; set; } = new[] { "bal", "sea" };
// Run your logic here after a binding event
private void DoSomething()
{
Console.WriteLine(string.Join(',', SelectedCities));
}
}
My recommendation is, if possible, use an EditForm wrapper around your form elements. Then you can detect a change of any of the form elements, in one place. This is good for, for example, a bunch of search filters. Any change in any of the filters should trigger another query of the data, etc.
Example of how to trigger event on form changes is here:
blazor editform change events
According to Microsoft's documentation, this is their preferred way of handling this problem:
<InputText Value="#NewPaymentAmount" class="mdl-textfield__input"
ValueExpression="() => NewPaymentAmount"
ValueChanged="(string value) => ValidateAmount(value)" />
private void ValidateAmount(string amount)
{
NewPaymentAmount = amount;
// Do validation or whatever
}
Or the async way:
<InputText Value="#NewPaymentAmount" class="mdl-textfield__input"
ValueExpression="() => NewPaymentAmount"
ValueChanged="async (string value) => await ValidateAmountAsync(value)" />
private async Task ValidateAmountAsync(string amount)
{
NewPaymentAmount = amount;
// Do validation or whatever
}
<div class="form-group">
<label for="client">Client Name</label>
<select class="form-control" #onchange="#((e) => { myVar = e.Value.ToString(); MyMethod(); })">
<option value="val1">val1</option>
<option value="val2">val2</option>
</select>
</div>
This is how I was able to set the property while calling for a method in the #onchange event.
-Blazor Server
-Dotnet Core 3.1
I use oninput to essentially have bind and onchange.
oninput triggers when the input value is changed whereas bind/onchange triggers when the element loses focus and the input has changed.
This might not fit every scenario however as it wll trigger on every input while typing, depending on your need or for inputs such as selects, radios, etc. it should be suitable.

Laravel - Form - Eager Loading

Hi I'm working on a CRUD interface to update an Event.
Event Class Model a Event belongs to many Organizer
class Event extends Model
{
public function organizers()
{
return $this->belongsToMany('App\Organizer')->withTimestamps();
}
}
Organizer Class Model
class Organizer extends Model
{
public function event()
{
return $this->belongsToMany('App\Event')->withTimestamps();
}
}
Event controller I send the event along with its organizer(s)
public function edit($id)
{
$event = Event::with(['organizers'])->findOrFail($id);
return view('event.edit', ['event' => $event]);
}
event.edit view
<? $organizers = DB::table('organizers')->lists('name', 'id'); ?>
{!! Form::model($event, [
'method' => 'PATCH',
'route' => ['eventUpdate', $event->id]
]) !!}
<div id="organizer_id-group" class="form-group">
{{Form::select('organizer[id]', $organizers, null,['class'=>'form-control'])}}
</div>
{!! Form::submit('Update', ['class' => 'btn btn-primary']) !!}
{!! Form::close() !!}
For the moment I see the whole list of organizers which is good but the default value, or the selected one does not correspond to the one linked to the event.
I tried
<div id="organizer_id-group" class="form-group">
#foreach($event->organizers as $key =>$organizer)
{{Form::select($organizer->id, $organizers, null,['class'=>'form-control'])}}
#endforeach
</div>
but the selected organizer is always the first even if $organizer->id = 2
Any help would be welcome. Thank you
When working with Form::model it could be easier to work with the name of the property in the database.
If you have
{{Form::select('organizer[id]', $organizers, null,['class'=>'form-control'])}}
Then the value of the form will be set to $event->organizer->id if it exists.
Also from your use case it looks like an organizer belongsTo an event. You do not show the organizer model, but inside the event model you define a many-to-many relationship. This implies that you have $event->organizers.
From the information available you might want to change the relationship to
return $this->belongsTo('App\Organizer');
Amendment / Update (after additional information)
It is clear now, you are indeed working with a many-to-many collection. I have not managed to get Form model binding to work with belongsToMany collections so you need to set the 3rd parameter to physically define the organizers that need to be selected.
The most elegant way I know about is to define an accessor on your Event object for the event organizer ids. This way you can use a call to old for a validation error and then define a default as well which are the values for the initial edit.
Notice that in the form we are using an array for the multiple values.
{{Form::select('organizers[]', $organizers, old('organizers', $event->organizers_ids), ['class' => 'form-control', 'multiple'])}}
Then inside your Model you add the accessor to return the ids.
public function getOrganizersIdsAttribute()
{
return $this->organizers->lists('id')->all();
}
Answer# Update after Leon suggestions
This is what I needed to do to make it works
<div id="organizer_id-group" class="form-group">
{{Form::select('organizers[]', $organizers, $event->organizers->lists('id')->all(),['class'=>'form-control', 'multiple'])}}
<span class="help-block"></span>
</div>
To make a selection by default you have to change the third argument (https://laravelcollective.com/docs/5.0/html#opening-a-form)

The value "" is invalid

Why does the Edit screen show the value I'm editing but when I try to save, the Edit HTTPPOST has a null object?
Getting a an error I've tracked down but don't see the cause of. I have a class used for a drop list, so it only has AdvisoryTypeID and AdvisoryType. But now I'm creating screens for users to see all in a grid (Index), add new (Create), and change existing ones (Edit). The index screen reads from the DB no problem. So does Edit. Create doesn't need to.
But both Edit and Create give the same error on HTTPPOST. The error is "The Value 'whatever I typed in' is invalid". Debugging the code turned up why: the "advisoryTypes" variable is null for reasons I don't understand:
CONTROLLER (for Edit post)
[Authorize]
[HttpPost]
public ActionResult Edit(AdvisoryTypes advisoryType)
{
try
{
if (ModelState.IsValid) //never get past this because it's null above
etc....
}
I have compared this to another screen that does Edit and Create fine and they're identical (except field names, for example). I'm stumped.
MODEL
public class AdvisoryTypes
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int AdvisoryTypeID { get; set; }
[Display(Name = "Type")]
[Required]
public string AdvisoryType { get; set; }
}
CONTROLLER (for Edit Get)
[Authorize]
public ActionResult Edit(int id = 0)
{
AdvisoryTypes advisoryType = db.AdvisoryType.Find(id);
if (advisoryType == null)
{
return HttpNotFound();
}
return View(advisoryType);
}
VIEW
model TheNewSolution.Models.AdvisoryTypes
#{
ViewBag.Title = "Edit Advisory Type";
}
<h2>Edit Advisory Type</h2>
#*<br />*#
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<fieldset>
<legend>Advisory</legend>
<table>
<tr>
<td>
#Html.HiddenFor(model => model.AdvisoryTypeID)
<div class="editor-label">
#Html.LabelFor(model => model.AdvisoryType)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.AdvisoryType)
#Html.ValidationMessageFor(model => model.AdvisoryType)
</div>
</td>
</tr>
</table>
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
}
PART TWO
Now some odd background: this is the first model/controller/view I created since updating EF from 5 to 6 alpha3 (had to because I had POCO and edmx in same project and this solved that bug). I created the model manually. When I tried to create the Controller via the wizard, the wizard gave an error "Unable to retrieve metadata for ProjectName.Models.AdvisoryProviders'. Unable to cast obect of type 'System.Data.Entity.Core.Objects.ObjectContext' to type 'System.Data.Objects.ObjectContext'.".
The result was having to manually create the controller by copying/pasting another one and making changes, then creating my views the same way. I'm suspicious this is all related but maybe not. If this proves unrelated I'll remove this and post as another question.
the error from EF of "Unable to retrieve metadata for ProjectName.Models.AdvisoryProviders i have seen before, but this has only been because i was using MySQL, the only way i found around this kind of error and to make sure everything worked was to use http://visualstudiogallery.msdn.microsoft.com/72a60b14-1581-4b9b-89f2-846072eff19d to create models from the database, and then use http://blog.stevensanderson.com/2011/01/13/scaffold-your-aspnet-mvc-3-project-with-the-mvcscaffolding-package/ to create the controllers, with view, rather than the buggy version of create view for EF.
I posted about these problems a while back with EF5 and its a real pain, MVCScaffolding seems to handle that pain alot better than the built in TT templates with MVC 4
hope this helps
I am not sure why this resolved, but here's what I did. I needed to create the screens mentioned above (index with grid, create, edit) for three different things (types, providers, categories) in my app. I did the first, type, resulting in the above issues.
I decided to create the same for "providers" by copying and pasting the controllers and views from "type" screens, then changing the model and field names as needed, expecting the screen to have the same bugs. But the screens all worked. I did it again for "categories", which also worked. Then I deleted my failing Create and Edit screens for "type", and recreated them from the "providers" screens. And they worked.
I have no explanation.

Passing values from a form to a new action

Ok guys i have a question, if this is my Form , and is generated for every item in the DB, i want to send the item with the quantity specified.
to send the quantity , from this razor view
#using (Html.BeginForm("AddToCart", "Prices"))
{
string qtname = "qt" + #item.id;
<div>
<input id="#qtname" name="#qtname" class="quantity" type="text" value="0" readonly="readonly" />
</div>
<input type="submit" value="Adauga" class="addToCart" />`
}
i need just these?
[HttpPost]
public ActionResult AddToCart(ProductsModel Products, string qtname)
{ }
and do i need some html.hidden for passing along the item.id too?
In the form you are sending only the qtname parameter as input field, whereas your controller action also expects a ProductsModel parameter which is never sent. If you want to bind it you will have to create input fields for all properties of this view model.
But in your case a better solution would be to simply include the id of the product as hidden field and then fetch the corresponding product from your datastore given this id:
[HttpPost]
public ActionResult AddToCart(string id, string qtname)
{
ProductsModel products = _repository.GetProduct(id);
...
}

How can I make ASP.NET MVC 2 persist submitted form values across multiple submits?

Say I have a strongly-typed view of ViewPage<Song> and some additional fields in the form of create/edit scenarios that are not directly part of the Song entity (because I have to do some parsing before I fetch the composers from a foreign table and create the bridge entities SongComposers).
public ActionResult Create(Song song, string composers) {
if (ModelState.IsValid) {
// redirect to details
}
// return a view with errors
return View(song);
}
And in the view
<% using (Html.BeginForm()) { %>
<%= Html.EditorForModel() %>
<div class="editor-label"><label for="composers">Composers</label></div>
<div class="editor-field"><input id="composers" type="text" name="composers" /></div>
<% } %>
Now when I try to create a food and get validation errors, the previously entered form values for fields in Html.EditorForModel get filled properly but the composers field is blank after a submit. How can I fix this (ie. make it so that it "remembers" what the user wrote in the composers field)?
ViewData["composers"] = composers;