I would like to do a css animation before deleting an item from my data table. The deletion of an element is triggered by an event #click. So I'd like to see first what my animation does (class delete_animation) and only after delete the element.
var vm = new Vue({
el: '#app',
data: {
addedId: null,
array: [
{ id: 1, text: "lorem ipsum" },
{ id: 2, text: "lorem ipsum" },
]
},
methods: {
add() {
this.addedId = this.array[this.array.length - 1].id + 1;
this.array.push({ id: this.addedId, text: "lorem ipsum"} );
},
remove(item, index) {
this.array.splice(index, 1);
this.addedId = null;
// ???
}
}
});
table {
border-collapse: collapse;
}
table, th, td {
border: 1px solid black;
}
.add_animation {
animation: addItem 1s;
}
#keyframes addItem {
0% {
background-color: green;
}
100% {
background-color: white;
}
}
.deleted_animation {
animation: deleteItem 1s;
}
#keyframes deleteItem {
0% {
background-color: red;
}
100% {
background-color: white;
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/1.0.11/vue.min.js"></script>
<div id="app">
<table>
<tr v-for="(index, item) in array" :key="item.id" :class="addedId == item.id ? 'add_animation' : ''">
<td>{{ item.text }}</td>
<td> <button type="button" #click="remove(item, index)">remove</button></td>
</tr>
</table>
<button type="button" #click="add()">Add</button>
</div>
I would simply like to do the opposite of what the "add" button does. However, I do not see how to handle events to wait for the animation to display. I think i need to trigger click once my animation was displayed, but i don't know how...
Thanks !
I am not sure,but as i understood,you want to animate the deletion of an item in array using vue.js.
Everything is simple with vue.js so please see Vue.js Transitions
I made a simple example for you,animating items when you delete them.It may help you.
See it in action here
The "html" part
<div id="app">
<transition-group name="fade">
<div v-for="(todo,index) in todos" :key="todo.text" #click="deleteItem(index)">
{{ todo.text}}
</div>
</transition-group>
</div>
The javascript part
new Vue({
el: "#app",
data: {
todos: [
{ text: "Learn JavaScript", done: false },
{ text: "Learn Vue", done: false },
{ text: "Play around in JSFiddle", done: true },
{ text: "Build something awesome", done: true }
]
},
methods: {
deleteItem(index) {
this.todos.splice(index, 1);
}
}
})
The css part
.fade-leave-active {
transition: all 1s;
}
.fade-leave-to {
opacity: 0;
}
Add a <transition-group> element around your list, and write your transitions as CSS transitions and Vue.js will take care of setting the correct CSS classes keeping the element there while the exit transition is running. No need to change your logic. For the exact details, check the "List Transitions" section of the documentation.
https://v2.vuejs.org/v2/guide/transitions.html#List-Transitions
If you add the deletedItem class to the clicked item first:
document.querySelectorAll('tr')[index].classList.add('deleted_animation')
(I'm sure you could find a better way to select the clicked item)
And then after that use a setTimeout to delay the action:
setTimeout(() => {
this.array.splice(index, 1);
this.addedId = null;
}, 500)
You will achieve most of this. Depending on the indexes won't be great though since you might quickly click the remove button multiple times. So maybe after the button is clicked you can block the click action on all of the buttons and then add it back after the item is spliced out.
Related
I am in the process of learning vue and I'm stumped on how to get these buttons to dynamically style separately when clicked. These are filters for a list of products and I would like the apply one style when the filter is 'on' and a different style when the filter is 'off'. I can get the styles to update dynamically, but all of the buttons change style when any of them are clicked. The actual filter functionality is working as expected (the products are being filtered out when the button for that product is clicked).
In the code snippet, mode is passed to the BaseButton component, which is then applied as the class.
<template>
<ul>
<li v-for="genus of genusList" :key="genus.label">
<BaseButton #click="filterGenus(genus.label)" :mode="genusClicked.clicked ? 'outline' :''">
{{ genus.label }}
</BaseButton>
</li>
<BaseButton #click="clearFilter()" mode="flat">Clear Filter</BaseButton>
</ul>
</template>
methods: {
filterGenus(selectedGenus) {
this.clickedGenus = selectedGenus
this.clicked = !this.clicked
this.$emit('filter-genus', selectedGenus)
},
clearFilter() {
this.$emit('clear-filter')
}
},
I have tried making a computed value to add a .clicked value to the genusList object but that didn't seem to help.
Maybe something like following snippet (if you need more buttons to be styled at once save selected in array, if only one just save selected):
const app = Vue.createApp({
data() {
return {
genusList: [{label: 1}, {label: 2}, {label: 3}],
selGenus: [],
};
},
methods: {
isSelected(selectedGenus) {
return this.selGenus.includes(selectedGenus)
},
filterGenus(selectedGenus) {
if (this.isSelected(selectedGenus)) {
this.selGenus = this.selGenus.filter(s => s !== selectedGenus)
} else {
this.selGenus = [...this.selGenus, selectedGenus]
}
this.$emit('filter-genus', selectedGenus)
},
clearFilter() {
this.selGenus = []
this.$emit('clear-filter')
}
},
})
app.component('baseButton', {
template: `<button :class="mode"><slot /></button>`,
props: ['mode']
})
app.mount('#demo')
.outline {
outline: 2px solid red;
}
<script src="https://unpkg.com/vue#3/dist/vue.global.prod.js"></script>
<div id="demo">
<ul>
<li v-for="genus of genusList" :key="genus.label">
<base-button #click="filterGenus(genus.label)"
:mode="isSelected(genus.label) ? 'outline' :''">
{{ genus.label }}
</base-button>
</li>
<base-button #click="clearFilter()" mode="flat">
Clear Filter
</base-button>
</ul>
</div>
I have a loop where I loop over an Array.
for each item in this array I render a new component. Now when a user clicks on a certain component I only want to add a class to that component to highlight it and remove it from others that have it. Think of it as a menu active item.
<step-icon
v-for="(step, currentStep) in steps"
/>
data() {
return {
steps: [{foo: 'bar'}, {foo2: 'bar2'}]
}
}
my step-icon.vue:
<template>
<div :class="{'selected': selected}" #click="clickStep()">
hello
</div>
</template>
data() {
return {
selected: false
}
},
methods: {
clickStep() {
this.selected = true;
}
}
This works only 1 way, I can only add the selected class but never remove it.
I created a simple example illustrating your use case since you didn't provided enough detail to go with. Below you can find the items selected and unselected. Firstly, we added a key isSelected and set it to false as default. This will act as a status for all items.
steps: [
{key:"0", tec:"foo", isSelected:false},
{key:"1", tec:"bar", isSelected:false},
{key:"2", tec:"foo2", isSelected:false},
{key:"3", tec:"bar2", isSelected:false},
]
Next, we looped over the array and displayed all the items.
<ul>
<li
v-for="l in steps"
id="l.key"
#click="select(l.key, l.isSelected)"
v-bind:class="{ selected : l.isSelected, notselected : !l.isSelected }"
> {{ l.tec }} </li>
<ul>
Here you can se we have set our status property isSelected on v-bind directive which will add or remove the class based on the value of isSelected.
Next, once the item is clicked we will trigger select method.
methods: {
select(key) {
for (let i = 0; i < this.steps.length; i++) {
if (this.steps[i].key !== key) {
this.steps[i].isSelected = false
}
}
this.toggleSelection(key)
},
toggleSelection(key) {
const stepsItem = this.steps.find(item => item.key === key)
if (stepsItem) {
stepsItem.isSelected = !stepsItem.isSelected
}
}
}
The select method will firstly unselect all those except the one which is selected and then call toggleSelection which will set the selected Item to true or false.
Complete Code:
new Vue({
el: '#app',
data: {
steps: [
{key:"0", tec:"foo", isSelected:false},
{key:"1", tec:"bar", isSelected:false},
{key:"2", tec:"foo2", isSelected:false},
{key:"3", tec:"bar2", isSelected:false},
]
},
methods: {
select(key) {
for (let i = 0; i < this.steps.length; i++) {
if (this.steps[i].key !== key) {
this.steps[i].isSelected = false
}
}
this.toggleSelection(key)
},
toggleSelection(key) {
const stepsItem = this.steps.find(item => item.key === key)
if (stepsItem) {
stepsItem.isSelected = !stepsItem.isSelected
}
}
}
})
.selected {
background: grey;
}
.notselected {
background:transparent;
}
<script src="https://unpkg.com/vue#2.6.10"></script>
<div id="app">
<ul>
<li
v-for="l in steps"
id="l.key"
#click="select(l.key, l.isSelected)"
v-bind:class="{ selected : l.isSelected, notselected : !l.isSelected }"
> {{ l.tec }} </li>
<ul>
</div>
You can keep all the step state in the parent component.
Now the parent component can listen for toggle_selected event from the nested one, and call toggle_selected(step) with the current step as a param.
Toggle_selected method should deselect all steps except the current one, and for the current one just toggle the selected prop.
If You would like to modify more props of the step in the nested component You could use .sync modifier (:step.sync="step") and then this.#emit('update:step', newStepState) in the nested component.
I've also made a snippet (my first). In this example I omitted clickStep and just put #click="$emit('toggle_selected') in the step-icon component.
new Vue({
el: '#app',
// for this example only defined component here
components: {
'step-icon': {
props: { step: Object }
}
},
data: {
steps: [
{ name: 'Alfa', selected: false},
{ name: 'Beta', selected: false},
{ name: 'Gamma', selected: false},
]
},
methods: {
toggle_selected(step) {
this.steps.filter(s => s != step).forEach(s => s.selected = false);
step.selected = true;
}
}
})
#app {
padding: 2rem;
font-family: sans-serif;
}
.step-icon {
border: 1px solid #ddd;
margin-bottom: -1px;
padding: 0.25rem 0.5rem;
cursor: pointer;
}
.step-icon.selected {
background: #07c;
color: #fff;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<!-- Ive used inline-template for this example only -->
<div id="app">
<step-icon v-for="(step, currentStep) in steps" :key="currentStep"
:step.sync="step"
#toggle_selected="toggle_selected(step)" inline-template>
<div class="step-icon"
:class="{selected: step.selected}"
#click="$emit('toggle_selected')">
{{ step.name }}
</div>
</step-icon>
</div>
I have a list in utils that I use it almost everywhere and it only has text variable in each object.
In one of my components, I need to add done variable to each item in that list so I can toggle them. I can see that the variable is added, but whenever I toggle it, the view does not get updated.
const arrayFromUtils = [{
text: "Learn JavaScript"
},
{
text: "Learn Vue"
},
{
text: "Play around in JSFiddle"
},
{
text: "Build something awesome"
}
];
new Vue({
el: "#app",
data: {
todos: arrayFromUtils
},
mounted() {
this.todos.forEach(item => (item.done = false));
},
methods: {
toggle: function(todo) {
todo.done = !todo.done
console.log('toggled item: ', todo);
}
}
})
.red {
color: red;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div v-for="todo in todos" :key="todo.text">
<div #click="toggle(todo)" :class="{ red: todo.done }">
{{ todo.text }}
</div>
</div>
</div>
I can see that done variable gets updated when toggling an item, but the view does not get updated. What am I doing wrong?
You are adding a property to each of your todos items. To make it reactive, you need to use this.$set. Take a look at the documention about changes detection caveats.
Your mounted function should be:
mounted() {
this.todos.forEach(item => this.$set(item, "done", false));
}
Hey it's problem with Vue Reactivity
https://v2.vuejs.org/v2/guide/reactivity.html
You are changing item in array if you want to do this you need to use vm.$set or map whole array.
If you do this in your way vue can't detect if something changed that will be posible in vue3 :)
mounted() {
this.todos.map(item => ({...item, done: false});
}
I'm looking to loop through an array of span tags and add is-active to the next one in line, every 3 seconds. I have it working but after the first one, it adds all the rest. How do I just pull that class from the active one and add it to the next array item?
I've read through the official documentation several times and there doesn't seem to be any mention of iterating individual items, just listing them all or pushing an item onto the list.
I'm not sure if 'index' comes in to play here, and how to grab the index of the span element to add/subtract is-active. what am I doing wrong?
var firstComponent = Vue.component('spans-show', {
template: `
<h1>
<span class="unset">Make</span>
<br>
<span class="unset">Something</span>
<br>
<span v-for="(span, index) of spans" :class="{ 'is-active': span.isActive, 'red': span.isRed, 'first': span.isFirst }" :key="index">{{ index }}: {{ span.name }}</span>
</h1>
`,
data() {
return {
spans: [
{
name: 'Magical.',
isActive: true,
isRed: true,
isFirst: true
},
{
name: 'Inspiring.',
isActive: false,
isRed: true,
isFirst: true
},
{
name: 'Awesome.',
isActive: false,
isRed: true,
isFirst: true
}
]
};
},
methods: {
showMe: function() {
setInterval(() => {
// forEach
this.spans.forEach(el => {
if (el.isActive) {
el.isActive = false;
} else {
el.isActive = true;
}
});
}, 3000);
}
},
created() {
window.addEventListener('load', this.showMe);
},
destroyed() {
window.removeEventListener('load', this.showMe);
}
});
var secondComponent = Vue.component('span-show', {
template: `
<span v-show="isActive"><slot></slot></span>
`,
props: {
name: {
required: true
}
},
data() {
return {
isActive: false
};
}
});
new Vue({
el: "#app",
components: {
"first-component": firstComponent,
"second-component": secondComponent
}
});
.container {
position: relative;
overflow: hidden;
width: 100%;
}
.wrapper {
position: relative;
margin: 0 auto;
width: 100%;
padding: 0 40px;
}
h1 {
font-size: 48px;
line-height: 105%;
color: #4c2c72;
letter-spacing: 0.06em;
text-transform: uppercase;
font-family: archia-semibold, serif;
font-weight: 400;
margin: 0;
height: 230px;
}
span {
position: absolute;
clip: rect(0, 0, 300px, 0);
}
span.unset {
clip: unset;
}
span.red {
color: #e43f6f;
}
span.is-active {
clip: rect(0, 900px, 300px, -300px);
}
<div id="app">
<div class="container">
<div class="wrapper">
<spans-show>
<span-show></span-show>
</spans-show>
</div>
</div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.min.js"></script>
To achieve desired result, I'd suggest to change the approach a bit.
Instead of changing value of isActive for individual items, we can create a variable (e.g. activeSpan, that will be responsible for current active span and increment it over time.
setInterval(() => {
// Increment next active span, or reset if it is the one
if (this.activeSpan === this.spans.length - 1) {
this.activeSpan = 0
} else {
this.activeSpan++
}
}, 3000);
In component's template, we make class is-active conditional and dependent on activeSpan variable:
:class="{ 'is-active': index === activeSpan, 'red': span.isRed, 'first': span.isFirst }"
If you still need to update values inside spans array, it can be done in more simple way, via map for example. Also included such case as optional in solution below.
Working example:
JSFiddle
Sidenote: there is no need to add window listeners for load event, as application itself is loaded after DOM is ready. Instead, method can be invoked inside created hook. It is included in solution above.
I'm a total newbie in VueJS. I've been working on customizing a tree view example from the vuejs docs: Example.
On selecting an item in the treeview, I'm not able to understand how to unselect i.e. unset the class of the previously selected item. Some approaches I've tried include
Setting a global variable using Vue.prototype and accessing it in the computed function in which case the computed function doesn't even run.
I'm aware of the event object that is passed. Using that and jQuery, removing the class of the previously selected div would work but that seems like a hack.
Setting an array of selected items in data on the click event and accessing it in the computed function. This also does not work.
Is there a way that would work or am I not understanding something?
The codepen link that I'm working on: Codepen. For selecting a node, just click on the node and try selecting some other node. The previous node doesn't get cleared.
Thanks!
Update:
The below answer works but it would remove the selected class if clicked somewhere else. I wanted a solution where the selected class would only be removed if I clicked on some other node. All I had to do was create an Event Bus and store the previously selected component object in a parent variable. On clicking a new node, a global event would be emitted which would be listened to by the main instance method. There, it would set a boolean value which would unset the previous component selection and another boolean value to set the selected class to the new component object. I'm not sure if a better way exists.
Updated codepen with some changes: CodePen link
It's nothing to do with VueJS, We have to play with CSS by setting the required css properties when the folder node is focused.
//https://github.com/vuejs/Discussion/issues/356
// demo data
Vue.prototype.$selectedNode = []
var data = {
name: 'My Tree',
children: [{
name: 'hello'
},
{
name: 'wat'
},
{
name: 'child folder',
children: [{
name: 'child folder',
children: [{
name: 'hello'
},
{
name: 'wat'
}
]
},
{
name: 'hello'
},
{
name: 'wat'
},
{
name: 'child folder',
children: [{
name: 'hello'
},
{
name: 'wat'
}
]
}
]
}
]
}
// define the item component
Vue.component('item', {
template: '#item-template',
props: {
model: Object
},
data: function() {
return {
open: false,
selectedNode: []
}
},
computed: {
isFolder: function() {
return this.model.children &&
this.model.children.length
},
setChevronClass: function() {
return {
opened: this.isFolder && this.open,
closed: this.isFolder && !this.open,
folderChevronSpan: this.isFolder
}
},
setSelected: function() {
if (this.selectedNode.length > 0 && this.selectedNode[0].title == this.model.name)
return true;
else
return false;
}
},
methods: {
toggle: function() {
if (this.isFolder) {
this.open = !this.open
this.$refs.toggler.focus();
}
},
changeType: function() {
if (!this.isFolder) {
Vue.set(this.model, 'children', [])
this.addChild()
this.open = true
}
},
addChild: function() {
this.model.children.push({
name: 'new stuff'
})
},
selectNode: function() {
this.selectedNode = [];
this.selectedNode.push({
'title': this.model.name,
'isSelected': true
});
}
}
})
// boot up the demo
var demo = new Vue({
el: '#demo',
data: {
treeData: data
}
})
body {
font-family: Menlo, Consolas, monospace;
color: #444;
}
.item {
cursor: pointer;
}
.folderTitleSpan:hover {
font-weight: bold;
border: 1px solid darkblue;
}
.folderTitleSpan:focus,
li span:nth-child(1):focus+.folderTitleSpan {
background-color: darkblue;
color: white;
}
.node,
.add {
list-style-type: none;
padding-left: 10px !important;
}
.folderChevronSpan::before {
color: #444;
content: '\25b6';
font-size: 10px;
margin-left: -1em;
position: absolute;
transition: -webkit-transform .1s ease;
transition: transform .1s ease;
transition: transform .1s ease, -webkit-transform .1s ease;
-webkit-transition: -webkit-transform .1s ease;
}
.folderChevronSpan.opened::before {
transform: rotate(90deg);
-webkit-transform: rotate(90deg);
}
ul {
padding-left: 1em;
line-height: 1.5em;
list-style-type: dot;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17-beta.0/vue.js"></script>
<!-- item template -->
<script type="text/x-template" id="item-template">
<li>
<span :class="setChevronClass" tabindex="0" ref="toggler" #click="toggle">
</span>
<span #click="selectNode" tabindex="1" :class="{folderTitleSpan: isFolder}">
{{ model.name }}
</span>
<span v-if="isFolder">[{{ open ? '-' : '+' }}]</span>
<ul v-show="open" v-if="isFolder">
<item class="item node" v-for="(model, index) in model.children" :key="index" :model="model">
</item>
<li class="add" #click="addChild">+</li>
</ul>
</li>
</script>
<p>(You can double click on an item to turn it into a folder.)</p>
<!-- the demo root element -->
<ul id="demo">
<item class="item node" :model="treeData">
</item>
</ul>