Why emitter #current-items not working for v-data-table? - javascript

I want to console.log visible items in table but #current-items emitter not working, please help me to figure out why?
I have vuetify: "1.3.11"
methods: {
currentItems(val) {
console.log(val) // the method is not called for some reason
this.test = val
console.log(this.test)
}
}
<v-data-table
#input="updateSelected"
v-bind="calculatedTableProps"
ref="dataTable"
:pagination.sync="localPagination"
:value="selected"
:headers="localHeaders"
:items="filteredItems"
:headers-length="headerCount"
:total-items="totalCount"
:loading="tableLoading"
class="big-data-table"
:class="tableFitContent ? 'table-fit-content' : ''"
:hide-actions="customActions"
v-scroll:[scrollTarget]="onScroll"
#current-items="currentItems"
>

The #current-items event does not seem to exist in Vuetify 1.x. Take a look at the docs of the v-data-table for Vuetify 1.5.24.

Related

Reactive binding with VueJS

I'm trying to bind a class like so:
:class="{active: favs.medium_title.fontWeight === 'bold'}"
Except that fontWeight doesn't exist yet when the component is mounted.
Here's my object:
favs: {
...
medium_title: {},
...
}
So when I add the fontWeight property and it's value it doesn't set the active class.
You could use vm.$set (as you can read here) to add the new property:
this.$set( this.favs.medium_title , 'fontWeight' , 'bold' )
In this way it will be reactive and changes will be detected.
You need to add fontWeight to your data object and give it a default value so fontWeight exists on mounted.
Because the property fontWeight ain't declared it is not reactive.Hence you will need to declare it in the declaration of object.
In this the class is binding properly on change of value in the property on button click.
<div id="app">
<p :class="{active: favs.medium_title.fontWeight === 'bold'}">{{ message }}</p>
<button #click="favs.medium_title.fontWeight = 'bold'">
Click here
</button>
</div>
new Vue({
el: '#app',
data: {
message: 'Hello Vue.js!',
favs: {
medium_title: {
fontWeight:''
},
}
}
})
Refer the below fiddle for the solution.
https://jsfiddle.net/xgh63uLo/
If u don't want to declare the property in advance you may use Vue.$set to set the new property with making it reactive
Check below link
https://v2.vuejs.org/v2/guide/reactivity.html#Change-Detection-Caveats

Data is not updated when using as object, but changes normally when it's a variable

I'm writing a function to update a custom checkbox when clicked (and I don't want to use native checkbox for some reasons).
The code for checkbox is
<div class="tick-box" :class="{ tick: isTicked }" #click="() => isTicked = !isTicked"></div>
which works find.
However, there are so many checkboxes, so I use object to keep track for each item. It looks like this
<!-- (inside v-for) -->
<div class="tick-box" :class="{ tick: isTicked['lyr'+layer.lyr_id] }" #click="() => {
isTicked['lyr'+layer.lyr_id] = !isTicked['lyr'+layer.lyr_id]
}"></div>
Now nothing happens, no error at all.
When I want to see isTicked value with {{ isTicked }}, it's just shows {}.
This is what I define in the <script></script> part.
export default {
data() {
return {
isTicked: {},
...
};
},
...
}
Could you help me where I get it wrong?
Thanks!
Edit:
I know that declaring as isTicked: {}, the first few clicks won't do anything because its proerty is undefined. However, it should be defined by the first/second click not something like this.
Objects does not reflect the changes when updated like this.
You should use $set to set object properties in order to make them reactive.
Try as below
<div class="tick-box" :class="{ tick: isTicked['lyr'+layer.lyr_id] }" #click="onChecked"></div>
Add below method:
onChecked() {
this.$set(this.isTicked,'lyr'+this.layer.lyr_id, !this.isTicked['lyr'+this.layer.lyr_id])
}
VueJS watches data by reference so to update object in state you need create new one.
onChecked(lyr_id) {
const key = 'lyr'+lyr_id;
this.isTicked = {...this.isTicked, [key]: !this.isTicked[key]};
}

How to differentiate between user input and data change with vue-select

I'm using vue-select in my app and am trying to prevent an event handler from firing when the default values are first loaded into the vue-select input.
The component looks like this:
<v-select
multiple
v-model="product.recommended_accessories"
label="name"
:options="accessoryOptions"
#input="saveProduct"
#search="onAccessorySearch">
<template slot="option" slot-scope="option">
<h4>{{ option.name }}</h4>
<h5>{{ option.sku }}</h5>
</template>
</v-select>
As you can see, I want to save the product when the user changes the values in this multi-select. It works fine, but with one issue.
The value of the select is tied to the product.recommended_accessories data. Elsewhere in the app, a product is loaded from the server, which includes a recommended_accessories attribute. Loading the product then triggers saveProduct to be called since vue-select set the preselected options for the input, which apparently triggers the #input event.
Is there anyway around this? Maybe I have made some sort of design error here. Or maybe there is a hook I should be using to bind the event handler, or to set some sort of flag indicating that the product is in the process of being loaded and saving the product shouldn't occur.
I'm just trying to avoid saving the product immediately after it's loaded for no reason.
For now I am just tracking an accessoryEventCount variable that gets initialized to 0 whenever a product is loaded. Then I make sure accessoryEventCount > 0 before calling saveProduct on an v-select input event.
It works, but I am still wondering if there is a more elegant solution.
UPDATE
Looks like Vue.nextTick is what I was looking for. Before setting the value of product in code, I set a flag this.isSettingProduct = true. Then I set the product, and call Vue.nextTick(() => { this.isSettingProduct = false });.
Now I can avoid saving the product if this.isSettingProduct == true. Using Vue.nextTick ensures that the flag isn't set back to false until after the asynchronous data update completes.
It looks you should bind prop=onChange, though #input still seems working (check v-select github: line# 544).
Below is my solution, before loading your product, bind onChange with function () {}, after loaded, bind it with the function you like.
Vue.component('v-select', VueSelect.VueSelect)
app = new Vue({
el: "#app",
data: {
accessoryOptions: [
{'name':'a1', 'sku':'a2'},
{'name':'b1', 'sku':'b2'},
{'name':'c1', 'sku':'c2'}
],
product: {
recommended_accessories: []
},
saveProduct: function (){}
},
methods: {
onAccessorySearch: function () {
console.log('emit input')
},
loadProduct: function () {
this.product.recommended_accessories = ['a1', 'b1'] // simulate get data from the server
setTimeout( () => {
this.saveProduct = (value) => {
console.log('emit input', this.product.recommended_accessories)
}
}, 400)
}
}
})
#app {
width: 400px;
}
<script src="https://unpkg.com/vue#latest"></script>
<script src="https://unpkg.com/vue-select#latest"></script>
<div id="app">
<button #click="loadProduct()">Click Me!</button>
<v-select
multiple
v-model="product.recommended_accessories"
label="name"
:options="accessoryOptions"
:on-change="saveProduct"
#search="onAccessorySearch">
<template slot="option" slot-scope="option">
<span>{{ option.name }}</span>
<span>{{ option.sku }}</span>
</template>
</v-select>
</div>

How to limit selected items in vue-select?

I use vue-select for multiple values.
Here is an example: https://codepen.io/sagalbot/pen/opMGro
I have the following code:
<v-select multiple v-model="selected" :options="options"></v-select>
And JS:
Vue.component('v-select', VueSelect.VueSelect)
new Vue({
el: '#app',
data: {
selected: ['foo','bar'],
options: ['foo','bar','baz']
}
Thank you!
You can use the v-on:input listener to see how many items are selected.
Then pass it a simple function as:
<v-select multiple v-model="selected" :options="options" v-on:input="limiter"></v-select>
After this, create a function called limiter in your methods and you'll get the current list of selected inputs,as
methods: {
limiter(e) {
if(e.length > 2) {
console.log(' you can only select two', e)
e.pop()
}
},
}
Now, if you add more than 2 items then the last one will be remove and you will see the console log
You can simply use inline condition:
<v-select multiple v-model="selected" :options="selected.length < 2 ? options: []">
I have limited to 2 options in the example above. The options will not be generated if there are 2 options selected. Remove the selected one and then you'll see the options dropdown.
Here's the updated demo.
you can use
:selectable="() => selected.length < 3"
from the official documentation https://vue-select.org/guide/selectable.html#limiting-the-number-of-selections

DOM element to corresponding vue.js component

How can I find the vue.js component corresponding to a DOM element?
If I have
element = document.getElementById(id);
Is there a vue method equivalent to the jQuery
$(element)
Just by this (in your method in "methods"):
element = this.$el;
:)
The proper way to do with would be to use the v-el directive to give it a reference. Then you can do this.$$[reference].
Update for vue 2
In Vue 2 refs are used for both elements and components: http://vuejs.org/guide/migration.html#v-el-and-v-ref-replaced
In Vue.js 2 Inside a Vue Instance or Component:
Use this.$el to get the HTMLElement the instance/component was mounted to
From an HTMLElement:
Use .__vue__ from the HTMLElement
E.g. var vueInstance = document.getElementById('app').__vue__;
Having a VNode in a variable called vnode you can:
use vnode.elm to get the element that VNode was rendered to
use vnode.context to get the VueComponent instance that VNode's component was declared (this usually returns the parent component, but may surprise you when using slots.
use vnode.componentInstance to get the Actual VueComponent instance that VNode is about
Source, literally: vue/flow/vnode.js.
Runnable Demo:
Vue.config.productionTip = false; // disable developer version warning
console.log('-------------------')
Vue.component('my-component', {
template: `<input>`,
mounted: function() {
console.log('[my-component] is mounted at element:', this.$el);
}
});
Vue.directive('customdirective', {
bind: function (el, binding, vnode) {
console.log('[DIRECTIVE] My Element is:', vnode.elm);
console.log('[DIRECTIVE] My componentInstance is:', vnode.componentInstance);
console.log('[DIRECTIVE] My context is:', vnode.context);
// some properties, such as $el, may take an extra tick to be set, thus you need to...
Vue.nextTick(() => console.log('[DIRECTIVE][AFTER TICK] My context is:', vnode.context.$el))
}
})
new Vue({
el: '#app',
mounted: function() {
console.log('[ROOT] This Vue instance is mounted at element:', this.$el);
console.log('[ROOT] From the element to the Vue instance:', document.getElementById('app').__vue__);
console.log('[ROOT] Vue component instance of my-component:', document.querySelector('input').__vue__);
}
})
<script src="https://unpkg.com/vue#2.5.15/dist/vue.min.js"></script>
<h1>Open the browser's console</h1>
<div id="app">
<my-component v-customdirective=""></my-component>
</div>
If you're starting with a DOM element, check for a __vue__ property on that element. Any Vue View Models (components, VMs created by v-repeat usage) will have this property.
You can use the "Inspect Element" feature in your browsers developer console (at least in Firefox and Chrome) to view the DOM properties.
Hope that helps!
this.$el - points to the root element of the component
this.$refs.<ref name> + <div ref="<ref name>" ... - points to nested element
💡 use $el/$refs only after mounted() step of vue lifecycle
<template>
<div>
root element
<div ref="childElement">child element</div>
</div>
</template>
<script>
export default {
mounted() {
let rootElement = this.$el;
let childElement = this.$refs.childElement;
console.log(rootElement);
console.log(childElement);
}
}
</script>
<style scoped>
</style>
So I figured $0.__vue__ doesn't work very well with HOCs (high order components).
// ListItem.vue
<template>
<vm-product-item/>
<template>
From the template above, if you have ListItem component, that has ProductItem as it's root, and you try $0.__vue__ in console the result unexpectedly would be the ListItem instance.
Here I got a solution to select the lowest level component (ProductItem in this case).
Plugin
// DomNodeToComponent.js
export default {
install: (Vue, options) => {
Vue.mixin({
mounted () {
this.$el.__vueComponent__ = this
},
})
},
}
Install
import DomNodeToComponent from'./plugins/DomNodeToComponent/DomNodeToComponent'
Vue.use(DomNodeToComponent)
Use
In browser console click on dom element.
Type $0.__vueComponent__.
Do whatever you want with component. Access data. Do changes. Run exposed methods from e2e.
Bonus feature
If you want more, you can just use $0.__vue__.$parent. Meaning if 3 components share the same dom node, you'll have to write $0.__vue__.$parent.$parent to get the main component. This approach is less laconic, but gives better control.
Since v-ref is no longer a directive, but a special attribute, it can also be dynamically defined. This is especially useful in combination with v-for.
For example:
<ul>
<li v-for="(item, key) in items" v-on:click="play(item,$event)">
<a v-bind:ref="'key' + item.id" v-bind:href="item.url">
<!-- content -->
</a>
</li>
</ul>
and in Vue component you can use
var recordingModel = new Vue({
el:'#rec-container',
data:{
items:[]
},
methods:{
play:function(item,e){
// it contains the bound reference
console.log(this.$refs['key'+item.id]);
}
}
});
I found this snippet here. The idea is to go up the DOM node hierarchy until a __vue__ property is found.
function getVueFromElement(el) {
while (el) {
if (el.__vue__) {
return el.__vue__
} else {
el = el.parentNode
}
}
}
In Chrome:
Solution for Vue 3
I needed to create a navbar and collapse the menu item when clicked outside. I created a click listener on windows in mounted life cycle hook as follows
mounted() {
window.addEventListener('click', (e)=>{
if(e.target !== this.$el)
this.showChild = false;
})
}
You can also check if the element is child of this.$el. However, in my case the children were all links and this didn't matter much.
If you want listen an event (i.e OnClick) on an input with "demo" id, you can use:
new Vue({
el: '#demo',
data: {
n: 0
},
methods: {
onClick: function (e) {
console.log(e.target.tagName) // "A"
console.log(e.targetVM === this) // true
}
}
})
Exactly what Kamil said,
element = this.$el
But make sure you don't have fragment instances.
Since in Vue 2.0, no solution seems available, a clean solution that I found is to create a vue-id attribute, and also set it on the template. Then on created and beforeDestroy lifecycle these instances are updated on the global object.
Basically:
created: function() {
this._id = generateUid();
globalRepo[this._id] = this;
},
beforeDestroy: function() {
delete globalRepo[this._id]
},
data: function() {
return {
vueId: this._id
}
}

Categories