Here is a ToDoList from Vue examples.
I want to add some extra features to this small app, e.g. set date for task. Therefore I'd like to show more operations of the task when I click "...".
Below is what I want to avoid, which after clicking another task, the previous click action doesn't be removed:
I try to add a property for each todo, and bind a click function on the "..." (more). Each time click "more", firstly set "isMoreClick" property of all task to false, then toggle the value of "isMoreClick" of current clicked task:
<button class="more"
#click="isMoreClick(todo)"
v-show="!todo.isMoreClick">
</button>
<div class="more-opt" v-show="todo.isMoreClick">
<button class="destroy" #click="removeTodo(todo)"></button>
</div>
...
this.todos.push({
id: todoStorage.uid++,
title: value,
completed: false,
isMoreClick: false // this is what I added
})
...
isMoreClick (todo) {
this.todos.forEach(todo => {
todo.isMoreClick = false
})
todo.isMoreClick = !todo.isMoreClick
}
I think my approach is a little stupid. Is there any better solution? (set a flag symbol?)
You don't say how you're rendering the todo elements. But if you're using a v-for loop, one approach could be
<ul>
<li
v-for="(todo, index) in todos"
:key="index"
>
{{todo.whatever}}
<button
v-if="index !== visibleTodoIndex"
class="more"
#click="visibleTodoIndex = index"
/>
<div
v-else
class="more-opt"
>
<button
class="destroy"
#click="visibleTodoIndex = -1"
/>
</div>
</li>
<ul>
Then just add visibleTodoIndex to the component's data.
It looks to me you need to use a global variable accessible from all todos, not to have a local variable inside each todo and updating it everywhere every time. I recommend using vuex store and updating isMoreClick value via mutations.
Related
While this is not the exact code, it represents the issue. I have an object that has a method that returns an array. This array is then updated asynchronously. In the demo it is a timeout but in reality it would be an HTTP call:
function () {
let a = ['apple'];
setTimeout(() => {
a.push['banana'];
}, 3000);
return a;
}
Then it is included in a Vue component via a computed property and displayed, e.g.
<ul>
<li v-for="(row, index) in testObjectArr" v-bind:key="index">
{{row}}
</li>
</ul>
However this only displays the first entry (apple) and not the second (banana). If an event does cause the template to refresh then it does appear.
Using things like watch also doesn't work. The only way to get it working is to pass a handler in to the object that manually triggers a redraw which is oviously less than ideal.
Is there any way to get responsivity to actually work with the above situation?
In order to make variables reactive, you should declare them as such by using ref or reactive. So your code should be:
<script setup>
import { ref } from 'vue';
const items = ref(['apple']);
setTimeout(() => {
items.value.push('banana');
}, 1000);
</script>
<template>
<ul>
<li v-for="(item, index) of items" :key="index">
{{ item }}
</li>
</ul>
</template>
See it live
I've created a simple vue.js program that uses a for loop to toggle the visibility of text. I have a button that should toggle it, but when it is clicked, it changes the variable but doesn't update the button.
let app = new Vue({
data() {
return {
array: [true,true,false],
text: ["0","1","2"]
}
},
methods:{
change(index){
this.array[index]=!this.array[index]
console.log(this.array[index],index)
}
},
el: "#app"
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div v-for="(item,index) in text">
<button #click="change(index)">toggle show</button>
<a v-if="!array[index]">...</a>
<a v-else>{{item}}</a>
</div>
</div>
Is there any way I could get this simple app to work without changing the arrays?
You’re running into a reactivity caveat: you cannot update a specific entry in an array by index in that way.
You’ll need to use Vue.set or this.$set (the latter is simply an alias for the former):
change(index){
this.$set(this.array, index, !this.array[index]);
}
I'm building some app using Laravel & Vue, and so far so good, but I'm no expert with Vue.
So I have one very "begginers" problem, using live data.
So I want to make button that will check if live data is on or off, and if they turn it on, it must refresh data and set liveData state to true.
For example:
This is my "button" and it's not working as expected, It will change state but data is still no live
<div v-if="liveData">
<div #click="liveData = false">
Turn OFF Live data
</div>
</div>
<div v-else="liveData">
<div #click="liveData = true">
Turn On Live data
</div>
</div>
I have defined state like so:
data() {
return {
liveData: false
}
},
And this is my created() function:
created() {
if(this.liveData){
window.Echo.channel("addOrder").listen(".order-created", (order) => {
this.$store.commit("ADD_ORDER", order);
});
}
this.$store.dispatch("GET_ORDERS");
},
So in this case only button is not working, but if I set state to true it's working perfectly.
What do I need to do here? Do I need to make new function to work or?
Created will only be executed once in your component lifecycle. At this point the value of liveData is always false.
If you click on your "button" the value should change but your code inside of created will not be executed once more.
Instead of created you can use an immediate watcher:
watch: {
liveData: {
immediate: true,
handler(val) {
// your code from created here
}
}
Correct the mistake
<div v-else!="liveData">
<div v-if="liveData">
<div #click="liveData = false">
Turn OFF Live data
</div>
</div>
<div v-else!="liveData">
<div #click="liveData = true">
Turn On Live data
</div>
</div>
or
<div v-else>
I am re-vamping a program for my Engineering departments research team, my app has been built using Electron, Vuex, HTML/Sass/Javascript. The program's purpose is to allow the user to create "steps" which are items/tiles that you can go into & set system parameters & processes to run X amount of times. Setting up the values and settings within each step can take a while, so if the user creates 5 steps, and realizes they want to delete step number 2 entirely, there needs to be a 'remove' or a small 'x' within each step/item. Currently, I can only remove the last populated step/item using the 'pop' function to remove the last item in the array 'stepsGrid'.
Using vue-grid-layout to populate grid-items/tiles which will each be a 'step' that our system will run. I am able to dynamically add new 'steps', however I have only been able to successfully remove steps by using pop() to remove the last item from the array in which grid-items are. I want to place a
<button id="remove" #click="removeStep()">Remove</button>
within each step, so that I can delete any item on the grid instead of just the last item.
I have seen a few examples, but not being the best at javascript- I haven't had any luck. I tried referencing the functions used here: https://chrishamm.io/grid-demo/
and https://github.com/chrishamm/vue-dynamic-grid
for removing grid items, however this library is a little more complicated than the regular Vue-Grid-Layout.
Here is my code, the key is the step.i component which is the ID of each item populated, each tile will have an 'x' in the corner and the function needs to be able to recognize the ID of that tile, and delete the corresponding item from the stepsGrid array:
<h2 style="color: #f6a821;">Steps</h2>
<hr class="hr" />
<grid-layout
:layout.sync="stepsGrid"
:col-num="8"
:row-height="75"
:is-draggable="true"
:is-resizable="false"
:is-mirrored="false"
:vertical-compact="true"
:margin="[50, 50]"
:use-css-transforms="true">
<grid-item
v-for="step in stepsGrid" :key= "step.i"
:x="step.x"
:y="step.y"
:w="step.w"
:h="step.h"
:i="step.i"
:isDraggable="step.isDraggable">
<div class="Panel__name">Step: {{step.i}} //displays item # on each tile
<div class="Panel__stepcount"> Loop Count: <input type="number" value="1">
</div>
</div>
<div class="editButton">
<div class="Panel__status">Status:</div>
<button #click="removeStep()">Remove</button>
</grid-item>
</grid-layout>
//Grid arrays are in store.js, as shown below(this is for an electron app):
import Vue from 'vue';
import Vuex from 'vuex';
import createPersistedState from 'vuex-persistedstate'
Vue.use(Vuex);
export const store = new Vuex.Store({
state: {
stepsGrid : [
{"x":0,"y":0,"w":2,"h":1,"i":"0"}
],
mutations: {
addStep (state, step){
state.stepsGrid.push(step);
}
},
actions: {
addStep ({state, commit}){
const step =
{"x":0, "y": 1, "w": 2,"h":1,"i":String(state.stepsGrid.length) };
commit('addStep', step);
},
I know I need to incorporate the use of a key here, but I am unsure how to do it. I found this, but he goes about it an unusual way:
"I deleted the element in array by the button and could not understand the problem, as the last element was always deleted, although the key was unique. I added an extra unique uuid and it all worked."
<dish-item v-else class="dishItem"
v-for="(item, key) in dishes" :key="key + $uuid.v1()" :value="item"
#removeDish="$delete(dishes, key)"
#editDish="$refs.modal.showEdit({item, key})"
/>
Lastly, I went digging through the files for Vue-Dynamic-Grid and found that they are removing items with the following method:
methods: {
removeElement(item) {
if (item == this.selectedItem) {
this.selectElement(null);
}
this.gridItems.splice(item.i, 1);
this.items.splice(item.i, 1);
breakpoints.forEach(function(size) {
this.layouts[size].splice(item.i, 1);
this.layouts[size].forEach(function(layout, i) {
if (layout.i > item.i) {
layout.i--;
}
});
}, this);
this.$emit("itemRemoved", item);
}
If anyone could be of help, I would appreciate it! I have been learning JS on the go with this project, so if anyone has some tips please let me know! Thanks
I have tried attempting to write a function based on the way Vue-Dynamic-Layout's method for removing items, but I was not successful. As of right now, I don't have a small 'x' in each tile, I have a button for adding/removing a step in the bottom left of my app, to remove items I am simply popping the last item out of the stepsGrid array, so I can only remove the last populated item.
First let the event handler know which step is being clicked
<button #click="removeStep(step)">Remove</button>
Then find that step in the array and remove it
methods: {
removeStep(step) {
const index = this.stepsGrid.indexOf(step);
if (index >= 0) this.stepsGrid.splice(index, 1);
}
}
So basically what I am doing is iterating through an array of data and making some kind of list. What I want to achieve here is on clicking on a particular list item a css class should get attached.
Iteration to make a list
var sports = allSports.sportList.map((sport) => {
return (
<SportItem icon= {sport.colorIcon} text = {sport.name} onClick={this.handleClick()} key= {sport.id}/>
)
})
A single list item
<div className="display-type icon-pad ">
<div className="icons link">
<img className="sport-icon" src={icon}/>
</div>
<p className="text-center">{text}</p>
</div>
I am not able to figure out what to do with handleClick so that If I click on a particular list it gets highlighted.
If you want to highlight the particular list item it's way better to call the handleClick function on the list item itself, and you can add CSS classes more accurately with this approach,
here is my sample code to implement the single list component
var SingleListItem = React.createClass({
getInitialState: function() {
return {
isClicked: false
};
},
handleClick: function() {
this.setState({
isClicked: true
})
},
render: function() {
var isClicked = this.state.isClicked;
var style = {
'background-color': ''
};
if (isClicked) {
style = {
'background-color': '#D3D3D3'
};
}
return (
<li onClick={this.handleClick} style={style}>{this.props.text}</li>
);
}
});
Keep a separate state variable for every item that can be selected and use classnames library to conditionally manipulate classes as facebook recommends.
Edit: ok, you've mentioned that only 1 element can be selected at a time,it means that we only need to store which one of them was selected (I'm going to use the selected item's id). And also I've noticed a typo in your code, you need to link the function when you declare a component, not call it
<SportItem onClick={this.handleClick} ...
(notice how handleClick no longer contains ()).
And now we're going to pass the element's id along with the event to the handleClick handler using partial application - bind method:
<SportItem onClick={this.handleClick.bind(this,sport.id} ...
And as I said we want to store the selected item's id in the state, so the handleClick could look like:
handleClick(id,event){
this.setState({selectedItemId: id})
...
}
Now we need to pass the selectedItemId to SportItem instances so they're aware of the current selection: <SportItem selectedItemId={selectedItemId} ....Also, don't forget to attach the onClick={this.handleClick} callback to where it needs to be, invoking which is going to trigger the change of the state in the parent:
<div onClick={this.props.onClick} className={classNames('foo', { myClass: this.props.selectedItemId == this.props.key}); // => the div will always have 'foo' class but 'myClass' will be added only if this is the element that's currently selected}>
</div>