So, I have jsfiddle here.
We can add new nodes and delete all the children in parent node. But how can I delete specific child without iterating array? I know that we can use:
Array.prototype.splice()
If we want to remove, for example, this object (screenshot #1), we can get its index and use splice().
But if I want to remove deeply nested object, I don't want iterate array and use splice(), because of perfomance.
In my console I got only:
Object { name: "Node-8-6-2", menu: false, $$hashKey: "object:151" }
And I don't have an access to nodes of parent array. And I need to iterate all array, so that I could remove it.
Anybody knows solution of this issue?
Here is your plunker updated. http://jsfiddle.net/marduke182/uXbn6/2828/
The little changes are:
Adding the parent references to the object using parentNodes .
$scope.add = function(data) {
var post = data.nodes.length + 1;
var newName = data.name + '-' + post;
data.nodes.push({name: newName,nodes: [], parentNodes: data.nodes});
};
Create method delete node and pass the $index, do the splice to the parent given the index attribute:
$scope.delete_node = function(data, index) {
data.parentNodes.splice(index, 1);
};
Add the new method to the template:
<script type="text/ng-template" id="tree_item_renderer.html">
{{data.name}}
<button ng-click="add(data)">Add node</button>
<button ng-click="delete(data)" ng-show="data.nodes.length > 0">Delete nodes</button>
<button ng-click="delete_node(data, $index)" >Delete node {{$index}}</button>
<ul>
<li ng-repeat="data in data.nodes" ng-include="'tree_item_renderer.html'"></li>
</ul>
</script>
When you are building your nested tree, you can add a parent attribute to your arrays:
var parentNode = [];
var node = [];
node.parent = parentNode;
parentNode.push(node);
Now, if you want to remove node, you can say:
var index = node.parent.indexOf(node);
node.parent.splice(index, 1);
Related
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
I have an ng-repeat with a select in every item.
The user can select a value (trigging a function that pushes the an object into an array), but they can also change their mind, in which case the code just pushes a second object with the new value, duplicating the first one.
How could I manage to actually delete existing values, leaving only the last one on every ng-change?
Here's my HTML:
<select ng-change="insertproduct(pa.nom, basket)" ng-model="basket">
<option ng-repeat="select in numberofproducts">{{select}}</option>
</select>
And my javascript:
$scope.numberofproducts = [1,2,3,4,5,6,7,8,9,10]
$scope.singleorder = [];
$scope.insertproduct = function(nom, basket){
$scope.numero = {
'producte': nom,
'numero': basket
};
$scope.singleorder.push($scope.numero);
console.log($scope.singleorder);
}
The idea is to create a condition in which if the array contains an object with the parameter ´producte´ equal to the new one, delete the existing and push the new one.
Any tips?
First, use the findIndex method to check if an object with the same property is already in the singleorder array.
function duplicateOrder(order) {
return order.producte === nom;
}
var index = $scope.singleorder.findIndex(duplicateOrder);
Note: browser support for findIndex is limited; it is not supported in Internet Explorer.
Then remove the item with splice:
if(index > -1){
$scope.singleorder.splice(index, 1);
}
You can then push the new one in.
You should also clean up your coding style: don't mix french and english, and use either camelCase or snake_case for your functions to improve readability.
Observation :
Use AngularJS ngOptions attribute instead of ng-repeat.
You can check the index of the element in an array if that was already there you can easily remove previous one.
DEMO
var myApp = angular.module('myApp',[]);
myApp.controller('MyCtrl',function($scope) {
$scope.numberofproducts = [1,2,3,4,5,6,7,8,9,10]
$scope.newArray = [];
$scope.insertproduct = function(basket) {
var prevIndex = $scope.newArray.indexOf(basket);
if(prevIndex > -1) {
$scope.newArray.splice(prevIndex, 1);
} else {
$scope.newArray.push(basket);
}
console.log($scope.newArray);
}
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="myApp" ng-controller="MyCtrl">
<select ng-change="insertproduct(basket)" ng-model="basket" ng-options="select for select in numberofproducts">
</select>
</div>
Hello I have a questions on ng-repeat on Angularand function for change value.
I have this ng-repeat that cycling a ObjectArray and have a button for reset value.
<div ng-repeat="element in data.elements">
<button ng-click="reset(element)" >reset</button>
</div>
Where data.elements is array of objects example:
[{id:1, name:"element1"},{id:2, name : "element2"}];
In my Controller I set function Reset in $scope that should do a copy of object passed to an default object:
$scope.reset = function(el){
$scope.defaultObject = {id:500, name:"default"};
el = angular.copy($scope.defaultObject);
}
But doesn't work, but if I do:
$scope.reset = function(el){
$scope.defaultObject = {id:500, name:"default"};
el.name = $scope.defaultObject.name;
}
It work.
So I would like that when I do (in this example):
el = angular.copy($scope.defaultObject);
have the object el equals to object $scope.defaultObject my question is, Can i copy entire object without cycling all properties?
You're passing an object to the reset function, then you're overwriting this object. That's it, nothing happens because it won't affect the original object, which is in the data.elements array.
You need to use a different approach. Track the element by its index :
<div ng-repeat="element in data.elements track by index">
<button ng-click="reset(index)" >reset</button>
</div>
...then amend data.elements[index]:
$scope.reset = function(index){
$scope.data.elements[index] = {id:500, name:"default"};
}
are you saying, your updated object does not reflect on the UI? you can try forcing the scope update by running $scope.$apply() after you have assigned the object.
I am using track by $index because I want to allow repeated elements in my array, but at the same time this is causing a side effect when removing elements from this collection.
I have this set of players which is declared in the controller as $scope.players = [].
You can populate this array as follows:
<input type="text" ng-model="player">
<button ng-click="addPlayer()">
addPlayer() just pushes the player model to the players array:
$scope.addPlayer = function() {
if (!$scope.player)
return;
$scope.players.push($scope.player);
$scope.player = null;
};
And the collection is shown using ng-repeat. But also when an item is clicked on, it should be deleted.
<div ng-repeat="player in players track by $index" ng-click="deletePlayer($index)">
{{player}}
</div>
$scope.deletePlayer = function(index) {
if (index > -1)
$scope.players.splice(index, 1);
};
The issue is that since it's tracking by index, when an element is removed the collection of players will be short by 1 because the collection has changed.
What I mean by this is the following: say I have the array of players ["p1", "p2", "p3"]. If I remove one of these except the last, for example, p1, the ng-repeat is not showing [p2, p3] even though these are the contents of the array, but it shows just p3. This is what I mean when I say the collection is one element short.
I think the issue happens because it's unknown to ng-repeat in the track by $index mode that the length of the array has changed. Therefore, it's skipping one element when iterating through the changed array, because it's using the old indices to iterate it, I believe.
Is there a standard way of tackling this side effect?
You can make each item in players array to be an object that has name and id properties. Demo.
Object.assign($scope, {
players: [],
player: '',
addPlayer: function() {
if(!$scope.player) {
return
}
$scope.players = $scope.players.concat({
name: $scope.player,
id: Date.now() //fake id (timestamp)
})
$scope.player = ''
},
deletePlayer: function(id) {
$scope.players = $scope.players.filter(function(player){
return player.id !== id
})
}
})
<div ng-repeat="player in players track by player.id" ng-click="deletePlayer(player.id)">
{{player.name}}
</div>
The problem is with your deletePlayer function. Your argument name is 'i' but you are trying to use 'index' instead.
This:
$scope.deletePlayer = function(i) {
if (i > -1)
$scope.players.splice(index, 1);
};
should be:
$scope.deletePlayer = function(i) {
if (i > -1)
$scope.players.splice(i, 1);
};
In the element you could use: ng-click="remove(phones, $index)
And in the code:
$scope.remove = function(array, index){
array.splice(index, 1);
}
You shouldn't have any problems with this approach.
I know how to save the position of the list elements to a database or localstorage or something similar. But how can I reorder the list with JavaScript from the positions which are saved in my array?
I had a look and StackOverflow and found the following code, but it doesn't work (it just empties my list):
// Get your list items
var items = $('#sortable').find('li');
// The new index order for each item
var order = store.get('sortableIDsOrder');
// Map the existing items to their new positions
var orderedItems = $.map(order, function(value) {
return items.get(value);
});
// Clear the old list items and insert the newly ordered ones
$('#sortable').empty().html(orderedItems);
My array looks like:
[portrait-sms,portrait-pc,portrait-mail,portrait-calendar,portrait-facebook,portrait-twitter,portrait-whatsapp,portrait-skype,portrait-viber,portrait-instagram]
And my HTML looks like:
<li id="portrait-sms"><a href="sms:">...</li>
<li id="portrait-mail"><a href="mailto:">...</li>
<li id="portrait-pc"><a href="#">...</li>
...
The simplest solution I can think of, given only the array (that I assume you've retrieved from somewhere), is:
// assuming this is the array you've recovered from whereever:
var storedArray = ['portrait-sms',
'portrait-pc',
'portrait-mail',
'portrait-calendar',
'portrait-facebook',
'portrait-twitter',
'portrait-whatsapp',
'portrait-skype',
'portrait-viber',
'portrait-instagram'];
function reorder(orderedArray) {
// caching variables:
var el, pre, p;2
// iterating over the elements of the array, using Array.prototype.forEach:
orderedArray.forEach(function (a, b, c) {
// a: the current element in the array,
// b: the index of the current element in the array,
// c: the array itself
if (b > 0) {
// caching the element with the id of the element in the array:
el = document.getElementById(a);
// finding the parentNode of that element:
p = el.parentNode;
// getting the previous element:
pre = document.getElementById(c[b - 1]);
// inserting the element with the id of the current element
// before the nextSibling of the element with the id of the
// previous element in the array:
p.insertBefore(el, pre.nextSibling);
}
});
}
reorder(storedArray);
JS Fiddle demo.
References:
Array.prototype.forEach().
Node.insertBefore().
Node.parentNode.
If you know the elements you have in the database array before hand and they have static values, you can create a new JavaScript array variable by iterating over database array and by forming a new JS array which you use while loading the UI.
On the other hand, if your requirement is to just sort the array during UI loading time instead of showing elements in a fixed order(as retrieved from database), you can use JQuery Table Plugins like DataTable.