wise-web-client
Version:
Based on Spine MVC framework
181 lines (141 loc) • 5.17 kB
JavaScript
/*
Backbone adaptor plugin
=======================
Version 0.2.0. Copyright 2013 - 2014 @rich_harris, MIT licensed.
This plugin allows Ractive.js to work seamlessly with Backbone.Model and
Backbone.Collection instances.
For more information see ractivejs.org/examples/backbone and
https://github.com/Rich-Harris/Ractive/wiki/Adaptors.
==========================
Troubleshooting: If you're using a module system in your app (AMD or
something more nodey) then you may need to change the paths below,
where it says `require( 'ractive' )` or `define([ 'ractive' ]...)`.
==========================
Usage: Include this file on your page below Ractive, e.g:
<script src='lib/ractive.js'></script>
<script src='lib/ractive-adaptors-backbone.js'></script>
Or, if you're using a module loader, require this module:
define( function ( require ) {
var Ractive = require( 'ractive' );
// requiring the plugin will 'activate' it - no need to use
// the return value
require( 'ractive-adaptors-backbone' );
});
Then tell Ractive to expect Backbone objects by adding an `adapt` property:
var ractive = new Ractive({
el: myContainer,
template: myTemplate,
data: { foo: myBackboneModel, bar: myBackboneCollection },
adapt: [ 'Backbone' ]
});
*/
(function ( global, factory ) {
'use strict';
// Common JS (i.e. browserify) environment
if ( typeof module !== 'undefined' && module.exports && typeof require === 'function' ) {
factory( require( 'ractive' ), require( 'backbone' ) );
}
// AMD?
else if ( typeof define === 'function' && define.amd ) {
define([ 'ractive', 'backbone' ], factory );
}
// browser global
else if ( global.Ractive && global.Backbone ) {
factory( global.Ractive, global.Backbone );
}
else {
throw new Error( 'Could not find Ractive or Backbone! Both must be loaded before the ractive-adaptors-backbone plugin' );
}
}( typeof window !== 'undefined' ? window : this, function ( Ractive, Backbone ) {
'use strict';
var BackboneModelWrapper, BackboneCollectionWrapper, lockProperty = '_ractiveAdaptorsBackboneLock';
if ( !Ractive || !Backbone ) {
throw new Error( 'Could not find Ractive or Backbone! Check your paths config' );
}
function acquireLock( key ) {
key[lockProperty] = ( key[lockProperty] || 0 ) + 1;
return function release() {
key[lockProperty] -= 1;
if ( !key[lockProperty] ) {
delete key[lockProperty];
}
};
}
function isLocked( key ) {
return !!key[lockProperty];
}
Ractive.adaptors.Backbone = {
filter: function ( object ) {
return object instanceof Backbone.Model || object instanceof Backbone.Collection;
},
wrap: function ( ractive, object, keypath, prefix ) {
if ( object instanceof Backbone.Model ) {
return new BackboneModelWrapper( ractive, object, keypath, prefix );
}
return new BackboneCollectionWrapper( ractive, object, keypath, prefix );
}
};
BackboneModelWrapper = function ( ractive, model, keypath, prefix ) {
this.value = model;
model.on( 'change', this.modelChangeHandler = function () {
var release = acquireLock( model );
ractive.set( prefix( model.changed ) );
release();
});
};
BackboneModelWrapper.prototype = {
teardown: function () {
this.value.off( 'change', this.modelChangeHandler );
},
get: function () {
return this.value.attributes;
},
set: function ( keypath, value ) {
// Only set if the model didn't originate the change itself, and
// only if it's an immediate child property
if ( !isLocked( this.value ) && keypath.indexOf( '.' ) === -1 ) {
this.value.set( keypath, value );
}
},
reset: function ( object ) {
// If the new object is a Backbone model, assume this one is
// being retired. Ditto if it's not a model at all
if ( object instanceof Backbone.Model || !(object instanceof Object) ) {
return false;
}
// Otherwise if this is a POJO, reset the model
//Backbone 1.1.2 no longer has reset and just uses set
this.value.set( object );
}
};
BackboneCollectionWrapper = function ( ractive, collection, keypath ) {
this.value = collection;
collection.on( 'add remove reset sort', this.changeHandler = function () {
// TODO smart merge. It should be possible, if awkward, to trigger smart
// updates instead of a blunderbuss .set() approach
var release = acquireLock( collection );
ractive.set( keypath, collection.models );
release();
});
};
BackboneCollectionWrapper.prototype = {
teardown: function () {
this.value.off( 'add remove reset sort', this.changeHandler );
},
get: function () {
return this.value.models;
},
reset: function ( models ) {
if ( isLocked( this.value ) ) {
return;
}
// If the new object is a Backbone collection, assume this one is
// being retired. Ditto if it's not a collection at all
if ( models instanceof Backbone.Collection || Object.prototype.toString.call( models ) !== '[object Array]' ) {
return false;
}
// Otherwise if this is a plain array, reset the collection
this.value.reset( models );
}
};
}));