How to get the context of the template? - vue.js

Consider the following Vue component
<template>
<div class="myclass">
<div class="myotherclass">I am in {{getcurrentClass()}}</div>
</div>
</template>
<script>
export default {
data() {
return {
currentClass: null
}
},
methods: {
getCurrentClass() {
// code to get the class in the context of the place
// it is called in the template
}
}
}
</script>
What I am trying to understand is how to account for the "template context" in a method. In the code above getCurrentClass() would return the class of the element (this is an example, it can be explained to "name of the element", or "id of the element", ...).
Is this at all possible? If so I would appreciate even a general pointer (and I can post the response once I have a solution) - I am not sure which direction I should be looking at to start with.

You can use basic javascript dom referencing techiniques inside the mounted lifecycle hooks.
<template>
<div class="myclass">
<div id="elem" :class="getcurrentClass('myotherclass')">I am in {{ currentClass }}</div>
</div>
</template>
<script>
export default {
data() {
return {
currentClass: null
}
},
methods: {
getCurrentClass(className) {
this.currentClass = className;
return className;
}
},
mounted() {
// here you can get reference to a dom element by using basic javascript dom referencing techniques
const elem = document.querySelector("#elem");
element.classList ....
}
}
</script>
You can also use the Vue $refs object. If used on a child component, it will give you the reference to that child component instance. If used on a plain DOM element, it will give a reference to that element. Read more from here
<template>
<div class="myclass">
<div ref="myElem" :class="getcurrentClass('myotherclass')">I am in {{ currentClass }}</div>
</div>
</template>
<script>
export default {
data() {
return {
currentClass: null
}
},
methods: {
getCurrentClass(className) {
this.currentClass = className;
return className;
}
},
mounted() {
// here you can get reference to a dom element or child component context by using $refs object
const elem = this.$refs.myElem;
myElem.classList ....
}
}
</script>

You could set the currentClass = <className> in state then render it in template:
<template>
<div class="myclass">
<div :class="getcurrentClass('myotherclass')">I am in {{ currentClass }}</div>
</div>
</template>
<script>
export default {
data() {
return {
currentClass: null
}
},
methods: {
getCurrentClass(className) {
this.currentClass = className;
return className;
}
}
}
</script>

Related

How can I emit from component and listen from another one?

I have in Layout.vue to components one TheSidebar second TheHeader, there is a button in TheHeader to open the sidebar in TheSidebarcomponent.
I need to when I click the button in header open the sidebar:
My try:
in TheHeader:
methods: {
openSidebar() {
this.$root.$emit("open-sidebar");
},
},
in TheSidebar
data() {
return {
sidebarOpen: false,
};
},
mounted() {
this.$root.$on("open-sidebar", (this.sidebarOpen = true));
},
I'm using VUE 3 so I got this error in console: TypeError: this.$root.$on is not a function so How can communicate ?
you can use something like tiny emitter it works fine and doesn't care about parent child relationship
var emitter = require('tiny-emitter/instance');
emitter.on('open-sidebar', ({isOpen}) => {
//
});
emitter.emit('open-sidebar', {isOpen : true} );
You can only pass props to a direct child component, and
you can only emit an event to a direct parent. But
you can provide and eject from anywhere to anywhere
Per another answer, provide and eject may be your best bet in Vue 3, but I created a simple example of how to implement with props/events. Built with Vue 2 as I haven't worked with 3 yet, but should be usable in Vue 3 as well.
Parent.vue
<template>
<div class="parent">
<div class="row">
<div class="col-md-6">
<h4>Parent</h4>
<hr>
<child-one #show-child-two-event="handleShowChildTwoEvent" />
<hr>
<child-two v-if="showChildTwo" />
</div>
</div>
</div>
</template>
<script>
import ChildOne from './ChildOne.vue'
import ChildTwo from './ChildTwo.vue'
export default {
components: {
ChildOne,
ChildTwo
},
data() {
return {
showChildTwo: false
}
},
methods: {
handleShowChildTwoEvent() {
this.showChildTwo = true;
}
}
}
</script>
ChildOne.vue
<template>
<div class="child-one">
<h4>Child One</h4>
<button class="btn btn-secondary" #click="showChildTwo">Show Child Two</button>
</div>
</template>
<script>
export default {
methods: {
showChildTwo() {
this.$emit('show-child-two-event');
}
}
}
</script>
ChildTwo.vue
<template>
<div class="child-two">
<h4>Child Two</h4>
</div>
</template>

Vue JS Using local variable without defining in data() function

I am a beginner to Vue JS. I have to use a variable inside a component whose value changes often.
So when I declare and define it under data() the following warn is coming in Chrome console
Since when there is a change in data() variables automatically Vue framework calls render function.
Is there any way to declare and use a variable other than declaring it in data() method ??
<template>
<ul>
<div v-for="(list,index) in itemlist" :key="index">
<div v-if="!isFirstCharSame(list.label)" >{{ firstChar }} </div>
<li>
<span>{{ list.label }}</span>
</li>
</div>
</ul>
</template>
<script>
export default {
data() {
return {
itemlist: [
{"label":"Alpha"},
{"label":"Beta"},
{"label":"Charlie"},
{"label":"Delta"}],
firstChar:"$"
}
},
methods : {
isFirstCharSame: function(str) {
if(str.startsWith(this.firstChar)) {
return true;
}
this.firstChar = str.charAt(0);
return false;
}
}
}
</script>
Expected output should be like this
Inside Group A It should display all the elements starting with A
Below we will render using a computed property to make sure its sorted alphabetically and then render your first char. Though You should be using grouping imo.
<template>
<ul>
<div v-for="(list, index) in sortedlist" :key="`people_${index}`">
<div v-if="!isFirstCharSame(list.label)" >{{ firstChar }} </div>
<li>
<span>{{ list.label }}</span>
</li>
</div>
</ul>
</template>
<script>
export default {
data() {
return {
itemlist: [
{"label":"Alpha"},
{"label":"Beta"},
{"label":"Charlie"},
{"label":"Delta"},
],
firstChar: '',
};
},
methods: {
isFirstCharSame(char) {
if (str.startsWith(this.firstChar)) {
return true;
}
this.firstChar = str.charAt(0);
return false;
},
},
computed: {
sortedList() {
return this.itemList.sort((a, b) => {
if (a.label > b.label) {
return 1;
}
if (b.label > a.label) {
return -1;
}
return 0;
});
},
},
};
</script>
And yes, You can update your data any time you wish and the component will do a re render to reflect it.
You can declare variables in your component within your methods or inside computed properties, etc., but they won't be reachable from the template or the rest of the code nor they would be reactive.
The only way for them to be reactive and reachable from the higher scope is adding the data property to the component in the following way:
data: function () {
return {
foo: 'bar'
}
},
or
data () {
return {
foo: 'bar'
}
},
Besides this, the reason of your error is that you are mutating the state of your variables inside the render. When this happens, Vue re-renders the template because the values have mutated and calls again to the function and voilĂ : there you have an infinite loop.
You should probably check the function you are calling and try to replace the changing variables from the data property with local variables that take their data from the actual data variables.

How to emit component's method from router-view?

I have 50 models and for All model CRUDs, I would like to make toolbar for each page (like index, create, update, delete and etc.).
Look at this picture please:
My folder structure:
App.vue
<template>
<div id="app">
<ul class="nav">
<router-link to="/posts">Posts</router-link>|
<router-link to="/products">Products</router-link>|
</ul>
<hr>
<router-view class="content"/>
<hr>
<router-view name="toolbar" />
</div>
</template>
<script>
import Posts from "./views/posts/Index";
import Products from "./views/products/Index";
export default {
name: "App",
components: {
Posts,
Products
},
data() {
return {
status: "This is the default status message"
};
}
};
</script>
views/posts/Index.vue
<template>
<div class="w-full">
<div class="card-header">
<span>test</span>
</div>
</div>
</template>
<script>
export default {
methods: {
my_func(type) {
this.$notification[type]({
message: "Notification Title",
description: "This test."
});
}
}
};
</script>
views/posts/components/Toolbar.vue
<template>
<toolbar>
<toolbar-section>
<div class="toolbar-link">
<button></button>
</div>
</toolbar-section>
</toolbar>
</template>
<script>
export default {
data() {
return {
checked: null
};
},
methods: {
update: function() {
this.$emit("my_func");
}
}
};
</script>
Fiddle: https://codesandbox.io/s/trigger-event-views-165yz?fontsize=14
UPDATE
Now, I want when the user clicks on the edit button, I check the table and find the selected row and redirect to the update page and if a row does not select, something alerted.
You can have a another vue instance just for Events.
vueEventManager.js
import Vue from 'vue';
class vueEventManager {
constructor() {
this.vue = new Vue;
}
trigger(event, data = null) {
this.vue.$emit(event, data);
}
listen(event, callback) {
this.vue.$on(event, callback);
}
off(event, callback) {
this.vue.$off(event, callback);
}
once(event, callback) {
this.vue.$once(event, callback);
}
}
export default vueEventManager;
Then you can register it in your main.js file:
import vueEventManager from './folder/vueeventmanager';
window.Event = new vueEventManager();
Now you can use it in your components to emit events.
Event.trigger('eventName', {'valueName': value})
And listen to them
Event.listen('eventName', (value) => {
//do something
});

How do i get the ViewModel from Vue's :is

i have these components:
<template id="test-button-component">
<div class="test-button__container">
This is test button
<button #click="clickButton">{{buttonTitle}}</button>
</div>
</template>
<template id="test-button-component2">
<div class="test-button__container">
<button></button>
</div>
</template>
I try to use the Vue's :is binding to do a component binding by name as follow:
<div :is='myComponentName' ></div>
every time the myComponentName changed to other component, the new component will replace the old component. The thing i need is, is there any way i can get the instance of the component so i can get the view model instance of the currently bound component?
You can add a ref attribute (for example ref="custom") to the <div> tag for the dynamic component. And then reference the component instance via this.$refs.custom.
Here's a simple example where the data of the component gets logged whenever the value being bound to the is prop is changed:
new Vue({
el: '#app',
data() {
return {
value: 'foo',
children: {
foo: {
name: 'foo',
template: '<div>foo</div>',
data() {
return { value: 1 };
}
},
bar: {
name: 'bar',
template: '<div>bar</div>',
data() {
return { value: 2 };
}
}
}
}
},
computed: {
custom() {
return this.children[this.value];
}
},
watch: {
custom() {
this.$nextTick(() => {
console.log(this.$refs.custom.$data)
});
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.min.js"></script>
<div id="app">
<select v-model="value">
<option>foo</option>
<option>bar</option>
</select>
<div :is="custom" ref="custom"></div>
</div>
Note that the $data for the component reference by $refs.custom is getting logged inside of a $nextTick handler. This is because the bound component won't update until the parent view has re-rendered.

How to listen all child components rendered in a parent component?

synonym.vue
<template>
<div id="synonym-container">
<div></div>
<div id="synonym-group-list-wrapper">
<ul id="synonym-group-list">
<li v-for="synonymGroup of synonymGroupList" :key="synonymGroup._id">
<carousel :synonyms="synonymGroup.synonyms"></carousel>
</li>
</ul>
</div>
</div>
</template>
<script>
import Carousel from './synonym-carousel.vue'
import $ from 'jquery'
export default {
components: {
'carousel': Carousel,
},
mounted() {
console.log($('.synonym-list'));
},
}
</script>
synonym-carousel.vue
<template>
<div class="synonym-group">
<ul class="synonym-list">
<li></li>
</ul>
</div>
</template>
<script>
export default {
}
</script>
My goal is that I want to get $('.synonym-list').width() in synonym.vue file so I can change it for all child component in parent component, but I don't know how to manage it.I checked document theres no hook for it. If u have any idea please tell me, thanks!
Use events.
<carousel #update="onCarouselUpdate" :synonyms="synonymGroup.synonyms"></carousel>
...
export default {
components: { ... },
mounted () { ... },
methods: {
onCarouselUpdate () {
console.log('Carousel Updated!')
}
}
}
synonym-carousel:
export default {
updated () {
this.$emit('update')
}
}
docs: https://v2.vuejs.org/v2/guide/components.html#Custom-Events
synonym.js
<li v-for="(synonymGroup, i) of synonymGroupList" :key="synonymGroup._id">
<carousel #update="onCarouselUpdate" :synonyms="synonymGroup.synonyms" :isLast="i === (synonymGroupList.length - 1)? true: false"></carousel>
</li>
export default {
components: { ... },
mounted () { ... },
methods: {
onCarouselUpdate () {
console.log('Carousel Updated!')
}
}
}
synonym-carousel:
export default {
mounted() {
if(this.isLast) {
this.$emit('update');
}
}
Based on #CodinCat answer, this is my final resolution. It has the advantage that it does not trigger update for each list-item (since v-for is used) but only for the last one, so when 'everything is ready'.