Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support Bootstrap Select (Selectpicker) Library #45

Closed
intellow opened this issue Jul 17, 2019 · 13 comments
Closed

Support Bootstrap Select (Selectpicker) Library #45

intellow opened this issue Jul 17, 2019 · 13 comments

Comments

@intellow
Copy link
Contributor

When I create a select input, I can easliy set wire:model=myVariable and the data binding works as expected.

However, when I use the Selectpicker library the data binding does not work when I make a selection.

I don't know much about how this library works, but here are a few things that might get someone started in the right direction:

  • Making a selection with this library doesn't actually change the selected attribute on the HTML <option>. That might get someone pointed in the right direction.
  • Also, I have found when using this library with Vue and other frameworks, I will regularly call $('.selectpicker').refresh() any time a variable is changed (for example, a form is submitted and the value is changed via Vue instead of via user input)
@calebporzio
Copy link
Collaborator

TL;DR; Here's how to make selectpicker work in Livewire:

    <div id="for-picker" wire:ignore>
        <select wire:model="foo" class="selectpicker" data-container="#for-picker">
            <option>bar</option>
            <option>baz</option>
        </select>
    </div>

Ok, there were two problems contributing to the issue:

A. Normal <select> elements fire both an "input" and "change" event when changed. Livewire listens for "input" by default.
B. When Selectpicker is initialized, it wraps the <select> element in a <div class="bootstrap select">. This completely throws off Livewire's dom-diffing strategy. In other words, when any update is made on the page, Livewire receives back new DOM from Laravel, and when it encounters the div it thinks it should be a select, so it kills bootstrap selectpicker.

Here is the 2-pronged solution:

A. I tagged a new release (v0.0.15) that changes the default listener event to "change" for non-text elements. I think this is a more sensible default
B. This one is a bit harder, but fortunately, selectpicker offers an option called "container" that we can use in conjunction with a livewire directive called "wire:ignore", to make this work. Here is the code that will work:

    <div id="for-picker" wire:ignore>
        <select wire:model="foo" class="selectpicker" data-container="#for-picker">
            <option>bar</option>
            <option>baz</option>
        </select>
    </div>

Hope this helps, let me know if it fixed it for you!

@intellow
Copy link
Contributor Author

Thanks! It works (sometimes)!

I wonder if I'm doing something wrong, but I'm seeing a few issues. It's a bit hard to explain so I made a quick video to show you what's happening:

https://www.youtube.com/watch?v=_-LRkPwxdJI

To summarize:

  • I'm not getting reactivity with printing the variable to the screen when I change the value in the selectpicker (even though I know the value is getting updated in the class thanks to dumping the variable).
  • The initial value is not selected on the initial load of the page
  • Sometimes (I haven't found any rhyme or reason to when or why) when I change the selectpicker value, I see an error in the console and based on what I can tell when this happens the value is not changed. The error in the console is:
Uncaught (in promise) SyntaxError: Unexpected token < in JSON at position 0
    at JSON.parse (<anonymous>)
    at livewire.js?id=eaf04216e7ec07b7167b:1

@intellow
Copy link
Contributor Author

I started over on this in a new project and I think a lot of the issues I was seeing must have been related to that original project. However, it doesn't seem that this approach works when I am trying to use the selected option from one selectpicker to populate the values of another selectpicker.

I can make the original selection just fine, but if I am trying to do this

public function updatedFirstSelectPicker($value)
{
    $this->secondSelectPickerOptions = Model::find($value)->relation->pluck('name', 'id');
}

The problem is, I have the second selectpicker also set to wire:ignore so it doesn't get the updated values. If I remove wire:ignore then the whole thing disappears when updatedFirstSelectPicker is called.

@intellow
Copy link
Contributor Author

I have found a workaround in the console, and maybe you can help me figure out how to implement this programatically.

What I'd like to do is run some custom javascript just before livewire swaps out the DOM, then run more custom javascript right after the DOM is swapped. I have looked through the docs at events, but those all seem to just run PHP and not Javascript.

This would work

  • Run $('#my-selectpicker').selectpicker('destroy')
  • Livewire Magic
  • Run $('#my-selectpicker').selectpicker()

selectpicker livewire

@calebporzio
Copy link
Collaborator

There are two "lifecycle hooks" that have been added to the JavaScript livewire object to make things like this easier:

livewire.beforeDomUpdate(callback) and livewire.afterDomUpdate(callback)

You can find documentation for them here: https://livewire-framework.com/docs/lifecycle-hooks/

@japasanto
Copy link

TL;DR; Here's how to make selectpicker work in Livewire:

    <div id="for-picker" wire:ignore>
        <select wire:model="foo" class="selectpicker" data-container="#for-picker">
            <option>bar</option>
            <option>baz</option>
        </select>
    </div>

Ok, there were two problems contributing to the issue:

A. Normal <select> elements fire both an "input" and "change" event when changed. Livewire listens for "input" by default.
B. When Selectpicker is initialized, it wraps the <select> element in a <div class="bootstrap select">. This completely throws off Livewire's dom-diffing strategy. In other words, when any update is made on the page, Livewire receives back new DOM from Laravel, and when it encounters the div it thinks it should be a select, so it kills bootstrap selectpicker.

Here is the 2-pronged solution:

A. I tagged a new release (v0.0.15) that changes the default listener event to "change" for non-text elements. I think this is a more sensible default
B. This one is a bit harder, but fortunately, selectpicker offers an option called "container" that we can use in conjunction with a livewire directive called "wire:ignore", to make this work. Here is the code that will work:

    <div id="for-picker" wire:ignore>
        <select wire:model="foo" class="selectpicker" data-container="#for-picker">
            <option>bar</option>
            <option>baz</option>
        </select>
    </div>

Hope this helps, let me know if it fixed it for you!

Hi caleb, this simply doesn´t work for me. I have a livewire component which is a modal and it´s called on a button click that is inside a laravel blade view...everything works great, but the bootstrap-select(selectpicker) it is not rendered, only a normal bootstrap-select, but i need the selectpicker because of the multiple option with the selectAll options...in my app.js i have the:
require('../../node_modules/bootstrap-select/dist/js/bootstrap-select');

and inside my app.scss i have:

 @import '~bootstrap/scss/bootstrap';
 @import '~bootstrap-select/sass/bootstrap-select.scss';

In my web.php i have:

Route::livewire('/new','new')->layout('adminlte::page');
The layout i´m using is because i´m using the adminlte by jeronen

Could you help me out with this please? Reagrds

@futurewebsites
Copy link

I am having the same issue with tail-select

<div class="mt-2"> <select wire:model.defer="location_id" name="location_id" id="location_id" data-search="true" class="tail-select w-full"> @foreach($locations as $location) <option value="{{$location->id}}">{{$location->name}}</option> @endforeach </select> </div>

which renders:

<div class="mt-2"> <select wire:model.defer="location_id" name="location_id" id="location_id" data-search="true" class="tail-select w-full" data-select-hidden="0" data-tail-select="tail-1" style="display: none;"> <option value="2" selected="selected">My house</option> </select> <div class="tail-select tail-select w-full" tabindex="0"><div class="select-label"><span class="label-inner">My house</span></div><div class="select-dropdown" style="max-height: 350px;"><div class="dropdown-search"><input type="text" class="search-input" placeholder="Type in to search..."></div><div class="dropdown-inner"><ul class="dropdown-optgroup" data-group="#"><li class="dropdown-option selected" data-key="2" data-group="#">My house</li></ul></div></div></div></div>

@ChristianAdrian
Copy link

I have found a workaround in the console, and maybe you can help me figure out how to implement this programatically.

What I'd like to do is run some custom javascript just before livewire swaps out the DOM, then run more custom javascript right after the DOM is swapped. I have looked through the docs at events, but those all seem to just run PHP and not Javascript.

This would work

  • Run $('#my-selectpicker').selectpicker('destroy')
  • Livewire Magic
  • Run $('#my-selectpicker').selectpicker()

selectpicker livewire

this works

<script> document.addEventListener('DOMContentLoaded', () => { Livewire.hook('element.updating', (fromEl, toEl, component) => { console.log('being update'); $('#type').selectpicker('destroy') }) Livewire.hook('message.processed', (message, component) => { console.log('processed'); $('#type').selectpicker() }) }); </script>

@hasnain8890
Copy link

I have found a workaround in the console, and maybe you can help me figure out how to implement this programatically.
What I'd like to do is run some custom javascript just before livewire swaps out the DOM, then run more custom javascript right after the DOM is swapped. I have looked through the docs at events, but those all seem to just run PHP and not Javascript.
This would work

  • Run $('#my-selectpicker').selectpicker('destroy')
  • Livewire Magic
  • Run $('#my-selectpicker').selectpicker()

selectpicker livewire

this works

<script> document.addEventListener('DOMContentLoaded', () => { Livewire.hook('element.updating', (fromEl, toEl, component) => { console.log('being update'); $('#type').selectpicker('destroy') }) Livewire.hook('message.processed', (message, component) => { console.log('processed'); $('#type').selectpicker() }) }); </script>

This only works for the first time I select. When i select it choose it again i get an error.

Console:
being update
(458) being update
processed

@cata1818
Copy link

cata1818 commented Feb 6, 2023

For anyone looking, this worked for me. I have 2 selects in a livewire component, from which 2nd one is dependent on first one and its using selectpicker. Here is how i got 2nd one to render correctly with selectpicker
<script> document.addEventListener('DOMContentLoaded', () => { Livewire.hook('message.sent', (message, component) => { console.log('livewire request sent'); $('#mySelect').selectpicker('destroy') }) Livewire.hook('message.processed', (message, component) => { console.log('livewire request processed'); $('#mySelect').selectpicker() }) }); $('#mySelect').selectpicker(); </script>

@VenomXL
Copy link

VenomXL commented Feb 11, 2023

@cata1818 I tried using the same but suddenly after I select any option and livewire gets updated the select field disappears. What could be the possible reason for this any clue?

@cata1818
Copy link

@cata1818 I tried using the same but suddenly after I select any option and livewire gets updated the select field disappears. What could be the possible reason for this any clue?

I m not sure why .. i added the code i pasted in my previous comment at the bottom of the livewire component i use. And this livewire component contain just the 2 linked selects and this js code. I remember i had similar issue.. but i think i fixed by using destroy on message.sent and create new instance on message.processed... i played with almost all events and only this worked.

calebporzio added a commit that referenced this issue Jul 20, 2023
failing test: Greater than inside @if condition
@isisureste
Copy link

When you send a request Livewire represents the new DOM as if it were a select what you have to do is the following.

  • Example with the problem:
<div> 
  <select class="selectpicker" >
    <option>Mustard</option>
    <option>Ketchup</option>
    <option>Relish</option>
  </select>
</div>
  • Proposed solution:
<div wire:ignore> 
  <select class="selectpicker">
    <option>Mustard</option>
    <option>Ketchup</option>
    <option>Relish</option>
  </select>
</div>

When placing the wire:ignore in the div and not in the select, causing the DOM of the select to not update.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

9 participants