knockout child objects on array have the value of last item - javascript

I am quite baffled as why even though my ingredients get populated correctly in the list and rendered, when I change the value of for example first ingredient's "Dosage Reference System" it then gets mixed up with the Dosage Reference System's value of the last item in the array?
The values in each row don't update according to the values in that row
Any help would be appreciated:
Here is the fiddle: http://jsfiddle.net/makeitmorehuman/C6AvC/
Code for reference here is:
function ingredient(data) {
ingr = this;
ingr.Name = ko.observable(data.Name);
ingr.UnitCost = ko.observable(data.UnitCost);
ingr.DRS = ko.observable(data.DRS);
ingr.DP = ko.observable(data.DP);
ingr.PercenChange = ko.computed(function () {
return Math.round(ingr.DP() - ingr.DRS());
});
ingr.RawMaterialRS = ko.computed(function () {
return Math.round((ingr.DRS() / 100) * ingr.UnitCost() * 10000);
});
ingr.RawMaterialCostProp = ko.computed(function () {
return Math.round((ingr.DP() / 100) * ingr.UnitCost() * 10000);
});
ingr.CostDifference = ko.computed(function () {
return Math.round(ingr.RawMaterialCostProp() - ingr.RawMaterialRS());
});
}
function ingredientsData() {
return [
{ "Name": "Skimmed milk", "UnitCost": 0.40, "DRS": 70, "DP": 87 },
{ "Name": "Cream 40% fat", "UnitCost": 1.80, "DRS": 18, "DP": 9 },
{ "Name": "Skim Milk Powder", "UnitCost": 2.5, "DRS": 12, "DP": 1 },
{ "Name": "N-Dulge SAI", "UnitCost": 3.5, "DRS": 0, "DP": 2 },
{ "Name": "Novation Indulge 1720", "UnitCost": 3.9, "DRS": 0, "DP": 1 }
];
}
function NovationIndulge() {
var self = this;
self.Ingredients = ko.observableArray();
self.init = function () {
ko.utils.arrayForEach(ingredientsData(), function (item) {
self.Ingredients.push(new ingredient(item));
});
};
function SumOfItems(propertyToSum) {
var total = 0;
ko.utils.arrayForEach(self.Ingredients(), function (item) {
total = parseInt(total) + parseInt(item[propertyToSum]());
});
return total;
}
self.TotalDRS = ko.computed(function () { return SumOfItems("DRS"); });
self.TotalDP = ko.computed(function () { return SumOfItems("DP"); });
self.TotalCostDiff = ko.computed(function () { return SumOfItems("CostDifference"); });
self.TotalRawMaterialRS = ko.computed(function () { return SumOfItems("RawMaterialRS"); });
self.TotalRawMCP = ko.computed(function () { return SumOfItems("RawMaterialCostProp") });
self.AnnualFinishedProduct = ko.observable(4000);
self.TotalCostSavingP1000 = ko.computed(function () { return self.TotalCostDiff() * -1 });
self.TotalAnnualSaving = ko.computed(function () {
return self.TotalCostSavingP1000() * self.AnnualFinishedProduct() / 1000;
});
}
var NI = new NovationIndulge();
NI.init();
ko.applyBindings(NI);

My first guess would be that because you define
function ingredient(data) {
ingr = this;
...
without the var keyword, it will instead create a global property (belonging to window).
Then, every time a function in your prototype runs, stuff is messed up.

This was a nice one. The offending line is this:
ingr = this;
should be
var ingr = this;
Updated fiddle: http://jsfiddle.net/6nXYE/1/

Related

(javascript) There seem to be two things wrong with this code

Do I need to execute a bind(this) somewhere and the console log placement seems to be off?
var company = {
employees: [{
name: "doug"
},
{
name: "AJ"
}
],
getName: function(employee) {
return employee.name
},
getNames: function() {
return this.employees.map(this.getName)
},
delayedGetNames: function() {
setTimeout(this.getNames, 500)
}
}
console.log(company.delayedGetNames());
setTimeout(this.getNames.bind(this), 500)
^
|
+----< HERE
var company = {
employees: [{
name: "doug"
},
{
name: "AJ"
}
],
getName: function(employee) {
return employee.name
},
getNames: function() {
return this.employees.map(this.getName)
},
delayedGetNames: function() {
var fn = function() {
var names = this.getNames();
console.log(names);
};
setTimeout(fn.bind(this), 500);
}
}
company.delayedGetNames();

Summarize count of occurrences in an array of objects with Array#reduce

I want to summarize an array of objects and return the number of object occurrences in another array of objects. What is the best way to do this?
From this
var arrayOfSongs = [
{"title":"Blue","duration":161.71,"audioUrl":"/assets/music/blue","playing":false,"playedAt":"2016-12-21T22:58:55.203Z"},
{"title":"Blue","duration":161.71,"audioUrl":"/assets/music/blue","playing":false,"playedAt":"2016-12-21T22:58:55.203Z"},
{"title":"Blue","duration":161.71,"audioUrl":"/assets/music/blue","playing":false,"playedAt":"2016-12-21T22:58:55.203Z"},
{"title":"Green","duration":161.71,"audioUrl":"/assets/music/blue","playing":false,"playedAt":"2016-12-21T22:58:55.203Z"}
];
To this
var newArrayOfSongs = [
{"title": "Blue", "playCount": 3 },
{"title": "Green", "playCount": 1}
]
I have tried
arrayOfSongs.reduce(function(acc, cv) {
acc[cv.title] = (acc[cv.title] || 0) + 1;
return acc;
}, {});
}
But it returns an object:
{ "Blue": 3, "Green": 1};
You should pass the initial argument to the reduce function as an array instead of object and filter array for the existing value as below,
Working snippet:
var arrayOfSongs = [
{"title":"Blue","duration":161.71,"audioUrl":"/assets/music/blue","playing":false,"playedAt":"2016-12-21T22:58:55.203Z"},
{"title":"Blue","duration":161.71,"audioUrl":"/assets/music/blue","playing":false,"playedAt":"2016-12-21T22:58:55.203Z"},
{"title":"Blue","duration":161.71,"audioUrl":"/assets/music/blue","playing":false,"playedAt":"2016-12-21T22:58:55.203Z"},
{"title":"Green","duration":161.71,"audioUrl":"/assets/music/blue","playing":false,"playedAt":"2016-12-21T22:58:55.203Z"}
];
var newArrayOfSongs = arrayOfSongs.reduce(function(acc, cv) {
var arr = acc.filter(function(obj) {
return obj.title === cv.title;
});
if(arr.length === 0) {
acc.push({title: cv.title, playCount: 1});
} else {
arr[0].playCount += 1;
}
return acc;
}, []);
console.log(newArrayOfSongs);
To build on what you already have done, the next step is to "convert" the object to an array
var arrayOfSongs = [
{"title":"Blue","duration":161.71,"audioUrl":"/assets/music/blue","playing":false,"playedAt":"2016-12-21T22:58:55.203Z"},
{"title":"Blue","duration":161.71,"audioUrl":"/assets/music/blue","playing":false,"playedAt":"2016-12-21T22:58:55.203Z"},
{"title":"Blue","duration":161.71,"audioUrl":"/assets/music/blue","playing":false,"playedAt":"2016-12-21T22:58:55.203Z"},
{"title":"Green","duration":161.71,"audioUrl":"/assets/music/blue","playing":false,"playedAt":"2016-12-21T22:58:55.203Z"}
];
var obj = arrayOfSongs.reduce(function(acc, cv) {
acc[cv.title] = (acc[cv.title] || 0) + 1;
return acc;
}, {});
// *** added code starts here ***
var newArrayOfSongs = Object.keys(obj).map(function(title) {
return {
title: title,
playCount:obj[title]
};
});
console.log(newArrayOfSongs);
I recommend doing this in two stages. First, chunk the array by title, then map the chunks into the output you want. This will really help you in future changes. Doing this all in one pass is highly complex and will increase the chance of messing up in the future.
var arrayOfSongs = [
{"title":"Blue","duration":161.71,"audioUrl":"/assets/music/blue","playing":false,"playedAt":"2016-12-21T22:58:55.203Z"},
{"title":"Blue","duration":161.71,"audioUrl":"/assets/music/blue","playing":false,"playedAt":"2016-12-21T22:58:55.203Z"},
{"title":"Blue","duration":161.71,"audioUrl":"/assets/music/blue","playing":false,"playedAt":"2016-12-21T22:58:55.203Z"},
{"title":"Green","duration":161.71,"audioUrl":"/assets/music/blue","playing":false,"playedAt":"2016-12-21T22:58:55.203Z"}
];
function chunkByAttribute(arr, attr) {
return arr.reduce(function(acc, e) {
acc[e[attr]] = acc[e[attr]] || [];
acc[e[attr]].push(e);
return acc;
}, {});
}
var songsByTitle = chunkByAttribute(arrayOfSongs, 'title');
var formattedOutput = Object.keys(songsByTitle).map(function (title) {
return {
title: title,
playCount: songsByTitle[title].length
};
});
There, now everything is named according to what it does, everything does just one thing, and is a bit easier to follow.
https://jsfiddle.net/93e35wcq/
I used a set object to get the unique track titles, then used Array.map to splice those and return a song object that contains play count inside the track title.
The Data:
var arrayOfSongs = [{
"title": "Blue",
"duration": 161.71,
"audioUrl": "/assets/music/blue",
"playing": false,
"playedAt": "2016-12-21T22:58:55.203Z"
}, {
"title": "Blue",
"duration": 161.71,
"audioUrl": "/assets/music/blue",
"playing": false,
"playedAt": "2016-12-21T22:58:55.203Z"
}, {
"title": "Blue",
"duration": 161.71,
"audioUrl": "/assets/music/blue",
"playing": false,
"playedAt": "2016-12-21T22:58:55.203Z"
}, {
"title": "Green",
"duration": 161.71,
"audioUrl": "/assets/music/blue",
"playing": false,
"playedAt": "2016-12-21T22:58:55.203Z"
}];
The Function:
function getPlayCount(arrayOfSongs) {
let songObj = {};
let SongSet = new Set();
arrayOfSongs.map(obj => (SongSet.has(obj.title)) ? true : SongSet.add(obj.title));
for (let songTitle of SongSet.values()) {
songObj[songTitle] = {
playCount: 0
};
arrayOfSongs.map(obj => (obj.title === songTitle) ? songObj[songTitle].playCount++ : false)
}
return songObj;
}
console.log(getPlayCount(arrayOfSongs));
Which isn't exactly what you wanted formatting wise, but if you're married to it, this will do the trick:
function getPlayCount(arrayOfSongs) {
let songObj = {};
let SongSet = new Set();
arrayOfSongs.map(obj => (SongSet.has(obj.title)) ? true : SongSet.add(obj.title));
for (let songTitle of SongSet.values()) {
songObj[songTitle] = 0;
arrayOfSongs.map(obj => (obj.title === songTitle) ? songObj[songTitle]++ : false)
}
return songObj;
}
console.log(getPlayCount(arrayOfSongs));
https://jsfiddle.net/93e35wcq/1/

Adding new Observable to ObservableArray mapped by ko.mapping.fromJS

I'm trying to add a new Observable to an ObservableArray which has been mapped initially with KO's mapping plugin. But I don't get that working. Firebug is telling me "TypeError: totalChf is not a function". Looking at the added Observable, I notice that the Computed functions were not created. I've tried several methods, still without success... What am I missing?
Thanks in advance
Here the code:
var vm;
var ClientsMapping = {
create: function (options) {
var client = ko.mapping.fromJS(options.data, ContainersMapping)
//Some computed observables for level one here...
return client;
}
}
var ContainersMapping = {
'Containers': {
create: function (options) {
var container = ko.mapping.fromJS(options.data, MoneyAccountsMapping)
container.totalChf = ko.computed(function () {
var total = 0;
$.each(container.MoneyAccounts(), function () {
if (this.Currency() == "CHF") {
total += this.Amount();
}
})
return total;
})
//Some computed observables for level two here...
return container;
}
}
}
var MoneyAccountsMapping = {
'MoneyAccounts': {
create: function (options) {
var macc = new MoneyAccountModel(options.data)
//Some computed observables for level three here...
return macc;
}
}
}
var ClientModel = function (data) {
ko.mapping.fromJS(data, {}, this);
}
var ContainerModel = function (data) {
ko.mapping.fromJS(data, {}, this);
}
var MoneyAccountModel = function (data) {
ko.mapping.fromJS(data, {}, this);
}
var data = [
{
'Clients': 'Thomas',
'Containers': [
{
'ContName': 'Cont01',
'MoneyAccounts': [
{ Currency: "CHF", Amount: 1000 },
]
}
]
},
{
'Clients': 'Ann',
'Containers': [
{
'ContName': 'Cont01',
'MoneyAccounts': [
{ Currency: 'CHF', Amount: 1000 },
{ Currency: 'EUR', Amount: 500 }
]
}
]
}
]
function viewModel() {
var self = this;
self.clients = ko.observableArray()
self.clientsCount = ko.computed(function () {
return self.clients().length
})
}
$(function () {
vm = new viewModel();
vm.clients(ko.mapping.fromJS(data, ClientsMapping)());
var cont1 = {
'ContName': 'ContXX',
'MoneyAccounts': [
{ Currency: "XXX", Amount: 1000 },
]
};
var cont2 = {
'ContName': 'ContYY',
'MoneyAccounts': [
{ Currency: "YYY", Amount: 1000 },
]
};
var cont3 = {
'ContName': 'ContZZ',
'MoneyAccounts': [
{ Currency: "ZZZ", Amount: 1000 },
]
};
var cont4 = {
'ContName': 'ContWW',
'MoneyAccounts': [
{ Currency: "WWW", Amount: 1000 },
]
};
vm.clients()[0].Containers.push(ko.mapping.fromJS(cont1, ContainersMapping));//Attempt1
vm.clients()[0].Containers.push(ko.mapping.fromJS(cont2));//Attempt2
vm.clients()[0].Containers.push(new ContainerModel(cont3));//Attempt3
vm.clients()[0].Containers.push(ko.mapping.fromJS([cont4], ContainersMapping)()[0]);//Attempt4
//...still no success.
})
You will generally want to keep your mappings independent of one another. A good way to do that is to define and perform the mappings within each class:
var ClientModel = function(data) {
var mapping = {
'Containers': {
create: function(options) {
var container = new ContainerModel(options.data)
//Some computed observables for level two here...
return container;
}
}
}
ko.mapping.fromJS(data, mapping, this);
}
Once this is organized, you have a couple options for adding a new item to the observableArray:
Create the new item and push it to the array:
vm.clients()[0].Containers.push(new ContainerModel(cont1))
Specify a key option in your mapping:
var mapping = {
'Containers': {
key: function(item) {
return ko.unwrap(item.ContName);
},
create: function(options) {
var container = new ContainerModel(options.data)
//Some computed observables for level two here...
return container;
}
}
}
Then use mappedCreate to add the item:
vm.clients()[0].Containers.mappedCreate(cont1);
JSFiddle

how to dynamically generate model names in a service

I have a service that i am using to apply a unique number to a model name. the result i am getting is this
"sectionInputs": [
{
"model": "price_min1"
},
{
"model": "price_max2"
},
{
"model": "units_occ3"
},
{
"model": "inv_mod4"
},
{
"model": "inv_fin5"
},
{
"model": "inv_vdl6"
},
{
"model": "inv_uc7"
},
{
"model": "inv_fut8"
},
{
"model": "inv_con9"
},
{
"model": "units_total10"
}
]
i need this to each have '1'. and then in the next object array i need them to have '2', etc... as of now every object array looks like this. i have a plunker with everything setup.
plunker
function sectionInputSvc(sectionInputs) {
var vm = this;
vm.sectionInputsArry = sectionInputs;
vm.sectionInputs = function () {
var arry = [];
var counter = 0;
for (var i = 0; i < vm.sectionInputsArry.length; i++) {
counter++
var obj = {
model: vm.sectionInputsArry[i].model + counter
};
arry.push(obj);
};
return arry;
};
};
[EDIT 2]
in app.js...
sections[i].sectionInputs = sectionInputSvc.sectionInputs(sections[i],i);
and in section.service.js...
function sectionInputSvc(sectionInputs) {
var vm = this;
vm.sectionInputsArry = sectionInputs;
var obj2={};
vm.sectionInputs = function (obj2,num) {
var arry = [];
var counter = 0;
for (var i = 0; i < vm.sectionInputsArry.length; i++) {
counter++
var obj = {
model: vm.sectionInputsArry[i].model + num
};
arry.push(obj);
};
return arry;
};
};
Using linq.js, and assuming the suffixes do not already exist on the names:
vm.sectionInputs = function () {
return Enumerable.From(vm.sectionInputsArry).Zip(Enumerable.ToInfinity(1),
"{ model: $.model+$$ }"
).ToArray();
};

Karma unit testing of controller using service. Ionic app

I'm new to Ionic and trying to test my controller which is using a service but keep getting 'undefined' with '$scope.order', '$scope.stock' and all the functions included in my controller. I have tested the service separately all tests passing there but can't get my head around controller testing. I'm sure I'm doing something wrong. Would be grateful is somebody could guide me a little bit.
controller.js
angular.module('shop.controllers', [])
.controller('StockCtrl', function($scope, Stock) {
$scope.stock = Stock.all();
$scope.order = Stock.order();
$scope.showTotalPrice = function() {
$scope.total = Stock.total();
}
$scope.addToBasket = function(chatId){
Stock.update(chatId);
Stock.updateBasket(chatId);
}
});
service.js
angular.module('shop.services', [])
.factory('Stock', function() {
var order = [];
var prices = [];
var total = 0;
var items = [{
id: 0,
name: "Black Shoes",
price: 50,
quantity: 7
},
{
id: 1,
name: "Blue Shoes",
price: 10,
quantity: 2
},
{
id: 2,
name: "Green Shoes",
price: 14,
quantity: 5
},
{
id: 3,
name: "Red Flip Flops",
price: 9,
quantity: 6
}
}];
return {
all: function() {
return items;
},
get: function(itemId) {
for (var i = 0; i < items.length; i++) {
if (items[i].id === parseInt(itemId)) {
return items[i];
}
}
return null;
},
update: function(itemId) {
for (var i = 0; i < items.length; i++) {
if (items[i].id === parseInt(itemId)) {
return items[i].quantity -= 1;
}
}
return null;
},
updateBasket: function(itemId) {
for (var i = 0; i < items.length; i++) {
if (items[i].id === parseInt(itemId)) {
order.push({name: items[i].name, price: items[i].price});
prices.push(items[i].price);
}
}
return null;
},
order: function() {
return order;
},
total: function() {
return total = eval(prices.join('+'));
},
};
});
controller.spec.js
describe('Controllers', function(){
var scope;
var control;
var StockMock;
beforeEach(module('shop.controllers'));
beforeEach(function(){
StockMock = jasmine.createSpyObj('Stock', ['all', 'get', 'update', 'updateBasket', 'order'])
inject(function($rootScope, $controller){
scope = $rootScope.$new();
control = $controller('StockCtrl', {$scope: scope, Stock: StockMock});
});
});
it("should have a scope variable defined", function() {
expect(scope).toBeDefined();
});
});
I think your problem is here:
beforeEach(module('shop.controllers'));
You should add the application module instead of controller module.
beforeEach(module('shop'));

Categories

Resources