JS Prototype Multi-layer Inheritance - javascript

Why doesn't this work?
function Component(actor) {
this.actor = actor;
//...
}
Component.prototype.run = function() {
console.log("Inheritance Error: Component.run()");
};
function Intersection(actor) {
if (actor !== undefined) {
Component.call(this, actor);
...
}
}
Intersection.prototype = new Component();
Intersection.prototype.run = function() {
//...
};
function FloorIntersection(actor, list) {
Intersection.call(this, actor);
...
}
FloorIntersection.prototype = new Intersection();
var fi = new FloorIntersection(actor, list);
fi.run();
It gives me 'undefined is not a function' when I try calling: fi.run(). However if I do
var i = new Intersection(actor);
i.run();
Then it works.
It's unbelievably frustrating because I have the exact same structure elsewhere (ZachCharacterView -> Sprite -> Component) and it works just fine.

Related

Javascript method is not a constructor

at the company where Im at we use jquery and a lot of the code is very spaghetti haphazard code. So in an effort to organize it better im researching implementing the pub sub model described in this article
So I made a really basic version of it like so:
var topics = {};
jQuery.Topic = function( id ) {
var callbacks, method,
topic = id && topics[ id ];
if ( !topic ) {
callbacks = jQuery.Callbacks();
topic = {
publish: callbacks.fire,
subscribe: callbacks.add,
unsubscribe: callbacks.remove
};
if ( id ) {
topics[ id ] = topic;
}
}
return topic;
};
$(function() {
var testService = new TestService();
testService.subscribe();
var testView = new TestView(testService);
testView.initEvents();
});
/* ---------------------VIEW----------------- */
var TestView = function(testService) {
this.testService = testService;
};
TestView.prototype.initEvents = function () {
this.publishers();
};
TestView.prototype.publishers = function() {
$("#search").on("click", function () {
var isValid = this.testService.validateForm("#container");
if(isValid){
$.Topic( "search" ).publish();
}
})
};
/* ---------------------SERVICE----------------- */
var TestService = function() {
this.testIdea = [];
};
TestService.prototype.validateForm = function (section) {
var referralValid = true;
$(section).find('input,select').filter('[required]:visible').each(function (i, requiredField) {
if(requiredField.value === '') {
//'breaks' the loop out
referralValid = false;
return referralValid;
}
});
return referralValid;
};
TestService.prototype.search = function() {
};
TestService.prototype.subscribe = function() {
var self = this;
$.Topic("search").subscribe( function() {
self.search()
});
};
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<div>
<div id="container">
<input type="text">
</div>
<button id="search">Search</button>
</div>
However when I put that in jsfiddle I get the error that Uncaught TypeError: TestService is not a constructor
in the stackoverflow snippet and on my local version I get a different error of Uncaught TypeError: Cannot read property 'validateForm' of undefined. I cant see what Im doing wrong. Any pointers?
You can declare constructor functions in the way you are doing it (assigning constructor to variable):
var TestView = function(testService) {
this.testService = testService;
};
Like in this simple example:
var myClass = function(name) {
this.name = name;
}
myClass.prototype = {
hello: function() {
console.log('Hello ' + this.name);
}
}
var me = new myClass('Andrew');
me.hello();
But you must remember to declare them before they are used. If you use function statement(https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function) as suggested by Chad Watkins it helps only because of hoisting(http://adripofjavascript.com/blog/drips/variable-and-function-hoisting.html) not because of function statement being mandatory for constructors.
The error in your code is in line:
$("#search").on("click", function () {
var isValid = this.testService.validateForm("#container");
you are referencing jQuery object inside a callback not TestView instance, you probably wanted something like this(pun not intended):
...
var self = this;
$("#search").on("click", function () {
var isValid = self.testService.validateForm("#container");
...

AngularJS 1.4x how to extend a factory object

SOLUTION
thanks to hege-hegedus answer below. Applied it to my actual code and works great.
// NOTE : called from another service, but removed function wrap around and angular module setup for brevity sake
// article is serverObject
var articleInstance = new Article(article);
console.log(articleInstance instanceof Article)
// true
console.log(articleInstance.isProduct(article))
// true (in my case)
/*
* Create the constructor from server article object using lodash
*/
ArticleConstructor.$inject = [];
function ArticleConstructor() {
return function(data) {
var keys = ['app_id', 'body', 'headline', 'object_type', 'url', 'status'
];
_.assign(this, _.pick(data, keys));
};
}
/*
* Extend the iief constuctor - ArticleConstruct
*/
Article.$inject = ['ArticleConstructor'];
function Article(ArticleConstructor) {
function ArticleExtended(data) {
ArticleConstructor.call(this, data);
}
// create the new Article object with the ArticleConstructor prototype object and properties
ArticleExtended.prototype = Object.create(ArticleConstructor.prototype);
// Article inherits a constructor property from its prototype i.e. ArticleConstructor
ArticleExtended.prototype.constructor = ArticleExtended;
ArticleExtended.prototype.isProduct = function () {
return this.object_type == 3;
};
ArticleExtended.prototype.hasImage = function () {
return _.has(this, 'image');
};
return ArticleExtended;
}
How do I extend the factory object below. I'm using lodash to auto hydrate the factory constructor, which works great, but now none of my original methods execute e.g. isIcon() returns an error msg - "isIcon is not a function". I've searched for an answer but most constructor examples use the traditional return service; at the end of object, which works fine but then forces me back to more manual approach to building the constructor. I feel like I'm missing something obvious.
Using AngularJS 1.4.8
FACTORY OBJECT TO EXTEND
// AJS factory - the problem child
ImageUnableToExtendFn.$inject = ['IMG_TYPE'];
function ImageUnableToExtendFn(IMG_TYPE) {
Image.prototype.isIcon = function (img) {
return img.type === IMG_TYPE.ICON;
};
return function(data) {
var keys = ['id', 'src', 'alt', 'type'];
_.assign(this, _.pick(data, keys));
};
});
I've tried extending the IIEF factory with angular.extend(), but that doesn't work either (example below):
angular.extend(imageOfUnableToExtendFn, {
isIcon: function(img) {
return img.type === IMG_TYPE.ICON;
}
})
MORE DETAILED OF THE ABOVE FOR REFERENCE PURPOSES
define([
'angular',
'lodash'
], function(angular, _) {
'use strict';
ImageService.$inject = ['ImageClassicFn', 'ImageUnableToExtendFn'];
function ImageService(ImageClassicFn, ImageUnableToExtendFn) {
var imageService = {
images: null,
createInstance: function(serverImageObject) {
var self = this,
imageOfClassicFn,
imageOfUnableToExtendFn,
isIcon,
if (angular.isDefined(serverImageObject)) {
imageOfClassicFn = new ImageClassicFn();
isIcon = imageOfClassicFn.isIcon(serverImageObject);
console.log('IS ICON', isIcon);
// > true of false
imageOfUnableToExtendFn = new ImageUnableToExtendFn(serverImageObject);
// result is a hydrated instance of ImageClassicFn with mapped keys using lodash
isIcon = imageOfClassicFn.isIcon(serverImageObject);
// ERROR - isIcon is not a function
// Attempting to extend manually fails silently
angular.extend(imageOfUnableToExtendFn, {
isIcon: function(img) {
return img.type === IMG_TYPE.ICON;
}
})
isIcon = imageOfClassicFn.isIcon(serverImageObject);
// SAME ERROR - isIcon is not a function
}
}
};
return imageService;
}
ImageClassicFn.$inject = ['IMG_TYPE'];
function Image(IMG_TYPE) {
function Image(id, src, alt, type) {
this.id = id;
this.src = src;
this.alt = alt;
this.type = type;
}
Image.prototype.isIcon = function (img) {
return img.type === IMG_TYPE.ICON;
};
return Image;
});
ImageUnableToExtendFn.$inject = ['IMG_TYPE'];
function Image(IMG_TYPE) {
Image.prototype.isIcon = function (img) {
return img.type === IMG_TYPE.ICON;
};
return function(data) {
var keys = ['id', 'src', 'alt', 'type'];
_.assign(this, _.pick(data, keys));
};
});
return angular.module('content.images', [
])
.constant("IMG_TYPE", {
"ICON": 1,
})
.factory('ImageClassicFn', ImageClassicFn)
.factory('ImageUnableToExtendFn', ImageUnableToExtendFn)
.service('ImageService', ImageService);
});
Subclassing in javascript is a bit tricky. Take a look at this SO post about javascript inheritance.
Basically, this is how you usually do this, wrapped in angular 1.x modules:
ImageClassicFactory.$inject = ['IMG_TYPE'];
function ImageClassicFactory(IMG_TYPE) {
function ImageClassic(id, src, alt, type) {
this.id = id;
this.src = src;
this.alt = alt;
this.type = type;
}
ImageClassic.prototype.isIcon = function (img) {
return img.type === IMG_TYPE.ICON;
};
return ImageClassic;
});
module.factory('ImageClassic', ImageClassicFactory);
ImageExtendedFactory.$inject = ['IMG_TYPE', 'ImageClassic'];
function ImageExtendedFactory(IMG_TYPE, ImageClassic) {
function ImageExtended(id, src, alt, type) {
ImageClassic.call(this, id, src, alt, type);
}
ImageExtended.prototype = Object.create(ImageClassic.prototype);
ImageExtended.prototype.constructor = ImageExtended;
ImageExtended.prototype.isIcon = function (img) {
return img.type === IMG_TYPE.ICON;
};
return ImageExtended;
});
module.factory('ImageExtended', ImageExtendedFactory);

javascript const in javascript library

I am trying to write a small javascript library as shown below. What I really want is when I call
console.log(tnd().pv);
it should output same number and not generate new number everytime. I know the issue is it calls Math.random everytime I console log. But how can I do so that it outputs same number?
(function () {
var tnd = function() {
return new tnlib();
};
var tnlib = function() {
this.version = function(){
console.log('1.0');
};
this.pv = Math.random()*10000000000000000;
};
if(!window.tnd) {
window.tnd = tnd;
}
})();
Don't execute Math.random() on each invocation of tnlib, but as a static variable:
(function () {
function tnd() {
return new tnlib();
}
function tnlib() {
}
tnlib.prototype.version = function(){
console.log('1.0');
};
tnlib.prototype.pv = Math.random()*10000000000000000;
if (!window.tnd) {
window.tnd = tnd;
}
}());
(or, if you really need to make pv an instance property):
var staticPv = Math.random()*10000000000000000;
function tnlib() {
this.pv = staticPv;
…
}

How do I use a constructor in javascript

Here is my problem.
I am using Backbone js and every collection I have defined requires the same check on save or destroy. Except that the destroy success functions need to be passed an element to remove from the page when the destroy succeeds.
I didn't want to copy and paste the same code into every save or destroy method so I created this:
window.SAVE_TYPE_DESTROY = 'destroy';
window.SaveResponseHandler = function(el,type){
if (!type){
this.success = function() {
this._success();
};
}else if (type == window.SAVE_TYPE_DESTROY){
this.success = function() {
this._success();
$(el).remove();
};
}
};
SaveResponseHandler.prototype._success = function(model, response, options) {
if ((response.success * 1) === 0) {
persistError(model, {
responseText: response.message
}, {});
}
};
SaveResponseHandler.prototype.error = persistError;
var saveResponseHandler = new SaveResponseHandler();
And I use it like this:
destroy: function() {
var el = this.el;
var model = this.model;
this.model.destroy(new SaveResponseHandler(el,'destroy'));
},
change: function() {
this.model.set({
job_category_name: $($(this.el).find('input')[0]).val()
});
var viewView = this.viewView;
this.model.save(null, saveResponseHandler);
}
The problem is when success is called I get the following error:
Uncaught TypeError: Object [object Window] has no method '_success'
Any help will be much appreciated. I'm also open to any suggestions on better ways to handle this.
this inside of SaveResponseHandler.success isn't SaveResponseHandler, it's window.
window.SaveResponseHandler = function(el, type) {
var self = this;
if (!type) {
this.success = function() {
self._success();
};
} else if (type == window.SAVE_TYPE_DESTROY) {
this.success = function() {
self._success();
$(el).remove();
};
}
};
http://jsfiddle.net/ethagnawl/VmM5z/

javascript object composition syntax

In the following code, I want to be able to call bindClickEvents() like so:
App.Utils.Modal.bindClickEvents();
However, I don't understand the syntax necessary to do this.
Current code:
var App = new Object;
App.Modal = {
bindClickEvents: function() {
return $('a.alert-modal').click(function(e) {
return console.log('Alert Callback');
});
}
};
$(document).ready(function() {
return App.Modal.bindClickEvents();
});
You can do it in one go:
var App = {
Modal : {
bindClickEvents : function () {/* ... */}
}
}
or if you want to break that up to separate steps:
var App = {};
App.Modal = {};
Modal.bindClickEvents = function () {/* ... */};
BTW, in reference to your original question title, this is not object chaining. This is object composition. Object chaining is being able to call methods in an object multiple times in a single statement.
Is this what you're trying to do?
var App = {};
App.Utils = {};
App.Utils.Modal = {
bindClickEvents: function() {
return $('a.alert-modal').click(function(e) {
return console.log('Alert Callback');
});
}
};
$(document).ready(function() {
return App.Utils.Modal.bindClickEvents();
});
Prefer the object literal syntax to the Object constructor; some authors go so far as to call the latter an anti-pattern
Here's the simplest way to set up App.Utils.Modal.bindClickEvents();
var App = {
Utils: {
Modal: {
bindClickEvents: function() {
return $('a.alert-modal').click(function(e) {
return console.log('Alert Callback');
});
}
}
}
};
Or you can piece it together one step at a time:
var App = {};
App.Utils = {};
App.Utils.Modal = {};
App.Utils.Modal.bindClickEvents = function() {
return $('a.alert-modal').click(function(e) {
return console.log('Alert Callback');
});
};

Categories