How to set v-if property from the template - vue.js

I was wondering whether its possible to set a property in a v-for from the template. Specifically, story.verifyDelete is not present in the original array, but my setting it to true on click doesn't seem to activate the v-if="story.verifyDelete just above it.
<div v-for="story in stories">
<div v-if="story.verifyDelete == true">
<div>Are you sure you want to delete this story?</div>
<div #click="remove(story.id)">Delete</div>
<div #click="story.verifyDelete=false">Cancel</div>
</div>
<div #click="story.state == 'published' ? read(story) : edit(story)">{{ story.title }}</div>
<div #click="story.verifyDelete = true">Delete</div>
</div>

Objects are not reactive by normal setters = or [] in vue.
In your click handler for the Delete div, you will need to do a set in order to have vue notice the value change
this.$set(this.story, 'verifyDelete', true)
https://v2.vuejs.org/v2/guide/reactivity.html

Related

v-on:click not working - dynamically added html element

I am trying to get all images for a category using Vue
<div class="col-md-12 col-sm-2 p-2">
<a v-on:click="onCategoryManageImageClick($event)" data-target="#location-
category-images">
</span>
</a>
</div>
So the event onCategoryManageImageClick ($event) does not work, if I am adding a html block and then click on get image button.
this is index.js
methods:{
onImagesTabClick(){
this.$root.$emit('activated-tab:location-images');
},
onCategoriesTabClick(){
window.j1App.eventBus.$emit("j1-location-image-list:shown");
},
onCategoryManageImageClick: function(event) {console.log('working event..');
event.preventDefault();
window.j1App.eventBus.$emit("j1-location-category-image-
list:shown",event.currentTarget.id);
}
}
So basically it need to to work like we do in jQuery
$(document).on('click',function{
})
So it works either page load or if adding any new html element on DOM. same I want in Vue.
You cannot alter the Vue template outside of Vue. That won't work. Vue compiles the template once when starting up and adds the event listeners to the rendered elements. If you add elements afterwards, Vue will not know about them.
The correct way of doing this would be to add those new elements in Vue.
<div
class="col-md-12 col-sm-2 p-2"
v-for="item in items"
:key="item.id"
>
<a
v-on:click="onCategoryManageImageClick($event, item)"
data-target="#location-category-images"
>
</a>
</div>
See https://v2.vuejs.org/v2/guide/list.html for documentation. In this case you need the items array variable in data and add more array elements to it, if you need more links.
Try raplacing your a tag with p
<div class="col-md-12 col-sm-2 p-2">
<p v-on:click="onCategoryManageImageClick" data-target="#location-
category-images">
</p>
</div>

Style Binding not updating with template structure [Vue]

In my example I want to draw a border around an element after clicking on it. In this example it works perfectly:
<div v-for="(parent, index) in $store.getters.getInfo" :key="index">
<div #click="setClicked" :id="child.id" v-for="child in parent"
:style="[child.clicked ? {'border-color': 'black'} : {'border-color': 'white'}]">
</div>
</div>
But when i try with a bit more complicated structure and template tags the style binding fails to be triggered:
<div v-for="i in 12">
<template v-for="(user, index) in $store.getters.getSharedUsers">
<div :id="child.id" v-for="child in $store.getters.getSharedInfo[user[0]][i-1]"
:userID="child.userID" #click="setClicked"
:style="[child.clicked ? {'border-color': 'black'} : {'border-color': 'white'}]">
</div>
</template>
</div>
In my mutation I set the attribute with:
Vue.set(state.element_map[payload.uID][payload.dID], 'clicked', true);
When I debug I see the change in both code examples in my data structure after calling the setClicked function, but only in the first one the style binding is triggered and the border is drawn. The only difference I see is the use of the template tag and the more complicated data structure. But it should also work in the second example as the clicked attribute is set correctly. So what am I missing? Thanks!

How to set a temporary variable in VueJS v-for loop

I have a v-for loop like this
<div v-for="deal in deals">
<div class="title">{{deal.title}}</div>
</div>
I'm trying to set a variable of a hover state, this is what I tried:
<div v-for="deal in deals" #mouseover="deal.hover = true" #mouseout="deal.hover = false">
<div class="title">{{deal.title}}</div>
<div class="description" v-if="deal.hover">{{deal.description}}</div>
</div>
Note that deal.hover is undefined by default (and it can't be pre-defined as false).
Is something like this possible in VueJS?
I'm not sure what you mean by "temporary" variable. Your code is attempting to add a hover property to each deal. You can certainly do that.
<div v-for="deal in deals"
#mouseover="$set(deal, 'hover', true)"
#mouseout="$set(deal, 'hover', false)"
<div class="title">{{deal.title}}</div>
<div class="description" v-if="deal.hover">{{deal.description}}</div>
</div>
Note the use of $set to add a reactive property to an object (see https://v2.vuejs.org/v2/guide/reactivity.html#For-Objects).

Using vuejs with existing html

I have a list of elements, rendered server-side.
Each item has a button that changes its status.
<div class="list">
<div class="list-item" data-is-published="1" data-item-id="11">
<div class="item-link">Item 1</div>
<form method="post" class="list-item-form">
<div class="vue-mount">
<input type="submit" name="changeStatus" class="button-unpublish" value="Unpublish">
<!-- some hidden fields -->
</div>
</form>
</div>
<div class="list-item" data-is-published="1" data-item-id="12">
<div class="item-link">Item 2</div>
<form method="post" class="list-item-form">
<div class="vue-mount">
<input type="submit" name="changeStatus" class="button-unpublish" value="Unpublish">
<!-- some hidden fields -->
</div>
</form>
</div>
</div>
I'm using the following code to replace each item with a vue component
var ms = document.querySelectorAll('.vue-mount')
for (var i = 0; i < ms.length; i++) {
new Vue({
render: h => h(SubscriptionButton)
}).$mount(ms[i])
}
And on the component i get the values of data-is-published and data-item-id and set them on the component
<template>
<input
type="submit"
#click.prevent="changeStatus()"
:value="isPublished === 1 ? 'Unpublish' : 'Publish'">
</template>
<script>
export default {
....
created: function () {
const el = this.$root.$el.parentNode.parentNode
const status = el.dataset.isPublished
this.isPublished = parseInt(status)
this.itemID = el.dataset.itemID
}
}
I'm doing it this way to ensure that it works even if javascript is disabled, but the part this.$root.$el.parentNode.parentNode doesn't feel right.
Is the way I'm doing it ok? Are there better ways to achieve the same?
EDIT
I can put the data attributes on the element that vue will mount to and access them with this.$root.$el.dataset.
What I'm not sure about is how compliant to the best practices the use of this.$root.$el is.
You could use Element.closest (with a polyfill if IE support is required)
const el = this.$root.$el.closest("*[data-is-published]");
That avoids having to know the hierarchy depth of the DOM.
Info on MDN
Edited to add:
The appropriateness of using this.$root.$el depends on how you plan to structure the code. If the element you're trying to find is guaranteed to be a parent of the Vue application, then starting the search from $root is fine. If that bothers you, though, starting the search from this.$el instead would work.
The less conventional part of your approach is that it effectively creates a separate Vue application for each .vue-mount

VueJS: How To Collapse Elements In A For Loop Separately?

I like to toggle the topic.text property. I want to collapse AND expand it alternately.
I have the following setup:
<template v-for="topic in $store.state.forum.topics">
<div class="topic">
<div class="date">{{ topic.user }}, {{ topic.date }}</div>
<span class="title">{{ topic.title }}</span>
<div class="text">{{ topic.text }}</div>
</div>
</template>
You could do something like:
<template v-for="topic in $store.state.forum.topics">
<div class="topic" #click="toggleCollapsation">
<div class="date">{{ topic.user }}, {{ topic.date }}</div>
<span class="title">{{ topic.title }}</span>
<div class="text" v-show="isCollapsed">{{ topic.text }}</div>
</div>
</template>
<script>
export default {
data() {
return {
isCollapsed: false
};
},
methods: {
toggleCollapsation() {
this.isCollapsed = !this.isCollapsed;
}
}
};
</script>
Instead of v-show you can also use v-if. The differences are (from the official docs):
v-if vs v-show
v-if is “real” conditional rendering because it ensures that event
listeners and child components inside the conditional block are
properly destroyed and re-created during toggles.
v-if is also lazy:
if the condition is false on initial render, it will not do anything -
the conditional block won’t be rendered until the condition becomes
true for the first time.
In comparison, v-show is much simpler - the
element is always rendered regardless of initial condition, with just
simple CSS-based toggling.
Generally speaking, v-if has higher toggle
costs while v-show has higher initial render costs. So prefer v-show
if you need to toggle something very often, and prefer v-if if the
condition is unlikely to change at runtime.
https://v2.vuejs.org/v2/guide/conditional.html#v-if-vs-v-show
I wouldn't read the topics directly from the $store global though. Assuming you're using VueX, I would use mapGetters in the parent view and feed the topic's component through props.
For added sugar you can take a look at Vue transitions: https://v2.vuejs.org/v2/guide/transitions.html