Javascript prototypíng and ''this'' of instantiated object - javascript

here's a tricky one. I create a class...
App = function(o) {
var _app = this;
this.events = {
listeners : {
list : new Array(),
add : function(event, fn) {
if (! this.list[event]) this.list[event] = new Array();
if (!(fn in this.list[event]) && fn instanceof Function) this.list[event].push(fn);
if (_app.debug.get()) _app.events.dispatch('log.append','EVENTS:ADD:'+event);
},
remove : function(event, fn) {
if (! this.list[event]) return;
for (var i=0, l=this.list[event].length; i<l; i++) {
if (this.list[event][i] === fn) {
if (_app.debug.get()) _app.events.dispatch('log.append','EVENTS:REMOVE:'+event);
this.list[event].slice(i,1);
break;
}
}
}
},
dispatch : function(event, params) {
if (! this.listeners.list[event]) return;
for (var i=0, l=this.listeners.list[event].length; i<l; i++) {
if (_app.debug.get()) _app.events.dispatch('log.append','EVENTS:DISPATCH:'+event);
this.listeners.list[event][i].call(window, params);
}
}
};
};
and prototype more functionality later. Here's one;
App.prototype.connection = {
source : { 'default' : null },
types : new Array(),
pool : new Array(),
count : function() { return this.pool.length },
active : {
pool : new Array(),
count : function() { return this.pool.length },
add : function(o) { this.pool.push(o) },
remove : function(o) { this.pool.splice(this.pool.indexOf(o), 1); }
},
create : function(o) {
if (! o || ! o.exe) o.exe = this.source.default;
if (! o || ! o.type) o.type = 'xhr';
var c = new this.types[o.type];
App.events.dispatch('connection.created',c);
this.pool.push(c);
return c;
},
remove : function(o) {
App.events.dispatch('connection.removed',o);
this.pool.splice(this.pool.indexOf(o), 1);
},
abort : function(o) {
var i = this.pool.indexOf(o);
if (i===-1) return;
this.pool[i].abort();
}
};
then instantiate this into an object.
app = new App();
The problem is, I have a line called App.events.dispatch('connection.removed',o) which doesn't work. App needs to be the instantiation 'app' which ideally would be 'this', but this refers to App.prototype.connection. How do you get at the root in this case?
Thanks - Andrew

You cannot use the object literal approach to define the connection on the prototype, otherwise there's no way to access the App instance.
Note that when you are referencing App, you are referencing the constructor function, not the App instance. Also, this inside create for instance would not be working because this will point to the connection object, not the App instance either.
There are a few options:
function Connection(eventBus) {
this.eventBus = eventBus;
}
Connection.prototype = {
someFunction: function () {
this.eventBus.dispatch(...);
}
};
function App() {
// this.events = ...
//the instance could also be injected, but you would need to implement
//a setEventBus on the connection object, or simply do conn.eventBus = this;
this.connection = new Connection(this);
}
var App = new App();
Also, please note that all mutable values (e.g. objects) defined on the prototype will be shared across all instances. That's probably not what you want.
Also note that:
listeners : {
list : new Array()
Should be:
listeners : {
list : {}
An array is meant to have numeric indexes only, while a plain object is a better structure to use as a map.

Related

Avoid calling same javascript plugin multiple time for the same selector

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
}());

Vue optionMergeStrategies Methods has no access to 'this' keyword

I am trying to build a plugin for Vue.
My plugin has a custom method caller customMethod for every component, I want it to run on after the page is mounted/created.
In a simple way, this is working as I want but I am having trouble accessing this inside customMethod.
It logs 'undefined' when I am trying to console.log(this).
so how can I access this inside my customMethod ?
var defaultParms = Object.freeze({
start : function(){},
leave : function(){},
});
const myPlugin = {
install(Vue, options = []) {
var ref = Vue.util;
var extend = ref.extend;
var assets = Object.create(null);
extend(assets, defaultParms);
Vue.options.customMethod = assets;
// set option merge strategy
var strats = Vue.config.optionMergeStrategies;
if (strats) {
strats.customMethod = (parent, child, vm)=>{
if (!child) return parent;
if (!parent) return child;
var ret = Object.create(null);
extend(ret, parent);
for (var key in child) {
ret[key] = child[key];
}
return ret
};
}
Vue.mixin({
customMethod:{
start: function(){
console.log('hi') // log 'hi'
console.log(this.$appName) // log 'undefined'
}
},
created: function () {
if(this.$options.customMethod){
this.$options.customMethod.start && this.$options.customMethod.start();
}
}
});
Vue.prototype.$appName = 'vikash';
}
}
Vue.use(myPlugin)
new Vue().$mount('#app')
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app"></div>
This is part of how the context this works in JS. You need to set it with bind, call or apply:
this.$options.customMethod.start.call(this)

Ways to access an object that triggered a breakpoint?

In chrome console, I have set some breakpoints in code of the sources. Those are set in a class for which an instance of this class was instantiated at loadtime. :
var SearcherBlock = function(n) {
function t() {
n.apply(this, arguments);
this.CurrentData = null;
this.onInput = null;
this.SearchInput = document.getElementById(this.BlockUniqueID + "_SearchInput");
this.SearchResults = document.getElementById(this.BlockUniqueID + "_Search_Results");
this.SearchResultsTitle = document.getElementById(this.BlockUniqueID + "_ResultsTitle");
this.onDocumentClick = this.onDocumentClickHandler.bind(this);
this.MinSearchStringLength = SearchController.getMinSearchStringLength(this.Configuration.LanguagesFor2SymbolSearch)
}
var i = {
destroyed: !0
};
return $.extend(t.prototype, n.prototype),
t.prototype.activate = function() {
n.prototype.activate.call(this);
this.onInput = this.textChange.bind(this);
this.SearchInput && this.SearchInput.addEventListener("input", this.onInput, !1);
this.tooltipsInit()
}
t.prototype.textChange = function(n) {
var t = n.srcElement || n.target;
if (this.SearchResultsTitle.innerHTML) {
... }
}
}
So now when I am at a breakpoint in this class whose instance I would like to achieve is triggered in this class. I would like to figure out a way to access this instance, like window.something.something - how can I do this if possible?
The reason is that when I call the function hasText() by window.hasText(), it complains because all properties of the this that is being used in the function are undefined. Therefore, I would like to have a way of accessing the instantiated object.

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);

Backbone.js: Can't add the same model to a set twice

I have just started with backbone.js. And I'm having a problem in fetching the data from the server. Here's the response I'm getting from server.
[{
"list_name":"list1",
"list_id":"4",
"created":"2011-07-07 21:21:16",
"user_id":"123456"
},
{
"list_name":"list2",
"list_id":"3",
"created":"2011-07-07 21:19:51",
"user_key":"678901"
}]
Here's my javascript code...
// Router
App.Routers.AppRouter = Backbone.Router.extend({
routes: {
'': 'index'
},
initialize: function() {
},
index: function() {
var listCollection = new App.Collections.ListCollection();
listCollection.fetch({
success: function() {
new App.Views.ListItemView({collection: listCollection});
},
error: function() {
alert("controller: error loading lists");
}
});
}
});
// Models
var List = Backbone.Model.extend({
defaults: {
name: '',
id: ''
}
});
App.Collections.ListStore = Backbone.Collection.extend({
model: List,
url: '/lists'
});
// Initiate Application
var App = {
Collections: {},
Routers: {},
Views: {},
init: function() {
var objAppRouter = new App.Routers.AppRouter();
Backbone.history.start();
}
};
I get the error "Can't add the same model to a set twice" on this line in Backbone.js
if (already) throw new Error(["Can't add the same model to a set twice", already.id]);
I checked out the Backbone.js annotated and found out that the first model gets added to the collection but the second one gives this error. Why is this happening? Should I change something in the server side response?
Your List has id in its defaults property, which is making each instance have the same ID by default, and Backbone is using that to detect dupes. If your data uses list_id as the ID, you need to tell that to Backbone by putting idAttribute: 'list_id' inside your List class definition.
As an aside, I prefer to NOT duplicate type information in object attributes (and Backbone.js agrees on this point). Having consistent attribute names is what backbone expects and is easier to work with. So instead of having list_id and list_name, just use id, and name on all classes.
Use this fix to add models with same id.
When adding, use: collection.add(model,{unique: false})
var __hasProp = {}.hasOwnProperty,
__extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
Backbone.Collection = (function(_super) {
__extends(Collection, _super);
function Collection() {
return Collection.__super__.constructor.apply(this, arguments);
}
Collection.prototype.add = function(models, options) {
var i, args, length, model, existing;
var at = options && options.at;
models = _.isArray(models) ? models.slice() : [models];
// Begin by turning bare objects into model references, and preventing
// invalid models from being added.
for (i = 0, length = models.length; i < length; i++) {
if (models[i] = this._prepareModel(models[i], options)) continue;
throw new Error("Can't add an invalid model to a collection");
}
for (i = models.length - 1; i >= 0; i--) {
model = models[i];
existing = model.id != null && this._byId[model.id];
// If a duplicate is found, splice it out and optionally merge it into
// the existing model.
if (options && options.unique) {
if (existing || this._byCid[model.cid]) {
if (options && options.merge && existing) {
existing.set(model, options);
}
models.splice(i, 1);
continue;
}
}
// Listen to added models' events, and index models for lookup by
// `id` and by `cid`.
model.on('all', this._onModelEvent, this);
this._byCid[model.cid] = model;
if (model.id != null) this._byId[model.id] = model;
}
// Update `length` and splice in new models.
this.length += models.length;
args = [at != null ? at : this.models.length, 0];
Array.prototype.push.apply(args, models);
Array.prototype.splice.apply(this.models, args);
// Sort the collection if appropriate.
if (this.comparator && at == null) this.sort({silent: true});
if (options && options.silent) return this;
// Trigger `add` events.
while (model = models.shift()) {
model.trigger('add', model, this, options);
}
return this;
};
return Collection;
})(Backbone.Collection);
Backbone prevent us to insert the same model into one collection...
You can see it in backbone.js line 676 to line 700
if you really want to insert the same models into collection,just remove the code there
if(existing = this.get(model)){//here
...
}

Categories