Rendering a component directive in Vue 2 without params - javascript

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

Vue Test Utils + Third-party component: cannot detect input event

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.

vue.js multi select change options inside axios GET

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');
}
},

The dynamically created components in vue.js shows random behaviour

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.

Can't Target repeated component in DOM - Vue.js

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.

VueJS Select2 directive doesn't fire #change event

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');
}

Categories

Resources