Can I use multiple controllers when using resource controllers? - laravel-9

I have created a resource controller(devices, DeviceController).
Now I want to add in the devices.edit a dropdown menu from another table with which the model device has a relationship.
I tried to create a DropdownController which returns the status of the devices but the controller does not reach the view although I created a route.
But when I send the statusse via DeviceController then it works.
My question is do I have to send everything via the DeviceController or can I split it up?
The error is Undefined variable $status
//DropController
public function show(){
$status = Status::all();
return view('devices.edit',compact('status'));
}
// web.php
Route::get('devices.edit',[\App\Http\Controllers\DropController::class,'show']);
Route::resource('devices', DeviceController::class);
//devices.edit
<select name="ddStatus">
#foreach($status as $state)
<option value="{{ $state->id }}">{{ $state->Status_Name }}</option>
#endforeach
</select></p>
If I send the statuses in the DeviceController then it works, but I would like to manage the dropdown with a separate controller. Or is it ok to send such things also in the DeviceController? Because I thought you should separate that

Related

htmx:afterSettle not working with hx-trigger

This code does not trigger a swap event, even though I can see that the afterSettle event is firing in the console.
<div id="product-gallery" hx-trigger="htmx:afterSettle" hx-get="{% url 'products' %}" hx-swap="outerHTML">
This works, but loops forever of course, with:
<div id="product-gallery" hx-trigger="load" hx-get="{% url 'products' %}" hx-swap="outerHTML">
I can see from htmx.logAll() that the htmx:afterSettle even is firing, it's just not triggering the above element. Have also tried htmx:afterSwap, which is also logged by logAll()
I'm trying to reload the gallery after a form has been swapped out (the form is inside this parent product-gallery div). Which I was hoping I could achieve by adding a from constraint:
<div id="product-gallery" hx-get="{% url 'products' %}" hx-swap="outerHTML" hx-trigger="afterSettle from:.product-form">
Structure is:
<div id="product-gallery">
<div id="product-form-1">
<form>
...
</form>
</div>
...
</div>
Update - it works! Followed solution 3 from https://htmx.org/examples/update-other-content/:
I added a header to my response in the form update view:
if form.is_valid():
form.save()
context = dict()
context['form'] = form
response = render(self.request, 'form_product.html', context)
response['HX-Trigger'] = 'productUpdate'
return response
Then I listen for this event in the gallery div:
<div id="product-gallery" hx-get="{% url 'products' %}" hx-swap="outerHTML" hx-trigger="productUpdate from:body">
The one bit of js I retain is for closing forms when they are valid:
htmx.on("htmx:afterSwap", function(evt) {
const eventIdTarget = evt['target'].id;
if (eventIdTarget === 'product-gallery') {
if ($("[id^=product-form] .alert-warning").length === 0) {
$.magnificPopup.close();
}
}
})
If you have troubles with http redirects, then this might help you:
If you want a response which was triggered via htmx to do a full page reload, then you should not return a http redirect response (302, aka as HttpResponseRedirect in Django).
You need to set the hx-redirect response header: https://htmx.org/reference/#response_headers
If you set hx-redirect and set the http response code to 302, then htmx will do a redirect on ajax-level (not on the full screen).
Next thing which might confuse new users: if you are used to the old post/redirect/get pattern, then there are good news: This is not needed any more.
If the client sends a http-post, and all data validates, you should return a http 2xx response containing the new HTML. There is no need for the outdated redirect/get dance.
If you think the htmx docs could get improved, then you might want to create a pull request to improve the docs.
AFAIK you can't use "afterSettle" like this: hx-trigger="htmx:afterSettle".
If you want to update a second part of the page, then you can use OOB (out-off-band):
The hx-swap-oob attribute allows you to specify that some content in a response should be swapped into the DOM somewhere other than the target, that is "Out of Band". This allows you to piggy back updates to other element updates on a response.
https://htmx.org/attributes/hx-swap-oob/
More about Update other content

Invoke an ASP.NET Core View Component inside a Partial View

I have a partial view (_FormCustomer) that displays a form for creating a customer. I also have a View Component (Countrylist) that generates a options list of countries. Now I want to show the country list in my form. This is what I do:
Index.cshtml
<partial name="_FormCustomer" for="#Model._Customer" />
_FormCustomer.cshtml
<select asp-for="#Model.Land" class="form-control">
#await Component.InvokeAsync("Countrylist");
</select>
CountrylistViewComponent.cs
public async Task<IViewComponentResult> InvokeAsync()
{
return View(await _countryRepository.GetCountriesAsync());
}
(The function GetCountriesAsync() returns a list of countries; this works fine.)
Pages/Componenst/Countrylist/default.cshtml
#model List<Country>
#foreach (Country country in Model)
{
<option value="#country.code">#country.name</option>
}
Unfortunately, select-box stays empty when I call the partial. When I call #await Component.InvokeAsync("Countrylist"); directly from Index.cshtml, however, it works fine.
So it looks like you cannot use a View Component inside a Partial View. Is this conclusion right? Or am I doing something wrong?
Thanks Phantom2018, found the problem after your post.
#0: I'm using Razor pages
#1: this had no effect
#2: this was a typo in my question, not in my code
#3: the debugger shows me that the vie component gets called, so
My actual code is a little different, I want to pre select a country if it's available:
<select asp-for="#Model.Country" class="form-control">
#if (Model == null)
{
await Component.InvokeAsync("Countrylist");
}
else
{
await Component.InvokeAsync("Countrylist", Model.Country);
}
</select>
And after some testing, I found the solution:
<select asp-for="#Model.Country" class="form-control">
#if (Model == null)
{
#await Component.InvokeAsync("Countrylist");
}
else
{
#await Component.InvokeAsync("Countrylist", Model.Country);
}
</select>
Don't know why, but I had to use #'s before the awaits.
I have now tested this scenario and can confirm that the data loads fine - both, when the view component is directly included on the page or when it is included in a partial View. (I have tested this on Razor pages - but it is likely to work the same when using MVC. You have not mentioned if you are using MVC or Razor pages.)
A couple of things you can try to see if the loading works fine:
1) From all "Select"s and "Partials" remove the "for*" attributes. That way you can first check if the data loads & then you can worry about binding to the selected item. (Also, in your provided code, you have omitted the model variables - so it is not possible to comment on them.)
2) Remove the last ";" in your _FormCustomer.cshtml
<select asp-for="#Model.Land" class="form-control">
#await Component.InvokeAsync("Countrylist")
</select>
Note that I have removed the trailing ";" in the await statement. I noticed that the ";" was added as another "option" in the select !
3) I also noticed that even minor syntax errors (not picked up by Intellisense) can cause the select to not load. Debug to see if your InvokeAsync is actually being called - in a scenario where there was a minor syntax error, the InvokeAsync was not even being called.
Also keep in mind that:
"When a partial view is instantiated, it receives a copy of the
parent's ViewData dictionary. Updates made to the data within the
partial view aren't persisted to the parent view. ViewData changes in
a partial view are lost when the partial view returns."

Set and update global values in the _layout view

I have an application which has a global setting - Enviromnet (not the build environment)
enum Environment {
TEST = 1,
PREPROD = 2,
PROD = 3
}
I have placed a dropdown in the Navbar to make it accessible from all views. I would like the selection
stored in a Session var.
This is just to get the idea.
<select asp-for="#Session.Item["ENV"]"
asp-items="Html.GetEnumSelectList<Environment>()">
<option selected="selected" value="">Please select</option>
</select>
I have been looking at partial views and viewComponents but I can't see
how they can solve my problem
How could this be solved ?
You can use ajax and call api on change of select option.

Aurelia Validation on Select List

I have a simple select list in my Aurelia view which I'm trying to set a default value on of 'Select...'. I'm also using the aurelia-validation plugin to ensure that the value is changed before the form is submitted. The plugin works great for other field types in my project.
<div class="form-group">
<label for="agencies" class="control-label">Agency</label>
<select value.bind="agencyId" class="form-control">
<option value="">Select..</option>
<option repeat.for="agency of agencies" value.bind="agency.id">${agency.name}</option>
</select>
</div>
In the VM:
constructor(validation) {
this.agencies = null;
this.agencyId = 0;
this.validation = validation.on(this)
.ensure('agencyId')
.isNotEmpty();
}
activate() {
//call api and populate this.agencies
}
After the page initially loads I get my agencies in the list and my default value is correct, but it shows the validation error message:
Other form fields, like text boxes don't do this and show no error message until the user interacts with the form controls.
Is there something special I need to do for a select list to hide validation errors on the initial loading of the view? I suspect that binding the select list in the view is somehow triggering a change event.
Thanks to a kind Aurelia user on Gitter, the problem was solved by setting the initial value of this.agencyId to "". Originally I had the this.agencyId = null. That was my mistake. Because it was null and not "" (as was the default value in the select list) the values didn't match so the select list was invalid when the view loaded. At least, that's my understanding.
The lesson is, if you want to validate a select list, make sure you VM property is initialized to the same value as your select list's default value.
constructor() {
this.agencyId = ""; **//must match the bound property's initial value**
}
And in the view:
<div class="form-group">
<label for="agencies" class="control-label">Agency</label>
<select value.bind="agencyId" class="form-control">
<option value="" **<!-- this value must match the VM initial value -->** selected="true">Select...</option>
<option repeat.for="agency of agencies" value.bind="agency.id">${agency.name}</option>
</select>
</div>

How can I post my value from a selectform without using Dropdownlistfor in MVC4?

I don't know how much code is needed in order for you to understand my problem so I'm starting with explaining what I want to do.
I have a form in my View where I create a new object.
In this form I want to use a dropdownlist and I've tried a lot with #Html.Dropdownlistfor() but realized I don't want to use that since I don't know how to use som Jquery UI-code on this and I don't know how to send a different value than what is shown in the dropdownlist.
Does any of you know how i just send the value selected in the dropdownlist to my controller?
DropDownListFor helper method esssentially render a SELECT element. So if you do not want to use the helper method, You may write the RAW HTML code
#model CreateUserVM
#using(Html.BeginForm())
{
<select name="SelectedCity" id="SelectedCity">
<option value='1'>Ann Arbor</option>
<option value='2'>Novi</option>
<option value='3'>Detroit</option>
</select>
}
Make sure your SELECT elements name property value is same as what you use in your Model, so that Model binding will work.
public class CreateUserVM
{
public int SelectedCity { set;get;}
//other properties
}
In view model:
puclic class ViewModel
{
int SelectId{get;set;}
}
In view
<select name="SelectId" id="SelectId">
<option value='1'>one</option>
<option value='2'>Two</option>
</select>
After post form ViewModel.SelectId will contain value of selected item in dropdown