I've got a class that is basically a native Javascript Array, but it raises events when items are added or removed.
hb.extend( {
Classes: {
Collection: hbClass.inherit({
init: function (arr) {
// get the functions we want to retain
var _on = this.on,
_trigger = this.trigger,
_push = this.push,
_remove = this.remove,
_reset = this.reset,
_from = this.fromArray,
_watch = this.watch;
// Set the object up as an Array
this.__proto__ = Array.prototype;
// get the Array functions we want to use
this.arrPush = this.push;
// reapply the old functions
this.push = _push;
this.remove = _remove;
this.reset = _reset;
this.fromArray = _from;
this.on = _on;
this.trigger = _trigger;
this.watch = _watch;
if (arr && (arr.length && typeof arr !== "string")) this.fromArray(arr, true);
},
fromArray: function (arr, stopEvent) {
this.reset();
for (var i = 0, len = arr.length; i < len; i++) {
this.arrPush(arr[i]);
}
if (!stopEvent) this.trigger('change', this);
},
push: function () {
this.arrPush.apply(this, arguments);
this.trigger('add', this);
this.trigger('change', this);
return this;
},
remove: function (from, to) {
var rest = this.slice((to || from) + 1 || this.length);
this.length = from < 0 ? this.length + from : from;
this.arrPush.apply(this, rest);
this.trigger('remove', this);
this.trigger('change', this);
return this;
},
reset: function () {
this.length = 0;
this.trigger('change', this);
this.trigger('remove', this);
}
})
}
});
There may be better ways to do it, but it works for me.......except in IE.
In IE at the line this.arrPush.appy(this, arguments); under the push method, it hits a Stack Overflow error.
Specifically:
SCRIPT28: Out of stack space
But this does NOT occur in Firefox or Chrome.
Anyone have any advice?
EDIT
Trigger code:
this.hbClass.prototype.trigger = function(type, data, context) {
var listeners, handlers, i, n, handler, scope;
if (!(listeners = this.listeners)) {
return;
}
if (!(handlers = listeners[type])){
return;
}
for (i = 0, n = handlers.length; i < n; i++){
handler = handlers[i];
if (handler.method.call(
handler.context, this, type, data
)===false) {
return false;
}
}
return true;
}
The issue is probably this line:
this.__proto__ = Array.prototype;
as __proto__ is not supported in some versions of IE. It has been codified in the ES6 specification, but that isn't implemented in some versions of IE. I don't understand exactly how your code works, but the safe way to set a prototype is like this:
Working demo: http://jsfiddle.net/jfriend00/ff99G/
function myClass() {
// add new methods to this instance in the constructor
this.fromArray = function() {};
};
// become an array and get all its methods
myClass.prototype = Array.prototype;
var x = new myClass();
Here's an example of the kind of thing you're doing using .prototype that works in IE:
function log(msg) {
var result = document.getElementById("result");
var div = document.createElement("div");
div.innerHTML = msg;
result.appendChild(div);
}
function myClass() {
var _push = this.push;
this.count = function() {
return this.length;
}
this.trigger = function(type, name) {
var str = type;
if (name) {
str += ", " + name;
}
log(str);
}
this.push = function() {
var retVal = _push.apply(this, arguments);
this.trigger("change", "push");
return retVal;
}
};
// become an array and get all its methods
myClass.prototype = Array.prototype;
var x = new myClass();
x.push("foo");
x.push("whatever");
log(x.count());
Related
I am trying to understand how to work jQuery and other libraries. I would like to know how to create a selector with this format:
$("#selector").get();
By the moment, I am trying the next, but i don't know how to run internal functions (get(), set()):
var $ = (function() {
var jQuery = {
get: function() {
console.log("get() function!!");
return this;
},
set: function() {
console.log("set() function!!");
return this;
}
};
return function(el) {
return document.querySelector(el);
}
})();
I have read something about modular pattern design in JavaScript, but I don't understand all.
The way to make chainable functions, is to first and foremost create instances with the new keyword.
This can be done "automatically" by making sure the this value of the called function is an instance of itself, if not explicitly call it with new.
Then it's just a matter of returning the instance and using prototyped methods.
var $ = function(selector) {
if (! (this instanceof $) ) {
return new $(selector);
}
this.el = document.querySelectorAll(selector);
return this;
}
$.prototype.css = function(prop, val) {
this.el.forEach(function(element) {
element.style[prop] = val;
});
return this;
}
$('#test').css('color', 'red').css('font-size', '30px')
<div id="test">test</div>
const $ = function(selector) {
if (!(this instanceof $)) {
return new $(selector);
};
this.el = document.querySelectorAll(selector);
};
$.prototype.css = function(obj) {
this.el.forEach(function(element) {
element.style[Object.keys(obj)[0]] = Object.values(obj);
});
};
$.prototype.click = function(callback) {
this.el.forEach(function(element) {
element.addEventListener('click', callback, false);
});
};
jQuery this or $(selector) is array like [div, div] not object {el: [div, div]}, so its not using this.el to modified the elements, here simplified version
if (window.$ === undefined) window.$ = (function () {
var $, fun = {}, emptyArray = [];
function Z(dom, selector) {
var i, len = dom ? dom.length : 0;
for (i = 0; i < len; i++) this[i] = dom[i];
this.length = len;
this.selector = selector || '';
}
fun.Z = function (dom, selector) {return new Z(dom, selector);};
fun.init = function (selector, context) {
if (!selector) return fun.Z();
var dom = document.querySelectorAll(selector);
return fun.Z(dom, selector);
};
Z.prototype = {
splice: emptyArray.splice,
forEach: emptyArray.forEach,
html: function (str) {
return this.forEach(function (el) {
el.innerHTML = str;
});
},
css: function(obj, value){
if(typeof obj == 'object'){ // like: .css({background: 'red'})
for(var k in obj){
return this.forEach(function (el) {
el.style[k] = obj[k];
});
}
}
else{ // called: .css('background', 'red')
return this.forEach(function (el) {
el.style[obj] = value;
});
}
}
};
$ = function (sel, ctx) {return fun.init(sel, ctx); };
return $;
})();
<div class="test"> AAAA </div>
<div class="test"> BBBB </div>
<button onclick="$('.test').css({background: 'red'})">red</button>
<button onclick="$('.test').css('background', 'blue')">blue</button>
<br />
<button onclick="console.log($('.test'))">log to console</button>
This doesn't work.
var genericClickHandler = function () {
this.handlers = [];
if (console && console.log) {
console.log("this:", this);
console.log("event:", event);
}
};
genericClickHandler.addHandler = function (handlerSpec) {
this.handlers.push(handlerSpec);
return this;
};
genericClickHandler.executeHandler = function (handlerName) {
for (var i = 0; i < this.handlers.length; i++) {
if (handlerName === this.handlers[i][0]) {
this.handlers[i][1]();
}
}
return this;
};
It doesn't work because the addHandler can't see the this.handlers in genericClickHandler.
Anyway what I'm after is function that gets defined once, but has methods and properties. I want to be able to use the function with Google Maps like this:
heatmap.addListener("click", genericClickHandler)
circle.addListener("click", genericClickHandler)
polygons.addListener("click", genericClickHandler)
So in the first instance, it only reports the this and event object. However, I then want to write code which extends the genericClickHandler dynamically so that it can implement map-object-specific behaviour.
Here's an example of what I meant using an object rather than a function.
var genericClickHandler = {
handlers: []
};
genericClickHandler.addHandler = function (name, fn) {
this.handlers.push([name, fn]);
return this;
};
genericClickHandler.executeHandler = function (name) {
for (var i = 0, l = this.handlers.length; i < l; i++) {
if (this.handlers[i][0] === name) this.handlers[i][1]();
}
};
genericClickHandler.addHandler('click', function () {
console.log('hi');
});
genericClickHandler.addHandler('click', function () {
console.log('hallo again');
});
genericClickHandler.executeHandler('click'); // hi... hallo again
DEMO
if you want to create an object, here you can see 2 ways to do the same thing, javascript got multiple way to write the same things.
var genericClickHandler = function()
{
this.handlers = [];
this.addHandler = function (handlerSpec)
{
this.handlers.push(handlerSpec);
return this;
},
this.executeHandler = function (handlerName)
{
this.handlers[handlerName]();
return this;
}
};
//sample:
var tmp = new genericClickHandler();
console.log(tmp.handlers);
console.log(tmp.addHandler("TEST"));
Another way to write the same object, but more optimised : prototype will be stored once for each object
var genericClickHandler = function(){}
genericClickHandler.prototype =
{
handlers:[],
addHandler : function (handlerSpec)
{
this.handlers.push(handlerSpec);
return this;
},
executeHandler : function (handlerName)
{
this.handlers[handlerName]();
return this;
}
}
//sample:
var tmp = new genericClickHandler();
console.log(tmp.handlers);
console.log(tmp.addHandler("TEST"));
I have a sealed object with an array member on which I want to prevent direct pushes.
var myModule = (function () {
"use strict";
var a = (function () {
var _b = {},
_c = _c = "",
_d = [];
Object.defineProperty(_b, "c", {
get: function () { return _c; }
});
Object.defineProperty(_b, "d", {
get { return _d; }
});
_b.addD = function (newD) {
_d.push(newD);
};
Object.seal(_b);
return _b;
}());
var _something = { B: _b };
return {
Something: _something,
AddD: _b.addD
};
}());
myModule.Something.c = "blah"; // doesn't update = WIN!!
myModule.AddD({}); // pushed = WIN!
myModule.Something.d.push({}); // pushed = sadness
How can I prevent the push?
UPDATE:
Thanks for all the thoughts. I eventually need the JSON to send to the server. It looks like I might need to use an object for the array then figure out a way to generate and return the JSON needed, or change _something to use .slice(). Will play and report.
you could override the push method:
var _d = [];
_d.__proto__.push = function() { return this.length; }
and when you need to use it in your module, call Array.prototype.push:
_b.addD = function (newD) {
Array.prototype.push.call(_d, newD);
};
I haven't done any performance tests on this, but this certainly helps to protect your array.
(function(undefined) {
var protectedArrays = [];
protectArray = function protectArray(arr) {
protectedArrays.push(arr);
return getPrivateUpdater(arr);
}
var isProtected = function(arr) {
return protectedArrays.indexOf(arr)>-1;
}
var getPrivateUpdater = function(arr) {
var ret = {};
Object.keys(funcBackups).forEach(function(funcName) {
ret[funcName] = funcBackups[funcName].bind(arr);
});
return ret;
}
var returnsNewArray = ['Array.prototype.splice'];
var returnsOriginalArray = ['Array.prototype.fill','Array.prototype.reverse','Array.prototype.copyWithin','Array.prototype.sort'];
var returnsLength = ['Array.prototype.push','Array.prototype.unshift'];
var returnsValue = ['Array.prototype.shift','Array.prototype.pop'];
var funcBackups = {};
overwriteFuncs(returnsNewArray, function() { return []; });
overwriteFuncs(returnsOriginalArray, function() { return this; });
overwriteFuncs(returnsLength, function() { return this.length; });
overwriteFuncs(returnsValue, function() { return undefined; });
function overwriteFuncs(funcs, ret) {
for(var i=0,c=funcs.length;i<c;i++)
{
var func = funcs[i];
var funcParts = func.split('.');
var obj = window;
for(var j=0,l=funcParts.length;j<l;j++)
{
(function() {
var part = funcParts[j];
if(j!=l-1) obj = obj[part];
else if(typeof obj[part] === "function")
{
var funcBk = obj[part];
funcBackups[funcBk.name] = funcBk;
obj[part] = renameFunction(funcBk.name, function() {
if(isProtected(this)) return ret.apply(this, arguments);
else return funcBk.apply(this,arguments);
});
}
})();
}
}
}
function renameFunction(name, fn) {
return (new Function("return function (call) { return function " + name +
" () { return call(this, arguments) }; };")())(Function.apply.bind(fn));
};
})();
You would use it like so:
var myArr = [];
var myArrInterface = protectArray(myArr);
myArr.push(5); //Doesn't work, but returns length as expected
myArrInterface.push(5); //Works as normal
This way, you can internally keep a copy of the interface that isn't made global to allow your helper funcs to modify the array as normal, but any attempt to use .push .splice etc will fail, either directly, or using the .bind(myArr,arg) method.
It's still not completely watertight, but a pretty good protector. You could potentially use the Object.defineProperty method to generate protected properties for the first 900 indexes, but I'm not sure of the implications of this. There is also the method Object.preventExtensions() but I'm unaware of a way to undo this effect when you need to change it yourself
Thank you, dandavis!
I used the slice method:
var myModule = (function () {
"use strict";
var a = (function () {
var _b = {},
_c = _c = "",
_d = [];
Object.defineProperty(_b, "c", {
get: function () { return _c; }
});
Object.defineProperty(_b, "d", {
get { return _d.slice(); } // UPDATED
});
_b.updateC = function (newValue) {
_c = newValue;
};
_b.addD = function (newD) {
_d.push(newD);
};
Object.seal(_b);
return _b;
}());
var _something = { B: _b };
return {
Something: _something,
AddD: _b.addD
};
}());
myModule.Something.c = "blah"; // doesn't update = WIN!!
myModule.AddD({}); // pushed = WIN!
myModule.Something.d.push({}); // no more update = happiness
This allows me to protect from direct push calls enforcing some logic.
If I have this code
var node = function(n) {
var name = n;
var children = [];
var finished = false;
var failed = false;
this.getName = function() {
return name
};
this.downloadData = function(obj) {
};
this.getChildren = function() {
return children;
};
this.setChildren = function(c) {
Array.prototype.push.apply(children, c);
};
this.isFinished = function() {
return finished;
};
this.setFinished = function() {
finished = true;
}
this.isFailed = function() {
return failed;
}
this.setFailed = function() {
failed = true;
}
};
How can I convert this into an object like:
var a = new node("a");
var j = JSON.stringify(a);
result
{"name":"a","children":[],"finished":false,"failed":false}
thanks
This could be done by implementing the toJSON function.
If an object being stringified has a property named toJSON whose value
is a function, then the toJSON() method customizes JSON
stringification behavior: instead of the object being serialized, the
value returned by the toJSON() method when called will be serialized.
- Mozilla
eg:
var node = function(n) {
var name = n;
var children = [];
var finished = false;
var failed = false;
this.toJson = function toJson() {
return {"name":name ... };
}
}
You need object properties instead of variables.
So, instead of declaring var name = n;, you would declare this.name = n;. Which would make it look something like
var node = function(n) {
this.name = n;
this.children = [];
this.finished = false;
this.failed = false;
///other functions here
}
When I override the clone() method of a Backbone.Model, is there a way to call this overriden method from my implantation? Something like this:
var MyModel = Backbone.Model.extend({
clone: function(){
super.clone();//calling the original clone method
}
})
You'll want to use:
Backbone.Model.prototype.clone.call(this);
This will call the original clone() method from Backbone.Model with the context of this(The current model).
From Backbone docs:
Brief aside on super: JavaScript does not provide a simple way to call
super — the function of the same name defined higher on the prototype
chain. If you override a core function like set, or save, and you want
to invoke the parent object's implementation, you'll have to
explicitly call it.
var Note = Backbone.Model.extend({
set: function(attributes, options) {
Backbone.Model.prototype.set.apply(this, arguments);
...
}
});
You can also use the __super__ property which is a reference to the parent class prototype:
var MyModel = Backbone.Model.extend({
clone: function(){
MyModel.__super__.clone.call(this);
}
});
Josh Nielsen found an elegant solution for this, which hides a lot of the ugliness.
Just add this snippet to your app to extend Backbone's model:
Backbone.Model.prototype._super = function(funcName){
return this.constructor.prototype[funcName].apply(this, _.rest(arguments));
}
Then use it like this:
Model = Backbone.model.extend({
set: function(arg){
// your code here
// call the super class function
this._super('set', arg);
}
});
Working from the answers given by geek_dave and charlysisto, I wrote this to add this._super(funcName, ...) support in classes that have multiple levels of inheritance. It's worked well in my code.
Backbone.View.prototype._super = Backbone.Model.prototype._super = function(funcName) {
// Find the scope of the caller.
var scope = null;
var scan = this.__proto__;
search: while (scope == null && scan != null) {
var names = Object.getOwnPropertyNames(scan);
for (var i = 0; i < names.length; i++) {
if (scan[names[i]] === arguments.callee.caller) {
scope = scan;
break search;
}
}
scan = scan.constructor.__super__;
}
return scan.constructor.__super__[funcName].apply(this, _.rest(arguments));
};
A year later I've fixed some bugs and made things faster. Below is the code that I use now.
var superCache = {};
// Hack "super" functionality into backbone.
Backbone.View.prototype._superFn = Backbone.Model.prototype._superFn = function(funcName, _caller) {
var caller = _caller == null ? arguments.callee.caller : _caller;
// Find the scope of the caller.
var scope = null;
var scan = this.__proto__;
var className = scan.constructor.className;
if (className != null) {
var result = superCache[className + ":" + funcName];
if (result != null) {
for (var i = 0; i < result.length; i++) {
if (result[i].caller === caller) {
return result[i].fn;
}
}
}
}
search: while (scope == null && scan != null) {
var names = Object.getOwnPropertyNames(scan);
for (var i = 0; i < names.length; i++) {
if (scan[names[i]] === caller) {
scope = scan;
break search;
}
}
scan = scan.constructor.__super__;
}
var result = scan.constructor.__super__[funcName];
if (className != null) {
var entry = superCache[className + ":" + funcName];
if (entry == null) {
entry = [];
superCache[className + ":" + funcName] = entry;
}
entry.push({
caller: caller,
fn: result
});
}
return result;
};
Backbone.View.prototype._super = Backbone.Model.prototype._super = function(funcName) {
var args = new Array(arguments.length - 1);
for (var i = 0; i < args.length; i++) {
args[i] = arguments[i + 1];
}
return this._superFn(funcName, arguments.callee.caller).apply(this, args);
};
Then given this code:
var A = Backbone.Model.extend({
// className: "A",
go1: function() { console.log("A1"); },
go2: function() { console.log("A2"); },
});
var B = A.extend({
// className: "B",
go2: function() { this._super("go2"); console.log("B2"); },
});
var C = B.extend({
// className: "C",
go1: function() { this._super("go1"); console.log("C1"); },
go2: function() { this._super("go2"); console.log("C2"); }
});
var c = new C();
c.go1();
c.go2();
The output in the console is this:
A1
C1
A2
B2
C2
What's interesting is that class C's call to this._super("go1") scans the class hierarchy until it gets a hit in class A. Other solutions do not do this.
P.S. Uncomment the className entries of the class definitions to enable caching of the _super lookup. (The assumption is that these class names will be unique in the application.)
If you want just to call this._super(); without passing the function name as an argument
Backbone.Controller.prototype._super = function(){
var fn = Backbone.Controller.prototype._super.caller, funcName;
$.each(this, function (propName, prop) {
if (prop == fn) {
funcName = propName;
}
});
return this.constructor.__super__[funcName].apply(this, _.rest(arguments));
}
Better use this plugin:
https://github.com/lukasolson/Backbone-Super
I believe you can cache the original method (although not tested):
var MyModel = Backbone.Model.extend({
origclone: Backbone.Model.clone,
clone: function(){
origclone();//calling the original clone method
}
});
backbone._super.js, from my gists: https://gist.github.com/sarink/a3cf3f08c17691395edf
// Forked/modified from: https://gist.github.com/maxbrunsfeld/1542120
// This method gives you an easier way of calling super when you're using Backbone in plain javascript.
// It lets you avoid writing the constructor's name multiple times.
// You still have to specify the name of the method.
//
// So, instead of having to write:
//
// var Animal = Backbone.Model.extend({
// word: "",
// say: function() {
// return "I say " + this.word;
// }
// });
// var Cow = Animal.extend({
// word: "moo",
// say: function() {
// return Animal.prototype.say.apply(this, arguments) + "!!!"
// }
// });
//
//
// You get to write:
//
// var Animal = Backbone.Model.extend({
// word: "",
// say: function() {
// return "I say " + this.word;
// }
// });
// var Cow = Animal.extend({
// word: "moo",
// say: function() {
// return this._super("say", arguments) + "!!!"
// }
// });
(function(root, factory) {
if (typeof define === "function" && define.amd) {
define(["underscore", "backbone"], function(_, Backbone) {
return factory(_, Backbone);
});
}
else if (typeof exports !== "undefined") {
var _ = require("underscore");
var Backbone = require("backbone");
module.exports = factory(_, Backbone);
}
else {
factory(root._, root.Backbone);
}
}(this, function(_, Backbone) {
"use strict";
// Finds the next object up the prototype chain that has a different implementation of the method.
var findSuper = function(methodName, childObject) {
var object = childObject;
while (object[methodName] === childObject[methodName]) {
object = object.constructor.__super__;
}
return object;
};
var _super = function(methodName) {
// Keep track of how far up the prototype chain we have traversed, in order to handle nested calls to `_super`.
this.__superCallObjects__ || (this.__superCallObjects__ = {});
var currentObject = this.__superCallObjects__[methodName] || this;
var parentObject = findSuper(methodName, currentObject);
this.__superCallObjects__[methodName] = parentObject;
// If `methodName` is a function, call it with `this` as the context and `args` as the arguments, if it's an object, simply return it.
var args = _.tail(arguments);
var result = (_.isFunction(parentObject[methodName])) ? parentObject[methodName].apply(this, args) : parentObject[methodName];
delete this.__superCallObjects__[methodName];
return result;
};
// Mix in to Backbone classes
_.each(["Model", "Collection", "View", "Router"], function(klass) {
Backbone[klass].prototype._super = _super;
});
return Backbone;
}));
In the case that you don't know what the parent class is exactly (multiple inheritance or you want a helper function) then you can use the following:
var ChildModel = ParentModel.extend({
initialize: function() {
this.__proto__.constructor.__super__.initialize.apply(this, arguments);
// Do child model initialization.
}
});
With helper function:
function parent(instance) {
return instance.__proto__.constructor.__super__;
};
var ChildModel = ParentModel.extend({
initialize: function() {
parent(this).initialize.apply(this, arguments);
// Do child model initialization.
}
});
Pass the parent class as an option during instantiation:
BaseModel = Backbone.Model.extend({
initialize: function(attributes, options) {
var self = this;
this.myModel = new MyModel({parent: self});
}
});
Then in your MyModel you can call parent methods like this
this.options.parent.method();
Keep in mind this creates a retain cycle on the two objects. So to let the garbage collector do it's job you would need to manually destroy the retain on one of the objects when finished with it. If you're application is pretty large. I would encourage you to look more into hierarchal setups so events can travel up to the correct object.
2 functions below, one requires you pass in the function name, the other can "discover" which function we want the super version of
Discover.Model = Backbone.Model.extend({
_super:function(func) {
var proto = this.constructor.__super__;
if (_.isUndefined(proto[func])) {
throw "Invalid super method: " + func + " does not exist in prototype chain.";
}
return proto[func].apply(this, _.rest(arguments));
},
_superElegant:function() {
t = arguments;
var proto = this.constructor.__super__;
var name;
for (name in this) {
if (this[name] === arguments.callee.caller) {
console.log("FOUND IT " + name);
break;
} else {
console.log("NOT IT " + name);
}
}
if (_.isUndefined(proto[name])) {
throw "Super method for: " + name + " does not exist.";
} else {
console.log("Super method for: " + name + " does exist!");
}
return proto[name].apply(this, arguments);
},
});
Here is how I would do this:
ParentClassName.prototype.MethodToInvokeName.apply(this);
so for your example this is:
Model.prototype.clone.apply(this)