Jasmine test for an array keeps failing - arrays

I'm trying to write a jasmine test to check if data is entered into an array. I have the view model, the unit (jasmine) test file, and the stage file to set up the information for the unit tests. The error that I am getting is saying that my array is undefined. The error message says "Expected undefined to be {SiteId: 0, FirstName: 'Jon', LastName: 'Walker', ObjectState: 1}"
This is my viewmodel. I am trying to test self.addPatient:
var EF = (function () {
return {
ObjectState: {
Unchanged: 0,
Added: 1,
Modified: 2,
Deleted: 3
}
};
})();
var patientMapping = {
'Patients': {
key: function (patient) {
return ko.utils.unwrapObservable(patient.PatientId);
},
create: function (options) {
return new PatientViewModel(options.data);
}
}
};
PatientViewModel = function (data) {
var self = this;
ko.mapping.fromJS(data, {}, self);
self.flagPatientAsEdited = function () {
if (self.ObjectState() != ObjectState.Added) {
self.ObjectState(ObjectState.Modified);
}
return true;
},
self.FullName = ko.computed(function () {
return (self.FirstName() + " " + self.LastName());
});
}
SiteViewModel = function (data) {
var self = this;
ko.mapping.fromJS(data, patientMapping, self);
self.save = function () {
$.ajax({
url: "/Site/Save/",
type: "POST",
data: ko.toJSON(self),
contentType: "application/json",
success: function (data) {
if (data.siteViewModel != null) {
alert("Changes were saved successfully.");
ko.mapping.fromJS(data.siteViewModel, {}, self);
}
if (data.newLocation != null) {
window.location = data.newLocation;
}
}
});
},
self.flagSiteAsEdited = function () {
if (self.ObjectState() != ObjectState.Added) {
self.ObjectState(ObjectState.Modified);
}
return true;
},
self.addPatient = function () {
var patient = new PatientViewModel({ SiteId: 0, FirstName: "", LastName: "", ObjectState: ObjectState.Added });
self.Patients.push(patient);
},
self.deletePatient = function (patient) {
self.Patients.remove(this);
if (patient.PatientId() > 0 && self.PatientsToDelete.indexOf(patient.PatientId()) == -1) {
self.PatientsToDelete.push(patient.PatientId());
}
};
}
This is my stage file to initialize information for the jasmine tests:
//staged data for view model testing
var OBP = OBP || {};
OBP.Testing = (function () {
var reset = function () {
var isReadOnly = true;
window.isReadOnly = true;
window.ObjectState = 2;
window.FirstName = "Joe";
window.LastName = "Mitchell";
window.SiteId = 5;
window.patient = { "SiteId": 0, "FirstName": "Jon", "LastName": "Walker", "ObjectState": 1 };
window.patientDelete = { "PatientId": 1, "SiteId": 0, "FirstName": "Frank", "LastName": "Smith", "ObjectState": 1 };
};
return {
reset: reset
};
})();
OBP.Testing.reset();
This is my jasmine test itself that is failing:
/// <reference path="../../lib/jasmine.js"/>
/// <reference path="../../lib/mock-ajax.js" />
/// <reference path="../../jquery-2.1.3.min.js" />
/// <reference path="../../lib/jasmin-jquery.js" />
/// <reference path="../../bootstrap.js" />
/// <reference path="../../knockout-3.2.0.js" />
/// <reference path="../../knockout.mapping-latest.js" />
/// <reference path="SiteViewModelStage.js" />
/// <reference path="../../siteviewmodel.js" />
describe("Add patient check:", function () {
beforeEach(function () {
OBP.Testing.reset()
});
var Patients = new Array();
var viewModel = new SiteViewModel({ patient: window.patient });
it("Add patient works", function () {
expect(Patients[0]).toBe(patient);
});
});
Any help debugging my jasmine test would be greatly appreciated.

I made Patients and observable array in the viewModel:
self.Patients = ko.observableArray();
Edited addPatient in the viewModel:
self.addPatient = function (patient) {
self.Patients.push(patient);
},
and modified the "Add patient check" unit test:
describe("Add patient check:", function () {
beforeEach(function () {
OBP.Testing.reset()
});
var viewModel = new SiteViewModel({ patient: window.patient });
viewModel.addPatient(new PatientViewModel({ PatientId: 1, SiteId: 0, FirstName: "", LastName: "", ObjectState: ObjectState.Added }));
it("Add patient works", function () {
expect(viewModel.Patients().length == 1).toBe(true);
});
});
These changes to the viewmodel and unit test solved my problem. From here I am also able to check the name of the patient and do other tests to verify. The explanation from Xv in the comments helped lead me to this solution

Related

Class to stateless function React

I kind of rolled into React when stateless functions where popular so I never experienced the Class approach of it, which is bothering me now..
I'm not sure what this function does:
var keywordMapper = this.createKeywordMapper({
"constant.false": 'false',
"constant.true": 'true',
}, "identifier", true);
Which is called as:
this.$rules = {
"start": [{
token: "constant.numeric", // float
regex: "[+-]?\\d+(?:(?:\\.\\d*)?(?:[eE][+-]?\\d+)?)?\\b"
}, {
token: keywordMapper,
regex: "[a-zA-Z_$][a-zA-Z0-9_$]*\\b"
}, {
token: "keyword.operator",
regex: "\\+|\\-|\\/|\\/\\/|%|<#>|#>|<#|&|\\^|~|<|>|<=|=>|==|!=|<>|="
}]
};
I think it maps the input (what input :s) and returns either 'false', 'true' or 'identifier' as default.
But what if I want to use it within a stateless functional component? Since I can't use this in there.
Any help or explanation on how the 'this' function works is much appreciated.
Greetings,
edit:
The whole useEffect:
useEffect(() => {
const newCompleter = {
getCompletions(editor, session, pos, prefix, callback) {
callback(null, completions);
},
};
const keywordMapper = this.createKeywordMapper({
"constant.false": 'false',
"constant.true": 'true',
}, "identifier", true);
const completionString = completions.map((x) => x.value).join('|');
const session = editor.current.editor.getSession();
session.setMode(`ace/mode/text`, () => {
const rules = session.$mode.$highlightRules.getRules();
if (Object.prototype.hasOwnProperty.call(rules, 'start')) {
rules.start = [
{
token: 'constant.numeric', // float
regex: '[+-]?\\d+(?:(?:\\.\\d*)?(?:[eE][+-]?\\d+)?)?\\b',
},
{
token: keywordMapper(),
regex: '[a-zA-Z_$][a-zA-Z0-9_$]*\\b',
},
{
token: 'keyword.operator',
regex:
'\\+|\\-|\\/|\\/\\/|%|<#>|#>|<#|&|\\^|~|<|>|<=|=>|==|!=|<>|=',
},
];
}
// }
// force recreation of tokenizer
session.$mode.$tokenizer = null;
session.bgTokenizer.setTokenizer(session.$mode.getTokenizer());
// force re-highlight whole document
session.bgTokenizer.start(0);
});
// to extend existing
// addCompleter(myCompleter);
// to override all
setCompleters([newCompleter]);
}, [completions]);
Original class component
ace.define("ace/mode/brms_highlight_rules", ["require", "exports",
"module", "ace/lib/oop", "ace/mode/text_highlight_rules"], function(require, exports, module) {
"use strict";
var oop = require("../lib/oop");
var TextHighlightRules = require("./text_highlight_rules").TextHighlightRules;
var BrmsHighlightRules = function() {
var FalseBool = (
"false"
);
var TrueBool = (
"true"
);
var keywordMapper = this.createKeywordMapper({
"constant.false": 'false',
"constant.true": 'true',
}, "identifier", true);
this.$rules = {
"start": [{
token: "constant.numeric", // float
regex: "[+-]?\\d+(?:(?:\\.\\d*)?(?:[eE][+-]?\\d+)?)?\\b"
}, {
token: keywordMapper,
regex: "[a-zA-Z_$][a-zA-Z0-9_$]*\\b"
}, {
token: "keyword.operator",
regex: "\\+|\\-|\\/|\\/\\/|%|<#>|#>|<#|&|\\^|~|<|>|<=|=>|==|!=|<>|="
}]
};
this.normalizeRules();
};
oop.inherits(BrmsHighlightRules, TextHighlightRules);
exports.BrmsHighlightRules = BrmsHighlightRules;
});
ace.define("ace/mode/brms", ["require", "exports", "module", "ace/lib/oop", "ace/mode/text", "ace/mode/brms_highlight_rules"], function(require, exports, module) {
"use strict";
var oop = require("../lib/oop");
var TextMode = require("./text").Mode;
var BrmsHighlightRules = require("./brms_highlight_rules").BrmsHighlightRules;
var Mode = function() {
this.HighlightRules = BrmsHighlightRules;
this.$behaviour = this.$defaultBehaviour;
};
oop.inherits(Mode, TextMode);
(function() {
this.$id = "ace/mode/brms";
}).call(Mode.prototype);
exports.Mode = Mode;
});
(function() {
ace.require(["ace/mode/brms"], function(m) {
if (typeof module == "object" && typeof exports == "object" && module) {
module.exports = m;
}
});
})();

AngularJs + Jasmin : testing a function wchich does not return anything but calls another function for validation and a factory service

I am trying to test this createParameterGroup function which is calling a private function, When I try to test this createParameterGroup function, it gives an error saying that validateParameterGroup is not a function.
controller
angular.module('PpmApp')
.controller('parameterGroupListController', ['$scope', '$injector', 'parameterGroups', parameterGroupListController]);
function parameterGroupListController($scope, $injector, parameterGroups) {
$scope.createParameterGroup = function (parameterGroup) {
var validationErrors = validateParameterGroup(parameterGroup);
if (validationErrors.isError) return;
parameterGroupApiService.createParameterGroup(parameterGroup);
}
function validateParameterGroup(parameterGroup) {
var validationErrors = {};
validationErrors.isError = false;
// Validations goes here
return validationErrors;
}
};
Test
describe('createParameterGroup', function() {
var validationErrors, parameterGroup;
beforeEach(function() {
validationErrors = {};
validationErrors.isError;
parameterGroup = {
GroupName: "ABC",
Description: "ABC",
fromMonth: 1,
fromYear: 18,
toMonth: 12,
toYear: 18
}
});
it('should create a parameter group', function() {
expect($scope.createParameterGroup(parameterGroup)).toEqual(false);
});
});
After spending some time around how to write test cases right, I figured it out that I was doing expecting the wrong condition to evaluate the correct output. So this is how I have done it now.
Test Cases
describe('createParameterGroup', function() {
it('with blank name returns error message of property name can not be blank', function() {
var parameterGroup = {
name: "",
description: "sss",
fromMonth: 1,
fromYear: 18,
toMonth: 12,
toYear: 18
};
$scope.createParameterGroup(parameterGroup);
for (var property in parameterGroup) {
if (!parameterGroup[property] && property != 'description') {
var propertyName = (property == 'name') ? 'Parameter group name' : property;
}
return property;
}
expect($scope.createPopupInfo.validationErrors.isError).toEqual(true);
expect($scope.createPopupInfo.validationErrors[property]).toEqual(propertyName + ' ' + constantsProvider.validationMessages.blankField);
});
it('with special characters returns error message of invalid parameter group name', function() {
var parameterGroup = {
name: "/*&",
description: "ABC",
fromMonth: 1,
fromYear: 18,
toMonth: 12,
toYear: 18
};
$scope.createParameterGroup(parameterGroup);
expect($scope.createPopupInfo.validationErrors.isError).toEqual(true);
expect($scope.createPopupInfo.validationErrors.name).toEqual('Parameter group name \'' + parameterGroup.name + '\' ' + constantsProvider.validationMessages.specialCharacters);
});
it('with invalid effective time period returns error message of Invalid effective time period', function() {
var parameterGroup = {
name: "ABC",
description: "ABC",
fromMonth: 5,
fromYear: 18,
toMonth: 4,
toYear: 18
};
$scope.createParameterGroup(parameterGroup);
expect($scope.createPopupInfo.validationErrors.isError).toEqual(true);
expect($scope.createPopupInfo.validationErrors.toYear).toEqual(constantsProvider.validationMessages.effectiveTimePeriod);
});
it('with valid input returns the given input back without any error message', function() {
var parameterGroup = {
GroupName: "ABC",
Description: "sss",
EffectiveStartDateTime: 1 / 18,
EffectiveEndDateTime: 12 / 18
};
var createResponse = {};
createResponse.IsSuccess = true;
spyOn(parameterGroupApiService, 'createParameterGroup').and.callFake(function() {
return {
then: function(callback) {
return callback(createResponse);
}
}
});
spyOn(parameterGroupApiService, 'getParameterGroups').and.callFake(function() {
return {
then: function(callback) {
callback(parameterGroup);
return {
catch: function() {}
}
}
}
});
$scope.createParameterGroup(parameterGroup);
expect($scope.createPopupInfo.validationErrors.isError).toEqual(false);
expect(parameterGroupApiService.createParameterGroup).toHaveBeenCalled();
expect(parameterGroupApiService.getParameterGroups).toHaveBeenCalled();
expect($scope.parameterGroups).toBe(parameterGroup);
});
});

How to wrap a JS script inside an Angular directive

I am learning AngularJS and I have a conventional JS Script that I wrote a while ago and I would like to use it inside my new Angular app.
Can I literally just dump the entire script in side the directive or do I need to change some things like the keyword this to element etc...?
directive.directive("skillLevel", ['$timeout', function($timeout) {
return{
link: function(scope, el, atts){
// CAN I PASTE MY SCRIPT HERE??
}
}]);
I have this 'quite length some' script that I want to use. How would I go about effectivly using this inside my directive?
(function ($) {
'use strict';
var RSS = function (target, url, options, callback) {
this.target = target;
this.url = url;
this.html = [];
this.effectQueue = [];
this.options = $.extend({
ssl: false,
host: 'www.feedrapp.info',
limit: null,
key: null,
layoutTemplate: '<ul>{entries}</ul>',
entryTemplate: '<li>[{author}#{date}] {title}<br/>{shortBodyPlain}</li>',
tokens: {},
outputMode: 'json',
dateFormat: 'dddd MMM Do',
dateLocale: 'en',
effect: 'show',
offsetStart: false,
offsetEnd: false,
error: function () {
console.log('jQuery RSS: url doesn\'t link to RSS-Feed');
},
onData: function () {},
success: function () {}
}, options || {});
// The current SSL certificate is only valid for *.herokuapp.com
if (this.options.ssl && (this.options.host === 'www.feedrapp.info')) {
this.options.host = 'feedrapp.herokuapp.com';
}
this.callback = callback || this.options.success;
};
RSS.htmlTags = [
'doctype', 'html', 'head', 'title', 'base', 'link', 'meta', 'style', 'script', 'noscript',
'body', 'article', 'nav', 'aside', 'section', 'header', 'footer', 'h1-h6', 'hgroup', 'address',
'p', 'hr', 'pre', 'blockquote', 'ol', 'ul', 'li', 'dl', 'dt', 'dd', 'figure', 'figcaption',
'div', 'table', 'caption', 'thead', 'tbody', 'tfoot', 'tr', 'th', 'td', 'col', 'colgroup',
'form', 'fieldset', 'legend', 'label', 'input', 'button', 'select', 'datalist', 'optgroup',
'option', 'textarea', 'keygen', 'output', 'progress', 'meter', 'details', 'summary', 'command',
'menu', 'del', 'ins', 'img', 'iframe', 'embed', 'object', 'param', 'video', 'audio', 'source',
'canvas', 'track', 'map', 'area', 'a', 'em', 'strong', 'i', 'b', 'u', 's', 'small', 'abbr', 'q',
'cite', 'dfn', 'sub', 'sup', 'time', 'code', 'kbd', 'samp', 'var', 'mark', 'bdi', 'bdo', 'ruby',
'rt', 'rp', 'span', 'br', 'wbr'
];
RSS.prototype.load = function (callback) {
var apiProtocol = 'http' + (this.options.ssl ? 's' : '');
var apiHost = apiProtocol + '://' + this.options.host;
var apiUrl = apiHost + '?callback=?&q=' + encodeURIComponent(this.url);
// set limit to offsetEnd if offset has been set
if (this.options.offsetStart && this.options.offsetEnd) {
this.options.limit = this.options.offsetEnd;
}
if (this.options.limit !== null) {
apiUrl += '&num=' + this.options.limit;
}
if (this.options.key !== null) {
apiUrl += '&key=' + this.options.key;
}
$.getJSON(apiUrl, callback);
};
RSS.prototype.render = function () {
var self = this;
this.load(function (data) {
try {
self.feed = data.responseData.feed;
self.entries = data.responseData.feed.entries;
} catch (e) {
self.entries = [];
self.feed = null;
return self.options.error.call(self);
}
var html = self.generateHTMLForEntries();
self.target.append(html.layout);
if (html.entries.length !== 0) {
if ($.isFunction(self.options.onData)) {
self.options.onData.call(self);
}
self.appendEntriesAndApplyEffects($('entries', html.layout), html.entries);
}
if (self.effectQueue.length > 0) {
self.executeEffectQueue(self.callback);
} else if ($.isFunction(self.callback)) {
self.callback.call(self);
}
});
};
RSS.prototype.appendEntriesAndApplyEffects = function (target, entries) {
var self = this;
$.each(entries, function (idx, entry) {
var $html = self.wrapContent(entry);
if (self.options.effect === 'show') {
target.before($html);
} else {
$html.css({ display: 'none' });
target.before($html);
self.applyEffect($html, self.options.effect);
}
});
target.remove();
};
RSS.prototype.generateHTMLForEntries = function () {
var self = this;
var result = { entries: [], layout: null };
$(this.entries).each(function () {
var entry = this;
var offsetStart = self.options.offsetStart;
var offsetEnd = self.options.offsetEnd;
var evaluatedString;
// offset required
if (offsetStart && offsetEnd) {
if (index >= offsetStart && index <= offsetEnd) {
if (self.isRelevant(entry, result.entries)) {
evaluatedString = self.evaluateStringForEntry(
self.options.entryTemplate, entry
);
result.entries.push(evaluatedString);
}
}
} else {
// no offset
if (self.isRelevant(entry, result.entries)) {
evaluatedString = self.evaluateStringForEntry(
self.options.entryTemplate, entry
);
result.entries.push(evaluatedString);
}
}
});
if (!!this.options.entryTemplate) {
// we have an entryTemplate
result.layout = this.wrapContent(
this.options.layoutTemplate.replace('{entries}', '<entries></entries>')
);
} else {
// no entryTemplate available
result.layout = this.wrapContent('<div><entries></entries></div>');
}
return result;
};
RSS.prototype.wrapContent = function (content) {
if ($.trim(content).indexOf('<') !== 0) {
// the content has no html => create a surrounding div
return $('<div>' + content + '</div>');
} else {
// the content has html => don't touch it
return $(content);
}
};
RSS.prototype.applyEffect = function ($element, effect, callback) {
var self = this;
switch (effect) {
case 'slide':
$element.slideDown('slow', callback);
break;
case 'slideFast':
$element.slideDown(callback);
break;
case 'slideSynced':
self.effectQueue.push({ element: $element, effect: 'slide' });
break;
case 'slideFastSynced':
self.effectQueue.push({ element: $element, effect: 'slideFast' });
break;
}
};
RSS.prototype.executeEffectQueue = function (callback) {
var self = this;
this.effectQueue.reverse();
var executeEffectQueueItem = function () {
var item = self.effectQueue.pop();
if (item) {
self.applyEffect(item.element, item.effect, executeEffectQueueItem);
} else if (callback) {
callback();
}
};
executeEffectQueueItem();
};
RSS.prototype.evaluateStringForEntry = function (string, entry) {
var result = string;
var self = this;
$(string.match(/(\{.*?\})/g)).each(function () {
var token = this.toString();
result = result.replace(token, self.getValueForToken(token, entry));
});
return result;
};
RSS.prototype.isRelevant = function (entry, entries) {
var tokenMap = this.getTokenMap(entry);
if (this.options.filter) {
if (this.options.filterLimit && (this.options.filterLimit === entries.length)) {
return false;
} else {
return this.options.filter(entry, tokenMap);
}
} else {
return true;
}
};
RSS.prototype.getFormattedDate = function (dateString) {
// If a custom formatting function is provided, use that.
if (this.options.dateFormatFunction) {
return this.options.dateFormatFunction(dateString);
} else if (typeof moment !== 'undefined') {
// If moment.js is available and dateFormatFunction is not overriding it,
// use it to format the date.
var date = moment(new Date(dateString));
if (date.locale) {
date = date.locale(this.options.dateLocale);
} else {
date = date.lang(this.options.dateLocale);
}
return date.format(this.options.dateFormat);
} else {
// If all else fails, just use the date as-is.
return dateString;
}
};
RSS.prototype.getTokenMap = function (entry) {
if (!this.feedTokens) {
var feed = JSON.parse(JSON.stringify(this.feed));
delete feed.entries;
this.feedTokens = feed;
}
return $.extend({
feed: this.feedTokens,
url: entry.link,
author: entry.author,
date: this.getFormattedDate(entry.publishedDate),
title: entry.title,
body: entry.content,
shortBody: entry.contentSnippet,
bodyPlain: (function (entry) {
var result = entry.content
.replace(/<script[\\r\\\s\S]*<\/script>/mgi, '')
.replace(/<\/?[^>]+>/gi, '');
for (var i = 0; i < RSS.htmlTags.length; i++) {
result = result.replace(new RegExp('<' + RSS.htmlTags[i], 'gi'), '');
}
return result;
})(entry),
shortBodyPlain: entry.contentSnippet.replace(/<\/?[^>]+>/gi, ''),
index: $.inArray(entry, this.entries),
totalEntries: this.entries.length,
teaserImage: (function (entry) {
try {
return entry.content.match(/(<img.*?>)/gi)[0];
}
catch (e) {
return '';
}
})(entry),
teaserImageUrl: (function (entry) {
try {
return entry.content.match(/(<img.*?>)/gi)[0].match(/src="(.*?)"/)[1];
}
catch (e) {
return '';
}
})(entry)
}, this.options.tokens);
};
RSS.prototype.getValueForToken = function (_token, entry) {
var tokenMap = this.getTokenMap(entry);
var token = _token.replace(/[\{\}]/g, '');
var result = tokenMap[token];
if (typeof result !== 'undefined') {
return ((typeof result === 'function') ? result(entry, tokenMap) : result);
} else {
throw new Error('Unknown token: ' + _token + ', url:' + this.url);
}
};
$.fn.rss = function (url, options, callback) {
new RSS(this, url, options, callback).render();
return this; // Implement chaining
};
})(jQuery);
I think a better way would be to wrap that script in an angular module and load that module as a dependancy in your main app and use it anywhere you see fit.

My service is returning the function's text and not an object

I have a service to share an object in my app... I want to post that object to the mongo db but when I call the function that should return the object it gives me the function's text.
The service is here:
angular.module('comhubApp')
.service('markerService', function () {
this.markers = [];
this.newMarker = { title: '',
description: '',
lat: '',
lon: '',
user: '',
created_at: '' };
// This is supposed to return the marker object
this.newMarker = function () {
return this.newMarker;
};
this.setTitle = function (title) {
this.newMarker.title = title;
console.log('title service set: ' + title);
};
this.setDescription = function (description) {
this.newMarker.description = description;
console.log('Description service set: ' + description);
};
this.setLat = function (lat) {
this.newMarker.lat = lat;
console.log('lat service set: ' + lat);
};
this.setLon = function (lon) {
this.newMarker.lon = lon;
console.log('lon service set: ' + lon);
};
this.reset = function () {
this.newMarker = { title: '',
description: '',
lat: '',
lon: '',
user: '',
created_at: ''};
}
this.setMarkers = function (markers) {
this.markers = markers;
}
this.markers = function () {
return this.markers;
}
this.addMarker = function (marker) {
//todo append marker
}
});
newMarker returns:
this.newMarker = function () {
return this.newMarker;
};
The Controller using the service is here
$scope.addMarker = function() {
if($scope.newMarker.title === '') {
console.log('newMarker title is empty');
return;
}
markerService.setTitle($scope.newMarker.title);
markerService.setDescription($scope.newMarker.description);
console.log(markerService.newMarker());
// $http.post('/api/markers', { name: $scope.newMarker });
// $scope.newMarker = '';
};
$scope new marker is form data.. i tried to put that right into my service with no success. Instead I out the form data into the controller then push it to the service. If there is a better way to do that please let me know.
If this service is bad in any other way let me know I am new to all this and so I followed another answer I saw on here.
You are overriding your object with function. Just give them different names and it should work just fine.
this.newMarker = { ... };
this.getNewMarker = function () { return this.newMarker };
EDIT:
You should also always create new instance from marker. Otherwise you just edit the same object all the time. Here is example I made. Its not best practice but hope you get the point.
angular.module('serviceApp', [])
.factory('Marker', function () {
function Marker() {
this.title = '';
this.descrpition = '';
}
// use setters and getters if you want to make your variable private
// in this example we are not using these functions
Marker.prototype.setTitle = function (title) {
this.title = title;
};
Marker.prototype.setDescription = function (description) {
this.description = description;
};
return Marker;
})
.service('markerService', function (Marker) {
this.markers = [];
this.getNewMarker = function () {
return new Marker();
}
this.addMarker = function (marker) {
this.markers.push(marker);
}
})
.controller('ServiceCtrl', function ($scope, markerService) {
$scope.marker = markerService.getNewMarker();
$scope.addMarker = function () {
markerService.addMarker($scope.marker);
$scope.marker = markerService.getNewMarker();
}
$scope.markers = markerService.markers;
});
You could also create Marker in controller and use markerService just to store your object.
And working demo:
http://jsfiddle.net/3cvc9rrs/
So, that function is the problem. I was blindly following another example and it was wrong in my case. The solution is to remove that function and access markerService.newMarker directly.
I am still a big enough noob that I am not sure why the call was returning the function as a string. It seems to have something to do with how it is named but it is just a guess.

Knockout model breaking on array length in computed observable

Slowly extending my nested form at http://jsfiddle.net/gZC5k/1004/ I'm running into a difficulty getting to work ko.computed that I want to use to compute the number of children in the nested JSON array. The code that breaks is at // this breaks
self.contacts = ko.observableArray(ko.utils.arrayMap(contacts, function (contact) {
return {
firstName: ko.observable(contact.firstName),
lastName: ko.observable(contact.lastName),
isKey: ko.observable(contact.isKey),
gender: ko.observable(contact.gender),
phones: ko.observableArray(ko.utils.arrayMap(contact.phones, function (phone) {
return {
type: ko.observable(phone.type),
number: ko.observable(phone.number),
calls: ko.observableArray(phone.calls),
callsVisible: ko.observable(false)
};
})),
addresses: ko.observableArray(contact.addresses),
optionGender: optionGender,
phonesVisible: ko.observable(false),
addressesVisible: ko.observable(false),
// this breaks
// numberOfPhones: ko.computed(function (contact) {
// return contact.phones.length;
// });
};
}));
Where is the error?
I think that you're going to need to create each contact as a function:
var ContactModel = function(contact)
{
var self = this;
self.firstName = ko.observable(contact.firstName);
self.lastName = ko.observable(contact.lastName);
self.isKey = ko.observable(contact.isKey);
self.gender = ko.observable(contact.gender);
self. phones = ko.observableArray(ko.utils.arrayMap(contact.phones, function (phone) {
return {
type: ko.observable(phone.type),
number: ko.observable(phone.number),
calls: ko.observableArray(phone.calls),
callsVisible: ko.observable(false)
};
}));
self.addresses = ko.observableArray(contact.addresses);
self.optionGender = optionGender;
self.phonesVisible = ko.observable(false);
self.addressesVisible = ko.observable(false);
self.numberOfPhones = ko.computed(function () {
return self.phones().length;
});
return self;
};
And create it like this:
self.contacts = ko.observableArray(ko.utils.arrayMap(contacts, function (contact) {
return new ContactModel(contact);
}));
You can try this:
numberOfPhones: ko.computed(function () {
return contact.phones.length;
})

Resources