ng-repeat splice showing double - javascript

For some reason, when I splice an object into my ng-repeat array, it doubles what I splice in and hides the last object in the array.
However, if I click the toggle hide and "refresh" the ng-repeat, it shows the right data.
Does anyone know why this would be happening and what I can do to fix it?
angular.module('app', [])
.controller('ctrl', function($scope) {
$scope.workflow = {
flow: [{
"id": "1334f68db820f664",
"step_number": 1,
"tasks": [{
"id": "1334f68e3f20f665"
}]
}, {
"id": "134967a5ba205f5b",
"step_number": 2,
"tasks": [{
"id": "134972c5b420e027"
}]
}, {
"id": "1334f68e7d209ae6",
"step_number": 3,
"tasks": [{
"id": "1334f68ef6209ae7"
}]
}]
};
$scope.insertStep = function() {
var insertStepIndex = 1,
task_data = {
"id": null,
"step_number": (insertStepIndex + 2),
"tasks": []
};
//go through each item in the array
$.each($scope.workflow.flow, function(index, step) {
//if the step number is greater then the place you want to insert it into, increase the step numbers
if (step.step_number > $scope.workflow.flow[insertStepIndex].step_number) step.step_number++;
});
$scope.workflow.flow.splice((insertStepIndex + 1), 0, task_data);
}
$scope.toggleHide = function() {
$scope.hide = !$scope.hide;
}
});
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.2/jquery.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/angular.js/1.3.14/angular.js"></script>
<div ng-app="app" ng-controller="ctrl">
<div ng-click="insertStep()">Insert Step</div>
<br />
<br />
<div ng-click="toggleHide()">Toggle Repeat</div>
<br />
<br />
<div ng-if="!hide">
<div ng-repeat="data in workflow.flow | orderBy:'+step_number'" ng-init="$stepIndex = workflow.flow.indexOf(data)">
{{ workflow.flow[$stepIndex].step_number }}
</div>
</div>
</div>

I got the problem. This is a tricky but the simple part. The ng-init directive only execute once. So your assignment to $stepIndex = workflow.flow.indexOf(data) will not updated when you push a new data to the array/list.
So adding a scope function will fix this problem since Angular will auto call the function when it's returning value changes.
angular.module('app', [])
.controller('ctrl', function($scope) {
$scope.workflow = {
flow: [{
"id": "1334f68db820f664",
"step_number": 1,
"tasks": [{
"id": "1334f68e3f20f665"
}]
}, {
"id": "134967a5ba205f5b",
"step_number": 2,
"tasks": [{
"id": "134972c5b420e027"
}]
}, {
"id": "1334f68e7d209ae6",
"step_number": 3,
"tasks": [{
"id": "1334f68ef6209ae7"
}]
}]
};
$scope.insertStep = function() {
var insertStepIndex = 1
var task_data = {
"id": null,
"step_number": (insertStepIndex + 2),
"tasks": []
};
//go through each item in the array
angular.forEach($scope.workflow.flow, function(step, index) {
//if the step number is greater then the place you want to insert it into, increase the step numbers
if (step.step_number > $scope.workflow.flow[insertStepIndex].step_number) step.step_number++;
});
$scope.workflow.flow.splice((insertStepIndex + 1), 0, task_data);
}
// This is a new function which I added to fix the problem
$scope.getIndex = function(data) {
return $scope.workflow.flow.indexOf(data);
};
$scope.toggleHide = function() {
$scope.hide = !$scope.hide;
};
});
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.2/jquery.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/angular.js/1.3.14/angular.js"></script>
<div ng-app="app" ng-controller="ctrl">
<div ng-click="insertStep()">Insert Step</div>
<br />
<br />
<div ng-click="toggleHide()">Toggle Repeat</div>
<br />
<br />
<div ng-if="!hide">
<div ng-repeat="data in workflow.flow | orderBy:'+step_number'">
{{ workflow.flow[getIndex(data)].step_number }}
</div>
</div>
</div>

Related

Angular: Watch collection on field change and make a calculations

I'm building a simple page with Angular and I got some problems with watchers that I need to create there. First of all my collection looks something like this:
$scope.products = [
{
"name": "Milk",
"price": 2,
"currency": "USD",
"exchangeRate": 1.5,
"localCurrencyPrice": 0
},
{
"name": "Bread",
"price": 1,
"currency": "EUR",
"exchangeRate": 1,
"localCurrencyPrice": 0
},
{
"name": "Skittles",
"price": 3,
"currency": "GBP",
"exchangeRate": 2,
"localCurrencyPrice": 0
},
{
"name": "Nesquik",
"price": 10,
"currency": "CHF",
"exchangeRate": 2.5,
"localCurrencyPrice": 0
}
]
I will be showing a field for each and every property of the object in the collection, so it's going to look something like this: https://plnkr.co/edit/hnuC1IAMZAQznDYP6tsQ?p=preview
As you can see I can add or remove items, so the array length is not a constant number on start up. I want to create watchers for every "PRICE" and "EXCHANGERATE" field in each item and when user changes the value of one of them, they both should be mulplied and the result should be filled up into the "LOCALCURRENCYPRICE" field. /e.g. in the first item "Milk" when I change the price to 1.7, it should be muplied with the exchangeRate of 1.5 and the result should be populated into the localCurrencyPrice model and field/. When you add a new item into the products array, watchers for it's "price" and "exchangeRate" properties should be added as well.
Any ideas how this can be done?
Thanks!
You need to define a ng-change function with your template ( https://docs.angularjs.org/api/ng/directive/ngChange )
For initialization; you can use ng-init ( https://docs.angularjs.org/api/ng/directive/ngInit)
Call it with :
<div class="field" ng-repeat="(fieldKey, fieldValue) in item">
<div ng-init="calcFunc(item)">{{ fieldKey.toUpperCase() }}:
<input type="text" name="item.name" ng-model="fieldValue" ng-change="myFunc(fieldKey, fieldValue,item)/> </div>
</div>
Add it to your controller with your logic :
$scope.calcFunc = function(item) {
item['LOCALCURRENCYPRICE'] = item['EXCHANGERATE'] * item['PRICE'];
}
$scope.myFunc = function(key,val,item){
if(key === 'EXCHANGERATE') .... // check if the updated field is one of your's requested
{
//edit item['LOCALCURRENCYPRICE']
$scope.calcFunc(item);
}
}
i tried it in another way and its in the way you like it:
what i did:
1.Added ng-model to price,exchangerate,localcurrencyrate.
2.Next in the ng-model of localcurrencyrate i multiplied price and exchangerate.thats it
just copy paste below code and test it you will get exactly wat you needed:)
<!DOCTYPE html>
<html>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.4/angular.min.js"></script>
<body>
<div ng-app="myApp" ng-controller="customersCtrl">
<table>
<tr ng-repeat="x in products">
<td>
<!-- field name -->
<br>
<!-- fields -->
<b>Item {{$index}}</b>
<button ng-click="RemoveItem()">Remove Item</button><br><br>
NAME:<input type="text" value="{{x.name}}" /><br>
PRICE:<input type="text" value="{{x.price}}" ng-model="x.price"/><br>
CURRENCY:<input type="text" value="{{x.currency}}"/><br>
EXCHANGERATE:<input type="text" value="{{x.exchangeRate}}" ng-model="x.exchangeRate"/><br>
LOCALCURRENCYPRICE:<input type="text" value="{{x.localCurrencyPrice}}" ng-model="x.price*x.exchangeRate"/><br>
<br>
</td>
</tr>
</table>
<button ng-click="AddNewItem()">Add New Item</button>
<br>
</div>
<script>
var app = angular.module('myApp', []);
app.controller('customersCtrl', function($scope)
{
$scope.AddNewItem = function() {
$scope.products.push( {
name: "",
price: 0,
currency: "",
exchangeRate: 0,
localCurrencyPrice: 0
} );
};
$scope.RemoveItem = function() {
$scope.products.splice( this.$index, 1 );
};
$scope.products = [
{
"name": "Milk",
"price": 2,
"currency": "USD",
"exchangeRate": 1.5,
"localCurrencyPrice": 0
},
{
"name": "Bread",
"price": 1,
"currency": "EUR",
"exchangeRate": 1,
"localCurrencyPrice": 0
},
{
"name": "Skittles",
"price": 3,
"currency": "GBP",
"exchangeRate": 2,
"localCurrencyPrice": 0
},
{
"name": "Nesquik",
"price": 10,
"currency": "CHF",
"exchangeRate": 2.5,
"localCurrencyPrice": 0
}
]
});
</script>
</body>
</html>

Angular / javascript product filter

I want to modify this product angular / javascript filter
When user select for example: Processor - 2GHz and Memory - 32GB I want to show only product that have all those properties.
Right now filter shows all products with 2GHz and all products with 32GB.
Plunker
https://plnkr.co/edit/vr9o2aHkHvCL1cvgKILd?p=preview
angular.module('app', [])
.controller('Controller', function($scope) {
$scope.items = [{
"name": "Product - 2GHZ, 32GB, Black",
"tags": [{
"category": 1,
"tag": 2
}, {
"category": 2,
"tag": 4
}, {
"category": 3,
"tag": 8
}]
}, {
"name": "Product - 2GHz, 128GB, Black",
"tags": [{
"category": 1,
"tag": 2
}, {
"category": 2,
"tag": 5
}, {
"category": 3,
"tag": 8
}]
}, {
"name": "Product - 1GHz, 128GB, White",
"tags": [{
"category": 1,
"tag": 1
}, {
"category": 2,
"tag": 5
}, {
"category": 3,
"tag": 7
}]
}];
$scope.items_dup = $scope.items
// checkbox selection
$scope.selectionTag = [];
$scope.selectionCat = [];
$scope.toggleSelection = function toggleSelection(tag, category) {
var idxTag = $scope.selectionTag.indexOf(tag);
if (idxTag > -1) {
$scope.selectionTag.splice(idxTag, 1);
} else {
$scope.selectionTag.push(tag);
}
// this is not working, probably need twodimensional array
// category
var idxCat = $scope.selectionCat.indexOf(category);
if (idxCat > -1) {
$scope.selectionCat.splice(idxCat, 1);
} else {
$scope.selectionCat.push(category);
}
};
// filter list
$scope.filter = function() {
filterTag($scope.selectionTag, $scope.items);
function filterTag(selected, items) {
var out = [];
angular.forEach(items, function(value, key) {
angular.forEach(selected, function(inner_value, key) {
angular.forEach(value.tags, function(inner_value2, key) {
if (value.tags[key].tag === inner_value) {
if (out.indexOf(value) == -1) {
out.push(value)
}
}
})
})
})
if (out.length > 0) {
$scope.items_dup = out;
} else {
$scope.items_dup = $scope.items;
}
}
};
})
<!DOCTYPE html>
<head>
<script src="//cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.0/angular.min.js"></script>
<script src="script.js"></script>
</head>
<body ng-app="app">
<div ng-controller="Controller">
<h1>Product filtering!</h1>
<li ng-repeat="item in items_dup">
{{item.name}}
</li>
<hr>
<p data-category="1">Processor</p>
<label>
<input type="checkbox" value="1" ng-click="toggleSelection(1,1)"> 1GHz
</label>
<br>
<label>
<input type="checkbox" value="2" ng-click="toggleSelection(2,1)"> 2GHz
</label><br>
<hr>
<p data-category="2">Memory</p>
<label>
<input type="checkbox" value="4" ng-click="toggleSelection(4,2)"> 32GB
</label>
<br>
<label>
<input type="checkbox" value="5" ng-click="toggleSelection(5,2)"> 128GB
</label><br>
<hr>
<p data-category="3">Color</p>
<label>
<input type="checkbox" value="7" ng-click="toggleSelection(7,3)"> White
</label>
<br>
<label>
<input type="checkbox" value="8" ng-click="toggleSelection(8,3)"> Black
</label><br>
<br><br>
<button ng-click="filter()">Filter list</button>
</div>
</body>
</html>
You could use Array#every to check if each selection appears in an item's tag list. If you want to switch back from an and to an or filter, you can use Array#some instead.
The good thing about these methods is that they return as soon as they know their result, ending the loop early when possible.
$scope.filter = function() {
$scope.items_dup = $scope.items.filter(function(item) {
return $scope.selectionCat.every(function(c) {
return item.tags
.some(function(t) {
return t.category === c &&
$scope.selectionTag.indexOf(t.tag) !== -1;
});
})
});
}
In a plunker: here
An example of how you could create a map, and how it would affect your filter:
var $scope.items = [ /* ... */ ].map(function(item) {
var tagMap = item.tags.reduce(function(result, t.tag) {
result[t.tag] = true; // Add the tag Id to the map
return result;
}, {});
// Add the tag map to the item
return Object.assign(item, { tagMap: tagMap });
});
// Now, you can filter like so:
var matches = selectedTagIds.every(function(id) {
return item.tagMap[id];
});
another approach is using filter with filter object on ng-repeat, in this case items are filtered immediately when check boxes state changes:
<li ng-repeat="item in items_dup | filter : mainFilter : true">
{{item.name}}
</li>
all check boxes use ng-model and trigger update function:
<label>
<input type="checkbox" ng-change="updateFilters()" ng-model="filters.processor['1GHZ']"> 1GHz
</label>
update function:
$scope.updateFilters = function updateFilters() {
$scope.mainFilter = {};
angular.forEach($scope.filters, function (category, categoryKey) {
angular.forEach(category, function (value, valueKey) {
if (value) {
if ($scope.mainFilter[categoryKey]) {
delete $scope.mainFilter[categoryKey];
}
else {
$scope.mainFilter[categoryKey] = valueKey;
}
}
});
});
};
each item has processor, memory and color attributes added.
plunker: https://plnkr.co/edit/6BDVYzV430N0MSaopGXt?p=preview

How to get back an array of objects which have a particular value using filters in AngularJS?

var foo = [
{
"id": 12,
"name": "Test"
},
{
"id": 2,
"name": "Beispiel"
},
{
"id": 3,
"name": "Sample"
},
{
"id": 4,
"name": "Test"
},
{
"id": 5,
"name": "Sample value"
},
{
"id": 6,
"name": "Testvalue"
}
];
I am trying to get a simple search input, which shows a couple of listings on ng-repeat. The search is basically a filter which shows searched items in that listing. What I have achieved is when I search something, with $http, it gets back the whole list of foo, and within that it filters. How can I just get the data with my keyword, and the whole JSON? For example if I search sample, how can I get the objects of id 3 and 5 so that I can display a new set of listings, or if I search with ID number 12, I get the object which has id as 12. The search term will be dynamic. I will be giving a $http call on every search as well.
Thanks.
If I understood well, it should work:
(function() {
angular
.module('app', [])
.controller('MainCtrl', MainCtrl);
MainCtrl.$inject = ['$scope', '$http'];
function MainCtrl($scope, $http) {
$scope.foo = [
{
"id":12,
"name":"Test"
},
{
"id":2,
"name":"Beispiel"
},
{
"id":3,
"name":"Sample"
},
{
"id":4,
"name":"Test"
},
{
"id":5,
"name":"Sample value"
},
{
"id":6,
"name":"Testvalue"
}
];
}
})();
<!DOCTYPE html>
<html ng-app="app">
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.8/angular.min.js"></script>
</head>
<body ng-controller="MainCtrl">
<input type="text" ng-model="search" placeholder="Search">
<ul>
<li ng-repeat="item in foo | filter: search">
<span ng-bind-template="{{item.id}} - {{item.name}}"></span>
</li>
</ul>
</body>
</html>
Note: If you want to perform a $http request based on search input, look at this DEMO.
You can use $filter to achieve this.
function YourController($filter) {
var result = $filter('filter')(foo, {"id":3,"id":5});
};
You can iterate result later.

Update json file based on input values using AngularJS

I've written the following code to read data from json file and draw a graph. now I want to get the data from user through input fields and update the json with new values. I wrote the code to copy the value and made it accessible outside the scope, I just wonder how to update the existent json with the new values.
json looks like this:
{
"nodes" : [
{ "id": 1, "label": "End Product", "level": 0 },
{ "id": 2, "label": "A 1", "level": 1 },
{ "id": 3, "label": "A 2", "level": 1 },
{ "id": 4, "label": "A 3", "level": 1 },
{ "id": 5, "label": "B 1", "level": 2 },
{ "id": 6, "label": "C 1", "level": 3 },
{ "id": 7, "label": "B 2", "level": 2 },
{ "id": 8, "label": "B 3", "level": 2 }
],
"edges" : [
{ "from": 1, "to": 2 },
{ "from": 1, "to": 3 },
{ "from": 1, "to": 4 },
{ "from": 2, "to": 5 },
{ "from": 5, "to": 6 },
{ "from": 3, "to": 7 },
{ "from": 4, "to": 8 }
]
}
var app = angular.module('bomApp', ['bomGraph']);
app.controller('bomController', ['$scope', 'appService', '$rootScope', function ($scope, appService, $rootScope) {
var get = function () {
appService.get().then(function (promise) {
$scope.graph = {
options: {
"hierarchicalLayout": {
"direction": "UD"
},
"edges": {
"style":"arrow-center",
"color":"#c1c1c1"
},
"nodes": {
"shape":"oval",
"color":"#ccc"
}
},
data: {
nodes: promise.nodes,
edges: promise.edges
}
};
});
};
$scope.newNode = {
id: undefined,
label: undefined,
level: undefined,
parent: undefined,
};
$scope.arrNode = {};
$scope.update = function (nodes) {
$scope.arrNode = angular.copy(nodes);
$rootScope.nodex = angular.copy(nodes);
};
$scope.newEdge = {
id: undefined,
from: undefined,
to: undefined
};
$scope.arrEdge = {};
$scope.updateE = function (edges) {
$scope.arrEdge = angular.copy(edges);
$rootScope.edgex = angular.copy(nodes);
};
get();
}]);
app.factory('appService', ['$q', '$http', '$rootScope', function ($q, $http, $rootScope) {
return {
get: function (method, url) {
var deferred = $q.defer();
$http.get('data.json')
.success(function (response) {
deferred.resolve(response);
})
return deferred.promise;
},
};
}]);
<body data-ng-app="bomApp">
<div data-ng-controller="bomController">
<h3>Create Your Own Graph</h3>
<div class="node">
<h4>Nodes:</h4>
<div class="panel">
<label>Id:</label>
<input type="text" id="idNode" data-ng-model="newNode.id"/>
<br />
<label>Label:</label>
<input type="text" id="label" data-ng-model="newNode.label" />
<br />
<label>Level:</label>
<input type="text" id="level" data-ng-model="newNode.level" />
</div>
<div>
<button id="addNode" data-ng-click="update(newNode)">Add</button>
</div>
</div>
<div class="node">
<h4>Edges:</h4>
<div class="panel" >
<label>Id:</label>
<input type="text" id="idEdge" data-ng-model="newEdge.id"/>
<br />
<label>From:</label>
<input type="text" id="from" data-ng-model="newEdge.from"/>
<br />
<label>To:</label>
<input type="text" id="to" data-ng-model="newEdge.to"/>
</div>
<div>
<button id="addEdge" data-ng-click="updateE(newEdge)">Add</button>
</div>
</div>
<div data-custom-dir="" id="graph" data="graph.data" options="graph.options"></div>
<div>
<h3>Monitor The Changes</h3>
<div class="node">
<h4>Nodes:</h4>
<div class="col">
<div id="nodes">
<pre>{{newNode | json}}</pre>
<pre>{{arrNode | json}}</pre>
</div>
</div>
</div>
<div class="node">
<h4>Edges:</h4>
<div class="col">
<div id="edges">
<pre>{{newEdge | json}}</pre>
<pre>{{arrEdge | json}}</pre>
</div>
</div>
</div>
</div>
</div>
</body>
You can add items to a javascript object using .push(). To make an update, you'll have to loop through the object until you find a matching id (or some other value).
Finally, you can use .appendTo() to update the section that indicates what changes have been made.
Example:
$('#addNode').click(function () {
// pseudo code to add item to javascript object:
// 'node' would be presumably nested based on the OP,
// but information on js object name is not provided
var nodeId = $('#idNode').val();
var nodeLabel = $('#label').val();
var nodeLevel = $('#level').val();
nodes.push({
"id": nodeId,
"label": nodeLabel,
"level": nodeLevel,
});
// psudeo code for updating js object
// loop through object to find matching id:
var locate = 0;
for (var i = 0; i < nodes.length; i++) {
if(node[i].id == nodeId){
locate = i;
node[i].label = nodeLabel;
node[i].level = nodeLevel;
}
}
// Adding results to element
var nodesElem = $('#nodes');
var addElem = "<pre> { newNode | " + JSON.stringify(node[locate]) + " }";
addElem.appendTo(nodesElem);
});
If you need it to be able to change existing records try :
$scope.graph.data.nodes[$scope.newNode.id] = $scope.newNode;
and
$scope.graph.data.edges.push($scope.newEdge);
These methods function calls should update the existing data with the newNode and the newEdge

How to submit dropdown inputfield as an array in angular

Live Code -- JSBIN Link
I have a nested lists of javascript objects Templates.schema
There will be many sections per page and I must keep track of them (via an index).
How do I make sure on submit of form, that addPage() function creates an array on the section via input ng-model="pageAdder.page.section
<form ng-model="pageAdder" ng-submit="addPage()">
<label for="page.name">PAGE</label>
<input type="text" ng-model="pageAdder.page.name">
<br>
<hr>
<label for="page.name">Section</label>
<select id="s1" ng-model="pageAdder.page.section" ng-options="item as item.type for item in items"></select>
<br>
<hr>
<input class="btn btn-primary" type="submit" value="add Page">
</form>
to...
templates: {
"schema": [
{
"page": {
"name": "asdfasdf",
"section": [{
"id": 1,
"type": "foo"
}]//adds array
}
}
]
}
controller
angular.module("ang6App")
.controller("MainCtrl", function ($scope) {
})
.controller("templatesCtrl", function ($scope, Templates) {
$scope.template = Templates;
$scope.addPage = function() {
$scope.template.schema.push($scope.pageAdder);
};
$scope.items = [
{ id: 1, type: 'foo' },
{ id: 2, type: 'bar' },
{ id: 3, type: 'blah' }];
$scope.pageAdder = {
};
//end of templates
});
add default value to $scope.pageAdder
$scope.pageAdder = {
"page": {
"name": null,
"section": [{
"id": null,
"type": null
}]
}
And it works fine:
http://jsbin.com/OGipAVUF/45/edit

Categories