I'm new to Vuejs. Made something, but I don't know it's the simple / right way.
what I want
I want some dates in an array and update them on a event. First I tried Vue.set, but it dind't work out. Now after changing my array item:
this.items[index] = val;
this.items.push();
I push() nothing to the array and it will update.. But sometimes the last item will be hidden, somehow... I think this solution is a bit hacky, how can I make it stable?
Simple code is here:
new Vue({
el: '#app',
data: {
f: 'DD-MM-YYYY',
items: [
"10-03-2017",
"12-03-2017"
]
},
methods: {
cha: function(index, item, what, count) {
console.log(item + " index > " + index);
val = moment(this.items[index], this.f).add(count, what).format(this.f);
this.items[index] = val;
this.items.push();
console.log("arr length: " + this.items.length);
}
}
})
ul {
list-style-type: none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/1.0.11/vue.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.10.6/moment.min.js"></script>
<div id="app">
<ul>
<li v-for="(index, item) in items">
<br><br>
<button v-on:click="cha(index, item, 'day', -1)">
- day</button>
{{ item }}
<button v-on:click="cha(index, item, 'day', 1)">
+ day</button>
<br><br>
</li>
</ul>
</div>
EDIT 2
For all object changes that need reactivity use Vue.set(object, prop, value)
For array mutations, you can look at the currently supported list here
EDIT 1
For vuex you will want to do Vue.set(state.object, key, value)
Original
So just for others who come to this question. It appears at some point in Vue 2.* they removed this.items.$set(index, val) in favor of this.$set(this.items, index, val).
Splice is still available and here is a link to array mutation methods available in vue link.
VueJS can't pickup your changes to the state if you manipulate arrays like this.
As explained in Common Beginner Gotchas, you should use array methods like push, splice or whatever and never modify the indexes like this a[2] = 2 nor the .length property of an array.
new Vue({
el: '#app',
data: {
f: 'DD-MM-YYYY',
items: [
"10-03-2017",
"12-03-2017"
]
},
methods: {
cha: function(index, item, what, count) {
console.log(item + " index > " + index);
val = moment(this.items[index], this.f).add(count, what).format(this.f);
this.items.$set(index, val)
console.log("arr length: " + this.items.length);
}
}
})
ul {
list-style-type: none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/1.0.11/vue.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.10.6/moment.min.js"></script>
<div id="app">
<ul>
<li v-for="(index, item) in items">
<br><br>
<button v-on:click="cha(index, item, 'day', -1)">
- day</button> {{ item }}
<button v-on:click="cha(index, item, 'day', 1)">
+ day</button>
<br><br>
</li>
</ul>
</div>
As stated before - VueJS simply can't track those operations(array elements assignment).
All operations that are tracked by VueJS with array are here.
But I'll copy them once again:
push()
pop()
shift()
unshift()
splice()
sort()
reverse()
During development, you face a problem - how to live with that :).
push(), pop(), shift(), unshift(), sort() and reverse() are pretty plain and help you in some cases but the main focus lies within the splice(), which allows you effectively modify the array that would be tracked by VueJs.
So I can share some of the approaches, that are used the most working with arrays.
You need to replace Item in Array:
// note - findIndex might be replaced with some(), filter(), forEach()
// or any other function/approach if you need
// additional browser support, or you might use a polyfill
const index = this.values.findIndex(item => {
return (replacementItem.id === item.id)
})
this.values.splice(index, 1, replacementItem)
Note: if you just need to modify an item field - you can do it just by:
this.values[index].itemField = newItemFieldValue
And this would be tracked by VueJS as the item(Object) fields would be tracked.
You need to empty the array:
this.values.splice(0, this.values.length)
Actually you can do much more with this function splice() - w3schools link
You can add multiple records, delete multiple records, etc.
Vue.set() and Vue.delete()
Vue.set() and Vue.delete() might be used for adding field to your UI version of data. For example, you need some additional calculated data or flags within your objects. You can do this for your objects, or list of objects(in the loop):
Vue.set(plan, 'editEnabled', true) //(or this.$set)
And send edited data back to the back-end in the same format doing this before the Axios call:
Vue.delete(plan, 'editEnabled') //(or this.$delete)
One alternative - and more lightweight approach to your problem - might be, just editing the array temporarily and then assigning the whole array back to your variable. Because as Vue does not watch individual items it will watch the whole variable being updated.
So you this should work as well:
var tempArray[];
tempArray = this.items;
tempArray[targetPosition] = value;
this.items = tempArray;
This then should also update your DOM.
Observe object and array reactivity here:
https://v2.vuejs.org/v2/guide/reactivity.html
Related
actually no biggie but how would a computed property filter function look like that always returns the current array + 5 more elements?
more in detail:
Template:
<span class="box-content" v-for="item in activeItems" :key="item.id">
<img class="item" :src="item.filename" />
</span>
Script
data: function() {
return {
items: [],
limit: 1,
};
},
computed: {
activeItems: function() {
return this.items.filter( function(s) {
if(s.length > this.limit) {
return s;
}
});
// return this.limit ? this.items : this.items;
}
},
on page load , an axios post request gets an object of items, whose response is pushed into the items array which is empty upon component declaration.
so axios -> get object with items -> push into empty array.
now i want to display ,like, 5 items and make a show more button.
The problem now is, my activeItems function is invalid, it does not know "this.limit" and i doubt anyway that it returns the correct result as i just made it return itself and not a set of objects / arrays.
What I would do next is trying around with splice and slice, array copies and pushing elements into it until a certain condition is met but.. is there a better way ?
Thanks in advance
The filter function should be used to filter based on the internal values of an array. Say you have an array of objects with persons, and each Person as an age, then you could use the Array.prototype.filter function to filter based on that age of each entry.
The filter function therefore goes through every entry in your array and determines whether an item should be included or excluded.
If you, on the other hand, want to limit the amount of entries based on a maximum number of entries, I would suggest you use Array.prototype.slice, as you mentioned already.
Your computed function could be rewritten to:
activeItems: function() {
return this.items.slice(0, this.limit)
}
First, in your code, this.limit is undefined because this is referencing the anonymous function. If you want to access the component, you will better use arrow functions syntax.
Also, s references an element of your array, so s.length will be undefined too I guess...
Now, filter does not seem to be the best choice for your need. I'll go with slice instead. Somthing like:
computed: {
activeItems() {
return this.items.splice(0, this.limit)
}
}
Where limit is increased by 5 when you click the show more button.
Of course you could do it. You just missed some code on it. Here how you fix it
activeItems: function() {
let limit = this.limit
return this.items.filter( function(item, s) {
return s <= limit
});
}
If you don't mind using filter, here are some way to do it.
First : put condition in your for loop, this one
<span class="box-content" v-for="(item, index) in items" :key="item.id" v-if="index <= limit">
<img class="item" :src="item.filename" />
</span>
Second is to slice your array on you desired length, this one
<span class="box-content" v-for="(item, index) in items.slice(0, limit)" :key="item.id">
<img class="item" :src="item.filename" />
</span>
I have two observable arrays, and I need to remove elements from the first one and push to the second one and vice versa. But when I do so, the alphabetical sorting is messed up.
self.allCourses = ko.observableArray([]);
self.selectedCourses = ko.observableArray([]);
I will interchange courses between the two arrays, and using this :
self.sortArrays = function(){
self.allCourses.sort(function (l, r) {
return l.code() < r.code() ;
});
self.selectedCourses.sort(function (l, r) {
return l.code() < r.code() ;
});
}
not only is it not efficient, but also doesnt work as expected ;I call the function each time I call one of these functions
self.addCourse = function(course){
self.selectedCourses.push(course);
self.allCourses.remove(course);
self.sortArrays();
};
self.removeCourse = function(course){
self.allCourses.push(course);
self.selectedCourses.remove(course);
self.sortArrays();
};
I would consider two approaches.
Keep your data always sorted. Instead of calling .sort(), search for the right location to put the element, and call .splice() to insert it in the right place. This is a O(n) algorithm, but should be fast in practice.
Use something like https://libraries.io/npm/dsjslib to maintain a sorted data structure at all times. This makes insert/delete a O(log(n)) operation. However every operation now has extra complexity.
Which one to use will depend on whether your operations are dominated by the effort of insert/delete, or by running through the list and displaying it. My best guess is that running through the list and displaying it matters more.
Furthermore the next question is whether it is better to do the search by scanning through the array, or by binary search. Scanning is O(n) but branch prediction mistakes cost so much that I've seen it be faster than binary search for inserting into lists of hundreds of elements.
Using knockout, u can also create computed based on your observable array, so you always will have sorted array
self.allCoursesSorted = ko.computed(function(){
return this.allCourses.sort(function (l, r) {
return l.code() < r.code() ;
});
}, this);
for selected courses you can use same approach but with filter
self.allCoursesSelected = ko.computed(function(){
return ko.utils.arrayFilter(this.allCoursesSorted(),
function (item) {
return item.selected === true;
});
}, this);
When removing an item from an array, you will never have to do a re-sort.
Instead of pushing and re-sorting, you could insert an item using your sort definition.
You'll only need to define the sorted inject function, since knockout observable arrays already have a remove method:
const sorter = (a, b) => a > b ? 1 : a < b ? -1 : 0;
const leftNumbers = ko.observableArray(
[3,5,1,2].sort(sorter)
);
const rightNumbers = ko.observableArray(
[4,1,3,5].sort(sorter)
);
// There are many ways to write this function, which you can probable
// find on stack overflow. The destructuring probably makes this slower
// than just re-sorting. I'll leave it up to you to optimize for performance.
const injectSorted = (sorter, arr, nr) => {
const pos = arr.findIndex(x => sorter(x, nr) > -1);
if (pos === -1) return arr.concat(nr);
return [
...arr.slice(0, pos),
nr,
...arr.slice(pos)
];
};
// Notice how we don't need to re-sort
const moveFromTo = (arr1, arr2) => x => {
arr2(injectSorted(sorter, arr2(), arr1.remove(x)));
};
ko.applyBindings({ leftNumbers, rightNumbers, moveFromTo });
div { display: flex; justify-content: space-around; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<p>Click numbers to move between lists</p>
<div>
<ul data-bind="foreach: leftNumbers">
<li data-bind="click: moveFromTo(leftNumbers, rightNumbers), text: $data"></li>
</ul>
<ul data-bind="foreach: rightNumbers">
<li data-bind="click: moveFromTo(rightNumbers, leftNumbers), text: $data"></li>
</ul>
</div>
Have a list that i want to output in random order.
I achieved this with computed property:
<div id="app">
<ul>
<li v-for="list in randomList" >
{{ list.text }}
</li>
</ul>
</div>
<script>
var vm = new Vue({
el:'#app',
data:{
lists:[
{text:'js',value:'one'},
{text:'css',value:'two'},
{text:'html',value:'three'}
]
},
computed: {
randomList: function(){
return this.lists.sort(function(){return 0.5 - Math.random()});
}
}
});
</script>
But if i have more than one list i that want to simplify this process by applying methods or filters?
I tried with methods without success:
<div id="app">
<ul>
<li v-for="list in randomList(lists)" >
{{ list.text }}
</li>
</ul>
<ul>
<li v-for="name in randomList(names)" >
{{ name.text }}
</li>
</ul>
</div>
<script>
var vm = new Vue({
el:'#app',
data:{
lists:[
{text:'js',value:'one'},
{text:'css',value:'two'},
{text:'html',value:'three'}
],
names:[
{text:'mary',value:'one'},
{text:'css',value:'two'},
{text:'html',value:'three'}
]
},
methods: {
randomList: function(rand){
return this.rand.sort(function(){return 0.5 - Math.random()});
}
}
});
</script>
There are few minor errors with your code, One error is in your method: randomList, you are using this.rand where rand is passed as parameter, so you just need to access it via rand, with this.rand it will look into vue instance data and will give following error:
TypeError: this.rand is undefined[Learn More]
See working fiddle here
Code:
methods: {
randomList: function(rand){
return rand.sort(function(){return 0.5 - Math.random()});
}
}
You have one typo here: el:'#vapp', => this shoud be el:'#app',
The list (array) needs to be randomized using javascript, it has nothing to do with Vue.js or v-for.
Your approach seems correct. I would also create a method to randomize the array items like randomList(myList) and use it directly in v-for.
But instead of using sort function with a random true/false return value, there is a better implementation to shuffle array: How to randomize (shuffle) a JavaScript array?
If you look at the third answer that uses sort() to randomize (similar to your attempt), you will know that it is an incorrect approach. (explained in comments)
The top most answer has the right approach, which you can plug into your randomList() method. Here is how you can do it (similar to the accepted answer in that question, but uses a new array, leaving the original list untouched):
methods: {
randomList: function(array){
var currentIndex = array.length;
var temporaryValue;
var randomIndex;
var myRandomizedList;
// Clone the original array into myRandomizedList (shallow copy of array)
myRandomizedList = array.slice(0)
// Randomize elements within the myRandomizedList - the shallow copy of original array
// While there remain elements to shuffle...
while (0 !== currentIndex) {
// Pick a remaining element...
randomIndex = Math.floor(Math.random() * currentIndex);
currentIndex -= 1;
// And swap it with the current element.
temporaryValue = myRandomizedList[currentIndex];
myRandomizedList[currentIndex] = myRandomizedList[randomIndex];
myRandomizedList[randomIndex] = temporaryValue;
}
// Return the new array that has been randomized
return myRandomizedList;
}
}
Please note: I have not tested the above. It is just a copy-paste from the most popular answer, enclosed within your Vue component as a method, after making necessary changes for randomizing the cloned array.
I hope you can help.
I can't remember where I got the the snippet of code in the deleteHandler function. It deletes the relevant listdata item from the JSON array and re-renders as expected. I just don't understand what it's doing. Is it specific React syntax? Is it rudimentary stuff that I am oblivious to?
I know the state.listdata.splice(id, 1); line gets the current JSON object, but what does the arrow function do? What is being returned? I'm quite baffled by it.
Any help is much appreciated.
var AppFront = React.createClass({
getInitialState:function(){
return{
listdata: [
{"id":1,"name":"Push Repo","description":"Job No 8790","priority":"Important"},
{"id":2,"name":"Second Note","description":"Job No 823790","priority":"Important"}
]
}
},
deleteHandler: function(e,id){
this.setState(state => {
state.listdata.splice(id, 1);
return {listdata: state.listdata};
});
},
render: function(){
var listDataDOM = this.state.listdata.map((item,index) => {return (<li key={item.id}>
{item.name}
<button onClick={()=>this.deleteHandler(item.id)}>delete</button>
</li>)});
return(
<div>
<h1>To-do List</h1>
<ul>
{listDataDOM}
</ul>
</div>
);
}
});
ReactDOM.render(<AppFront />,document.getElementById("container"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
1) About setState
setState function in React looks something like that :
setState(partialState, callback)
Where partialState may be : object , function or null.
In your particular case you use function, which returns an object of state variables.
setState(function(state){ return {some:data} })
and with arrow func (es6) , the same will look like
setState(state=> { return {some:data} })
in yout particular case arrow func used just for short
2) About splice
In handler, you use JS func splice() to remove element from state's array;
But it is bad practice, because it mutates the state of component.And It will cause bugs, problems and unpredictable behavior. You shouldn't mutate your state!
To avoid that you can copy your array through slice(), because slice returns new array.
var newArray = state.listdata.slice()
newArray.splice(index, 1);
3) About deleteHandler and data structure
deleteHandler doesnt work properly, and works only for first position.And if your data will look like that:
listdata: [
{"id":52,"name":"Push Repo","description":"Job No 8790","priority":"Important"},
{"id":11,"name":"Second Note","description":"Job No 823790","priority":"Important"}
]
It will not work at all
For proper result , you should change deleteHandler to this:
deleteHandler: function(e,id){
//find index of element
var index = this.state.listdata.findIndex(e=>e.id==id);
//copy array
var newAray = this.state.listdata.slice();
//delete element by index
newAray.splice(index, 1);
this.setState({listdata: newAray});
},
and button
<button onClick={e=>this.deleteHandler(e,item.id)}>delete</button>
> JSBIN example
or you can delete by index
deleteHandler: function(e,index){
//copy array
var newAray = this.state.listdata.slice();
//delete element by index
newAray.splice(index, 1);
this.setState({listdata: newAray});
},
<button onClick={e=>this.deleteHandler(e,index)}>delete</button>
> JSBIN example
In your AppFront component you have a state
{
listdata: [
{"id":1,"name":"Push Repo","description":"Job No 8790","priority":"Important"},
{"id":2,"name":"Second Note","description":"Job No 823790","priority":"Important"}
]
}
It represents initial data in your component. Every time you change state, your component gets rerendered.
You can change state by calling component's setState method
In deleteHandler
deleteHandler: function(e,id){
this.setState(state => {
// state.listdata - array of initial values,
state.listdata.splice(id, 1);
return {listdata: state.listdata}; // returns a new state
});
}
state.listdata.splice(id, 1) // removes an element with index == id from the array. You should not confuse listdata item.id and item index. In order for your code to work correctly you need to pass index in you deleteHandler.
<button onClick={()=>this.deleteHandler(index)}>delete</button>
Another thing is that you call deleteHandler only with one argument - item index so in your definition it should be
deleteHandler: function(index){
this.setState(state => {
// state.listdata - array of initial values,
state.listdata.splice(index, 1);
return {listdata: state.listdata}; // returns a new state
});
}
In your render method you iterate through this.state.listdata and return React.DOM nodes for each.
When you update component's state it gets rerendered and you see that item was deleted.
This code is written in es2015 so if it's new to you, it's better to start from reading something about new syntaxis.
state.listdata.splice(id, 1) deletes 1 element with the index equal to id from listdata array. For example if id equals to 0, then, after applying state.listdata.splice(id, 1), state.listdata will become:
listdata: [
{"id":2,"name":"Second Note","description":"Job No 823790","priority":"Important"}
]
And exactly this array will be returned by this arrow functions.
Keeping in mind, that splice method receives index as first argument, but you pass id property there, most probably you should change this code:
<button onClick={()=>this.deleteHandler(item.id)}>delete</button>
To:
<button onClick={()=>this.deleteHandler(index)}>delete</button>
I have a simple list in ui-select, if I choose to delete an item, and load in the ui-select the first element available in the list, the model associated don't get updated. Not sure what I am missing !.
Definition of the ui-select:
<ui-select on-select="loadSelected($item)" ng-model="selectedDude">
<ui-select-match placeholder="{{selectedDude.name}}">
<span> {{selectedDude.name}} </span>
</ui-select-match>
<ui-select-choices repeat="d in data | filter: $select.search">
<span ng-bind-html="d.name | highlight: $select.search"></span>
</ui-select-choices>
</ui-select>
This function is the one I am using for delete:
$scope.deleteSelected= function(){
$scope.data.splice($scope.data.indexOf($scope.selectedDude),1);
$scope.selectedDude = $scope.data[0];
};
Check the example in plunker
Thanks for any help.
I've modified the plunkr for you to get it working. https://plnkr.co/edit/rCKCng6ecXiZ8cNGTBlz?p=preview
First, I added a small utility method in Array to remove an item from a list of objects:
Array.prototype.remove = function(key, value) {
var index = -1;
angular.forEach(this, function(item, i) {
if (item[key] === value) {
index = i;
}
});
if (index > -1) {
this.splice(index, 1);
return true;
}
return false;
};
There were two problems, the first one was related to how you were removing the selectedDude from an array of objects.
$scope.data.splice($scope.data.indexOf($scope.selectedDude), 1);
Since the dude object reference instance stored in the array might be different from what the scope variable selectedDude has. So splice might not work properly all the time as you change anything in it.
So we precisly removing it by searching it through the index (using a utility method).
The second problem was of nested child scope. Read here for more information. We fixed this problem by creating an object dataStore and referencing selectedDude from that object like dataStore.selectedDude to prevent child inheritence problem in Javascript.