// Backbone.CollectionBinder v1.0.0
// (c) 2013 Bart Wood
// Distributed Under MIT License
( function ( ) {
if ( ! Backbone ) {
throw 'Please include Backbone.js before Backbone.ModelBinder.js' ;
}
if ( ! Backbone . ModelBinder ) {
throw 'Please include Backbone.ModelBinder.js before Backbone.CollectionBinder.js' ;
}
Backbone . CollectionBinder = function ( elManagerFactory , options ) {
_ . bindAll ( this ) ;
this . _elManagers = { } ;
this . _elManagerFactory = elManagerFactory ;
if ( ! this . _elManagerFactory ) throw 'elManagerFactory must be defined.' ;
// Let the factory just use the trigger function on the view binder
this . _elManagerFactory . trigger = this . trigger ;
this . _options = options || { } ;
} ;
Backbone . CollectionBinder . VERSION = '1.0.0' ;
_ . extend ( Backbone . CollectionBinder . prototype , Backbone . Events , {
bind : function ( collection , parentEl ) {
this . unbind ( ) ;
if ( ! collection ) throw 'collection must be defined' ;
if ( ! parentEl ) throw 'parentEl must be defined' ;
this . _collection = collection ;
this . _elManagerFactory . setParentEl ( parentEl ) ;
this . _onCollectionReset ( ) ;
this . _collection . on ( 'add' , this . _onCollectionAdd , this ) ;
this . _collection . on ( 'remove' , this . _onCollectionRemove , this ) ;
this . _collection . on ( 'reset' , this . _onCollectionReset , this ) ;
} ,
unbind : function ( ) {
if ( this . _collection !== undefined ) {
this . _collection . off ( 'add' , this . _onCollectionAdd ) ;
this . _collection . off ( 'remove' , this . _onCollectionRemove ) ;
this . _collection . off ( 'reset' , this . _onCollectionReset ) ;
}
this . _removeAllElManagers ( ) ;
} ,
getManagerForEl : function ( el ) {
var i , elManager , elManagers = _ . values ( this . _elManagers ) ;
for ( i = 0 ; i < elManagers . length ; i ++ ) {
elManager = elManagers [ i ] ;
if ( elManager . isElContained ( el ) ) {
return elManager ;
}
}
return undefined ;
} ,
getManagerForModel : function ( model ) {
var i , elManager , elManagers = _ . values ( this . _elManagers ) ;
for ( i = 0 ; i < elManagers . length ; i ++ ) {
elManager = elManagers [ i ] ;
if ( elManager . getModel ( ) === model ) {
return elManager ;
}
}
return undefined ;
} ,
_onCollectionAdd : function ( model ) {
this . _elManagers [ model . cid ] = this . _elManagerFactory . makeElManager ( model ) ;
this . _elManagers [ model . cid ] . createEl ( ) ;
if ( this . _options [ 'autoSort' ] ) {
this . sortRootEls ( ) ;
}
} ,
_onCollectionRemove : function ( model ) {
this . _removeElManager ( model ) ;
} ,
_onCollectionReset : function ( ) {
this . _removeAllElManagers ( ) ;
this . _collection . each ( function ( model ) {
this . _onCollectionAdd ( model ) ;
} , this ) ;
this . trigger ( 'elsReset' , this . _collection ) ;
} ,
_removeAllElManagers : function ( ) {
_ . each ( this . _elManagers , function ( elManager ) {
elManager . removeEl ( ) ;
delete this . _elManagers [ elManager . _model . cid ] ;
} , this ) ;
delete this . _elManagers ;
this . _elManagers = { } ;
} ,
_removeElManager : function ( model ) {
if ( this . _elManagers [ model . cid ] !== undefined ) {
this . _elManagers [ model . cid ] . removeEl ( ) ;
delete this . _elManagers [ model . cid ] ;
}
} ,
sortRootEls : function ( ) {
this . _collection . each ( function ( model , modelIndex ) {
var modelElManager = this . getManagerForModel ( model ) ;
if ( modelElManager ) {
var modelEl = modelElManager . getEl ( ) ;
var currentRootEls = $ ( this . _elManagerFactory . getParentEl ( ) ) . children ( ) ;
if ( currentRootEls [ modelIndex ] !== modelEl [ 0 ] ) {
modelEl . detach ( ) ;
modelEl . insertBefore ( currentRootEls [ modelIndex ] ) ;
}
}
} , this ) ;
}
} ) ;
// The ElManagerFactory is used for els that are just html templates
// elHtml - how the model's html will be rendered. Must have a single root element (div,span).
// bindings (optional) - either a string which is the binding attribute (name, id, data-name, etc.) or a normal bindings hash
Backbone . CollectionBinder . ElManagerFactory = function ( elHtml , bindings ) {
_ . bindAll ( this ) ;
this . _elHtml = elHtml ;
this . _bindings = bindings ;
if ( ! _ . isString ( this . _elHtml ) ) throw 'elHtml must be a valid html string' ;
} ;
_ . extend ( Backbone . CollectionBinder . ElManagerFactory . prototype , {
setParentEl : function ( parentEl ) {
this . _parentEl = parentEl ;
} ,
getParentEl : function ( ) {
return this . _parentEl ;
} ,
makeElManager : function ( model ) {
var elManager = {
_model : model ,
createEl : function ( ) {
this . _el = $ ( this . _elHtml ) ;
$ ( this . _parentEl ) . append ( this . _el ) ;
if ( this . _bindings ) {
if ( _ . isString ( this . _bindings ) ) {
this . _modelBinder = new Backbone . ModelBinder ( ) ;
this . _modelBinder . bind ( this . _model , this . _el , Backbone . ModelBinder . createDefaultBindings ( this . _el , this . _bindings ) ) ;
}
else if ( _ . isObject ( this . _bindings ) ) {
this . _modelBinder = new Backbone . ModelBinder ( ) ;
this . _modelBinder . bind ( this . _model , this . _el , this . _bindings ) ;
}
else {
throw 'Unsupported bindings type, please use a boolean or a bindings hash' ;
}
}
this . trigger ( 'elCreated' , this . _model , this . _el ) ;
} ,
removeEl : function ( ) {
if ( this . _modelBinder !== undefined ) {
this . _modelBinder . unbind ( ) ;
}
this . _el . remove ( ) ;
this . trigger ( 'elRemoved' , this . _model , this . _el ) ;
} ,
isElContained : function ( findEl ) {
return this . _el === findEl || $ ( this . _el ) . has ( findEl ) . length > 0 ;
} ,
getModel : function ( ) {
return this . _model ;
} ,
getEl : function ( ) {
return this . _el ;
}
} ;
_ . extend ( elManager , this ) ;
return elManager ;
}
} ) ;
// The ViewManagerFactory is used for els that are created and owned by backbone views.
// There is no bindings option because the view made by the viewCreator should take care of any binding
// viewCreator - a callback that will create backbone view instances for a model passed to the callback
Backbone . CollectionBinder . ViewManagerFactory = function ( viewCreator ) {
_ . bindAll ( this ) ;
this . _viewCreator = viewCreator ;
if ( ! _ . isFunction ( this . _viewCreator ) ) throw 'viewCreator must be a valid function that accepts a model and returns a backbone view' ;
} ;
_ . extend ( Backbone . CollectionBinder . ViewManagerFactory . prototype , {
setParentEl : function ( parentEl ) {
this . _parentEl = parentEl ;
} ,
getParentEl : function ( ) {
return this . _parentEl ;
} ,
makeElManager : function ( model ) {
var elManager = {
_model : model ,
createEl : function ( ) {
this . _view = this . _viewCreator ( model ) ;
$ ( this . _parentEl ) . append ( this . _view . render ( this . _model ) . el ) ;
this . trigger ( 'elCreated' , this . _model , this . _view ) ;
} ,
removeEl : function ( ) {
if ( this . _view . close !== undefined ) {
this . _view . close ( ) ;
}
else {
this . _view . $el . remove ( ) ;
console . log ( 'warning, you should implement a close() function for your view, you might end up with zombies' ) ;
}
this . trigger ( 'elRemoved' , this . _model , this . _view ) ;
} ,
isElContained : function ( findEl ) {
return this . _view . el === findEl || this . _view . $el . has ( findEl ) . length > 0 ;
} ,
getModel : function ( ) {
return this . _model ;
} ,
getView : function ( ) {
return this . _view ;
} ,
getEl : function ( ) {
return this . _view . $el ;
}
} ;
_ . extend ( elManager , this ) ;
return elManager ;
}
} ) ;
} ) . call ( this ) ;