I'm trying to figure out how to watch an array and update an object, like this.
var vm = this; //controller
vm.order = {
totalValue: 25,
products: [{id:0, val: 10, qtd: 1}, {id:1, val: 15, qtd: 1}]
};
If I push a new product into this order object, how do I update the totalValue just watching the collection ? (totalValue = All Val * All Qtd)
It is necessary because the "qtd" field is bind to an input field, so I can change the value of all "products" at any time.
UPDATE
$scope.$watchCollection(
function() { return self.order.products; },
function(products) {
self.order.totalValue = products.reduce(function (p, n) {
console.log(n.qtd);
console.log(n.val);
return p + n.qtd * n.val;
}, 0);
});
The code worked, but only when I push a new "product" into order.
I have to watch every order.products.product.qtd and change the order.totalValue every time the user change the qtd of a product
Try this way:
$scope.$watch('order.products', function (products) {
scope.order.totalValue = products.reduce(function (p, n) {
return p + n.qtd * n.val;
}, 0);
}, true);
This will deep watch array to detect qtd and val changes inside array items.
Related
I am working on a real time AmCharts in my AngularJS application and I wanted to know how I could push datapoints into the chart. The following is the code in my controller:
$http.get(hostNameService.getHostName()+"/dashboard/itemValue")
.then(function (response) {$scope.item = response.data.items;
function generateChartData(){
var chartData = [];
for (var i = 0; i < $scope.item.length; i++ ) {
var itemId = $scope.item[i].itemId;
var avail = $scope.item[i].itemAvailability;
chartData.push( {
"itemId": itemId,
"avail": avail
} );
}
return chartData;
}
/**
* Create the chart
*/
var chart = AmCharts.makeChart( "toolAvailability", {
"type": "serial",
"theme": "light",
"zoomOutButton": {
"backgroundColor": '#000000',
"backgroundAlpha": 0.15
},
"dataProvider": generateChartData(),
"categoryField": "itemId",
"graphs": [ {
"id": "g1",
"valueField": "avail",
"bullet": "round",
"bulletBorderColor": "#FFFFFF",
"bulletBorderThickness": 2,
"lineThickness": 2,
"lineColor": "#b5030d",
"negativeLineColor": "#0352b5",
"hideBulletsCount": 50
} ],
"chartCursor": {
"cursorPosition": "mouse"
}
} )
/**
* Set interval to push new data points periodically
*/
// set up the chart to update every second
setInterval( function() {
// normally you would load new datapoints here,
// but we will just generate some random values
// and remove the value from the beginning so that
// we get nice sliding graph feeling
// remove datapoint from the beginning
chart.dataProvider.shift();
// for (var i = 0; i < $scope.item.length; i++ ) {
// var itemId = $scope.item[i].itemId;
// var avail = $scope.item[i].itemAvailability;
// }
chart.dataProvider.push( {// not sure how I can push the json data here
date: //need to push the value here,
visits: //need to push the value here
} );
chart.validateData();
}, 1000 );
});
My chart is getting data from a web service. The web service returns 10 items and each item availability should be updated in real time. I am using itemId and availability in the chart. Initially I am able to load the data in the chart. But when the chart shifts one value how should I push the new value. Can anyone let me know how I could achieve this functionality.
I was able to achieve this by making the following changes in my code. I added the following function and called this function in the set timeout function. So for every shift in the chart one value will be pushed into the chart.
function pushNewData(index){
var itemId = $scope.tools[index].itemId;
var avail = $scope.tools[index].availability;
chart.dataProvider.push( {
itemId: itemId,
avail: avail
} );
}
var i =0;
setInterval( function() {
chart.dataProvider.shift();
pushNewData(i);
if(i < $scope.items.length - 1){
i++;
}else{
i = 0;
}
chart.validateData();
}, 1000 );
I am trying to to update the attribute of an onbject in angularjs array of object.
I have the folowing array of object:
$scope.formes= [
{id: 0, geo: 0,cx:0, cy:0, b:0, h:0, r:0, d:0, a:0, ax:0, ay:0, val: 0}
];
The values of each attribute are set to 0 by default until the user type in a new value in a field. Some values are updated after the user add a new objet or hit a button. This app is used to calculate the center of basic geometric shapes and there moments. Just fyi.
This is the function that is running when the user add an object.
$scope.ajoutForme = function(){
$scope.formes.a = parseInt($scope.formes.b) * parseInt($scope.formes.h); /// not working
$scope.formes.push({
id: $scope.nbrForme++ //just adding and new id
});
}
Before I add the objet I want to update some values with calculations. For exemple in this case I what to set the value of a with b*h.
I have try
$scope.formes[nbrForme].h = parseInt($scope.formes[nbrForme].b) * parseInt($scope.formes[nbrForme].h); //This is working but only the first time I press the button??
nbrForme is = to the id of the element I am working on and gets incremented when I add a new object.
Complete Controler
var moment = angular.module('moment',[]);
var nbrForme = 0;
moment.controller('momentCtrl', ['$scope', function($scope) {
$scope.nbrForme = nbrForme;
$scope.formes= [
{id: 0, geo: 0,cx:0, cy:0, b:0, h:0, r:0, d:0, a:0, ax:0, ay:0, val: 0}
];
$scope.ajoutForme = function(){
$scope.formes[nbrForme].a = parseInt($scope.formes[nbrForme].b) * parseInt($scope.formes[nbrForme].h); /// only work once
$scope.formes.push({
id: $scope.nbrForme++
});
}
}
}]);
Your object definition is wrong:
$scope.object = [
{id:0, attr1:2, attr2:4, attr3:0},
{id:1, attr1:2, attr2:4, attr3:0},
{id:2, attr1:2, attr2:4, attr3:0}
];
Note a added , to separate the elements of the array.
Edit:
You wrote that this line doesn't work $scope.formes.a = parseInt($scope.formes.b) * parseInt($scope.formes.h);
This is because $scope.formes is an array, so you must reference to a specific object inside the array that has the a/b/h property. The question is which one?
If it's the first index in the array you'll do
$scope.formes[0].a = parseInt($scope.formes[0].b) * parseInt($scope.formes[0].h);
The last element:
var lastIndex = $scope.formes.length - 1;
if (lastIndex >= 0) {
$scope.formes[lastIndex].a = parseInt($scope.formes[lastIndex].b) * parseInt($scope.formes[lastIndex].h);
}
If $scope.nbrForme is the ID of the element you're currently working on, then you need to decrease its value by 1, because you start with the value of 1, and the first index of an array is 0:
$scope.formes[$scope.nbrForme - 1].a = parseInt($scope.formes[$scope.nbrForme - 1].b) * parseInt($scope.formes[$scope.nbrForme - 1].h);
Suppose I have an array of objects like this:
$scope.data = [
{
"active": true,
"id": "4bcb19db-ec10-4de5-8ae6-b13388494651",
"name": "Owais Shah",
"username": "chekhov"
},
{
"active": false,
"id": "630d0582-1bf9-4331-9fa6-f6baa59319b2",
"name": "Tim Hunt",
"username": "one"
}
]
I can use this to deep watch the object:
$scope.$watch('data', function(){...}, true)
However, this is not efficient in my case, as the only property of these objects that I'm interest in watching is active.
Is there a way to watch only a specific property deep inside the object? Something like $scope.$watch('data[].active' ...)
I think you could do something like this:
for(var i=0,j=$scope.data.length;i<j;i++) {
$scope.$watch(data[i].active, function() { });
}
You can watch on a function which in turn returns your inner objects
$scope.$watch( function() {
return $scope.data.active[0];
},
function(newValue, oldValue) {
console.log('It changed!');
}
);
You can take advantage of the $watch function accepting a function and return a value you compute from the array. For example, you could look at it is a number based on the binary sequence of 0/1 for active = false or true. Something like this would work:
$scope.$watch(function() {
var value = 0, idx;
for (idx = 0; idx < $scope.data.length; idx += 1) {
value *= 2;
value += $scope.data[idx].active ? 1 : 0;
};
return value;
}, function() {
$scope.changes += 1;
$scope.status = "Changed " + $scope.changes + " times."; });
The jsFiddle here illustrates it: http://jsfiddle.net/jeremylikness/GrRtK/
You can see it illustrated by clicking the same button. The first time the element changes from true to false or vice versa, the change is registered, while subsequent updates do not trigger the watch. It will be even more readable if you use a mapping function from underscore to project the array onto your number or value to watch.
I just had a weird bug when using Backbone.Model
so I have the model declaration something like:
var MyMode = Backbone.Model.extend({
defaults:{
'someList': []
},
initialize: function(){
_.bindAll(this, 'toString', 'castFromString');
},
toString: function(){
return this.get('hierarchyNameList').join('+');
},
castFromString: function(str){
var strArray = [], index = 0;
if (str) {
strArray = str.split('+');
}
for (index = 0; index < strArray.length; index++){
this.get('hierarchyNameList').push(strArray[index]);
}
}
});
then I tried to test it
(function () {
'use strict';
var assert = function(condition, message) {
if (condition !== true) {
throw message || "Assertion failed";
}
};
var mA = new MyModel({'someList': ['a','b','c']});
var mB = new MyModel();
mB.castFromString('a+b+c');
//I have a added a equals function to array prototype and tested it
assert(mA.get('someList').equals(mB.get('someList'))); //true
var mC = new MyModel(mA.toJSON()); //behaves as expected
var mD = new MyModel(); //for some reason its list already contains the list from B
mD.castFromString(mB.toString()); //since castFromString used push, now B and D both has array of length 6
assert(mC.equals(mA)); //success
assert(mC.equals(mD)); //fail, mc has arrayLength 3, mD has 6
}).call(this);
The actual code is more complicated than this, but I think this is where I am probably doing something wrong, any suggestion on why this would happen? Thanks in advance!
The problem is with your defaults
defaults:{
'someList': []
},
objects in JavaScript are passed by reference not by value. It means that all instances, for which you didn't explicitly specified someList value will share array created in defaults definition above. To avoid it you can define your defaults as a function:
defaults: function () {
return { 'someList': [] };
},
This will create new array for every instance of MyModel, so they won't share the same array.
I am using this plugin for my autocomplete form:
http://www.planbox.com/blog/news/updates/jquery-autocomplete-plugin-for-backbone-js.html
Instead of checking only one item, as in the code below (if (inputVal == 'bakaxel')),
I would like to check the selected value against the entire collection
var collection = new Backbone.Collection([
{id:"AB", name:"Alberta"},
{id:"AD", name:"Album"},
{id:"BA", name:"barn"},
{id:"BC", name:"bak"},
{id:"BD", name:"baby"},
{id:"BE", name:"band"},
{id:"BF", name:"bakaxel"},
{id:"BG", name:"batteri"},
{id:"BH", name:"barbie"},
{id:"MB", name:"Manitoba"},
{id:"AP", name:"Armed Forces Pacific"}
]);
$('input.search').autocomplete({
collection: collection,
attr: 'name',
noCase: true,
ul_class: 'search_options tr_list',
ul_css: {'z-index':1234}
});
$('input.search').each(function(){
$(this).blur(function(){
var inputVal = $('input.search').val();
if (inputVal == 'bakaxel') {
$('#search_result_page').load('searchResult.html');
$('#searchPage').addClass('hidden');
}
});
});
I tried this, but I'd rather not create the ar array again, just use the backbone collection:
$('input.search').each(function(){
$(this).blur(function(){
var inputVal = $('input.search').val();
var ar = ["Alberta", "Album", "barn", "bak", "baby", "band", "bakaxel", "batteri", "barbie", "Manitoba", "Armed Forces Pacific"];
if (jQuery.inArray(inputVal, ar) != -1) {
$('#search_result_page').load('searchResult.html');
$('#searchPage').addClass('hidden');
}
});
});
Backbone proxies Underscore functions and most notably in your case http://underscorejs.org/#where
where _.where(list, properties)
Looks through each value in the list, returning an array of all the values that contain all of the
key-value pairs listed in properties.
Your test could be written as
var matches = collection.where({
name: inputVal
});
if (matches.length>0) {
...
}
Or as #mu suggested in the comments, you could just check the existence of the input with http://underscorejs.org/#find
var found = collection.find(function(model) {
return model.get('name') === inputVal
});
if (found) {
...
}