Backbone.js - Object doesn't support property or method 'each' - javascript

I'm very inexperienced with Backbone, but have inherited another dev's code to maintain. I'm trying to add some new functionality to a model. Here's what I have so far:
var AfeDetailCollection = Backbone.Collection.extend(
{
model: AfeDetailModel,
getSubtotalUSD: function(){
return _.reduce(this.models, function(memo, model, index, list){
return memo + model.getExtCostUSD();
}, 0, this);
},
getSubtotalLocal: function () {
return _.reduce(this.models, function (memo, model, index, list) {
return memo + model.getExtCostLocal();
}, 0, this);
},
hasMixedCurrency: function () {
var currCode = '';
this.models.each(function (model) {
if (currCode == '')
// Identify the currencyCode of the first AfeDetail object in the collection
currCode = model.get('currencyCode');
else if (currCode != model.get('currencyCode'))
// The currencyCode has changed from prior AfeDetail in the collection
return true;
});
return false;
}
}
);
The hasMixedCurrency() function is what I've added. The two pre-existing functions work fine. What I'm trying to do is determine whether the collection of AfeDetail objects contains multiple currencyCode values (a property on the AfeDetail model). So I was simply trying to iterate through this collection until I find a change in the currencyCode and then return true.
In the javascript on my page, however, as soon as I try to check this...
if (this.collection.hasMixedCurrency()) {
...
...I get this: Error: Object doesn't support property or method 'each'
I'm obviously doing something wrong, probably something simple, but I'm just not seeing it.

It is look like this.models is a javascript array, not a backbone collection.
You can try with underscore's each method:
_.each(arr, function(val) {
});
Also your logic in loop looks wrong. Try this code:
hasMixedCurrency: function() {
return _.size(_.uniq(this.models, "currencyCode")) > 1;
}
jsFiddle
Edit: More performanced way
hasMixedCurrency: function () {
return _.find(this.models, function(v) { return v.currencyCode !== _.first(this.models).currencyCode; }) !== undefined;
}

Related

Replace value with API value in ng-repeat

I am listing a bunch of JSON objects to a view, but only it's category ID is included. Not the name, which I need to display. I have to make a separate $http call to process which items match. Values will not render to view. Code:
$scope.cardCategories = function(id) {
angular.forEach($scope.categoryFilter, function(category, index){
if (category.id == id) {
//console.log(category.name);
return category.name;
} else {
return;
}
})
}
value inside a simplified ng-repeat
<div ng-repeat="topic in latest">
{{cardCategories(topic.category_id)}}
</div>
I have also attempted to write this as a filter, but the value will not display. Console shows the matches. My suspicion is that I have to process the original latest array. Thanks.
.filter('cardCategories', function($rootScope, $state){
return function(catId) {
if (catId !== undefined && $rootScope.cardCategories) {
angular.forEach($rootScope.cardCategories.categories, function(category, index){
if (category.id == catId.category_id) {
return category.name;
} else {
return;
}
})
}
}
})
View:
{{topic.category_id | cardCategories}}
That return statement in the forEach callback function will not return a value from the $scope.cardCategories, it returns something from the callback you provided to forEach (and angular happily ignores it). Try something like this:
$scope.cardCategories = function(id) {
var i = $scope.categoryFilter.length; // assuming it's an array
while(i--) {
if($scope.categoryFilter[i].id === id) {
return $scope.categoryFilter[i].name;
}
}
};
Also -- there's no way to break (stop early) an angular.forEach loop, so for performance reasons it's better to avoid it if you're locating something inside an array/object.
You need to make sure the forEach loop doesn't do a return if the current iteration doesn't match the ID you passed in.
Take out the else condition from your if statement
$scope.cardCategories = function(id) {
angular.forEach($scope.categoryFilter, function(category, index){
if (category.id == id) {
return category.name;
}
});
};

Knockout.js: computed observable not updating as expected

Edit: Added code for function populateDropdown and function isSystemCorrect (see bottom)
Edit 2 I have narrowed it down a bit and the problem seems to arise in the arrayFilter function in the computed observable. This returns an empty array, no matter what I try. I have checked that self.testsuites() looks ok right before filtering, but the filtering still fails.
I have a problem with my computed observable, filteredTestsuites.
As you can see from the screendump, the testsuites observable is populated correctly, but the computed observable remains empty. I have also tried choosing another option than "Payment" from the dropdown menu, to see if this will trigger the observable, it did not.
I would think the computed observable would be updated every time self.testsuites() or self.dropdownSelected() was changed, but it doesnt seem to trigger on neither of them.
What am I doing wrong here?
I simply want to make the computed observable filter the testsuites after the chosen dropdown option, every time either of them change.
function ViewModel() {
var self = this;
// The item currently selected from a dropdown menu
self.dropdownSelected = ko.observable("Payment");
// This will contain all testsuites from all dropdown options
self.testsuites = ko.mapping.fromJS('');
// This will contain only testsuites from the chosen dropdown option
self.filteredTestsuites = ko.computed(function () {
return ko.utils.arrayFilter(self.testsuites(), function (testsuite) {
return (isSystemCorrect(testsuite.System(), self.dropdownSelected()));
});
}, self);
// Function for populating the testsuites observableArray
self.cacheTestsuites = function (data) {
self.testsuites(ko.mapping.fromJS(data));
};
self.populateDropdown = function(testsuiteArray) {
for (var i = 0, len = testsuiteArray().length; i < len; ++i) {
var firstNodeInSystem = testsuiteArray()[i].System().split("/")[0];
var allreadyExists = ko.utils.arrayFirst(self.dropdownOptions(), function(option) {
return (option.Name === firstNodeInSystem);
});
if (!allreadyExists) {
self.dropdownOptions.push({ Name: firstNodeInSystem });
}
}
};
}
$(document).ready(function () {
$.getJSON("/api/TestSuites", function (data) {
vm.cacheTestsuites(data);
vm.populateDropdown(vm.testsuites());
ko.applyBindings(vm);
});
}
Function isSystemCorrect:
function isSystemCorrect(system, partialSystem) {
// Check if partialSystem is contained within system. Must be at beginning of system and go
// on to the end or until a "/" character.
return ((system.indexOf(partialSystem) == 0) && (((system[partialSystem.length] == "/")) || (system[partialSystem.length] == null)));
}
As suggested in a comment - rewrite the cacheTestsuites method:
self.testsuites = ko.observableArray();
self.filteredTestsuites = ko.computed(function () {
return ko.utils.arrayFilter(self.testsuites(), function (testsuite) {
return (isSystemCorrect(testsuite.System(), self.dropdownSelected()));
});
});
self.cacheTestsuites = function (data) {
var a = ko.mapping.fromJS(data);
self.testsuites(a());
};
The only thing different here is the unwrapping of the observableArray from the mapping function.

Knockout custom computed looses validation rules

I've created a custom function as seen below. It works perfekt while storing data and updating the observable MetaData values etc, but it breaks when it comes to validation.
I am using Knockout validation and have been debugging for a few hours and what I THINK i found out is the fact that the validation is run twice and the second time, all the rules of my observable has dropped, so every time the observable is valid, since there are no rules. The later code is copied from the source code here: https://github.com/Knockout-Contrib/Knockout-Validation/blob/master/Dist/knockout.validation.js
Why are my custom function making the observable dropping the validation rules?
My custom function
ko.observable.fn.valueByKey = function (key) {
return ko.computed({
read: function () {
var md = ko.utils.arrayFirst(ko.unwrap(this), function (item) {
return item.Key() == key;
});
if (md === null) {
md = new MetaData({ Key: key });
this.push(md);
}
return md.Value();
},
write: function (value) {
var md = ko.utils.arrayFirst(ko.unwrap(this), function (item) {
return item.Key() == key;
});
md.Value(value);
}
}, this);
};
Code that runs twice
var h_obsValidationTrigger = ko.computed(function () {
var obs = observable(),
ruleContexts = observable.rules();
console.log(ruleContexts);
exports.validateObservable(observable);
return true;
});
Another important part of knockout validation js
addRule: function (observable, rule) {
observable.extend({ validatable: true });
//push a Rule Context to the observables local array of Rule Contexts
observable.rules.push(rule);
return observable;
},
UPDATE 1:
I've come up with a simple solution that almost seems to work.
ko.observable.fn.valueByKey = function (key) {
var md = ko.utils.arrayFirst(ko.unwrap(this), function (item) {
return item.Key() == key;
});
if (md === null) {
md = new MetaData({ Key: key });
this.push(md);
}
return md.Value;
}
When using this, I get validation message on the element, but error-count does not rise on my view model, so the viewmodel it self is still valid, even though i get validation error.

How to get instance of the "class" from JSON string?

Given that I have a class defined such as
(function () {
function Dummy(){
var toReturn ={
myProp : "asdf",
myFunc : myFunc
}
return toReturn;
function myFunc(){};
}
})();
how does one get an instance of the same type after
var dummy = new Dummy();
JSON.stringify(dummy);
so that I have myFunc still available on the type.
JSON.parse(JSON.stringify(dummy)) returns same shape of the object by not the same type.
NOTE: I am not asking about capability of JSON, but how do people deal with this in general. Do you hand roll your mapping mechanism so that after parsing from JSON you map it onto instance of the type, or if there is such functionality in some library, such as underscore.
I created a helper function that helps me do this, but would like to hear from others how do you deal with situation like this. As I put in comments, JSON comes over the wire, for which we have a type defined. To get the values from JSON in our type, we parse json, create instance of type and then apply map function below.
function map(fromObj, toObj) {
Object.keys(fromObj)
.forEach(function (key) {
if (typeof fromObj[key] != 'function') {
if (toObj.hasOwnProperty(key)) {
if (typeof fromObj[key] !== 'object') {
toObj[key] = fromObj[key];
} else {
map(fromObj[key], toObj[key]);
}
}
}
}
});
}
Note, Not certain about requirement , if this similar to what posed at Question. If off-topic , please post comment , will withdraw.
Piece was originally composed for this Question Organizing large javascript files [on hold] . With a json response , having "x" type of contents , could map returned object to new object , copying properties utilizing $.extend() .
Result would be new object having both properties and functions of returned data. At piece below, at completion of process , $.Pages begins as function , then type gets converted to object - though it could retain both function and object properties by including || {} at definition stage.
Functions within returned json objects could be called within .then() callback ; see console at jsfiddle , object init functions.
At conclusion , $.Pages object has properties of returned json , including access to functions . Based on a jsonp - type processing flow.
Piece is "frame" of a processing approach ; could extend to include other functionality
$(function() {
var dfd = new $.Deferred();
dfd.progress(function(msg) {
console.log(msg);
});
ProductPage = {
name : "ProductPage",
addToCartBtn: "#add-to-cart",
initName : function() {return dfd.notify(this.name)},
init: function() {
this.initName();
// ProductPage.initAddToCartPopup();
// ProductPage.initSidebar();
}
};
ContactPage = {
name : "ContactPage",
validateEmail : function (e) {return dfd.notify(e)},
initName : function() {return dfd.notify(this.name)},
init: function() {
this.initName();
// ProductPage.initAddToCartPopup();
// ProductPage.initSidebar();
}
};
var mods = function() {
return {"ContactPage" : ContactPage
, "ProductPage" : ProductPage };
};
$.Pages = function() {
$.when(mods())
.done(function(pages) {
$.Pages = pages;
});
return $.Pages
};
$.when($.Pages())
.then(function() {
$.each($.Pages, function(k, v) {
v.init();
})
});
console.log($.Pages)
});
jsfiddle http://jsfiddle.net/guest271314/60kv2439/1/ (see console)
basic approach
$p = {};
var queue = [];
var mods = ["dep1.json", "service1.json"];
var mod = function(m) {
queue.push(m);
if (queue.length === mods.length) {
$.each(queue, function(k, v) {
$p = $.extend(v, $p)
})
}
};
$.each(mods, function(k, v) {
$.getScript(v, function(script, status, jqxhr) {
console.log($p)
})
})

How to cancel/revert changes to an observable model (or replace model in array with untouched copy)

I have a viewModel with an observableArray of objects with observable variables.
My template shows the data with an edit button that hides the display elements and shows input elements with the values bound. You can start editing the data and then you have the option to cancel. I would like this cancel to revert to the unchanged version of the object.
I have tried clone the object by doing something like this:
viewModel.tempContact = jQuery.extend({}, contact);
or
viewModel.tempContact = jQuery.extend(true, {}, contact);
but viewModel.tempContact gets modified as soon as contact does.
Is there anything built into KnockoutJS to handle this kind of situation or am I best off to just create a new contact with exactly the same details and replace the modified contact with the new contact on cancel?
Any advice is greatly appreciated. Thanks!
There are a few ways to handle something like this. You can construct a new object with the same values as your current one and throw it away on a cancel. You could add additional observables to bind to the edit fields and persist them on the accept or take a look at this post for an idea on encapsulating this functionality into a reusable type (this is my preferred method).
I ran across this post while looking to solve a similar problem and figured I would post my approach and solution for the next guy.
I went with your line of thinking - clone the object and repopulate with old data on "undo":
1) Copy the data object into a new page variable ("_initData")
2) Create Observable from original server object
3) on "undo" reload observable with unaltered data ("_initData")
Simplified JS:
var _viewModel;
var _initData = {};
$(function () {
//on initial load
$.post("/loadMeUp", {}, function (data) {
$.extend(_initData , data);
_viewModel = ko.mapping.fromJS(data);
});
//to rollback changes
$("#undo").live("click", function (){
var data = {};
$.extend(data, _initData );
ko.mapping.fromJS(data, {}, _viewModel);
});
//when updating whole object from server
$("#updateFromServer).live("click", function(){
$.post("/loadMeUp", {}, function (data) {
$.extend(_initData , data);
ko.mapping.fromJS(data, {}, _viewModel);
});
});
//to just load a single item within the observable (for instance, nested objects)
$("#updateSpecificItemFromServer).live("click", function(){
$.post("/loadMeUpSpecificItem", {}, function (data) {
$.extend(_initData.SpecificItem, data);
ko.mapping.fromJS(data, {}, _viewModel.SpecificItem);
});
});
//updating subItems from both lists
$(".removeSpecificItem").live("click", function(){
//object id = "element_" + id
var id = this.id.split("_")[1];
$.post("/deleteSpecificItem", { itemID: id }, function(data){
//Table of items with the row elements id = "tr_" + id
$("#tr_" + id).remove();
$.each(_viewModel.SpecificItem.Members, function(index, value){
if(value.ID == id)
_viewModel.SpecificItem.Members.splice(index, 1);
});
$.each(_initData.SpecificItem.Members, function(index, value){
if(value.ID == id)
_initData.SpecificItem.Members.splice(index, 1);
});
});
});
});
I had an object that was complicated enough that I didn't want to add handlers for each individual property.
Some changes are made to my object in real time, those changes edit both the observable and the "_initData".
When I get data back from the server I update my "_initData" object to attempt to keep it in sync with the server.
Very old question, but I just did something very similar and found a very simple, quick, and effective way to do this using the mapping plugin.
Background; I am editing a list of KO objects bound using a foreach. Each object is set to be in edit mode using a simple observable, which tells the view to display labels or inputs.
The functions are designed to be used in the click binding for each foreach item.
Then, the edit / save / cancel is simply:
this.edit = function(model, e)
{
model.__undo = ko.mapping.toJS(model);
model._IsEditing(true);
};
this.cancel = function(model, e)
{
// Assumes you have variable _mapping in scope that contains any
// advanced mapping rules (this is optional)
ko.mapping.fromJS(model.__undo, _mapping, model);
model._IsEditing(false);
};
this.save = function(model, e)
{
$.ajax({
url: YOUR_SAVE_URL,
dataType: 'json',
type: 'POST',
data: ko.mapping.toJSON(model),
success:
function(data, status, jqxhr)
{
model._IsEditing(false);
}
});
};
This is very useful when editing lists of simple objects, although in most cases I find myself having a list containing lightweight objects, then loading a full detail model for the actual editing, so this problem does not arise.
You could add saveUndo / restoreUndo methods to the model if you don't like tacking the __undo property on like that, but personally I think this way is clearer as well as being a lot less code and usable on any model, even one without an explicit declaration.
You might consider using KO-UndoManager for this. Here's a sample code to register your viewmodel:
viewModel.undoMgr = ko.undoManager(viewModel, {
levels: 12,
undoLabel: "Undo (#COUNT#)",
redoLabel: "Redo"
});
You can then add undo/redo buttons in your html as follows:
<div class="row center-block">
<button class="btn btn-primary" data-bind="
click: undoMgr.undoCommand.execute,
text: undoMgr.undoCommand.name,
css: { disabled: !undoMgr.undoCommand.enabled() }">UNDO</button>
<button class="btn btn-primary" data-bind="
click: undoMgr.redoCommand.execute,
text: undoMgr.redoCommand.name,
css: { disabled: !undoMgr.redoCommand.enabled() }">REDO</button>
</div>
And here's a Plunkr showing it in action. To undo all changes you'll need to loop call undoMgr.undoCommand.execute in javascript until all the changes are undone.
I needed something similar, and I couldn't use the protected observables, as I needed the computed to update on the temporary values. So I wrote this knockout extension:
This extension creates an underscore version of each observable ie self.Comments() -> self._Comments()
ko.Underscore = function (data) {
var obj = data;
var result = {};
// Underscore Property Check
var _isOwnProperty = function (isUnderscore, prop) {
return (isUnderscore == null || prop.startsWith('_') == isUnderscore) && typeof obj[prop] == 'function' && obj.hasOwnProperty(prop) && ko.isObservable(obj[prop]) && !ko.isComputed(obj[prop])
}
// Creation of Underscore Properties
result.init = function () {
for (var prop in obj) {
if (_isOwnProperty(null, prop)) {
var val = obj[prop]();
var temp = '_' + prop;
if (obj[prop].isObservableArray)
obj[temp] = ko.observableArray(val);
else
obj[temp] = ko.observable(val);
}
}
};
// Cancel
result.Cancel = function () {
for (var prop in obj) {
if (_isOwnProperty(false, prop)) {
var val = obj[prop]();
var p = '_' + prop;
obj[p](val);
}
}
}
// Confirm
result.Confirm = function () {
for (var prop in obj) {
if (_isOwnProperty(true, prop)) {
var val = obj[prop]();
var p = prop.replace('_', '');
obj[p](val);
}
}
}
// Observables
result.Properties = function () {
var obs = [];
for (var prop in obj) {
if (typeof obj[prop] == 'function' && obj.hasOwnProperty(prop) && ko.isObservable(obj[prop]) && !ko.isComputed(obj[prop])) {
var val = obj[prop]();
obs.push({ 'Name': prop, 'Value': val });
}
}
return obs;
}
if (obj != null)
result.init();
return result;
}
This extension will save you writing duplicates of each of your observables and ignores your computed. It works like this:
var BF_BCS = function (data) {
var self = this;
self.Score = ko.observable(null);
self.Comments = ko.observable('');
self.Underscore = ko.Underscore(self);
self.new = function () {
self._Score(null);
self._Comments('');
self.Confirm();
}
self.Cancel = function () {
self.Pause();
self.Underscore.Cancel();
self.Resume();
}
self.Confirm = function () {
self.Pause();
self.Underscore.Confirm();
self.Resume();
}
self.Pause = function () {
}
self.Resume = function () {
}
self.setData = function (data) {
self.Pause();
self._Score(data.Score);
self._Comments(data.Comments);
self.Confirm();
self.Resume();
}
if (data != null)
self.setData(data);
else
self.new();
};
So as you can see if you have buttons on html:
<div class="panel-footer bf-panel-footer">
<div class="bf-panel-footer-50" data-bind="click: Cancel.bind($data)">
Cancel
</div>
<div class="bf-panel-footer-50" data-bind="click: Confirm.bind($data)">
Save
</div>
</div>
Cancel will undo and revert your observables back to what they were, as were save will update the real values with the temp values in one line

Categories

Resources