Livewire ignore does not receive correct data - laravel

I'm using selectize.js for dropdown styling with livewire. I'm using livewire for a data table with sortable columns and pagination. The issue is, every time I make pagination or a sort by column, the javascript goes missing thus, there's no styling for the dropdown. I've solved the styling issue using wire:ignore. Now the new problem that I have is that the data passed to the dropdown is not accurate.
#foreach($applications as $application)
<p>{{$application->status}}</p>
<div wire:ignore>
<select class="selectize" name="status" data-width="200px">
#foreach(['Pending',
'Hired',
'Under consideration'] as $status)
<option
#if($application->status === $status) selected #endif>{{ $status }}</option>
#endforeach
</select>
</div>
#endforeach
Inside the <p>{{$application->status}}</p> tag, I get the status 'Pending' but on the dropdown, it shows 'Hired'. The correct status is 'Pending'.

(from comment) #stalwart1014 for example, when I use "select2" I do this
in content section
<select id="select2" wire:model="some">
//.....
</select>
in script section
$(document).ready(function() {
window.initSelectDrop=()=>{
$('#select2').select2({
placeholder: '{{ __('Select') }}',
allowClear: true});
}
initSelectDrop();
window.livewire.on('select2',()=>{
initSelectDrop();
});
});
and in component
public function hydrate()
{
$this->emit('select2');
}
This not always function properly with JS element...but hope this can help you. Greetings

it's maybe a bit late, but worth to mention for the future, I will do not point directly to your issue but explain the workaround of wire:ignore, the wire:ignore directive ignores the updates or changes for the further requests and updates, this attributes is defined for playing with JS third party library, the wire:ignore directive prevent all it's nested elements from updating if you wish to except Childs from updating you can use wire:ignore.self directive

Related

Laravel 8 multi value select filtering

I'm trying to build Laravel project that will have a multi-select dropdown with list of categories. Selecting category should reload the page and apply filter.
"scopeFilter" in my model is actually doing filtering, but here i just need to find a way to properly form URL. The problem is that this code i have:
<form id="cats-form" action="#" method="GET">
<select multiple class="chosen-select" name="test[]">
#foreach($categories->get() as $cat) //this loop goes through existing categories in the system
#php
//if category is part of URL, pre-select it in the select:
$arr = request()->all();
$selected = '';
if(array_key_exists('test', $arr)) {
$testArr = $arr['test'];
$selected = in_array($cat->id, explode(',', $testArr)) ? 'selected' : '';
}
#endphp
<option {{ $selected }} value="{{ $cat->id }}">{{ $cat->name }}</option>
#endforeach
</select>
</form>
<script>
$(".chosen-select").chosen({ })
$('.chosen-select').on('change', function(evt, params) {
$('#cats-form').submit();
});
</script>
Is actually giving me this URL:
http://localhost:11089/?test%5B%5D=19&test%5B%5D=5
While i actually need this:
http://localhost:11089/?test=19,5
I guess it's a trivial problem to solve for someone with better knowledge of Laravel, can you tell me pls what i'm doing wrong here?
This is rather how the language or framework or library reads an URL query string. Assuming that your prefered way of URL string is a valid format (as I usually see the format ?arr[]=val1&arr[]=val2 and never see the second format that you preferred), both query string formats should be acceptable to PHP.
As Yarin stated that the best way PHP reads an array is the first method, you don't have to worry too much because the decoded URL query string is exactly in the first format.

laravel livewire breaks styling (#push script)

I applied livewire on top of an already made laravel project.
It's simply applying wire:model to select like below.
<select wire:model="wired" id="#selector-01" >
<option>1</option>
<option>1</option>
</select>
and using the wire variable(wired) in if statement in for loop.
<select id="#selector-02">
#foreach($numbers in $number)
#if($some_id == $wired)
<option> some_id </option>
#endif
#endforeach
</select>
I use it only as a component.
all the data is controlled in the original laravel controller.
livewire component is called by #livewire('component-name')
and everything works, as I thought it would, except styling.
I think it has to do with using SlimSelect
At the end of livewire component-name.blade.php, I have SlimSelect styling just like the below.
#push('js')
<script>
new SlimSelect({
select: '#selector-01'
})
new SlimSelect({
select: '#selector-02'
})
</script>
#endpush
I tried moving the codes to
original.blade.php with #push and #stack
app.blade.php without #push
but still styling breaks.
I am thinking a solution might be adding few lines in update() in live wire controller to reinject the styling script.
am I going in the right direction?
Please help.
adding the below code fixed the problem.
<script>
document.addEventListener('livewire:update', function () {
// Your JS here.
new SlimSelect({
select: '#selector-01'
})
new SlimSelect({
select: '#selector-01'
})
})
</script>
you can also use livewire:onload instead of #stack and #push for loading styling onload
Reference to livewire doc

Laravel Livewire wire:click creates infinite loop

I have a Livewire component that's a product filter. The queries all work fine, but sometimes it creates an infinite loop of requests.
You can see that happening in the GIF below which is a capture of the Laravel Debugbar. I'm clicking some filters and then suddenly it goes into this request loop.
I specifically use wire:loading.attr="disabled" on the filters in the view so someone can not select a filter while a request is still processing.
My code and some background:
Livewire Component
use App\Models\Product;
use App\Models\Brand;
use App\Models\Color;
class SearchProducts extends Component
{
public ?array $brand = [];
public ?array $color = [];
protected $queryString = ['brand', 'color'];
public function render()
{
$products = Product::query();
$products = $products->with('brand');
$products = $products->with('colors');
$products = $this->filterBrands($products);
$products = $this->filterColors($products);
$products = $products->paginate(24);
return view('livewire.search-products', [
'all_brands' => Brand::where('status', 'active')->get(),
'all_colors' => Color::where('status', 'active')->get(),
])->extends('app');
}
public function filterBrands($query)
{
$queryFilterBrand = array_filter($this->brand);
return empty($queryFilterBrand) ? $query : $query->whereIn('brand_id', $queryFilterBrand);
}
public function filterColors($query)
{
$queryFilterColor = array_filter($this->color);
return empty($queryFilterColor) ? $query : $query->whereHas('colors', function ($q) use ($queryFilterColor) {
$q->whereIn('color_id', $queryFilterColor);
});
}
}
The reason that I use array_filter is because if I unselect a color value and use a character in the key (wire:model="brand.b{{ $brand->id }}"), instead of removing that from the array Livewire will set that key value to false. So then this false value will be put into the query which will give inaccurate results.
Livewire views and the issue
This works fine:
#foreach($all_brands as $brand)
<input type="checkbox" value="{{ $brand->id }}" id="brand.{{ $brand->id }}" wire:model="brand.{{ $brand->id }}" wire:loading.attr="disabled">
<label class="search-label search-wide-label mb-2" for="brand.{{ $brand->id }}">{{ $brand->title }} <i class="fal fa-times float-right selected-icon"></i></label>
#endforeach
But this creates an infinite loop when I select 2 or more colors after each other, or if I select 1 color and then deselect it. So it seems that issue occurs after the 2nd interaction:
#foreach($all_colors as $color)
<input type="checkbox" value="{{ $color->id }}" id="color.{{ $color->id }}" wire:model="color.{{ $color->id }}" wire:loading.attr="disabled">
<label class="search-label search-wide-label mb-2" for="color.{{ $color->id }}">{{ $color->title }} <i class="fal fa-times float-right selected-icon"></i></label>
#endforeach
This is weird because this blade snippet is exactly the same as for $brands as shown above:
The only thing that different is that the colors relationship is a hasMany vs a belongsTo for brand.
I'm now thinking that this is where the problem is...
The things I've tried and didn't work
Remove the #foreach loop for $all_colors and just put the filters in plain HTML (to check if the issue is related to the loop)
Adding wire:key="brand.{{ $brand->id }}" to the input element
Adding wire:key="brand.{{ $brand->id }}" to a div around the input element
Using wire:model="brand.{{ $brand->id }}" or wire:model="brand.{{ $loop->id }}" as was suggested in the comments (and what I thought solved the problem)
Using wire:model="brand.b{{ $brand->id }}" so there's a unique key name
Removing the array_filter approach (seems unlikely that this is the problem but just to test)
Using buttons instead of checkboxes
Using defer, lazy and/or debounce
Paying an expert to try and fix it...
Console error
Last piece, I get this error in my console only when the infinite loop happens so it's very likely either a cause or effect.
TypeError: null is not an object (evaluating 'directive.value.split')
Unhandled Promise Rejection: TypeError: null is not an object (evaluating 'directive.value.split')
Both in LoadingStates.js which I think is a Livewire Javascript file.
The error there seems to be happening here:
function startLoading(els) {
els.forEach(({ el, directive }) => {
if (directive.modifiers.includes('class')) {
let classes = directive.value.split(' ').filter(Boolean)
Answered on GitHub issue, copied here for others to be able to find.
The problem is a morphdom issue.
The code that is triggering the errors and the loop is the wire:loading on your heading row and products row.
The reason is, when you select two or more colours, there are no results shown. What happens then is you're swapping from showing heading/products/total to showing an empty state.
But morphdom doesn't know by default that it should delete the old divs and add the new one. Instead it is trying to "morph" the old first div into the new one. That means that the wire:loading listeners are still registered when they shouldn't be. Hence why the error and the loop.
It's a simple fix though. You need to add wire keys to the divs defining what they are, so morphdom knows that they have actually changed completely, and to delete the old ones and add new ones.
Have a look at this screenshot below of the diff for what I did to get it working. I added a wire key for all the top level divs inside this file.
It's recommended whenever using conditionals like that to add wire:keys to any elements that are first level inside the conditional, so morphdom knows there has been a change. It's the same problem VueJS has, where keys are required inside loops.
Screenshot of diff
So it turns out that if you're using wire:model in a foreach loop like this, you have to write it like this: wire:model="brand.{{ $brand->id }}". Couldn't find it in the docs so hopefully it helps others here.
Update
The infinite loop is solved by this, but what's happening now is that the array values are set to zero instead of removed from the array once you select a checkbox and then click it again to unselect. So then the whereIn is going to look for brand IDs with value 0 which will not return results.
Update 2
The loop is actually not solved... See original question. Slapping a bounty on this $%$£# because too many hours and coffee were wasted.
A couple issues I see:
You're using the same wire:key for your <div> element and your <input> element. I would keep those unique.
Also, you're using the same name on the input element. This will cause issues with checkboxes as the value passed to the backend has the same name and I believe livewire will attempt to sync based on that. Changing the name field in your input to be unique is likely the answer.

How do I return an option saved to the Laravel database?

I am programming a system in Laravel 5.8 and nenessito do the editing of the user through a select but when I make the request does not appear the option that the user selected.
I already tried to pass the Controller attractive but not right.
public function edit($id)
{
$users = User::find($id);
$institutions = Institution::all('razaosocial');
return view('users.edit', compact(array('users','institutions')));
}
<div class="form-row">
<div class="col-md-6">
{{Form::label('Instituição:')}}
<select class=form-control name="instituicao" id="instituicao">
<option value="null">Selecione uma Instituição</option>
#foreach($institutions as $institution)
<option value="{{$institution->razaosocial}}">
{{ $institution->razaosocial}}
</option>
#endforeach
</select>
</div>
</div>
I hope that appears in the view the option selects by during registration.
Hoping something works in programming is very frustrating and I am at the moment. compact() actually creates an associative array whose keys are variable names and their corresponding values are array values. So instead of
compact(array('users','institutions'));
make it
compact('users','institutions');
Here is a reference that solves this problem
Laravel: Display data in DropDown from database

Vue.js Calling function on a rendered component

I want to create an interactive scrumboard using Laravel and Vue.js containing multiple columns and within those columns multiple tickets.
These tickets are vue components with some nice edit / delete / (un)assign developer functionality and is used on other pages as well.
I have multiple columns defined like this:
<div id="scrumboard">
<div class="scrumboard__column">
<div class="scrumboard__title">Open</div>
<div class="scrumboard__tickets_wrapper" data-status="open">
#if( $sprint->hasTicketsOfStatus("open") )
#foreach( $sprint->getTicketsByStatus("open") as $ticket )
<ticket :data="{{ $ticket->getJsonData(true) }}"></ticket>
#endforeach
#endif
</div>
</div>
<div class="scrumboard__column">
<div class="scrumboard__title">In progress</div>
<div class="scrumboard__tickets_wrapper" data-status="progress">
#if( $sprint->hasTicketsOfStatus("progress") )
#foreach( $sprint->getTicketsByStatus("progress") as $ticket )
<ticket :data="{{ $ticket->getJsonData(true) }}"></ticket>
#endforeach
#endif
</div>
</div>
<div class="scrumboard__column">
<div class="scrumboard__title">Finished</div>
<div class="scrumboard__tickets_wrapper" data-status="closed">
#if( $sprint->hasTicketsOfStatus("closed") )
#foreach( $sprint->getTicketsByStatus("closed") as $ticket )
<ticket :data="{{ $ticket->getJsonData(true) }}"></ticket>
#endforeach
#endif
</div>
</div>
</div>
And as you can see it renders a ticket component for each ticket it finds for each column.
No i have turned the scrumboard__tickets_wrapper div's into jquery ui sortable lists which allows you to swap the tickets between columns.
<script>
$(document).ready(function(){
$(".scrumboard__tickets_wrapper").sortable({
connectWith: '.scrumboard__tickets_wrapper',
receive: function(event, ui){
console.log("Switched columns");
console.log(event);
console.log(ui);
var target = $(event.target);
target.css("background-color", "#ff0000");
}
});
</script>
Everything is working so far, now my question is: how do I dynamically call the "updateStatus()" function on a ticket component once the ticket is dropped into another list?
As you can see I can get the specific element being dropped and the sortable list it's been dropped into. So I know what the new status is by grabbing the data-status property of the wrapper + I know which element was dropped.
But how do I grab the instance of the ticket component in question and call the updateStatus function to save the new status?
Thanks in advance!
Screenshot of the scrumboard
Thanks David for pointing me in the right direction. The solution to my problem was proper component nesting.
The solution was to create 3 components with proper child-component inheritence. And declaring the child-components within the template of it's parent.
This way I end up only declaring "" and the magic happens :D.
So I have made 3 components:
- scrumboard > takes scrumboardColumn as component
- scrumboardColumn > takes ticket as component
- ticket
The root vue instance also loads the ticket component since the ticket component is also used on the backlog page.
I haven't completely finished the final product but I got the sortable working by calling it from within the ready function of the scrumboardColumn component like David suggested.
Hope this helps someone in the future!

Resources