I have an app that holds the student list data. The component is supposed to take in that list and render a select dropdown (with select2).
In the fiddles console, it's displaying jQuery is not defined. I thought all fiddles now included jQuery?
I'm really not sure why this is breaking the all together. Is there something wrong with my directive? I know with Vue 2.0 they removed params, but this should suffice. Any eyes on my code would be greatly appreciated.
// Define component
var studentsComponent = Vue.extend({
props: ['students'],
data(): {
return {}
},
methods:{},
directives: {
select: {
bind: function () {
var self = this;
var select = $('#select-student');
select.select2();
select.on('change', function () {
console.log('hey on select works!');
});
},
update: function (oldVal, newVal) {
var select = $('#select-student');
select.val(newVal).trigger('change');
}
},
},
template: `
<div>
<select
ref=""
id="select-student"
v-select>
<option value="0">Select Student</option>
<option
v-for="(student, index) in students"
:value="student.id">
{{ student.name }}
</option>
</select>
</div>
`,
});
// Register component
Vue.component('students-component', studentsComponent);
// App
new Vue({
el: '#app',
data: {
students: [
{ name: 'Jack', id: 0 },
{ name: 'Kate', id: 1 },
{ name: 'Sawyer', id: 2 },
{ name: 'John', id: 3 },
{ name: 'Desmond', id: 4 },
]
},
});
I made a fiddle https://jsfiddle.net/hts8nrjd/4/ for reference. Thank you for helping a noob out!
First, as I mentioned in comments, I would suggest you do this with a component. If you had to stick with a directive, however, you can't initialize select2 in the bind hook. You've defined your options in the DOM, so you need to wait until the component is inserted to initialize it.
directives: {
select: {
inserted: function (el, binding, vnode) {
var select = $(el);
select.select2();
select.on('change', function () {
console.log('hey on select works!');
});
},
},
},
Here is an update of your fiddle.
Related
I'm passing through a problem to detect the input event emission while using a third-party component only when testing. The component behaves as it's supposed to when testing on the browser. Using the Vue extension I also can see that it's emitting the event correctly.
I'm using Choices as my custom select.
Vue.component('choices', {
props: ['options', 'value'],
template: '#choices-template',
mounted: function() {
this.choicesInstance = new Choices(this.$refs.select);
this.$refs.select.addEventListener('addItem', this.handleSelectChange);
this.setChoices();
},
methods: {
handleSelectChange(e) {
this.$emit('input', e.target.value);
},
setChoices() {
this.choicesInstance.setChoices(this.options, 'id', 'text', true);
}
},
watch: {
value: function(value) {
console.log('input triggered: ' + value);
},
options: function(options) {
// update options
this.setChoices();
}
},
destroyed: function() {
this.choicesInstance.destroy();
}
})
var vm = new Vue({
el: '#el',
template: '#demo-template',
data: {
selected: 2,
options: [{
id: 1,
text: 'Hello'
},
{
id: 2,
text: 'World'
}
]
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/choices.js/public/assets/scripts/choices.min.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/choices.js/public/assets/styles/choices.min.css" />
<div id="el"></div>
<!-- using string template here to work around HTML <option> placement restriction -->
<script type="text/x-template" id="demo-template">
<div>
<p>Selected: {{ selected }}</p>
<choices :options="options" v-model="selected">
<option disabled value="0">Select one</option>
</choices>
</div>
</script>
<script type="text/x-template" id="choices-template">
<select ref="select">
<slot></slot>
</select>
</script>
My test file is like this (I'm using Jest):
import CustomSelect from '#/CustomSelect';
import { mount } from '#vue/test-utils';
let wrapper,
options = [
{
key: 'Foo',
value: 'foo',
},
{
key: 'Bar',
value: 'bar',
},
{
key: 'Baz',
value: 'baz',
},
];
beforeEach(() => {
wrapper = mount(CustomSelect, {
propsData: {
options,
},
});
});
it('Emits an `input` event when selection changes', () => {
wrapper.vm.choicesInstance.setChoiceByValue([options[1].value]);
expect(wrapper.emitted().input).not.toBeFalsy();
});
wrapper.emitted() is always an empty object for this test;
If I expose choicesInstance from my component to global context and manually call choicesInstance.setChoiceByValue('1') it correctly emits the input event.
Hi I'm using this modified wrapper to handle a multiple select for vue.js. I'm trying to change value of this inside vue component. Here is my code.
<select2-multiple :options="car_options" v-model="input.classification">
<option disabled value="0">Select one</option>
</select2-multiple>
And this is my script,
Vue.component('select2Multiple', {
props: ['options', 'value'],
template: '#select2-template',
mounted: function () {
var vm = this
$(this.$el)
// init select2
.select2({ data: this.options })
.val(this.value)
.trigger('change')
// emit event on change.
.on('change', function () {
vm.$emit('input', $(this).val())
})
},
watch: {
value: function (value) {
alert(value);
if ([...value].sort().join(",") !== [...$(this.$el).val()].sort().join(","))
$(this.$el).val(value).trigger('change');
},
options: function (options) {
// update options
$(this.$el).select2({ data: options })
}
},
destroyed: function () {
$(this.$el).off().select2('destroy')
}
});
var vm = new Vue({
el: '#el',
delimiters: ["[[", "]]"],
data: {
input: {
classification: []
},
},
created: function () {
var vm = this;
axios.get('http://jsonplaceholder.typicode.com/todos')
.then(function (response) {
$.each(response.data, function (i, item) {
response.data[i].id = String(response.data[i].id);
response.data[i].text = String(response.data[i].title);
vm.car_options.push(response.data[i]);
});
vm.input.classification = ["2"];
})
.catch(function (error) {
console.log(error);
});
}
});
I need to get vm.input.classification = ["2"] selected by default. And it's not working and no error message displays. I'm no expert in vue components but I feel like issue relies on vue component.
Here is a js fiddle for my example,
Finally I found the answer. We need to swapp the positions of options and value of component watch.
watch: {
options: function (options) {
// update options
$(this.$el).select2({ data: options })
},
value: function (value) {
if ([...value].sort().join(",") !== [...$(this.$el).val()].sort().join(","))
$(this.$el).val(value).trigger('change');
}
},
What i want is, there a list made from json data. When i click on a item, it creates a new list dynamically.
Now when i click a different item in the first list, i want the second list to change depending on data i receive.
html structure is :
div class="subject-list container-list" id="app-1">
<item-subject
v-for="item in subjectlist"
v-bind:item="item"
v-bind:key="item.id"
>
</item-subject>
</div>
//some other code
<div class="exam-list container-list" id="app-2">
<item-exam
v-for="item in examlist"
v-bind:item="item"
v-bind:key="item.id"
>
</item-exam>
</div>
The main.js file is :
//Dummy json data
var subjects_json = { 'subjectlist': [
{ id: 0, text: 'Computer Graphics' },
{ id: 1, text: 'Automata Theory' },
{ id: 2, text: 'Programming in C' }
]};
var exams_json = { 'examlist': [
{ id: 0, text: 'IAT 1' },
{ id: 1, text: 'IAT 2' },
{ id: 2, text: 'Sem 2' }
]};
/*Contains definition of component item-subject...
Its method() contains call to exam component because it will be
called depending on the subject selected dynamically*/
Vue.component('item-subject', {
props: ['item'],
template: '<li v-on:click="showExams" class="subject-list-item">{{
item.text }}</li>',
methods: {
showExams: function(){
// alert(this.item.text)
console.log("Subject Clicked: "+this.item.text)
var app2 = new Vue({
el: '#app-2',
data: exams_json,
methods: {
showStudents: function(){
console.log("exams rendered")
}
}
})
},
}
});
//Contains definition of component item-exam.
Vue.component('item-exam', {
props: ['item'],
template: '<li v-on:click="showStudents" class="exam-list-item">{{ item.text }}</li>',
methods: {
showStudents: function(){
alert(this.item.text)
console.log("exam component executed")
// console.log("Exam Clicked: "+this.item)
}
}
});
//Call to subject component
var app1 = new Vue({
el: '#app-1',
data: subjects_json,
methods: {
showExams: function(){
console.log("subjects rendered")
}
}
})
So what this code does is, when i click on the first list i.e. subjects list, it dynamically renders new exams list.
Now when i click on second list, alert() method is called successfully.
However if i click any of the subject list(first list), now the alert() is not triggered while clicking second list.
Please tell me whats wrong.
Excuse any syntax errors, It works perfectly, but I could have made an error copying over.
Problem: I have a component, 'dropdown', that is repeated three times with
v-for='(item, index) in search'
which is an array with three objects. Below in the 'filterInput' method, The for loop and if statement does indeed work as intended, HOWEVER, I do not know how to target the 'dropdown' element that matches the search[i]. I need to remove the search[i]'s element in the DOM when the search[i].text doesn't match the input.
<div id='app'>
<input type='text' placeholder='Start typing here...' v-model='input'
#click='drop()' v-on:blur='close()'>
<ul id="dropdown" class='nodisplay'>
<dropdown v-for='(item, index) in search' v-bind:item='item' v-
bind:index='index'></dropdown>
</ul>
</div>
Vue.component('dropdown', {
props: ['item', 'index'],
template: `<li> {{item.text}}</li>`
})
var app = new Vue({
el: '#app',
data: {
input: '', //reactive
search: [
{id: 1, text: 'Jacob'},
{id: 2, text: 'Jeff'},
{id: 3, text: 'Tom'}
]
},
methods: {
drop: function() {
let dropdown = document.getElementById('dropdown');
dropdown.classList.toggle('nodisplay');
},
close: function() {
let dropdown = document.getElementById('dropdown');
dropdown.classList.toggle('nodisplay');
document.querySelector('input').value = '';
},
filterInput: function(index) {
//dropdown items in console: app.search[index].text = 'xyz'
for (let i = 0; i < this.search.length; i++) {
if (!(this.search[i].text.startsWith(this.input))) {
//hide or remove this current search element from dropdown
}
}
}
},
watch: {
input: function() {
this.filterInput();
}
}
})
tl;dr; how do I target
What you are looking for is how to have parent child communication, which I have answered today itself here.
You need to $emit an event from the child component and set the value used in input field, just like the example in documentation.
Here is the code:
HTML
<div id='app'>
<input type='text' placeholder='Start typing here...' v-model='input'
#click='drop()' >
<ul id="dropdown" :class="{'nodisplay': dropDownClosed}">
<dropdown v-for='(item, index) in search' v-bind:item='item' v-
bind:index='index' v-on:getdropdowninput="getdropdowninput"></dropdown>
</ul>
</div>
JS
dropdown = Vue.component('dropdown', {
props: ['item', 'index'],
template: `<div><li ><a #click="selectval(item.text)" href="#"> {{item.text}}</a></li></div>`,
methods: {
selectval (value) {
this.$emit("getdropdowninput", value)
}
}
})
var app = new Vue({
el: '#app',
data: {
input: '', //reactive
dropDownClosed: false,
search: [
{id: 1, text: 'Jacob'},
{id: 2, text: 'Jeff'},
{id: 3, text: 'Tom'}
]
},
methods: {
drop: function() {
this.dropDownClosed = true
},
getdropdowninput: function(value) {
this.dropDownClosed = false
this.input = value;
},
filterInput: function(index) {
//dropdown items in console: app.search[index].text = 'xyz'
for (let i = 0; i < this.search.length; i++) {
if (!(this.search[i].text.startsWith(this.input))) {
//hide or remove this current search element from dropdown
}
}
}
},
watch: {
input: function() {
this.filterInput();
}
}
})
Here is the working fiddle.
Use dynamic classes: I have also modified how to add/remove a class dynamically in vue way, instead of document.getElementById. Notice in following line:
<ul id="dropdown" :class="{'nodisplay': dropDownClosed}">
nodisplay class will be applied when dropDownClosed variable will be true and it will be removed when dropDownClosed variable will be false.
How to Filter:
To filter, you can use a computed property in the v-for and whenever input changes you can filter the search array, like following
computed: {
filteredInput: function(){
if(this.input === '' || !this.input){
return this.search
} else {
var self = this
return this.search.filter(
function( s ) {
return s.text.indexOf( self.input ) !== -1; }
);
}
}
See working fiddle here.
As title says, I registered Select2 directive for VueJS 1.0.15 using example from their page.
I want to catch #change event but it doesn't work.
HTML:
<select v-select="item.service" :selected="item.service" #change="serviceChange(item)">
<option value="1">test 1</option>
<option value="2">test 2</option>
</select>
JS:
Vue.directive('select', {
twoWay: true,
params: ['selected'],
bind: function () {
var self = this
$(this.el)
.select2({
minimumResultsForSearch: Infinity
})
.val(this.params.selected)
.on('change', function () {
console.log('changed');
self.set(this.value);
})
},
update: function (value) {
$(this.el).val(value).trigger('change')
},
unbind: function () {
$(this.el).off().select2('destroy')
}
});
var Checkout = new Vue({
el: '.Checkout',
methods: {
serviceChange: function (item) {
console.log(item);
},
}
});
Expanded on Alberto Garcia answer.
var selectDropDown = $(".select-drop-down").select2();
selectDropDown.on('select2:select', function (e) {
var event = new Event('change');
e.target.dispatchEvent(event);
});
Yeah, I encountered this problem. Haven't figured out the solution yet, but this is my current work-a-round using watch:
In your VueJS:
watch: {
/**
* Watch changes on the select2 input
*
* #param integer personId
*/
'person': function (personId) {
var personName = $("#person").select2('data')[0].text
this.doSomethingWithPerson(personId, personName)
}
},
Then in your HTML:
<select
id="person"
v-select2="person"
class="form-control"
data-options="{{ $peopleJSON }}"
data-placeholder="Choose a person"
>
</select>
Note: In the above HTML, {{ $peopleJSON }} is just how I insert my server-side JSON using Blade template engine. Not really pertinent to the question.
I recently had a problem trying to bind a select2 result to a vuejs data model object, and I came with a workaround, it might help you:
https://jsfiddle.net/alberto1el/7rpko644/
var search = $(".search").select2({
ajax: {
url: "/echo/json",
dataType: 'json',
delay: 250,
data: function (params) {
return {
q: params.term
};
},
processResults: function (data, params) {
var mockResults = [{id: 1,text: "Option 1"}, {id: 2,text: "Option 2"}, {id: 3,text: "Option 3"}];
return {results: mockResults};
},
cache: true
},
escapeMarkup: function (markup) { return markup; },
minimumInputLength: 1
});
search.on("select2:select", function (e){
$('.result_search').val( $(this).val() ).trigger( 'change' );
});
var vm = new Vue({
el: "#app",
data: {
busqueda_result : ''
}
});
I know you are using a directive but maybe it helps you
shouldn't you use $emit?
update: function (value) {
$(this.el).val(value);
this.$emit('change');
}