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);
Related
Here's my plugin code:
( function() {
this.Modal = function( selector, options ) {
// Define option defaults
var defaults = {
open: false
}
this.options = extendDefaults( defaults, options );
alert();
}
function extendDefaults( source, properties ) {
var property;
for ( property in properties ) {
if ( properties.hasOwnProperty( property ) ) {
source[ property ] = properties[ property ];
}
}
return source;
}
}() );
Simply I need a way to prevent call the plugin again for the SAME selector if it has already called.
To be more clear if i try to initialize the plugin by doing this:
var firstSeelctor = new Modal( '.button' );
var secondSeelctor = new Modal( '.button' );
I need to call the first one and ignore the second one because it's already called for the same selector at the first one.
You need to store selectors somewhere (directly in the function constructor for example) that you have already created and then check it during every instance creation.
(function() {
this.Modal = function modal(selector, options) {
if (!modal.instances) {
modal.instances = {};
}
if (modal.instances[selector]) {
return modal.instances[selector];
}
modal.instances[selector] = this;
var defaults = {
open: false
};
this.options = extendDefaults(defaults, options);
console.log('created for selector: ' + selector);
// alert();
}
function extendDefaults(source, properties) {
var property;
for (property in properties) {
if (properties.hasOwnProperty(property)) {
source[property] = properties[property];
}
}
return source;
}
var firstSeelctor = new Modal('.button');
var secondSeelctor = new Modal('.button');
var thirdSeelctor = new Modal('.button-2');
console.log(firstSeelctor === secondSeelctor); // true, because it's the same instances
}());
Here is the pseudo-code in question: https://jsfiddle.net/yzps2gef/40/
I'm trying to understand why I cannot access an object's properties directly in one scenario (see ISSUE #1 in comments) but I can in another scenario (see ISSUE #2 in comments). I'm failing to see the difference between the two. Thanks!
Here's the fiddle code:
window.DataStore = function () {
var url = new Url(),
filters = new Filters(),
orderBy,
orderByDir,
setOrderBy = function (x, y) {
orderBy = x;
orderByDir = y;
},
getOrderBy = function () {
return orderBy;
},
getOrderByDir = function () {
return orderByDir;
};
return {
url: url,
filters: filters,
orderBy: orderBy,
orderByDir: orderByDir,
setOrderBy: setOrderBy,
getOrderBy: getOrderBy,
getOrderByDir: getOrderByDir
};
};
window.Url = function () {
var get = function (ds) {
var url = 'xyz.php';
console.log(ds);
// ISSUE #1: These do not work. It results in: xyz.php?orderby=undefined&orderbydir=undefined.
// Why can't I access them directly like I do below with the dataStore.filters.someFilterOption?
url = url + '?orderby=' + ds.orderBy;
url = url + '&orderbydir=' + ds.orderByDir;
// These work when I use the "get" functions.
// url = url + '?orderby=' + ds.getOrderBy();
// url = url + '&orderbydir=' + ds.getOrderByDir();
return url;
}
return {
get: get
};
};
window.Filters = function () {
var someFilterOption = 0;
return {
someFilterOption: someFilterOption
};
};
window.Grid = function () {
var dataStore = new DataStore(),
doSearch = function () {
console.log(dataStore.url.get(dataStore));
},
render = function () {
doSearch();
// ISSUE #2: Why can I access this one directly but not the order bys?
if (dataStore.filters.someFilterOption) {
console.log('Why was I able to read this one (dataStore.filters.someFilterOption) directly and not have to have a getSomeFilterOption() function to read it? But when it comes to the orderBy and orderByDir above I cannot read them directly.');
}
}
return {
dataStore: dataStore,
render: render
};
};
window.MyReUsableGrid = function () {
var grid = new Grid(),
showSomeFilterOption = function () {
grid.dataStore.filters.someFilterOption = 1;
},
render = function () {
grid.render();
};
grid.dataStore.setOrderBy(4, 'asc');
return {
showSomeFilterOption: showSomeFilterOption,
render: render
};
};
// The Screen
var myGridScreen = new MyReUsableGrid();
myGridScreen.showSomeFilterOption();
myGridScreen.render();
Because when your object gets returned from the function this line gets evaluated:
orderBy: orderBy,
And as the variable orderBy isnt set yet it is actually:
orderBy: undefined
Now later you call setOrderBy and set the internal variable orderBy to a value which you can expose through the getter, but that doesnt get reflected to the objects property.
IMO the whole thing should be restructured so that the methods work with their context:
window.DataStore = () => ({
url: new Url(),
filters: new Filters(),
applyOrder(order, dir) {
this.orderBy = order;
this.orderByDir = dir;
},
});
That way you dont need getters at all.
I'm using gmail.js for some project. In the library, there is a function like this :
api.dom.compose = function(element) {
// stuff
}
api.dom.email = function(element) {
this.id = element;
var message_class_id = 'm' + this.id;
this.id_element = $('div.ii.gt div.a3s.aXjCH.' + message_class_id);
element = this.id_element.closest('div.adn');
this.$el = element;
return this;
};
$.extend(api.dom.email.prototype, {
body: function(body) {
var el = this.dom('body');
if (body) {
el.html(body);
}
return el.html();
},
from: function(email, name) {
var el = this.dom('from');
if (email) {
el.attr('email',email);
}
if (name) {
el.attr('name',name);
el.html(name);
}
return {
email: el.attr('email'),
name: el.attr('name'),
el: el
};
},
// more extended functions
});
// more functions on the api.dom object
return api;
In my code I'm using it like so :
var email = provider.dom.email(mId);
console.log(email);
The console.log is really surprising. I was expecting to see the functions from the $.extend section. In that place, the functions showing are those registered on the api.dom object ! email() itself, compose, and more.
I don't get at all why this is happening. Thanks ahead for any help.
It was the prototype that has been extended. The functions are available when creating an instance with new. So do a console.log(api.dom.email.prototype); or create a new instance with new.
var email = new provider.dom.email(mId);
console.log(email);
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.
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');
});
};