Created
November 14, 2012 15:24
-
-
Save constantology/4072739 to your computer and use it in GitHub Desktop.
Backbone.View.$uper
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
;!function() { | |
"use strict"; | |
// fn is the super class' callSuper that has being curried with its __super__ prototype | |
// proto was curried in by $uper's custom extend method | |
function callNestedSuper( fn, proto/*, ...args*/ ) { | |
var args = Super._.toArray( arguments ).slice( 1 ), val1, val2; | |
if ( args[1] === this.__lastMethod__ ) // avoid possible recursion errors | |
return this; | |
val1 = getRetVal( this, callSuper.apply( this, args ) ); | |
val2 = getRetVal( this, fn.apply( this, args.slice( 1 ) ) ); | |
return val1 === this && val2 !== this | |
? val2 | |
: val1 !== this | |
? val1 | |
: this; | |
} | |
function callSuper( proto, method, args ) { | |
var fn = ( proto || empty )[method], val; | |
if ( Super._.isFunction( fn ) && method !== this.__lastMethod__ ) { // avoid possible recursion errors | |
this.__lastMethod__ = method; | |
val = fn.apply( this, args ); | |
delete this.__lastMethod__; | |
return val === UNDEF ? this : val; | |
} | |
return this; | |
} | |
function curry( fn ) { | |
var args = Super._.toArray( arguments ).slice( 1 ), fun; | |
fun = function curried() { | |
return fn.apply( this, args.concat( Super._.toArray( arguments ) ) ); | |
}; | |
fun.original = fn; | |
return fun; | |
} | |
function extend() { | |
var Class = Backbone.View.extend.apply( this, arguments ); | |
Class.extend = extend; | |
Class.prototype.$uper = curry( Super._.wrap( Class.__super__.$uper, callNestedSuper ), Class.__super__ ); | |
return Class; | |
} | |
function getRetVal( ctx, val ) { | |
return val === ctx || val === UNDEF | |
? ctx | |
: val; | |
} | |
var UNDEF, Super, empty = {}; | |
Super = Backbone.View.extend( { | |
constructor : function( options ) { | |
Super._.extend( this, options || {} ); | |
return this.$uper( 'constructor', arguments ); | |
}, | |
$uper : curry( callSuper, Backbone.View.prototype ), | |
_configure : function() { | |
this.$uper( '_configure', arguments ); | |
if ( this.className ) { | |
this.slcEl = '.' + this.className; | |
this.slcCt = this.slcEl + '-ct'; | |
} | |
}, | |
afterRender : function() { | |
return this; | |
}, | |
assignContainer : function( ct ) { | |
if ( ct ) { | |
if ( Super._.isElement( ct ) || Super._.isString( ct ) ) | |
ct = $( ct ); | |
if ( Super._.isObject( ct ) && ct.length ) | |
ct.append( this.el ); | |
this.$ct = ct; | |
this.ct = ct[0]; | |
} | |
return this; | |
}, | |
destroy : function() { | |
this.destroyed = true; | |
this.rendered = false; | |
this.undelegateEvents(); | |
return this.destroyRefs().remove().trigger( 'destroy', this ); | |
}, | |
destroyRefs : function() { | |
delete this.$ct; delete this.ct; | |
delete this.$elCt; delete this.elCt; | |
return this; | |
}, | |
make : function() { | |
return Super._.isFunction( this.template ) ? this.template( this ) : this.$uper( 'make', arguments ); | |
}, | |
onRender : function() { | |
return this; | |
}, | |
prepareRefs : function() { | |
if ( this.slcCt ) { | |
this.$elCt = this.$el.find( this.slcCt ); | |
this.elCt = this.$elCt[0]; | |
} | |
return this; | |
}, | |
render : function( ct ) { | |
if ( !this.rendered ) { | |
this.assignContainer( ct ).onRender().prepareRefs().afterRender(); | |
this.rendered = true; | |
return this.$uper( 'render', arguments ).trigger( 'render', this ); | |
} | |
return this; | |
} | |
} ); | |
define( ['Handlebars', 'Modernizr', 'jQuery', 'underscore'], function( Handlebars, Modernizr, $, _ ) { | |
return Backbone.View.$uper || ( Backbone.View.$uper = _.extend( Super, { | |
_ : _, | |
$ : $, | |
Handlebars : Handlebars, | |
Modernizr : Modernizr, | |
extend : extend | |
} ) ); | |
} ); | |
}(); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
define( ['Handlebars', 'Modernizr', 'jQuery', 'underscore', 'SuperView'], function( Handlebars, Modernizr, jQuery, _, $uper ) { | |
"use strict"; | |
describe( 'Trying to bring an iota of sanity back to UI development — and deal with Backbone mania — with the very inelegant Backbone.View.$uper', function() { | |
describe( 'Using some semblance of an inheritance pattern that gives the developer convenience rather than potential confusion.', function() { | |
it( 'allows you to create Backbone.Views', function() { | |
var called_ctor = false, | |
view1 = new $uper, | |
view2 = new ( $uper.extend( { | |
constructor : function() { | |
called_ctor = true; | |
this.$uper( 'constructor', arguments ); | |
} | |
} ) ); | |
assert( view1 instanceof $uper ); | |
assert( view1 instanceof Backbone.View ); | |
assert( view2 instanceof $uper ); | |
assert( view2 instanceof Backbone.View ); | |
assert( called_ctor === true ); | |
} ); | |
it( 'Allows you to call super methods using a nasty looking: ' | |
+ '`this.$uper( method_name:String, args:Arguments|Array )` — very crumby indeed — ' | |
+ 'however, being able to call super on a Class with an inheritance chain longer than 1 — which is ' | |
+ 'impossible in Backbone without making your code a refactoring nightmare and fuglier than a ' | |
+ 'naked dick cheeney — is nice.', function() { | |
var spy_ctor = sinon.spy( Backbone.View.prototype, 'constructor' ), | |
spy_init = sinon.spy( Backbone.View.prototype, 'initialize' ); | |
var called_ctor1 = false, called_ctor2 = false, called_ctor3 = false, | |
called_init1 = false, called_init2 = false, called_init3 = false, | |
called_foo1 = false, called_foo2 = false, called_foo3 = false; | |
var View1 = $uper.extend( { | |
constructor : function( config ) { | |
called_ctor1 = true; | |
assert( typeof config === 'object' ); | |
assert( config.id === id ); | |
this.id = config.id; | |
this.$uper( 'constructor', arguments ); | |
}, | |
initialize : function() { | |
called_init1 = true; | |
return this.$uper( 'initialize', arguments ); | |
}, | |
foo : function( val ) { | |
called_foo1 = true; | |
assert( val === 'bar', 'View1.foo received incorrect argument: ', val ); | |
return this; | |
} | |
} ), | |
View2 = View1.extend( { | |
constructor : function( config ) { | |
called_ctor2 = true; | |
assert( typeof config === 'object' ); | |
assert( config.id === id ); | |
this.$uper( 'constructor', arguments ); | |
}, | |
initialize : function() { | |
called_init2 = true; | |
return this.$uper( 'initialize', arguments ); | |
}, | |
foo : function( val ) { | |
called_foo2 = true; | |
assert( val === 'bar', 'View1.foo received incorrect argument: ', val ); | |
return this.$uper( 'foo', arguments ); | |
} | |
} ), | |
View3 = View2.extend( { | |
constructor : function( config ) { | |
called_ctor3 = true; | |
assert( typeof config === 'object' ); | |
assert( config.id === id ); | |
this.$uper( 'constructor', arguments ); | |
}, | |
initialize : function() { | |
called_init3 = true; | |
return this.$uper( 'initialize', arguments ); | |
}, | |
foo : function( val ) { | |
called_foo3 = true; | |
assert( val === 'bar', 'View1.foo received incorrect argument: ', val ); | |
return this.$uper( 'foo', arguments ); | |
} | |
} ); | |
var id = 'view-3', | |
conf = { id : id }, | |
view = new View3( conf ); | |
assert( view.id === id, 'view.id not set' ); | |
assert( view.foo( 'bar' ) === view, 'context not returned' ); | |
assert( view instanceof Backbone.View ); | |
assert( view instanceof $uper ); | |
assert( view instanceof View1 ); | |
assert( view instanceof View2 ); | |
assert( view instanceof View3 ); | |
assert( called_ctor3 === true, 'View3.constructor not called' ); | |
assert( called_ctor2 === true, 'View2.constructor not called' ); | |
assert( called_ctor1 === true, 'View1.constructor not called' ); | |
assert( called_init3 === true, 'View3.initialize not called' ); | |
assert( called_init2 === true, 'View2.initialize not called' ); | |
assert( called_init1 === true, 'View1.initialize not called' ); | |
assert( called_foo3 === true, 'View3.foo not called' ); | |
assert( called_foo2 === true, 'View2.foo not called' ); | |
assert( called_foo1 === true, 'View1.foo not called' ); | |
assert( spy_ctor.calledWith( conf ) ); | |
assert( spy_init.calledWith( conf ) ); | |
} ); | |
} ); | |
describe( 'Implementing actual render/destroy functionality, though did not bother to fix the fact that Backbone.View breaks lazy rendering.', function() { | |
it( 'Allows you to pass a container to render your view to', function() { | |
var view = new $uper( { | |
className : 'test', | |
template : $uper.Handlebars.compile( '<div class="{{className}}"><div class="{{className}}-ct"></div></dib></div>' ) | |
} ); | |
view.render( 'body' ); | |
assert( view.rendered ); assert( !view.destroyed ); | |
assert( view.ct === document.body ); | |
assert( view.$ct[0] === view.ct ); | |
assert( $uper.$.contains( view.$ct[0], view.el ) ); | |
assert( view.slcEl === '.' + view.className ); | |
assert( view.slcCt === view.slcEl + '-ct' ); | |
assert( $uper.$.contains( view.el, view.elCt ) ); | |
} ); | |
it( 'Allows you to destroy your view, because, how many times are you going to add and remove the same damn Backbone.View instance to the DOM‽ ⇐ notice: cool use of interobang :D', function() { | |
var ct, | |
view = new $uper( { | |
className : 'test', | |
template : $uper.Handlebars.compile( '<div class="{{className}}"><div class="{{className}}-ct"></div></dib></div>' ) | |
} ); | |
view.render( 'body' ); | |
assert( $uper.$.contains( view.$ct[0], view.el ) ); | |
ct = view.ct; | |
view.destroy(); | |
assert( !$uper.$.contains( ct, view.el ) ); | |
assert( !view.$ct ); assert( !view.ct ); | |
assert( !view.$elCt ); assert( !view.elCt ); | |
assert( !view.rendered ); assert( view.destroyed ); | |
} ); | |
} ); | |
} ); | |
describe( 'Conveniences', function() { | |
it( '$uper.$ === jQuery', function() { | |
assert( $uper.$ === $, 'jQueery not goodie' ); | |
} ); | |
it( '$uper._ === _', function() { | |
assert( $uper._ === _, 'underscore not goodie' ); | |
} ); | |
it( '$uper.Handlebars === Handlebars', function() { | |
assert( $uper.Handlebars === Handlebars, 'Handlebars not goodie' ); | |
} ); | |
it( '$uper.Modernizr === Modernizr', function() { | |
assert( $uper.Modernizr === Modernizr, 'Modernizr not goodie' ); | |
} ); | |
} ); | |
} ); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment