Having an object, say Book which has a collection of other objects Page. So I instantiate pages from raw data passed to Book:
function Book(data){
this.pages = [];
var self = this;
data.forEach(function(item){
self.add(item);
});
}
Book.prototype.add = function(data){
this.pages.push(new Page(data));
}
function Page(data){
// some validation code
this.prop = data.prop;
}
Page.prototype...
from lectures on testability I heard that it is a bad practice to instantiate(use new) in another object. What is the right way to do the same?
If it is okay - is there any difference if I instantiate a new Page in add() method or pass to it as an object already(this.add(new Page(data)))?
The problem is when you want to write unit tests for you code.
Let's take an actual example. Some times later, your code is this one :
function Book(data){
this.pages = [];
var self = this;
data.forEach(function(item){
self.add(item);
});
}
Book.prototype.add = function(data){
var page = new Page(data);
// Because the page need to know its book
page.book = this;
this.pages.push(page);
}
function Page(data){
// some validation code
this.data = data;
}
Page.prototype...
Now you want to write an unit test for the add method, and you want to check that after you added a new page in your book, the page.book is the book. But with a code like that, you can't do it. Becase the page is created inside the method, you can't check anything.
Buf if we rewrite the code like that :
Book.prototype.add = function(page){
page.book = this;
this.pages.push(page);
}
We can now write a unit test :
describe('book.add', function() {
it('should set the current book in the book property of the page', function() {
var book = new Book(),
page = new Page();
book.add(page);
expect(page.book).toBe(book);
});
});
But, if you do not want to write any unit test like this one, there's no reason to not create the new page inside the method add.
Related
What I am trying to create is a basic Tic-Tac-Toe game using knockout.js. I am also using jade as a preprocessor; let me know if you are confused by it, but it should be pretty straight forward.
So I need to use a function on my objects in my foreach databind in my HTML, but I also need to have access to other objects in the viewModel. I am not quite sure how to do this. I can access the function if I put it on the objects in the foreach, but then I can't access objects outside of that object.
So my question is: How do I call a function from a foreach context that has access to everything on the viewModel?
Here is my code:
HTML
table.bg-success(style="table-layout:fixed;")
tr#row1(data-bind="foreach:topRow")
td(data-bind="text:symbol,click:function(){$parent.PlayerTurn.bind($root)();$root.changeSymbol()}")
tr#row2(data-bind="foreach:middleRow")
td(data-bind="text:symbol,click:function(){$parent.PlayerTurn.bind($root)();$root.changeSymbol()}")
tr#row3(data-bind="foreach:bottomRow")
td(data-bind="text:symbol,click:function(){$parent.PlayerTurn.bind($root)();$root.changeSymbol()}")
JAVASCRIPT
var aBox = (function(position){
function ABox(){
this.symbol = ko.observable("");
this.position = position
this.count = 0;
}
return ABox;
})()
var viewModel = (function(){
function ViewModel(){
this.theMessage = new message();
this.thePlayers = new players();
this.topRow = ko.observableArray([
new aBox("r1c1"),
new aBox("r1c2"),
new aBox("r1c3"),
]);
this.middleRow = ko.observableArray([
new aBox("r2c1"),
new aBox("r2c2"),
new aBox("r2c3"),
]);
this.bottomRow = ko.observableArray([
new aBox("r3c1"),
new aBox("r3c2"),
new aBox("r3c3"),
]);
}
ViewModel.prototype.changeSymbol = function(){
this.count+=1;
if(this.count%2==0){
this.symbol("O");
}else{
this.symbol("X")
}
}
return ViewModel;
})()
ko.applyBindings(new viewModel())
Get into the habit of placing all logic in the view model / javascript, not in the view. Also, read through the click binding handler documentation it actually answers most of your questions and is only a short read.
Basically, this:
td(data-bind="text:symbol, click: $root.changeSymbol")
Then you can have this function on $root:
var self = this;
self.changeSymbol = function(childVm) {
console.log(self); // is the $root
console.log(childVm); // the item from the foreach
}
If you need to access elements between the "child" and "root" (i.e. there lies a $parent in between) you should either:
Move the changeSymbol function to $parent and make sure the parent view model has a reference to its parent if you need to use that inside the function; or
Place a property for the parent on the child vm so that the function can call childVm.parent to get to that view model.
As a foot note, if you absolutely have to, you could do this:
td(data-bind="text:symbol, click: function() { $root.changeSymbol($root, $parent, $data); }")
But that probably means you have an XY-problem and your view models are not structured the way they should be.
Kindly help me to find the solution just wanted know how to export 2 functions into spec or in another js file. pls check my below code for ur reference. This is some agentdetails.js file. I want to call the below functions in spec(both the functions) some times i use only one
var AddAgent = function()
{
var AGP = require('D:/Automation/ServCloud/PageObjects/AgentDetailsObjects.js');
var Login = require('D:/Automation/ServCloud/Test DATA/TestData.json');
AGP.Agent.click();
AGP.AddAgentbtn.click();
AGP.Supervisor.click();
AGP.AgtSave.click();
}
module.exports = new AddAgent();
var EditAgent = function()
{
var AGP = require('D:/Automation/ServCloud/PageObjects/AgentDetailsObjects.js');
var Login = require('D:/Automation/ServCloud/Test DATA/TestData.json');
AGP.AgentEdit.click();
AGP.AgtSave.click();
browser.sleep(3000);
var alertDialog = browser.switchTo().alert(); }
module.exports = new EditAgent();
I have tried like this. (FYI - I can make this into one function and i can call it but i wanted to split into 2 functions and call both in one spec separately so that i can comment which ever is not required at times
it('Add Agent Details', function()
{
var aa = require('D:/Automation/ServCloud/Actions/AgentAction.js');
aa.AddAgent();
aa.EditAgent();
});
I don`t think it is possible,
You can do:
varr AggentFunctions = function() {
this.AddAgent = function(){
//some code
};
this.EditAgent = function(){
//some code
};
};
module.exports = AggentFunctions;
Then you can use it like this:
var agentsFuncs = require('yourAgentFile');
var agents = new agentsFuncs;
//and call what you want
agents.AddAgent();
agents.EditAgent();
I have a config.json that I am going to load into my app as a Backbone Model like:
var Config = Backbone.Model.extend({
defaults: {
base: ''
},
url: 'config.json'
});
Other models should be dependent on some data contained in Config like:
var ModelA = Backbone.Collection.extend({
initialize: function(){
//this.url should be set to Config.base + '/someEndpoint';
}
});
In above example, ModelA's url property is dependent on Config's base property's value.
How do I go about setting this up properly in a Backbone app?
As I see it, your basic questions are:
How will we get an instance of the configuration model?
How will we use the configuration model to set the dependent model's url?
How can we make sure we don't use the url function on the dependent model too early?
There are a lot of ways to handle this, but I'm going to suggest some specifics so that I can just provide guidance and code and "get it done," so to speak.
I think the best way to handle the first problem is to make that configuration model a singleton. I'm going to provide code from backbone-singleton GitHub page below, but I don't want the answer to be vertically long until I'm done with the explanation, so read on...
var MakeBackboneSingleton = function (BackboneClass, options) { ... }
Next, we make a singleton AppConfiguration as well as a deferred property taking advantage of jQuery. The result of fetch will provide always(callback), done(callback), etc.
var AppConfiguration = MakeBackboneSingleton(Backbone.Model.extend({
defaults: {
base: null
},
initialize: function() {
this.deferred = this.fetch();
},
url: function() {
return 'config.json'
}
}));
Now, time to define the dependent model DependentModel which looks like yours. It will call AppConfiguration() to get the instance.
Note that because of MakeBackboneSingleton the follow is all true:
var instance1 = AppConfiguration();
var instance2 = new AppConfiguration();
instance1 === instance2; // true
instance1 === AppConfiguration() // true
The model will automatically fetch when provided an id but only after we have completed the AppConfiguration's fetch. Note that you can use always, then, done, etc.
var DependentModel = Backbone.Model.extend({
initialize: function() {
AppConfiguration().deferred.then(function() {
if (this.id)
this.fetch();
});
},
url: function() {
return AppConfiguration().get('base') + '/someEndpoint';
}
});
Now finally, putting it all together, you can instantiate some models.
var newModel = new DependentModel(); // no id => no fetch
var existingModel = new DependentModel({id: 15}); // id => fetch AFTER we have an AppConfiguration
The second one will auto-fetch as long as the AppConfiguration's fetch was successful.
Here's MakeBackboneSingleton for you (again from the GitHub repository):
var MakeBackboneSingleton = function (BackboneClass, options) {
options || (options = {});
// Helper to check for arguments. Throws an error if passed in.
var checkArguments = function (args) {
if (args.length) {
throw new Error('cannot pass arguments into an already instantiated singleton');
}
};
// Wrapper around the class. Allows us to call new without generating an error.
var WrappedClass = function() {
if (!BackboneClass.instance) {
// Proxy class that allows us to pass through all arguments on singleton instantiation.
var F = function (args) {
return BackboneClass.apply(this, args);
};
// Extend the given Backbone class with a function that sets the instance for future use.
BackboneClass = BackboneClass.extend({
__setInstance: function () {
BackboneClass.instance = this;
}
});
// Connect the proxy class to its counterpart class.
F.prototype = BackboneClass.prototype;
// Instantiate the proxy, passing through any arguments, then store the instance.
(new F(arguments.length ? arguments : options.arguments)).__setInstance();
}
else {
// Make sure we're not trying to instantiate it with arguments again.
checkArguments(arguments);
}
return BackboneClass.instance;
};
// Immediately instantiate the class.
if (options.instantiate) {
var instance = WrappedClass.apply(WrappedClass, options.arguments);
// Return the instantiated class wrapped in a function so we can call it with new without generating an error.
return function () {
checkArguments(arguments);
return instance;
};
}
else {
return WrappedClass;
}
};
This may be a duplicate, and if so, I apologize. I've looked through a few questions and haven't found one that quite matches my situation (which maybe a bad sign to begin with).
I've got a class, say RandomClass, that is defined as follows
function RandomClass(id){
this._id = id;
}
RandomClass.prototype.getID = function(){
return this._id;
}
var rc = new RandomClass(1);
rc.getID(); //returns 1, as expected
Say I want to define a set of handlers, and keep them in a sub-object (while continuing to use prototype) of RandomClass. My knowledge of prototypes is somewhat limited, so apologies if this next bit is extremely bad form.
RandomClass.prototype.handlers = {};
RandomClass.prototype.handlers.HandlerOne = function(){
console.log("Handler one calling from ID: "+this._id);
//the context is not the context of RandomClass, but of RandomClass.prototype.handlers!
}
rc.handlers.HandlerOne(); //prints "Handler one calling from ID: unknown"
Again, maybe this is bad form, but I have several handlers which need to be called and doing things this way simplifies the code to:
var handler = "one of many many handlers returned from an ajax request";
rc.handlers[handler]();
So, my question is how do I make HandlerOne's context be the context of RandomClass rather than of handlers? I'd like to continue to use prototypes, because then they are not cloned multiple times (as in the following example):
function RandomClass(id){
this._id = id;
this._handlers = {};
}
function HandlerOne(){
console.log("Handler one calling from ID: "+this._id);
}
var rc = new RandomClass(1);
rc._handlers["HandlerOne"] = HandlerOne.bind(rc);
rc._handlers["HandlerOne"]() //prints as expected, but I believe performance is much worse here
Could satisfy to you do this, instead of bind the context try to pass it as a parameter.
function RandomClass(id){
this._id = id;
this._handlers = {};
}
function HandlerOne(instance){
var parentScope = instance;
console.log("Handler one calling from ID: "+parentScope._id);
}
//call it like this
var rc = new RandomClass(1);
rc._handlers["HandlerOne"] = HandlerOne;
rc._handlers["HandlerOne"](rc)
You could simply make Handlers it's own class. Note that you should not access private members from outside the class like I did in the exemple below. You must expose the correct public API to make objects work together without violating encapsulation.
function RandomClass(id){
this._id = id;
this.handlers = new Handlers(this);
}
function Handlers(randomClassInstance) {
this._randomClassInstance = randomClassInstance;
}
Handlers.prototype = {
constructor: Handlers,
handlerOne: function () {
console.log("Handler one calling from ID: "+ this._randomClassInstance._id);
}
};
Then you can do:
var rnd = new RandomClass('test');
rnd.handlers.handlerOne(); //Handler one calling from ID: test
Both answers submitted at this point are good alternatives (that I would say are acceptable), but I've decided to take another route (that lead to the least amount of modification to my code :)).
Similar to #BlaShadow's answer, rather than passing the context and setting a parentScope variable, I simply use Javascript's function.call() method to pass the correct context.
function RandomClass(id){
this._id = id;
}
function.prototype.handlers = {}
function.prototype.handlers.HandlerOne = function(data){
console.log("Handler one calling from ID: "+this._id+" with data: "+data);
}
var rc = new RandomClass(1);
rc.handlers.HandlerOne.call(rc, {"some": "data"});
//prints "Handler one calling from ID: 1 with data { "some" : "data" }
This is my first SO post. I'm eternally grateful for the information this community has and shares. Thanks.
I'm coming from Flash and I'm not even sure what the right question to ask is. All I can do is lay out my code example and then explain what I am trying to do. I do not fully grasp the terms that I am trying to illustrate here so I feel it is best to omit them.
The code below is incomplete as it only includes the parts that I feel are relevant to my question. Please refer to the comments in my code to see my issue.
EDIT: Full source file here: [link removed] The console.log outputs the issue in question.
<script type="text/javascript">
var a_chests = [];
var chestID = 0;
//I'm creating a plugin to be able to make multiple instances
(function ($) {
$.fn.chestPlugin = function (option) {
//This function creates a master sprite object which many of my sprites will use
//I've simplified the features to get to the heart of my question
var DHTMLSprite = function (params) {
var ident = params.ident,
var that = {
getID: function(){
return ident;
}
};
return that;
};
//ChestSprite inherits DHTMLSprites properties and then adds a few of its own
var chestSprite = function(params) {
var ident = params.ident,
that = DHTMLSprite(params);
that.reveal=function(){
console.log(ident);
};
return that;
};
//Here I create multiple instances of the chests
var treasure = function ( $drawTarget,chests) {
for (i=0;i<chests;i++){
var cs = chestSprite({
ident: "chest"+chestID
})
console.log(cs.reveal())
//This logs "chest0", "chest1", "chest2" as the for loop executes
//This behavior is correct and/or expected!
a_chests[chestID]={id:i,ob:cs};
//I add a reference to the new chestSprite for later
chestID++;
//increment the chestID;
}
console.log(a_chests[1].ob.reveal());
//This always logs "chest2" (the last chest that is created), even though
//the logs in the for loop were correct. It seems it is referencing the
//DHTML object (since the DHTMLSprite function returns that;) and since
//there is no reference to which chest I need, it passes the last one.
//Is there any way I can pass a reference to DHTMLSprite in order to retain
//the reference to the three individual chests that are created?
//Is there another solution altogether? Thanks!!!
};
//The rest of the code.
return this.each(function () {
var $drawTarget = $(this);
treasure($drawTarget,3);
});
};
})(jQuery);
</script>
You forgot to declare `that' as a local variable, so it's being overwritten on each iteration.
var chestSprite = function(params) {
var that;
var animInterval;
...
When you write:
a_chests[chestID]={id:i,ob:cs};
You are assigning the cs object itself, not an instance of this object. If later you modify cs, this will also modify what you stored in the ob property.
I guess what you need is a closure:
for (i=0;i<chests;i++){
(function(){
var cs = chestSprite({ident: "chest"+chestID});
a_chests[chestID]={id:i,ob:cs};
})();
}
This way, each loop creates a different cs object.