Detect if an element has been resized via javascript? - javascript

Is it possible to simply add event listeners to certain elements to detect if their height or width have been modified? I'd like do this without using something intensive like:
$(window).resize(function() { ... });
Ideally, I'd like to bind to specific elements:
$("#primaryContent p").resize(function() { ... });
It seems like using a resize handler on the window is the only solution, but this feels like overkill. It also doesn't account for situations where an element's dimensions are modified programatically.

I just came up with a purely event-based way to detect element resize for any element that can contain children, I've pasted the code from the solution below.
See also the original blog post, which has some historical details. Previous versions of this answer were based on a previous version of the blog post.
The following is the JavaScript you’ll need to enable resize event listening.
(function(){
var attachEvent = document.attachEvent;
var isIE = navigator.userAgent.match(/Trident/);
var requestFrame = (function(){
var raf = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame ||
function(fn){ return window.setTimeout(fn, 20); };
return function(fn){ return raf(fn); };
})();
var cancelFrame = (function(){
var cancel = window.cancelAnimationFrame || window.mozCancelAnimationFrame || window.webkitCancelAnimationFrame ||
window.clearTimeout;
return function(id){ return cancel(id); };
})();
function resizeListener(e){
var win = e.target || e.srcElement;
if (win.__resizeRAF__) cancelFrame(win.__resizeRAF__);
win.__resizeRAF__ = requestFrame(function(){
var trigger = win.__resizeTrigger__;
trigger.__resizeListeners__.forEach(function(fn){
fn.call(trigger, e);
});
});
}
function objectLoad(e){
this.contentDocument.defaultView.__resizeTrigger__ = this.__resizeElement__;
this.contentDocument.defaultView.addEventListener('resize', resizeListener);
}
window.addResizeListener = function(element, fn){
if (!element.__resizeListeners__) {
element.__resizeListeners__ = [];
if (attachEvent) {
element.__resizeTrigger__ = element;
element.attachEvent('onresize', resizeListener);
}
else {
if (getComputedStyle(element).position == 'static') element.style.position = 'relative';
var obj = element.__resizeTrigger__ = document.createElement('object');
obj.setAttribute('style', 'display: block; position: absolute; top: 0; left: 0; height: 100%; width: 100%; overflow: hidden; pointer-events: none; z-index: -1;');
obj.__resizeElement__ = element;
obj.onload = objectLoad;
obj.type = 'text/html';
if (isIE) element.appendChild(obj);
obj.data = 'about:blank';
if (!isIE) element.appendChild(obj);
}
}
element.__resizeListeners__.push(fn);
};
window.removeResizeListener = function(element, fn){
element.__resizeListeners__.splice(element.__resizeListeners__.indexOf(fn), 1);
if (!element.__resizeListeners__.length) {
if (attachEvent) element.detachEvent('onresize', resizeListener);
else {
element.__resizeTrigger__.contentDocument.defaultView.removeEventListener('resize', resizeListener);
element.__resizeTrigger__ = !element.removeChild(element.__resizeTrigger__);
}
}
}
})();
Usage
Here’s a pseudo code usage of this solution:
var myElement = document.getElementById('my_element'),
myResizeFn = function(){
/* do something on resize */
};
addResizeListener(myElement, myResizeFn);
removeResizeListener(myElement, myResizeFn);
Demo
http://www.backalleycoder.com/resize-demo.html

Here is a jQuery plugin with watch and unwatch methods that can watch particular properties of an element. It is invoked as a method of a jQuery object. It uses built-in functionality in browsers that return events when the DOM changes, and uses setTimeout() for browsers that do not support these events.
The general syntax of the watch function is below:
$("selector here").watch(props, func, interval, id);
props is a comma-separated string of the properties you wish to
watch (such as "width,height").
func is a callback function, passed the parameters watchData, index, where watchData refers to an object of the form { id: itId, props: [], func: func, vals: [] }, and index is the index of the changed property. this refers to the changed element.
interval is the interval, in milliseconds, for setInterval() in browsers that do not support property watching in the DOM.
id is an optional id that identifies this watcher, and is used to remove a particular watcher from a jQuery object.
The general syntax of the unwatch function is below:
$("selector here").unwatch(id);
id is an optional id that identifies this watcher to be removed. If id is not specified, all watchers from the object will be removed.
For those who are curious, the code of the plugin is reproduced below:
$.fn.watch = function(props, func, interval, id) {
/// <summary>
/// Allows you to monitor changes in a specific
/// CSS property of an element by polling the value.
/// when the value changes a function is called.
/// The function called is called in the context
/// of the selected element (ie. this)
/// </summary>
/// <param name="prop" type="String">CSS Property to watch. If not specified (null) code is called on interval</param>
/// <param name="func" type="Function">
/// Function called when the value has changed.
/// </param>
/// <param name="func" type="Function">
/// optional id that identifies this watch instance. Use if
/// if you have multiple properties you're watching.
/// </param>
/// <param name="id" type="String">A unique ID that identifies this watch instance on this element</param>
/// <returns type="jQuery" />
if (!interval)
interval = 200;
if (!id)
id = "_watcher";
return this.each(function() {
var _t = this;
var el = $(this);
var fnc = function() { __watcher.call(_t, id) };
var itId = null;
if (typeof (this.onpropertychange) == "object")
el.bind("propertychange." + id, fnc);
else if ($.browser.mozilla)
el.bind("DOMAttrModified." + id, fnc);
else
itId = setInterval(fnc, interval);
var data = { id: itId,
props: props.split(","),
func: func,
vals: []
};
$.each(data.props, function(i) { data.vals[i] = el.css(data.props[i]); });
el.data(id, data);
});
function __watcher(id) {
var el = $(this);
var w = el.data(id);
var changed = false;
var i = 0;
for (i; i < w.props.length; i++) {
var newVal = el.css(w.props[i]);
if (w.vals[i] != newVal) {
w.vals[i] = newVal;
changed = true;
break;
}
}
if (changed && w.func) {
var _t = this;
w.func.call(_t, w, i)
}
}
}
$.fn.unwatch = function(id) {
this.each(function() {
var w = $(this).data(id);
var el = $(this);
el.removeData();
if (typeof (this.onpropertychange) == "object")
el.unbind("propertychange." + id, fnc);
else if ($.browser.mozilla)
el.unbind("DOMAttrModified." + id, fnc);
else
clearInterval(w.id);
});
return this;
}

Yes it is possible. You will have to track all of the elements on load and store it. You can try out the demo here. In it, you don't have to use any libraries, but I used jQuery just to be faster.
First thing first - Store their initial size
You can do that by using this method:
var state = []; //Create an public (not necessary) array to store sizes.
$(window).load(function() {
$("*").each(function() {
var arr = [];
arr[0] = this
arr[1] = this.offsetWidth;
arr[2] = this.offsetHeight;
state[state.length] = arr; //Store all elements' initial size
});
});
Again, I used jQuery just to be fast.
Second - Check!
Of course you will need to check if it has been changed:
function checksize(ele) {
for (var i = 0; i < state.length; i++) { //Search through your "database"
if (state[i][0] == ele) {
if (state[i][1] == ele.offsetWidth && state[i][2] == ele.offsetHeight) {
return false
} else {
return true
}
}
}
}
Simply it will return false if it has not been change, true if it has been change.
Hope this helps you out!
Demo: http://jsfiddle.net/DerekL/6Evk6/

Related

How to have the same Javascript Self Invoking Function Pattern running more that one time in paralel without overwriting values?

I am looking for an OOP solution to have the same object literal/class running in parallel or nested, without interfering with the variables of other instances.
I created a demo in jsfiddle with some buttons to show the desired functionality, I tried to pass an id to call the setTimeout actions independently, but I realize that all variables are also shared between different instances
(function( skillet, $, undefined ){
var t;
var timer_is_on = 0;
var startingPoint = 0;
var endingPoint = 99;
var timerLapse = 1000;
var timerMatrixIndex = 0;
var timerMatrix = [1000];
var callback;
function timedCount(timerLapse) {
console.log(timerMatrix[timerMatrixIndex]);
if (startingPoint > endingPoint || timer_is_on === 0) {
clearTimeout(t[timerId]);
skillet.stopCount();
} else {
document.getElementById("txt").value = startingPoint;
if (timerMatrixIndex+1 < timerMatrix.length) {
timerMatrixIndex++;
}
startingPoint++;
callback();
t = { timerId: setTimeout( function() {timedCount(timerMatrix[timerMatrixIndex]);} ,timerLapse) };
}
}
skillet.startCount = function(options) {
options = options || {};
startingPoint = options.startingPoint || 0;
endingPoint = options.endingPoint || 100;
timerMatrix = options.timerMatrix || [1000];//[ 25,25,50,50,50,100,100,100,150,150,200,200,300,400,500,600,700,800,1000 ];
timerId = options.timerId || 0;
callback = options.callback || function(){};
timerMatrixIndex= 0 ;
if (!timer_is_on) {
timer_is_on = 1;
timedCount(timerMatrix[0]);
}
}
skillet.stopCount = function() {
clearTimeout(t[timerId]);
timer_is_on = 0;
}
}( window.skillet = window.skillet || {}, jQuery ))
I am looking an elegant way, not just adding "myid" to all methods or properties, probably I should had created a class instead of an object literal but I am just learning Javascript OOP.
It's there a simple way to do it?
https://jsfiddle.net/phrad/ut4yde9p/1/
As in the example the "callback count myid" should be independent of the other instances using the default (no id included)
Right now every call nulls the parameters of the previous call

Backbone.js source code - eventsApi

The following is a snippet from the Backbone.js annotated source code:
var eventsApi = function(iteratee, events, name, callback, opts) {
var i = 0, names;
if (name && typeof name === 'object') {
if (callback !== void 0 && 'context' in opts && opts.context === void 0) opts.context = callback;
for (names = _.key(names); i < names.length; i++) {
events = eventsApi(iteratee, events, names[i], name[names[i]], opts);
}
} else if (name && eventSplitter.test(name)) {
for (names = name.split(eventSplitter); i < names.length; i++) {
events = iteratee(events, names[i], callback, opts);
}
} else {
events = iteratee(events, name, callback, opts);
}
return events;
};
The eventsApi function checks for events as objects, in order to handle jQuery-style event maps.
But why does it call eventsApi recursively as it iterates through all the events?
The iteratee in the above is the following:
var onApi = function(events, name, callback, options) {
if (callback) {
var handlers = events[name] || (events[name] = []);
var context = options.context, ctx = options.ctx, listening = options.listening;
if (listening) listening.count++;
handlers.push({callback: callback, context: context, ctx: context || ctx, listening: listening});
}
return events;
};
it registers an event for single attribute (name).
eventsApi checks if the second parameter is an object, in that case it calls itself to register events for all the properties of the object, for example the attributes object of a model. This is a common use of recursion.

javascript - how to take selected element from function and pass it through as a parameter of another function

I have been trying for days upon weeks with trying to build a personal library without jQuery for my school club, and so far I am hitting a rut when it comes to passing through an element or objects through to another function. The notation I am trying for is this :
CC(function(){
CC('id:wrapper').set('html','Hello World!');
});
That is my test code, and the library looks as it does below:
"use strict";
var CC = function () {
var args = arguments[0] || {};
if(typeof args === "object") {
args = args || {};
}
else if(typeof args === "function") {
args = arguments[0];
return window.onload = args;
}
else if(typeof args !== "object" || typeof args !== "function") {
var elem = get(args);
return elem;
}
};
CC({
//Can only be done once. Will return TypeError because '$' won't exist afterward
noConflict : function (name) {
name = new CC();
return name;
}
});
//The way to modify things
CC.mod = CC.prototype = {};
CC.extend = CC.mod.extend = function () {
var args = arguments[0] || {};
var target = get(args);
return target;
};
CC.mod.extend({
//Use psuedo types to set specific values (required)
set : function(type, value) {
return set(this.target, type, value);
}
});
//General get function to get selectors, generate functions, or return values
function get() {
var args = arguments[0] || {};
//Check if the argument is a function
//If it is, return the function on page load
if (typeof args === "function") {
return window.onload = args;
}
//Check argument type
if(typeof args !== "object") {
args = arguments[0];
return args;
}
else {
args = {};
return args;
}
//Check if args has an elem psuedo
if(args.indexOf("id:") > -1 || args.indexOf("class:") > -1 || args.indexOf("tag:") > -1) {
var target = args;
//Run id psuedo
if(target.indexOf("id:") > -1) {
target = target.replace('id:','');
console.log(target);
return document.getElementById(target);
}
//Run class psuedo
else if(target.indexOf("class:") > -1) {
target = target.replace('class:','');
console.log(target);
return document.getElementsByClassName(target);
}
//Run tag psuedo
else if(target.indexOf("tag:") > -1) {
target = target.replace('class:','');
console.log(target);
return document.getElementsByTagName(target);
}
}
//Check if args is not null
//If not, then return args value
if(args !== null) {
return args.value;
}
else {
return null;
}
}
//General function to set things for elements
function set(elem, property, value) {
//If the element provided is part of getting an element
//If it is, run the psuedo checker
if(elem.indexOf("id:") > -1 || elem.indexOf("class:") > -1 || elem.indexOf("tag:") > -1) {
elem = get(elem);
//Rerun the set() function to set properties and values
set(elem, property, value);
}
//If not, then run the type psuedo checker
else {
//Check if style
if(property.indexOf("css:") > -1 || property.indexOf("style:") > -1) {
//Check for the independent types
if(property.indexOf("css:") > -1) {
property = property.replace('css:','');
return elem.style[property] = value;
}
else if(property.indexOf("style:") > -1) {
property = property.replace('style:','');
return elem.style[property] = value;
}
}
//Check if attribute
else if(property.indexOf("attr:") > -1) {
property = property.replace('attr:','');
return elem.setAttribute(property, value);
}
//Check if html
else if(property.indexOf("html") > -1) {
return elem.innerHTML = value;
}
//To add more, just add another else if(condition...) {Code} statement
//Condition must be defined in psuedo selectors
//Condition must be property.indexOf("selector:" > -1)
//return statement must consist of a return value from the value parameter
}
}
I don't know how to get my methods to pass through correctly and I don't know how to get my methods to apply to the element in the CC('id:wrapper') code. I already have the 'psuedo selector' made to get rid of the id: code. Any help would be much appreciated!
You've posted quite some code which I wasn't able to get to work quickly, so I'm not sure if this will help you out.
The basic idea is that your CC method will always have to return an object with a set method. If there's no element with id="wrapper", you'll have to figure out a way to handle exceptions.
You can use bind to create a new function from an earlier defined function with a pre-bound this context and pre-filled in arguments.
A simplified example:
var CC = function(query) {
return {
set: set.bind(null, document.querySelector(query))
};
}
function set(element, attr, val) {
element.setAttribute(attr, val);
}
CC("input").set("placeholder", "I was set by js");
<input type="text" />
If you want to do more advanced binding of arguments, I'd suggest you google "Currying". With some code, you can make functions automatically return new functions when called with less arguments than needed.
What .bind does:
The bind method is defined in Function.prototype. You can call it on any function you've defined to create a new function.
The first argument that goes in to bind, is used as the this context in the newly created function. You could, for example, do:
var myDiv = document.querySelector("div");
var logText = function() {
console.log(this.innerText);
};
var logDivText = logText.bind(myDiv);
logText(); // Bound to window, logs undefined
logDivText(); // Bound to div, logs text
<div>Text in a div</div>
Any other arguments passed to bind, are automatically passed as arguments. For example:
var sum = function(a, b) {
return a + b;
};
var sum3 = sum.bind(null, 3); // we don't use this, so we don't define it
console.log(sum3(5)); // Prints 8

How do I invoke my function on keyup?

I have a JavaScript function that I want to fire once the user enters text inside an input element. Currently I can only see the function firing if I console.log it. How do I get it to fire using keyup method?
The relevant code is below.
var $ = function (selector) {
var elements = [],
i,
len,
cur_col,
element,
par,
fns;
if(selector.indexOf('#') > 0) {
selector = selector.split('#');
selector = '#' + selector[selector.length -1];
}
selector = selector.split(' ');
fns = {
id: function (sel) {
return document.getElementById(sel);
},
get : function(c_or_e, sel, par) {
var i = 0, len, arr = [], get_what = (c_or_e === 'class') ? "getElementsByClassName" : "getElementsByTagName";
if (par.length) {
while(par[I]) {
var temp = par[i++][get_what](sel);
Array.prototype.push.apply(arr, Array.prototype.slice.call(temp));
}
} else {
arr = par[get_what](sel);
}
return (arr.length === 1)? arr[0] : arr;
}
};
len = selector.length;
curr_col = document;
for ( i = 0; i < len; i++) {
element = selector[i];
par = curr_col;
if( element.indexOf('#') === 0) {
curr_col = fns.id(element.split('#'[1]));
} else if (element.indexOf('.') > -1) {
element = element.split('.');
if (element[0]) {
par = fns.get('elements', element[0], par);
for ( i =0; par[i]; i++) {
if(par[i].className.indexOf(element[1]> -1)) {
elements.push(par[i]);
}
}
curr_col = elements;
} else {
curr_col = fns.get('class', element[1], par);
}
} else {
curr_col = fns.get('elements', element, par);
}
}
return elements;
};
You need to bind your method to the keyup event on the page.
You could try
document.addEventListener('keyup', $)
Or assuming you have the input element as element you could do
element.addEventListener('keyup', $)
Your function will be passed the event which you could use to investigate the state of the element if you needed that information to trigger or not trigger things in the function.
Here's a quick sample where the function that get's run on keypress is changeColor.
var COLORS = ['red', 'blue','yellow', 'black']
var NCOLORS = COLORS.length;
function changeColor(ev) {
var div = document.getElementById('colored');
var colorIdx = parseInt(Math.random() * NCOLORS);
console.log(colorIdx);
var newColor = COLORS[colorIdx];
div.style.color = newColor
console.log("New color ", newColor)
}
document.body.addEventListener('keyup', changeColor)
Though I'm not using the event (ev), I like to show, in the code, that I expect that variable to be available.
See it in action here - http://codepen.io/bunnymatic/pen/yyLGXg
As a sidenote, you might be careful about calling your function $. Several frameworks (like jQuery) use that symbol and you may run into conflicts where you're overriding the global variable $ or where the framework overrides your version if it.

Super in Backbone

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)

Categories