Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Comparing changes

Choose two branches to see what's changed or to start a new pull request. If you need to, you can also compare across forks.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also compare across forks.
base fork: gmac/backbone.epoxy
base: v0.10.1
...
head fork: gmac/backbone.epoxy
compare: v0.11.0
Checking mergeability… Don't worry, you can still create the pull request.
  • 14 commits
  • 13 files changed
  • 0 commit comments
  • 2 contributors
View
3  .gitignore
@@ -1,2 +1,3 @@
.DS_Store
-node_modules
+node_modules
+local-test.html
View
28 README.md
@@ -1,6 +1,6 @@
# Epoxy.js : Elegant Data Binding for Backbone
-[Epoxy.js](http://epoxyjs.org "Epoxy.js") is an elegant and extensible data binding library for [Backbone.js](http://backbonejs.org "Backbone.js"); it provides feature-rich extensions of Backbone's `Model` and `View` components designed to hook view elements directly to data models. Epoxy captures some great aspects of [Knockout.js](http://knockoutjs.com "Knockout.js") and [Ember.js](http://emberjs.com "Ember.js") in a familiar API that feels tastefully like Backbone, with minimal additional file size (8k min, 2k gzip). Some key features in Epoxy include:</p>
+[Epoxy.js](http://epoxyjs.org "Epoxy.js") is an elegant and extensible data binding library for [Backbone.js](http://backbonejs.org "Backbone.js"); it provides feature-rich extensions of Backbone's `Model` and `View` components designed to hook view elements directly to data models. Epoxy captures some great aspects of [Knockout.js](http://knockoutjs.com "Knockout.js") and [Ember.js](http://emberjs.com "Ember.js") in a familiar API that feels tastefully like Backbone, with minimal additional file size (8.5k min, 2k gzip). Some key features in Epoxy include:</p>
- Computed Model Attributes
- Declarative View Bindings
@@ -11,14 +11,14 @@ Epoxy builds on [jQuery](http://jquery.com "jQuery.js")+[Backbone](http://backbo
## Installation
-Epoxy requires [jQuery](http://jquery.com "jQuery.js") 1.7.0+, [Underscore](http://underscorejs.org "Underscore.js") 1.4.3+, and [Backbone](http://backbonejs.org "Backbone.js") 0.9.9+. To quickly install Epoxy, download the full Epoxy library (`backbone.epoxy.min.js`, 8k min/2k gzip) and include its script tag in your document after all dependencies:
+Epoxy requires [jQuery](http://jquery.com "jQuery.js") 1.7.0+, [Underscore](http://underscorejs.org "Underscore.js") 1.4.3+, and [Backbone](http://backbonejs.org "Backbone.js") 0.9.9+. To quickly install Epoxy, download the full Epoxy library (`backbone.epoxy.min.js`, 8.5k min/2k gzip) and include its script tag in your document after all dependencies:
<script src="jquery-min.js"></script>
<script src="underscore-min.js"></script>
<script src="backbone-min.js"></script>
<script src="backbone.epoxy.min.js"></script>
-You may choose to replace Underscore with the [Lo-Dash](http://lodash.com "Lodash.js") alternative (Epoxy's test suite runs slightly faster using Lo-Dash, although the difference is not drastic).
+You may choose to replace Underscore with the [Lo-Dash](http://lodash.com "Lodash.js") alternative (Epoxy's test suite runs slightly faster using Lo-Dash, although the difference is not drastic). Also remember to include the [json2](https://github.com/douglascrockford/JSON-js "JSON2") library when targeting IE6/7.
## Help & Documentation
@@ -27,11 +27,31 @@ For all complete information and documentation, visit the project's website at [
## Change Log
+**0.11.0***March 25, 2013* – [Diff](https://github.com/gmac/backbone.epoxy/compare/v0.10.1...v0.11.0 "Diff: v0.10.1/v0.11.0")
+
+Adds final features planned for 1.0 release.
+
+ - Adds `template` binding for rendering data attributes with an Underscore template.
+ - Adds `bindingFilters` namespace for defining custom data filters.
+ - Defines `binding` API for core configuration and add-ons.
+ - Browser compatibility fixes to the `options` binding.
+ - Refactoring for performance (at slight expense to file size).
+ - Adds environment setup and AMD definition, courtesy of jpurcell001 and Marionette.
+ - The notion of "operators" is officially reclassified as "filters".
+ - Revised docs.
+
+**0.10.1***March 21, 2013* – [Diff](https://github.com/gmac/backbone.epoxy/compare/v0.10.0...v0.10.1 "Diff: v0.10.0/v0.10.1")
+
+Large codebase refactoring.
+
+ - Shifts bindings from element level down to the handler level.
+ - Opens default binding handlers, operators, and options to a public API.
+
**0.10.0***March 19, 2013* – [Diff](https://github.com/gmac/backbone.epoxy/compare/v0.9.0...v0.10.0 "Diff: v0.9.0/v0.10.0")
Adds additional binding features to stage for a 1.0 release.
- - Adds select menu options binding suite and supporting tests; includes:
+ - Adds select menu `options` binding suite and supporting tests; includes:
- `options` : Binds the contents of an Array or Collection to select menu options.
- `optionsDefault` : Provides a default option to include above `options` items.
- `optionsEmpty` : Provides an empty placeholder option to display when `options` is empty.
View
419 backbone.epoxy.js
@@ -5,13 +5,23 @@
// For usage and documentation:
// http://epoxyjs.org
-(function() {
+(function( root, factory ) {
- // Operations scope:
- var root = this;
- var Backbone = root.Backbone;
- var _ = root._;
+ var backbone = "backbone";
+ var underscore = "underscore";
+ if ( typeof exports !== "undefined" ) {
+ // Define as CommonJS export:
+ module.exports = factory( require(underscore), require(backbone) );
+ } else if ( typeof define === "function" && define.amd ) {
+ // Define as AMD:
+ define( [underscore, backbone], factory );
+ } else {
+ // Just run it:
+ factory( root._, root.Backbone );
+ }
+
+}(this, function( _, Backbone ) {
// Epoxy namespace:
var Epoxy = Backbone.Epoxy = {};
@@ -56,7 +66,7 @@
// Add all default observable attributes:
if ( this.observableDefaults ) {
_.each(this.observableDefaults, function( value, attribute ) {
- this.addObservable( attribute, isFunction(value) ? value() : copyValue(value) );
+ this.addObservable( attribute, isFunction(value) ? value() : copyModelValue(value) );
}, this);
}
@@ -98,7 +108,7 @@
// Array and Object values will return a shallow copy,
// primitive values will be returned directly.
getCopy: function( attribute ) {
- return copyValue( this.get(attribute) );
+ return copyModelValue( this.get(attribute) );
},
// Backbone.Model.set() override:
@@ -123,13 +133,27 @@
// All param properties are tested against observable setters,
// properties set to observables will be removed from the params table.
// Optionally, an observable setter may return key/value pairs to be merged into the set.
- params = modelDeepSet(this, params, {}, []);
+ params = deepModelSet(this, params, {}, []);
}
// Pass all resulting set params along to the underlying Backbone Model.
return modelSuper( this, "set", [params, options] );
},
+ // Backbone.Model.toJSON() override:
+ // adds an "obs" option, specifying to include observable attributes.
+ toJSON: function( options ) {
+ var json = modelSuper( this, "toJSON", arguments );
+
+ if ( options && options.obs ) {
+ _.each(this.obs, function( observable, attribute ) {
+ json[ attribute ] = observable.value;
+ });
+ }
+
+ return json;
+ },
+
// Backbone.Model.destroy() override:
// clears all observable attributes before destroying.
destroy: function() {
@@ -249,6 +273,8 @@
}
});
+ // Epoxy.Model -> Private
+ // ----------------------
// Model deep-setter:
// Attempts to set a collection of key/value attribute pairs to observable attributes.
@@ -259,7 +285,7 @@
// @param toSet: an object of key/value pairs to attempt to set within the observable model.
// @param toReturn: resolved non-ovservable attribute values to be returned back to the native model.
// @param trace: property stack trace (prevents circular setter loops).
- function modelDeepSet( model, toSet, toReturn, stack ) {
+ function deepModelSet( model, toSet, toReturn, stack ) {
// Loop through all setter properties:
for ( var attribute in toSet ) {
@@ -281,7 +307,7 @@
// Recursively set new values for a returned params object:
// creates a new copy of the stack trace for each new search branch.
if ( value && isObject(value) ) {
- toReturn = modelDeepSet( model, value, toReturn, stack.slice().concat([attribute]) );
+ toReturn = deepModelSet( model, value, toReturn, stack.slice().concat([attribute]) );
}
} else {
@@ -303,7 +329,7 @@
// Creates a shallow-copy of object values:
// Array values are sliced, objects are cloned, primitives are returned.
- function copyValue( value ) {
+ function copyModelValue( value ) {
if ( isArray(value) ) {
return value.slice();
} else if ( isObject(value) ) {
@@ -346,7 +372,7 @@
// the first pass sets up all observable attributes,
// then the second pass initializes all bindings.
if ( !model._init ) this.init();
- };
+ }
_.extend(EpoxyObservable.prototype, Backbone.Events, {
@@ -454,8 +480,14 @@
});
- // Read Accessor
- // -------------
+ // Epoxy.binding -> Binding API
+ // ----------------------------
+
+ var bindingSettings = {
+ optionText: "label",
+ optionValue: "value"
+ };
+
// Reads value from an accessor:
// Accessors come in three potential forms:
// => A function to call for the requested value.
@@ -610,7 +642,7 @@
} else {
// Reset with new views:
- this.wipe();
+ this.clean();
collection.each(function( model ) {
views[ model.cid ] = view = new collection.view({model: model});
@@ -622,7 +654,7 @@
$element.show();
}
},
- wipe: function() {
+ clean: function() {
for (var id in this.v) {
if ( this.v.hasOwnProperty( id ) ) {
this.v[ id ].remove();
@@ -662,6 +694,11 @@
// Options: write-only. Sets option items to a <select> element, then updates the value.
options: {
+ init: function( $element, value, context, bindings ) {
+ this.e = bindings.optionsEmpty;
+ this.d = bindings.optionsDefault;
+ this.v = bindings.value;
+ },
set: function( $element, value ) {
// Pre-compile empty and default option values:
@@ -669,66 +706,95 @@
// 1) we need to need to guarentee that both values are reached for mapping purposes.
// 2) we'll need their values anyway to determine their defined/undefined status.
var self = this;
- var optionsEmpty = readAccessor( self.optionsEmpty );
- var optionsDefault = readAccessor( self.optionsDefault );
+ var optionsEmpty = readAccessor( self.e );
+ var optionsDefault = readAccessor( self.d );
+ var currentValue = readAccessor( self.v );
+ var selection = isArray(currentValue) ? currentValue : [ currentValue ];
var options = isCollection( value ) ? value.models : value;
- var selection = $element.val();
+ var numOptions = options.length;
var enabled = true;
var html = "";
- // Throw error for invalid options list:
- if (!options) throw( "Binding 'options' is "+options );
-
// No options or default, and has an empty options placeholder:
// display placeholder and disable select menu.
- if ( !options.length && !optionsDefault && optionsEmpty ) {
+ if ( !numOptions && !optionsDefault && optionsEmpty ) {
- html += self.opt( optionsEmpty );
+ html += self.opt( optionsEmpty, numOptions, selection );
enabled = false;
} else {
// Try to populate default option and options list:
- // Create the default option, if defined:
+ // Configure list with a default first option, if defined:
if ( optionsDefault ) {
- html += self.opt( optionsDefault );
+ options = [ optionsDefault ].concat( options );
}
// Create all option items:
- _.each(options, function( option ) {
- html += self.opt( option );
+ _.each(options, function( option, index ) {
+ html += self.opt( option, numOptions, selection );
});
}
-
- // Set new HTML to the element, toggle disabled status, and apply selection:
- $element
- .html( html )
- .prop( "disabled", !enabled )
- .val( selection );
-
- // Test if previous selection state was successfully applied to the new options:
- // if not, then flash the element's "change" event to trigger view-capture bindings.
- if ( !_.isEqual($element.val(), selection) ) {
- $element.trigger( "change" );
+
+ // Set new HTML to the element and toggle disabled status:
+ $element.html( html ).prop( "disabled", !enabled );
+
+ // Pull revised value with new options selection state:
+ var revisedValue = $element.val();
+
+ // Test if the current value was successfully applied:
+ // if not, set the new selection state into the model.
+ if ( self.v && !_.isEqual(currentValue, revisedValue) ) {
+ self.v( revisedValue );
}
},
- opt: function( option ) {
+ opt: function( option, numOptions, selection ) {
// Set both label and value as the raw option object by default:
var label = option;
var value = option;
-
+ var textAttr = bindingSettings.optionText;
+ var valueAttr = bindingSettings.optionValue;
+
// Dig deeper into label/value settings for non-primitive values:
if ( isObject( option ) ) {
// Extract a label and value from each object:
// a model's "get" method is used to access potential observable values.
- label = isModel( option ) ? option.get( "label" ) : option.label;
- value = isModel( option ) ? option.get( "value" ) : option.value;
+ label = isModel( option ) ? option.get( textAttr ) : option[ textAttr ];
+ value = isModel( option ) ? option.get( valueAttr ) : option[ valueAttr ];
}
-
- return "<option value='"+ value +"'>"+ label +"</option>";
+
+ // Configure selection-state option fragment:
+ // option should be selected if it's the only item (default/empty), or exists in the selection:
+ var select = (!numOptions || _.contains(selection, value)) ? "' selected='selected'>" : "'>";
+
+ return "<option value='"+ value + select + label +"</option>";
+ },
+ clean: function() {
+ this.d = this.e = this.v = null;
}
},
-
+
+ // Template: write-only. Renders the bound element with an Underscore template.
+ template: {
+ init: function( $element, value, context ) {
+ var raw = $element.find("script,template");
+ this.tmpl = _.template( raw.length ? raw.html() : $element.html() );
+
+ // If an array of template attributes was provided,
+ // then replace array with a compiled hash of attribute accessors:
+ if ( isArray(value) ) {
+ return _.pick(context, value);
+ }
+ },
+ set: function( $element, value ) {
+ value = isModel(value) ? value.toJSON({obs:true}) : value;
+ $element.html( this.tmpl(value) );
+ },
+ clean: function() {
+ this.tmpl = null;
+ }
+ },
+
// Text: write-only. Sets the text value of an element.
text: {
set: function( $element, value ) {
@@ -749,100 +815,118 @@
return $element.val();
},
set: function( $element, value ) {
- if ( $element.val() != value ) $element.val( value );
+ try {
+ if ( $element.val() != value ) $element.val( value );
+ } catch (error) {
+ // Error setting value in IE6: IGNORE.
+ // This occurs in IE6 while attempting to set an undefined multi-select option.
+ // unfortuantely, jQuery doesn't gracefully handle this error for us.
+ // remove this try/catch block when IE6 is officially deprecated!
+ }
}
}
};
-
- // Binding Options
- // ---------------
- // Defines special binding options made available to handlers:
- // these optional params are used by handler functions, but are not actually handlers themselves.
- // Options will be removed from the binding context and copied directly onto the binding.
- var bindingOptions = [ "events", "optionsDefault", "optionsEmpty" ];
+ // Binding Filters
+ // ---------------
+ // Filters are special binding handlers that may be invoked while binding;
+ // they will return a wrapper function used to modify how accessors are read.
+ // Partial application wrapper for creating binding filters:
+ function makeFilter( handler ) {
+ return function() {
+ var params = arguments;
+ return function( input ) {
+ return handler.apply(this, readFilterParams(params, input));
+ };
+ };
+ }
- // Binding Operators
- // -----------------
- // Operators are special binding handlers that may be invoked while binding;
- // they will return a wrapper function used to modify how accessors are read.
- // IMPORTANT: binding operators must access ALL of their dependent params while running,
- // otherwise accessor params become unreachable and will not provide binding hooks.
- // Therefore, assessment loops must NOT exit early... so do not optimize!
+ // Reads a list of cached filter params:
+ // this makes sure all params are accessed (for mapping purposes),
+ // and also unpacks the current value of each parameter for use within the handler.
+ function readFilterParams( params, input ) {
+ if ( input ) throw("binding error: filtered values are read-only.");
+
+ var args = [];
+ for (var i=0, len=params.length; i < len; i++) {
+ args.push( readAccessor(params[i]) );
+ }
+ return args;
+ }
- var bindingOperators = {
+ var bindingFilters = {
// Tests if all of the provided accessors are truthy (and):
- all: makeOperator(function( params ) {
- var result = true;
+ all: makeFilter(function() {
+ var params = arguments;
for ( var i=0, len=params.length; i < len; i++ ) {
- if ( !readAccessor(params[i]) ) result = false;
+ if ( !params[i] ) return false;
}
- return result;
+ return true;
}),
// Tests if any of the provided accessors are truthy (or):
- any: makeOperator(function( params ) {
- var result = false;
+ any: makeFilter(function() {
+ var params = arguments;
for ( var i=0, len=params.length; i < len; i++ ) {
- if ( readAccessor(params[i]) ) result = true;
+ if ( params[i] ) return true;
}
- return result;
+ return false;
}),
// Reads the length of the accessed property:
// assumes accessor value to be an Array or Collection; defaults to 0.
- length: makeOperator(function( params ) {
- return readAccessor( params[0] ).length || 0;
+ length: makeFilter(function( value ) {
+ return value.length || 0;
}),
// Tests if none of the provided accessors are truthy (and not):
- none: makeOperator(function( params ) {
- var result = true;
+ none: makeFilter(function() {
+ var params = arguments;
for ( var i=0, len=params.length; i < len; i++ ) {
- if ( readAccessor(params[i]) ) result = false;
+ if ( params[i] ) return false;
}
- return result;
+ return true;
}),
// Negates an accessor's value:
- not: makeOperator(function( params ) {
- return !readAccessor( params[0] );
+ not: makeFilter(function( value ) {
+ return !value;
}),
// Formats one or more accessors into a text string:
// ("$1 $2 did $3", firstName, lastName, action)
- format: makeOperator(function( params ) {
- var str = readAccessor(params[0]);
+ format: makeFilter(function( str ) {
+ var params = arguments;
for ( var i=1, len=params.length; i < len; i++ ) {
// TODO: need to make something like this work: (?<!\\)\$1
- str = str.replace( new RegExp("\\$"+i, "g"), readAccessor(params[i]) );
+ str = str.replace( new RegExp("\\$"+i, "g"), params[i] );
}
return str;
}),
// Provides one of two values based on a ternary condition:
// uses first param (a) as condition, and returns either b (truthy) or c (falsey).
- select: makeOperator(function( params ) {
- var a = readAccessor(params[0]);
- var b = readAccessor(params[1]);
- var c = readAccessor(params[2]);
- return a ? b : c;
+ select: makeFilter(function( condition, truthy, falsey ) {
+ return condition ? truthy : falsey;
})
};
- // Partial application wrapper for creating binding operators:
- function makeOperator( handler ) {
- return function() {
- var params = arguments;
- return function() {
- return handler( params );
- };
- };
- }
+ // Define binding API:
+ Epoxy.binding = {
+ addHandler: function( name, handler ) {
+ bindingHandlers[ name ] = isFunction( handler ) ? {set:handler} : handler;
+ },
+ addFilter: function( name, handler ) {
+ bindingFilters[ name ] = makeFilter( handler );
+ },
+ config: function( settings ) {
+ _.extend( bindingSettings, settings );
+ }
+ };
// Epoxy.View
@@ -880,6 +964,7 @@
var sources = self.bindingSources;
var declarations = self.bindings;
var handlers = _.clone( bindingHandlers );
+ var filters = _.clone( bindingFilters );
var context = {};
// Compile a complete set of binding handlers for the view:
@@ -889,6 +974,12 @@
handlers[ name ] = isFunction(handler) ? {set: handler} : handler;
});
+ // Compile a complete set of binding filters for the view:
+ // mixes all custom filters into a copy of default filters.
+ _.each(self.bindingFilters||{}, function( filter, name ) {
+ filters[ name ] = makeFilter( filter );
+ });
+
// Add native "model" and "collection" data sources:
self.model = addSourceToViewContext( self.model, context );
self.collection = addSourceToViewContext( self.collection, context );
@@ -906,18 +997,13 @@
// Object declaration method:
// {"span.my-element": "text:attribute"}
- _.each(declarations, function( bindings, selector ) {
+ _.each(declarations, function( elementDecs, selector ) {
// Get DOM jQuery reference:
- var $elements = self.$( selector );
+ var $element = queryViewForSelector( self, selector );
- // Include top-level view in bindings search:
- if ( self.$el.is(selector) ) {
- $elements = $elements.add( self.$el );
- }
-
// Ignore empty DOM queries (without errors):
- if ( $elements.length ) {
- bindElementToView( self, $elements, bindings, context, handlers );
+ if ( $element.length ) {
+ bindElementToView( self, $element, elementDecs, context, handlers, filters );
}
});
@@ -926,18 +1012,10 @@
// DOM attributes declaration method:
// <span data-bind="text:attribute"></span>
- var selector = "["+ declarations +"]";
- var $elements = self.$( selector );
-
- // Include top-level view in bindings search:
- if ( self.$el.is(selector) ) {
- $elements = $elements.add( self.$el );
- }
-
// Create bindings for each matched element:
- $elements.each(function() {
+ queryViewForSelector( self, "["+declarations+"]" ).each(function() {
var $element = $(this);
- bindElementToView( self, $element, $element.attr(declarations), context, handlers );
+ bindElementToView( self, $element, $element.attr(declarations), context, handlers, filters );
});
}
},
@@ -955,17 +1033,10 @@
this.removeBindings();
viewSuper( this, "remove" );
}
-
- }, {
- // Define core components as static properties of the view:
- // these components are available through the "Epoxy.View" namespace for extension.
- defaultHandlers: bindingHandlers,
- defaultOperators: bindingOperators,
- defaultOptions: bindingOptions,
- makeOperator: makeOperator,
- readAccessor: readAccessor
});
+ // Epoxy.View -> Private
+ // ---------------------
// Adds a data source to a view:
// Data sources are Backbone.Model and Backbone.Collection instances.
@@ -983,9 +1054,6 @@
// Establish source prefix:
var prefix = name ? name+"_" : "";
- // Create a table of all model attributes (native and -optionally- observable):
- var attrs = _.extend({}, source.attributes, source.obs||{});
-
// Create a read-only accessor for the model instance:
context[ "$"+(name||"model") ] = function() {
viewMap && viewMap.push([source, "change"]);
@@ -993,30 +1061,13 @@
};
// Compile all model attributes as accessors within the context:
- _.each(attrs, function(value, attribute) {
+ _.each(source.toJSON({obs:true}), function(value, attribute) {
// Create named accessor functions:
// -> Attributes from "view.model" use their normal names.
// -> Attributes from additional sources are named as "source_attribute".
context[ prefix+attribute ] = function( value ) {
-
- // Register the attribute to the bindings map, if enabled:
- viewMap && viewMap.push([source, "change:"+attribute]);
-
- // Set attribute value when accessor is invoked with an argument:
- if ( !isUndefined(value) ) {
-
- // Set Object (non-null, non-array) hashtable value:
- if ( isObject(value) && !isArray(value) ) {
- return source.set( value );
- }
-
- // Set single attribute/value pair:
- return source.set( attribute, value );
- }
-
- // Get the attribute value by default:
- return source.get( attribute );
+ return accessViewDataAttribute( source, attribute, value );
};
});
@@ -1035,6 +1086,44 @@
return source;
}
+ // Attribute data accessor:
+ // exchanges individual attribute values with model sources.
+ // This function is broken out from the accessor creation process for performance.
+ // @param source: the model data source to interact with.
+ // @param attribute: the model attribute to read/write.
+ // @param value: the value to set, or "undefined" to get the current value.
+ function accessViewDataAttribute( source, attribute, value ) {
+ // Register the attribute to the bindings map, if enabled:
+ viewMap && viewMap.push([source, "change:"+attribute]);
+
+ // Set attribute value when accessor is invoked with an argument:
+ if ( !isUndefined(value) ) {
+
+ // Set Object (non-null, non-array) hashtable value:
+ if ( isObject(value) && !isArray(value) ) {
+ return source.set( value );
+ }
+
+ // Set single attribute/value pair:
+ return source.set( attribute, value );
+ }
+
+ // Get the attribute value by default:
+ return source.get( attribute );
+ }
+
+ // Queries element selectors within a view:
+ // matches elements within the view, and the view's container element.
+ function queryViewForSelector( view, selector ) {
+ var $elements = view.$( selector );
+
+ // Include top-level view in bindings search:
+ if ( view.$el.is(selector) ) {
+ $elements = $elements.add( view.$el );
+ }
+
+ return $elements;
+ }
// Binds an element into a view:
// The element's declarations are parsed, then a binding is created for each declared handler.
@@ -1043,38 +1132,31 @@
// @param declarations: the string of binding declarations provided for the element.
// @param context: a compiled binding context with all availabe view data.
// @param handlers: a compiled handlers table with all native/custom handlers.
- function bindElementToView( view, $element, declarations, context, handlers ) {
-
+ function bindElementToView( view, $element, declarations, context, handlers, filters ) {
+
// Parse localized binding context:
- // parsing function is invoked with "operators" and "context" properties made available,
+ // parsing function is invoked with "filters" and "context" properties made available,
// yeilds a native context object with element-specific bindings defined.
try {
- context = new Function("$o","$c","with($o){with($c){return{"+ declarations +"}}}")(bindingOperators, context);
+ var bindings = new Function("$f","$c","with($f){with($c){return{"+ declarations +"}}}")(filters, context);
} catch ( error ) {
throw( "Error parsing bindings: "+declarations );
}
- // Pick out special binding options from the main context:
- var options = _.pick(context, bindingOptions);
-
// Format the "events" option:
// include events from the binding declaration along with a default "change" trigger,
// then format all event names with a ".epoxy" namespace.
- options.events = _.map( _.union(options.events || [], ["change"]), function(name) {
+ var events = _.map( _.union(bindings.events || [], ["change"]), function(name) {
return name+".epoxy";
}).join(" ");
// Apply bindings from native context:
- _.each(_.omit(context, bindingOptions), function( accessor, handlerName ) {
+ _.each(bindings, function( accessor, handlerName ) {
// Validate that each defined handler method exists before binding:
if ( handlers.hasOwnProperty(handlerName) ) {
// Create and add binding to the view's list of handlers:
- view._bind.push( new EpoxyBinding($element, handlers[handlerName], accessor, options) );
- } else {
- // Invalid/undefined handler was declared:
- // <data-bind="sfoo:attribute"> -- "sfoo" does not exist.
- throw( "Invalid binding: "+handlerName );
+ view._bind.push( new EpoxyBinding($element, handlers[handlerName], accessor, events, context, bindings) );
}
});
}
@@ -1087,7 +1169,7 @@
// @param handler: the handler object to apply (include all handler methods).
// @param accessor: an accessor method from the binding context that exchanges data with the model.
// @param options: a compiled set of binding options that was pulled from the declaration.
- function EpoxyBinding( $element, handler, accessor, options ) {
+ function EpoxyBinding( $element, handler, accessor, events, context, bindings ) {
var self = this;
var tag = ($element[0].tagName).toLowerCase();
@@ -1098,10 +1180,12 @@
};
self.$el = $element;
- _.extend(self, handler, options);
+ self.evt = events;
+ _.extend( self, handler );
// Initialize the binding:
- self.init(self.$el, readAccessor(accessor));
+ // allow the initializer to redefine/modify the attribute accessor if needed.
+ accessor = self.init( self.$el, readAccessor(accessor), context, bindings ) || accessor;
// Set default binding, then initialize & map bindings:
// each binding handler is invoked to populate its initial value.
@@ -1115,7 +1199,7 @@
// => Binding handler has a getter method.
// => Value accessor is a function.
if ( changable && handler.get && isFunction(accessor) ) {
- self.$el.on(self.events, function() {
+ self.$el.on(events, function() {
accessor( self.get(self.$el, readAccessor(accessor)) );
});
}
@@ -1136,16 +1220,17 @@
init: blankMethod,
get: blankMethod,
set: blankMethod,
- wipe: blankMethod,
+ clean: blankMethod,
// Destroys the binding:
// all events and managed sub-views are killed.
dispose: function() {
- this.wipe();
+ this.clean();
this.stopListening();
- this.$el.off( this.events );
+ this.$el.off( this.evt );
this.$el = null;
}
});
-}).call( this );
+ return Epoxy;
+}));
View
4 backbone.epoxy.min.js
@@ -1,5 +1,5 @@
-// Backbone.Epoxy 0.10.1
+// Backbone.Epoxy 0.11.0
// (c) 2013 Greg MacWilliam
// Freely distributed under the MIT license
// http://epoxyjs.org
-(function(){function t(t){return function(e,n,i){return t.prototype[n].apply(e,i)}}function e(t,n,i,s){for(var o in n)if(n.hasOwnProperty(o)){var r=n[o];if(t.hasObservable(o)){if(s.length&&!(0>f.indexOf(s,o)))throw"Recursive setter: "+s.join(" > ");r=t.obs[o].set(r),r&&b(r)&&(i=e(t,r,i,s.slice().concat([o])))}else i[o]=r}return i}function n(t){return m(t)?t.slice():b(t)?f.clone(t):t}function i(t,e,n){n=n||{},n.get&&p(n.get)&&(n._get=n.get),n.set&&p(n.set)&&(n._set=n.set),delete n.get,delete n.set,f.extend(this,n),this.model=t,this.name=e,this.deps=this.deps||[],t._init||this.init()}function s(t){return p(t)?t():(b(t)&&(t=m(t)?t.slice():f.clone(t),f.each(t,function(e,n){t[n]=s(e)})),t)}function o(t){return function(){var e=arguments;return function(){return t(e)}}}function r(t,e,n){if(t){if(p(t)&&(t=t()),w(t)){var i=n?n+"_":"",s=f.extend({},t.attributes,t.obs||{});e["$"+(n||"model")]=function(){return x&&x.push([t,"change"]),t},f.each(s,function(n,s){e[i+s]=function(e){return x&&x.push([t,"change:"+s]),g(e)?t.get(s):b(e)&&!m(e)?t.set(e):t.set(s,e)}})}else y(t)&&(e["$"+(n||"collection")]=function(){return x&&x.push([t,"reset add remove sort update"]),t});return t}}function a(t,e,n,i,s){try{i=Function("$o","$c","with($o){with($c){return{"+n+"}}}")(C,i)}catch(o){throw"Error parsing bindings: "+n}var r=f.pick(i,B);r.events=f.map(f.union(r.events||[],["change"]),function(t){return t+".epoxy"}).join(" "),f.each(f.omit(i,B),function(n,i){if(!s.hasOwnProperty(i))throw"Invalid binding: "+i;t._bind.push(new c(e,s[i],n,r))})}function c(t,e,n,i){var o=this,r=t[0].tagName.toLowerCase(),a="input"==r||"select"==r||"textarea"==r,c=[],h=function(t){o.set(o.$el,s(n),t)};if(o.$el=t,f.extend(o,e,i),o.init(o.$el,s(n)),x=c,h(),x=null,a&&e.get&&p(n)&&o.$el.on(o.events,function(){n(o.get(o.$el,s(n)))}),c.length)for(var u=0,l=c.length;l>u;u++)o.listenTo(c[u][0],c[u][1],h)}var h,u=this,l=u.Backbone,f=u._,d=l.Epoxy={},v=Array.prototype,g=f.isUndefined,p=f.isFunction,b=f.isObject,m=f.isArray,w=function(t){return t instanceof l.Model},y=function(t){return t instanceof l.Collection},O=function(){},_=t(l.Model);d.Model=l.Model.extend({constructor:function(){this.obs={},_(this,"constructor",arguments),this._init=!0,this.observableDefaults&&f.each(this.observableDefaults,function(t,e){this.addObservable(e,p(t)?t():n(t))},this),this.computeds&&f.each(this.computeds,function(t,e){this.addComputed(e,t)},this),f.each(this.obs,function(t){t.init()}),delete this._init},get:function(t){return h&&h.push([t,this]),this.hasObservable(t)?this.obs[t].get():_(this,"get",arguments)},getCopy:function(t){return n(this.get(t))},set:function(t,n,i){var s=t;return s&&!b(s)?(s={},s[t]=n):i=n,i=i||{},i.unset||(s=e(this,s,{},[])),_(this,"set",[s,i])},destroy:function(){return this.clearObservables(),_(this,"destroy",arguments)},addObservable:function(t,e){return this.removeObservable(t),this.obs[t]=new i(this,t,{value:e}),this},addComputed:function(t,e,n){this.removeObservable(t);var s=e;if(p(e)){var o=2;s={},s._get=e,p(n)&&(s._set=n,o++),s.deps=v.slice.call(arguments,o)}return this.obs[t]=new i(this,t,s),this},hasObservable:function(t){return this.obs.hasOwnProperty(t)},removeObservable:function(t){return this.hasObservable(t)&&(this.obs[t].dispose(),delete this.obs[t]),this},clearObservables:function(){for(var t in this.obs)this.removeObservable(t);return this},modifyArray:function(t,e){var n=this.get(t);if(m(n)&&p(v[e])){var i=v.slice.call(arguments,2),s=v[e].apply(n,i);return this.trigger("change change:"+t),s}return null},modifyObject:function(t,e,n){var i=this.get(t),s=!1;return b(i)?(g(n)&&i.hasOwnProperty(e)?(delete i[e],s=!0):i[e]!==n&&(i[e]=n,s=!0),s&&this.trigger("change change:"+t),i):null}}),f.extend(i.prototype,l.Events,{init:function(){if(h=this.deps,this.get(!0),h=null,this.deps.length){var t={};f.each(this.deps,function(e){var n=this.model;m(e)&&(n=e[1],e=e[0]),e.indexOf("change:")&&(e="change:"+e),t.hasOwnProperty(e)?f.contains(t[e],n)||t[e].push(n):t[e]=[n]},this),f.each(t,function(t,e){for(var n=0,i=t.length;i>n;n++)this.listenTo(t[n],e,f.bind(this.get,this,!0))},this)}},get:function(t){if(t===!0&&this._get){var e=this._get.call(this.model);this.change(e)}return this.value},set:function(t){if(this._get){if(this._set)return this._set.apply(this.model,arguments);throw"Cannot set read-only computed observable."}return this.change(t),null},fire:function(){this.model.trigger("change change:"+this.name)},change:function(t){f.isEqual(t,this.value)||(this.value=t,this.fire())},dispose:function(){this.stopListening(),this.off(),this.model=this.value=null}});var x,E={attr:{set:function(t,e){t.attr(e)}},checked:{get:function(t,e){var n=!!t.prop("checked"),i=t.val();if(t.is(":radio"))return i;if(m(e)){e=e.slice();var s=f.indexOf(e,i);return n&&0>s?e.push(i):!n&&s>-1&&e.splice(s,1),e}return n},set:function(t,e){var n=!!e;t.is(":radio")?n=e==t.val():m(e)&&(n=f.contains(e,t.val())),t.prop("checked",n)}},classes:{set:function(t,e){f.each(e,function(e,n){t.toggleClass(n,!!e)})}},collection:{init:function(t,e){if(!y(e)||!p(e.view))throw"Binding 'collection' requires a Collection with a 'view' constructor.";this.v={}},set:function(t,e,n){var i,s=e.models,o=this.v;if(n=n||e,w(n))if(o.hasOwnProperty(n.cid))i=o[n.cid],i.remove(),delete o[n.cid];else{o[n.cid]=i=new e.view({model:n});var r=f.indexOf(s,n);r>t.children().length?t.eq(r).before(i.$el):t.append(i.$el)}else if(y(n)){var a=s.length&&f.every(s,function(t){return o.hasOwnProperty(t.cid)});t.hide(),a?e.each(function(e){t.append(o[e.cid].$el)}):(this.wipe(),e.each(function(n){o[n.cid]=i=new e.view({model:n}),t.append(i.$el)})),t.show()}},wipe:function(){for(var t in this.v)this.v.hasOwnProperty(t)&&(this.v[t].remove(),delete this.v[t])}},css:{set:function(t,e){t.css(e)}},disabled:{set:function(t,e){t.prop("disabled",!!e)}},enabled:{set:function(t,e){t.prop("disabled",!e)}},html:{set:function(t,e){t.html(e)}},options:{set:function(t,e){var n=this,i=s(n.optionsEmpty),o=s(n.optionsDefault),r=y(e)?e.models:e,a=t.val(),c=!0,h="";if(!r)throw"Binding 'options' is "+r;r.length||o||!i?(o&&(h+=n.opt(o)),f.each(r,function(t){h+=n.opt(t)})):(h+=n.opt(i),c=!1),t.html(h).prop("disabled",!c).val(a),f.isEqual(t.val(),a)||t.trigger("change")},opt:function(t){var e=t,n=t;return b(t)&&(e=w(t)?t.get("label"):t.label,n=w(t)?t.get("value"):t.value),"<option value='"+n+"'>"+e+"</option>"}},text:{set:function(t,e){t.text(e)}},toggle:{set:function(t,e){t.toggle(!!e)}},value:{get:function(t){return t.val()},set:function(t,e){t.val()!=e&&t.val(e)}}},B=["events","optionsDefault","optionsEmpty"],C={all:o(function(t){for(var e=!0,n=0,i=t.length;i>n;n++)s(t[n])||(e=!1);return e}),any:o(function(t){for(var e=!1,n=0,i=t.length;i>n;n++)s(t[n])&&(e=!0);return e}),length:o(function(t){return s(t[0]).length||0}),none:o(function(t){for(var e=!0,n=0,i=t.length;i>n;n++)s(t[n])&&(e=!1);return e}),not:o(function(t){return!s(t[0])}),format:o(function(t){for(var e=s(t[0]),n=1,i=t.length;i>n;n++)e=e.replace(RegExp("\\$"+n,"g"),s(t[n]));return e}),select:o(function(t){var e=s(t[0]),n=s(t[1]),i=s(t[2]);return e?n:i})},P=t(l.View);d.View=l.View.extend({constructor:function(){this._bind=[],P(this,"constructor",arguments),this.applyBindings()},bindings:"data-bind",applyBindings:function(){if(this.removeBindings(),this.model||this.collection||this.bindingSources){var t=this,e=t.bindingSources,n=t.bindings,i=f.clone(E),s={};if(f.each(t.bindingHandlers||{},function(t,e){i[e]=p(t)?{set:t}:t}),t.model=r(t.model,s),t.collection=r(t.collection,s),f.each(e,function(t,n){e[n]=r(t,s,n)}),b(n))f.each(n,function(e,n){var o=t.$(n);t.$el.is(n)&&(o=o.add(t.$el)),o.length&&a(t,o,e,s,i)});else{var o="["+n+"]",c=t.$(o);t.$el.is(o)&&(c=c.add(t.$el)),c.each(function(){var e=$(this);a(t,e,e.attr(n),s,i)})}}},removeBindings:function(){for(;this._bind.length;)this._bind.pop().dispose()},remove:function(){this.removeBindings(),P(this,"remove")}},{defaultHandlers:E,defaultOperators:C,defaultOptions:B,makeOperator:o,readAccessor:s}),f.extend(c.prototype,l.Events,{init:O,get:O,set:O,wipe:O,dispose:function(){this.wipe(),this.stopListening(),this.$el.off(this.events),this.$el=null}})}).call(this);
+(function(t,e){var n="backbone",i="underscore";"undefined"!=typeof exports?module.exports=e(require(i),require(n)):"function"==typeof define&&define.amd?define([i,n],e):e(t._,t.Backbone)})(this,function(t,e){function n(t){return function(e,n,i){return t.prototype[n].apply(e,i)}}function i(e,n,s,o){for(var r in n)if(n.hasOwnProperty(r)){var c=n[r];if(e.hasObservable(r)){if(o.length&&!(0>t.indexOf(o,r)))throw"Recursive setter: "+o.join(" > ");c=e.obs[r].set(c),c&&y(c)&&(s=i(e,c,s,o.slice().concat([r])))}else s[r]=c}return s}function s(e){return w(e)?e.slice():y(e)?t.clone(e):e}function o(e,n,i){i=i||{},i.get&&m(i.get)&&(i._get=i.get),i.set&&m(i.set)&&(i._set=i.set),delete i.get,delete i.set,t.extend(this,i),this.model=e,this.name=n,this.deps=this.deps||[],e._init||this.init()}function r(e){return m(e)?e():(y(e)&&(e=w(e)?e.slice():t.clone(e),t.each(e,function(t,n){e[n]=r(t)})),e)}function c(t){return function(){var e=arguments;return function(n){return t.apply(this,u(e,n))}}}function u(t,e){if(e)throw"binding error: filtered values are read-only.";for(var n=[],i=0,s=t.length;s>i;i++)n.push(r(t[i]));return n}function a(e,n,i){if(e){if(m(e)&&(e=e()),O(e)){var s=i?i+"_":"";n["$"+(i||"model")]=function(){return k&&k.push([e,"change"]),e},t.each(e.toJSON({obs:!0}),function(t,i){n[s+i]=function(t){return h(e,i,t)}})}else x(e)&&(n["$"+(i||"collection")]=function(){return k&&k.push([e,"reset add remove sort update"]),e});return e}}function h(t,e,n){return k&&k.push([t,"change:"+e]),b(n)?t.get(e):y(n)&&!w(n)?t.set(n):t.set(e,n)}function l(t,e){var n=t.$(e);return t.$el.is(e)&&(n=n.add(t.$el)),n}function f(e,n,i,s,o,r){try{var c=Function("$f","$c","with($f){with($c){return{"+i+"}}}")(r,s)}catch(u){throw"Error parsing bindings: "+i}var a=t.map(t.union(c.events||[],["change"]),function(t){return t+".epoxy"}).join(" ");t.each(c,function(t,i){o.hasOwnProperty(i)&&e._bind.push(new d(n,o[i],t,a,s,c))})}function d(e,n,i,s,o,c){var u=this,a=e[0].tagName.toLowerCase(),h="input"==a||"select"==a||"textarea"==a,l=[],f=function(t){u.set(u.$el,r(i),t)};if(u.$el=e,u.evt=s,t.extend(u,n),i=u.init(u.$el,r(i),o,c)||i,k=l,f(),k=null,h&&n.get&&m(i)&&u.$el.on(s,function(){i(u.get(u.$el,r(i)))}),l.length)for(var d=0,v=l.length;v>d;d++)u.listenTo(l[d][0],l[d][1],f)}var v,g=e.Epoxy={},p=Array.prototype,b=t.isUndefined,m=t.isFunction,y=t.isObject,w=t.isArray,O=function(t){return t instanceof e.Model},x=function(t){return t instanceof e.Collection},_=function(){},C=n(e.Model);g.Model=e.Model.extend({constructor:function(){this.obs={},C(this,"constructor",arguments),this._init=!0,this.observableDefaults&&t.each(this.observableDefaults,function(t,e){this.addObservable(e,m(t)?t():s(t))},this),this.computeds&&t.each(this.computeds,function(t,e){this.addComputed(e,t)},this),t.each(this.obs,function(t){t.init()}),delete this._init},get:function(t){return v&&v.push([t,this]),this.hasObservable(t)?this.obs[t].get():C(this,"get",arguments)},getCopy:function(t){return s(this.get(t))},set:function(t,e,n){var s=t;return s&&!y(s)?(s={},s[t]=e):n=e,n=n||{},n.unset||(s=i(this,s,{},[])),C(this,"set",[s,n])},toJSON:function(e){var n=C(this,"toJSON",arguments);return e&&e.obs&&t.each(this.obs,function(t,e){n[e]=t.value}),n},destroy:function(){return this.clearObservables(),C(this,"destroy",arguments)},addObservable:function(t,e){return this.removeObservable(t),this.obs[t]=new o(this,t,{value:e}),this},addComputed:function(t,e,n){this.removeObservable(t);var i=e;if(m(e)){var s=2;i={},i._get=e,m(n)&&(i._set=n,s++),i.deps=p.slice.call(arguments,s)}return this.obs[t]=new o(this,t,i),this},hasObservable:function(t){return this.obs.hasOwnProperty(t)},removeObservable:function(t){return this.hasObservable(t)&&(this.obs[t].dispose(),delete this.obs[t]),this},clearObservables:function(){for(var t in this.obs)this.removeObservable(t);return this},modifyArray:function(t,e){var n=this.get(t);if(w(n)&&m(p[e])){var i=p.slice.call(arguments,2),s=p[e].apply(n,i);return this.trigger("change change:"+t),s}return null},modifyObject:function(t,e,n){var i=this.get(t),s=!1;return y(i)?(b(n)&&i.hasOwnProperty(e)?(delete i[e],s=!0):i[e]!==n&&(i[e]=n,s=!0),s&&this.trigger("change change:"+t),i):null}}),t.extend(o.prototype,e.Events,{init:function(){if(v=this.deps,this.get(!0),v=null,this.deps.length){var e={};t.each(this.deps,function(n){var i=this.model;w(n)&&(i=n[1],n=n[0]),n.indexOf("change:")&&(n="change:"+n),e.hasOwnProperty(n)?t.contains(e[n],i)||e[n].push(i):e[n]=[i]},this),t.each(e,function(e,n){for(var i=0,s=e.length;s>i;i++)this.listenTo(e[i],n,t.bind(this.get,this,!0))},this)}},get:function(t){if(t===!0&&this._get){var e=this._get.call(this.model);this.change(e)}return this.value},set:function(t){if(this._get){if(this._set)return this._set.apply(this.model,arguments);throw"Cannot set read-only computed observable."}return this.change(t),null},fire:function(){this.model.trigger("change change:"+this.name)},change:function(e){t.isEqual(e,this.value)||(this.value=e,this.fire())},dispose:function(){this.stopListening(),this.off(),this.model=this.value=null}});var E={optionText:"label",optionValue:"value"},P={attr:{set:function(t,e){t.attr(e)}},checked:{get:function(e,n){var i=!!e.prop("checked"),s=e.val();if(e.is(":radio"))return s;if(w(n)){n=n.slice();var o=t.indexOf(n,s);return i&&0>o?n.push(s):!i&&o>-1&&n.splice(o,1),n}return i},set:function(e,n){var i=!!n;e.is(":radio")?i=n==e.val():w(n)&&(i=t.contains(n,e.val())),e.prop("checked",i)}},classes:{set:function(e,n){t.each(n,function(t,n){e.toggleClass(n,!!t)})}},collection:{init:function(t,e){if(!x(e)||!m(e.view))throw"Binding 'collection' requires a Collection with a 'view' constructor.";this.v={}},set:function(e,n,i){var s,o=n.models,r=this.v;if(i=i||n,O(i))if(r.hasOwnProperty(i.cid))s=r[i.cid],s.remove(),delete r[i.cid];else{r[i.cid]=s=new n.view({model:i});var c=t.indexOf(o,i);c>e.children().length?e.eq(c).before(s.$el):e.append(s.$el)}else if(x(i)){var u=o.length&&t.every(o,function(t){return r.hasOwnProperty(t.cid)});e.hide(),u?n.each(function(t){e.append(r[t.cid].$el)}):(this.clean(),n.each(function(t){r[t.cid]=s=new n.view({model:t}),e.append(s.$el)})),e.show()}},clean:function(){for(var t in this.v)this.v.hasOwnProperty(t)&&(this.v[t].remove(),delete this.v[t])}},css:{set:function(t,e){t.css(e)}},disabled:{set:function(t,e){t.prop("disabled",!!e)}},enabled:{set:function(t,e){t.prop("disabled",!e)}},html:{set:function(t,e){t.html(e)}},options:{init:function(t,e,n,i){this.e=i.optionsEmpty,this.d=i.optionsDefault,this.v=i.value},set:function(e,n){var i=this,s=r(i.e),o=r(i.d),c=r(i.v),u=w(c)?c:[c],a=x(n)?n.models:n,h=a.length,l=!0,f="";h||o||!s?(o&&(a=[o].concat(a)),t.each(a,function(t){f+=i.opt(t,h,u)})):(f+=i.opt(s,h,u),l=!1),e.html(f).prop("disabled",!l);var d=e.val();i.v&&!t.isEqual(c,d)&&i.v(d)},opt:function(e,n,i){var s=e,o=e,r=E.optionText,c=E.optionValue;y(e)&&(s=O(e)?e.get(r):e[r],o=O(e)?e.get(c):e[c]);var u=!n||t.contains(i,o)?"' selected='selected'>":"'>";return"<option value='"+o+u+s+"</option>"},clean:function(){this.d=this.e=this.v=null}},template:{init:function(e,n,i){var s=e.find("script,template");return this.tmpl=t.template(s.length?s.html():e.html()),w(n)?t.pick(i,n):void 0},set:function(t,e){e=O(e)?e.toJSON({obs:!0}):e,t.html(this.tmpl(e))},clean:function(){this.tmpl=null}},text:{set:function(t,e){t.text(e)}},toggle:{set:function(t,e){t.toggle(!!e)}},value:{get:function(t){return t.val()},set:function(t,e){try{t.val()!=e&&t.val(e)}catch(n){}}}},B={all:c(function(){for(var t=arguments,e=0,n=t.length;n>e;e++)if(!t[e])return!1;return!0}),any:c(function(){for(var t=arguments,e=0,n=t.length;n>e;e++)if(t[e])return!0;return!1}),length:c(function(t){return t.length||0}),none:c(function(){for(var t=arguments,e=0,n=t.length;n>e;e++)if(t[e])return!1;return!0}),not:c(function(t){return!t}),format:c(function(t){for(var e=arguments,n=1,i=e.length;i>n;n++)t=t.replace(RegExp("\\$"+n,"g"),e[n]);return t}),select:c(function(t,e,n){return t?e:n})};g.binding={addHandler:function(t,e){P[t]=m(e)?{set:e}:e},addFilter:function(t,e){B[t]=c(e)},config:function(e){t.extend(E,e)}};var k,q=n(e.View);return g.View=e.View.extend({constructor:function(){this._bind=[],q(this,"constructor",arguments),this.applyBindings()},bindings:"data-bind",applyBindings:function(){if(this.removeBindings(),this.model||this.collection||this.bindingSources){var e=this,n=e.bindingSources,i=e.bindings,s=t.clone(P),o=t.clone(B),r={};t.each(e.bindingHandlers||{},function(t,e){s[e]=m(t)?{set:t}:t}),t.each(e.bindingFilters||{},function(t,e){o[e]=c(t)}),e.model=a(e.model,r),e.collection=a(e.collection,r),t.each(n,function(t,e){n[e]=a(t,r,e)}),y(i)?t.each(i,function(t,n){var i=l(e,n);i.length&&f(e,i,t,r,s,o)}):l(e,"["+i+"]").each(function(){var t=$(this);f(e,t,t.attr(i),r,s,o)})}},removeBindings:function(){for(;this._bind.length;)this._bind.pop().dispose()},remove:function(){this.removeBindings(),q(this,"remove")}}),t.extend(d.prototype,e.Events,{init:_,get:_,set:_,clean:_,dispose:function(){this.clean(),this.stopListening(),this.$el.off(this.evt),this.$el=null}}),g});
View
2  package.json
@@ -15,7 +15,7 @@
"backbone": ">=0.9.9"
},
"main": "backbone.epoxy.min.js",
- "version": "0.10.1",
+ "version": "0.11.0",
"devDependencies": {
"grunt": "~0.4.1",
"grunt-contrib-uglify": "~0.1.2"
View
29 test/epoxy-test.js
@@ -96,6 +96,19 @@ describe("Backbone.Epoxy.Model", function() {
});
+ it("should get native model attributes using '.toJSON()'.", function() {
+ var json = model.toJSON();
+ expect( _.size(json) ).toBe( 3 );
+ });
+
+
+ it("should get native and observable model attributes using '.toJSON({obs:true})'.", function() {
+ var json = model.toJSON({obs:true});
+ expect( _.size(json) ).toBe( 9 );
+ expect( json.fullName ).toBe( "Charlie Brown" );
+ });
+
+
// Deprecating this feature within the published API...
it("should allow direct access to observable property values using their own getters and setters.", function() {
var sel = model.obs[ "isSelected" ];
@@ -397,7 +410,6 @@ describe("Backbone.Epoxy.View", function() {
// Basic bindings test view:
-
var domView = new (Backbone.Epoxy.View.extend({
el: "#dom-view",
model: bindingModel,
@@ -418,7 +430,6 @@ describe("Backbone.Epoxy.View", function() {
}
}));
-
// Modifiers / Collections testing view:
var modView = new (Backbone.Epoxy.View.extend({
@@ -783,7 +794,7 @@ describe("Backbone.Epoxy.View", function() {
expect( $el.html() ).toMatch( /<strong>Skywalker<\/strong>, Anakin/i );
});
-
+
it("binding 'options:' should bind an array of strings to a select element's options.", function() {
var $el = $(".test-select");
bindingModel.set("optionsList", ["Luke", "Leia"]);
@@ -907,6 +918,18 @@ describe("Backbone.Epoxy.View", function() {
expect( $el.find(":first-child").text() ).toBe( "empty" );
});
+
+ it("binding 'template:' should render a bound Model with a provided template reference.", function() {
+ var $el = $(".test-template");
+
+ });
+
+
+ it("binding 'template:' should render a bound Object with a provided template reference.", function() {
+ var $el = $(".test-template");
+
+ });
+
it("binding 'text:' should establish a one-way binding with an element's text contents.", function() {
var $el = $(".test-text-first");
View
22 test/index.html
@@ -42,7 +42,7 @@
</style>
</head>
<body>
-
+ <div id="ie-console"></div>
<div id="dom-view" class="test-view">
<strong>DOM / Attribute Mapping</strong>
<div class="section">
@@ -115,6 +115,11 @@
<p><select class="test-select-multi" multiple="multiple" data-bind="value:valMulti,options:optionsList"></select></p>
</div>
<div class="section">
+ <div class="test-template" data-bind="template:['firstName','lastName']">
+ <script type="text/tmpl"><p>{{ firstName }} {{ lastName }}</p></script>
+ </div>
+ </div>
+ <div class="section">
<strong>Collection Management</strong>
<input class="name-input" type="text" value="">
<button class="name-add">Add</button>
@@ -129,12 +134,25 @@
</div>
</script>
+ <script src="../www/js/json2.js"></script>
<script src="../www/js/jquery.js"></script>
- <script src="../www/js/lodash.js"></script>
+ <script src="../www/js/underscore.js"></script>
<script src="../www/js/backbone.js"></script>
<script src="../backbone.epoxy.js"></script>
+ <script>
+ if (!window.console) {
+ console = {
+ log: function( mssg ) {
+ $("#ie-console").append("<p>"+mssg+"</p>");
+ }
+ };
+ }
+
+ _.templateSettings = { interpolate : /\{\{(.+?)\}\}/g };
+ </script>
<script src="epoxy-test.js"></script>
<script type="text/javascript">
+
$(function() {
var jasmineEnv = jasmine.getEnv();
jasmineEnv.updateInterval = 1000;
View
229 www/documentation.html
@@ -34,8 +34,8 @@
<div class="banner clearfix" role="banner">
<p class="title"><span><b>Epoxy</b>.js</span> Elegant Data Binding for Backbone</p>
<div class="download">
- <a href="js/backbone.epoxy.min.js" class="download-button">Download Epoxy 0.10.0</a>
- <p class="download-info">8k min, 2k gzip <i>|</i> <a href="https://github.com/gmac/backbone.epoxy">GitHub Full Source</a></p>
+ <a href="js/backbone.epoxy.min.js" class="download-button">Download Epoxy 0.11.0</a>
+ <p class="download-info">9k min, 2k gzip <i>|</i> <a href="https://github.com/gmac/backbone.epoxy">GitHub Full Source</a></p>
</div>
</div>
<div class="navigation" role="navigation">
@@ -63,6 +63,7 @@
<li><a href="#model-observable-defaults">observableDefaults</a></li>
<li><a href="#model-remove-observable">removeObservable</a></li>
<li><a href="#model-set">set</a></li>
+ <li><a href="#model-tojson">toJSON</a></li>
</ul>
<b><a href="#view">Epoxy.View</a></b>
@@ -71,6 +72,7 @@
<li><b><a href="#view-constructor">constructor</a></b></li>
<li><b><a href="#view-binding-context">binding context</a></b></li>
<li><a href="#view-apply-bindings">applyBindings</a></li>
+ <li><a href="#view-binding-filters">bindingFilters</a></li>
<li><a href="#view-binding-handlers">bindingHandlers</a></li>
<li><a href="#view-bindings">bindings</a></li>
<li><a href="#view-binding-sources">bindingSources</a></li>
@@ -92,20 +94,28 @@
<li><a href="#handler-options">options</a></li>
<li><a href="#handler-options-default">optionsDefault</a></li>
<li><a href="#handler-options-empty">optionsEmpty</a></li>
+ <li><a href="#handler-template">template</a></li>
<li><a href="#handler-text">text</a></li>
<li><a href="#handler-toggle">toggle</a></li>
<li><a href="#handler-value">value</a></li>
</ul>
- <b><a href="#binding-operators">View Binding Operators</a></b>
+ <b><a href="#binding-filters">View Binding Filters</a></b>
<ul>
- <li><a href="#operator-all">all</a></li>
- <li><a href="#operator-any">any</a></li>
- <li><a href="#operator-format">format</a></li>
- <li><a href="#operator-length">length</a></li>
- <li><a href="#operator-none">none</a></li>
- <li><a href="#operator-not">not</a></li>
- <li><a href="#operator-select">select</a></li>
+ <li><a href="#filter-all">all</a></li>
+ <li><a href="#filter-any">any</a></li>
+ <li><a href="#filter-format">format</a></li>
+ <li><a href="#filter-length">length</a></li>
+ <li><a href="#filter-none">none</a></li>
+ <li><a href="#filter-not">not</a></li>
+ <li><a href="#filter-select">select</a></li>
+ </ul>
+
+ <b><a href="#binding">Epoxy.binding</a></b>
+ <ul>
+ <li><a href="#binding-add-filter">addFilter</a></li>
+ <li><a href="#binding-add-handler">addHandler</a></li>
+ <li><a href="#binding-config">config</a></li>
</ul>
</div>
</div>
@@ -333,6 +343,12 @@ <h3 id="model-set">set</h3>
</div>
<div class="section">
+ <h3 id="model-tojson">toJSON</h3>
+ <code>model.toJSON([options])</code>
+ <p>Override wrapper for the Backbone Model's native <b>toJSON</b> method. Allows an optional <tt>{obs:true}</tt> param to be passed, specifying that observable/computed attribute values should be included in the returned data.</p>
+ </div>
+
+ <div class="section">
<h2 id="view">Epoxy.View</h2>
<p>The Epoxy View object extends <tt>Backbone.View</tt>, providing a new view abstract to be extended into your application.</p>
@@ -372,6 +388,30 @@ <h3 id="view-apply-bindings">applyBindings</h3>
</div>
<div class="section">
+ <h3 id="view-binding-filters">bindingFilters</h3>
+ <code>view.bindingFilters</code>
+ <p>A hash table defining a view's custom binding filters. Binding filters are used to format model data for display within the view. While Epoxy includes a <a href="#binding-filters">core set</a> of basic binding filters for common formatting operations, developers are encouraged to extend their views with customized binding filters.</p>
+
+ <p>To declare binding filters, define a new property on the <b>bindingFilters</b> hash with a formatting function that receives data arguments and returns a formatted value:</p>
+
+<pre><code class="js">var BindingView = Backbone.Epoxy.View.extend({
+ bindingFilters: {
+ yourName: function( firstName, lastName ) {
+ return "Your name is: "+ firstName +" "+ lastName;
+ }
+ }
+});</code></pre>
+
+ <p>The above filter definition would be bound as:</p>
+
+<pre><code class="html">&lt;div data-bind="text:yourName(firstName,lastName)"&gt;&lt;/div&gt;</code></pre>
+
+ <p>Binding filter functions are called anonymously and operate in global scope. Note that filters generate read-only values, therefore may only be applied to one-way binding handlers. Filters applied to two-way handlers will throw an error when data is submitted back into the filter.</p>
+
+ <p>Also of note – unlike computed model properties, you may safely use conditional logic in filter functions without creating holes in the dependency graph. Epoxy has enough information about filter functions to pre-process their arguments and guarantee their dependency mapping.</p>
+ </div>
+
+ <div class="section">
<h3 id="view-binding-handlers">bindingHandlers</h3>
<code>view.bindingHandlers</code>
<p>A hash table defining a view's custom binding handlers. Binding handlers are used to interchange model data with elements in the view. While Epoxy includes a <a href="#binding-handlers">core set</a> of basic binding handlers for managing an element's content and formatting, developers are encouraged to extend their views with customized binding handlers.</p>
@@ -463,7 +503,7 @@ <h3 id="view-binding-sources">bindingSources</h3>
<p class="draft">This API is a working draft. Implementation may change.</p>
<p>A hash table defining additional binding sources to be added to the view's binding context. Binding sources are instances of <tt>Backbone.Model</tt> and/or <tt>Backbone.Collection</tt>. By default, an Epoxy view configures two potential binding sources: the view's <b>model</b> and <b>collection</b> properties are automatically added to the binding context under the aliases <tt>$model</tt> and <tt>$collection</tt>. Additional model and collection data sources may be specified within the <b>bindingSources</b> hash; these additional data sources will be added into the binding context under the alias <tt>$sourceName</tt>.</p>
- <p>Additionally, model data sources provide aliases to all of their attributes within the binding context. In the case of the default <tt>$model</tt> data source, all model attributes are aliased within the binding context using their normal <tt>attribute</tt> name. For additional model sources added through the <b>bindingSources</b> hash, model attributes are added under the alias <tt>source_attributeName</tt>. This avoids attribute naming conflicts while binding multiple models of the same type.</p>
+ <p>Additionally, model data sources provide aliases to all of their attributes within the binding context. In the case of the default <tt>$model</tt> data source, all model attributes are aliased within the binding context using their normal <tt>attribute</tt> name. For additional model sources added through the <b>bindingSources</b> hash, model attributes are added under the alias <tt>source_attribute</tt>. This avoids attribute naming conflicts while binding multiple models of the same type.</p>
<pre>
<code class="js">// Epoxy view specifying five data sources:
@@ -484,9 +524,9 @@ <h3 id="view-binding-sources">bindingSources</h3>
$han: &lt;Backbone.Model&gt;,
$obiwan: &lt;Backbone.Model&gt;,
$users: &lt;Backbone.Collection&gt;,
- name: &lt;$model:name&gt;,
- han_name: &lt;$han:name&gt;,
- obiwan_name: &lt;$obiwan:name&gt;
+ name: &lt;$model.name&gt;,
+ han_name: &lt;$han.name&gt;,
+ obiwan_name: &lt;$obiwan.name&gt;
}
</code>
</pre>
@@ -709,6 +749,74 @@ <h3 id="handler-options-empty">optionsEmpty</h3>
</div>
<div class="section">
+ <h3 id="handler-template">template</h3>
+ <span class="alias">read-only</span>
+ <code>data-bind="template:$source"</code>
+ <p>The <b>template</b> binding extracts its element's HTML content while initializing, and then parses that content into an Underscore template using the bound data. You have some options on how both the template string and the bound data sources are defined.</p>
+
+ <p><b>Defining the template string</b></p>
+
+ <p>When the <b>template</b> binding initializes, it will extract content from its element to use as the template string. However, to safeguard against malformed HTML within the DOM, the <b>template</b> binding will first search its element for a <tt>&lt;script&gt;</tt> or a <tt>&lt;template&gt;</tt> tag to extract content from, such as:</p>
+
+<pre><code class="html">&lt;div data-bind="template:$model"&gt;
+ &lt;script type="text/tmpl"&gt;&lt;%= firstName %&gt; &lt;%= lastName %&gt;&lt;/script&gt;
+&lt;/div&gt;
+
+&lt;div data-bind="template:$model"&gt;
+ &lt;template&gt;&lt;%= firstName %&gt; &lt;%= lastName %&gt;&lt;/template&gt;
+&lt;/div&gt;</code></pre>
+
+ <p>If a <tt>&lt;script&gt;</tt> or <tt>&lt;template&gt;</tt> container is <i>not</i> found within the element, then the element's entire contents are parsed as the template string. If you choose this approach, then it's highly recommended that you modify Underscore's <a href="http://underscorejs.org/#template">templateSettings</a> param to choose more DOM-friendly field delimiters. Also, to avoid the infamous <a href="http://www.bluerobot.com/web/css/fouc.asp/">FOUC</a>, you're best off hiding the element with CSS and then toggle it's visibility with a binding:</p>
+
+<pre><code class="html">&lt;div style="display:none;" data-bind="template:$model,toggle:true"&gt;
+ {{ firstName }} {{ lastName }}
+&lt;/div&gt;</code></pre>
+
+ <p><b>Defining data sources</b></p>
+
+ <p>The <b>template</b> binding may specify data sources in a few different ways. First, let's assume the following example view configuration:</p>
+
+<pre><code class="js">var MyView = Backbone.Epoxy.View.extend({
+ el: "#my-view",
+ model: new Backbone.Model({
+ firstName: "Luke",
+ lastName: "Skywalker",
+ isActive: false
+ });
+});</code></pre>
+
+ <ul>
+ <li>
+ <p><b>Model Source</b> : given the above <tt>MyView</tt> example, the <b>template</b> binding may reference a <tt>Backbone.Model</tt> data source directly through its context reference (<tt>$sourceName</tt>), such as:</p>
+
+<pre><code class="html">&lt;div id="my-view" data-bind="template:$model"&gt;
+ &lt;template&gt;&lt;%= firstName %&gt; &lt;%= lastName %&gt;&lt;/template&gt;
+&lt;/div&gt;</code></pre>
+
+ <p>When binding to a model source, all model attributes will be available to the template (this is equivalent to providing <tt>model.toJSON()</tt> to the template renderer). While this is a quick and easy binding method, it may not be the most efficient. The bound template will re-render in response to <i>all</i> model changes, rather than just changes to specific attributes used in the template. In this scenario, our template would re-render in response to <tt>isActive</tt> changing, even though it's not included within the template.</p>
+ </li>
+ <li>
+ <p><b>Array Source</b> : Given the above <tt>MyView</tt> example, the <b>template</b> binding may specify an array of attribute name strings to bind on, such as:</p>
+
+<pre><code class="html">&lt;div id="my-view" data-bind="template:['firstName','lastName']"&gt;
+ &lt;template&gt;&lt;%= firstName %&gt; &lt;%= lastName %&gt;&lt;/template&gt;
+&lt;/div&gt;</code></pre>
+
+ <p>When binding on attribute names, only the specified names are made available to the template. Also, our template will only re-render in response to changes in these attributes (in this scenario, changing the <tt>isActive</tt> attribute will no longer trigger a template refresh).</p>
+ </li>
+ <li>
+ <p><b>Hash Source</b> : Given the above <tt>MyView</tt> example, the <b>template</b> binding may define an object hash that maps keys to data attributes, such as:</p>
+
+<pre><code class="html">&lt;div id="my-view" data-bind="template:{first:firstName, last:lastName}"&gt;
+ &lt;template&gt;&lt;%= first %&gt; &lt;%= last %&gt;&lt;/template&gt;
+&lt;/div&gt;</code></pre>
+
+ <p>When defining a mapping hash, the declared object keys are the variables available within the template, and their values are bound attributes from the model. This is similar to the array declaration method (which effectively builds this hash mapping for you with matching key/value pairs). Like the array method, the template will only re-render in response to changes in the specifically referenced model attributes.</p>
+ </li>
+ </ul>
+ </div>
+
+ <div class="section">
<h3 id="handler-text">text</h3>
<span class="alias">read-only</span>
<code>data-bind="text:modelAttribute"</code>
@@ -730,56 +838,129 @@ <h3 id="handler-value">value</h3>
</div>
<div class="section">
- <h2 id="binding-operators">View Binding Operators</h2>
- <p>Binding operators provide a layer of flexibility for formatting data attributes directly within a binding declaration; they allow values to be formatted for binding-specific implementations:</p>
+ <h2 id="binding-filters">View Binding Filters</h2>
+ <p>Binding filters provide a layer of flexibility for formatting data attributes directly within a binding declaration; they allow values to be formatted for binding-specific implementations:</p>
<pre><code class="html">&lt;span data-bind="toggle:not(firstName)"&gt;Please enter a first name.&lt;/span&gt;</code></pre>
- <p>Binding operators may be parameterized with any data attribute available in the <a href="#view-binding-context">binding context</a>, or with primitive values (<tt>String</tt>, <tt>Number</tt>, or <tt>Boolean</tt>). Be aware that you may <strong>NOT</strong> nest binding operators within one another &mdash; this is very deliberate, and a principle on which Epoxy is fairly opinionated: application logic does not belong within a binding declaration. If you need to perform multi-pass value transformations, that work should be done within your model or else in a custom binding handler.</p>
+ <p>Binding filters may be parameterized with any data attribute(s) available in the <a href="#view-binding-context">binding context</a>, or with primitive values (<tt>String</tt>, <tt>Number</tt>, or <tt>Boolean</tt>). However, binding filters may <b>NOT</b> be nested within one another &mdash; this is very deliberate, and a principle on which Epoxy is opinionated: application logic does not belong within binding declarations. If you need to perform multi-pass value filtering, that work should either be done within your model or else through a single custom binding filter and/or handler.</p>
+
+ <p>Also note: binding filters are <b>one-way</b> sources, meaning they can format data for the view, but can <b>not</b> submit it back to the model. Assigning a filter to a two-way binding will throw an error when data is passed back into the filter.</p>
</div>
<div class="section">
- <h3 id="operator-all">all</h3>
+ <h3 id="filter-all">all</h3>
<code>data-bind="toggle:all(dataAttribute,[...])"</code>
<p>Assesses one or more data attributes as <tt>true</tt> if <i>all</i> attributes are truthy.</p>
</div>
<div class="section">
- <h3 id="operator-any">any</h3>
+ <h3 id="filter-any">any</h3>
<code>data-bind="toggle:any(dataAttribute,[...])"</code>
<p>Assesses one or more data attributes as <tt>true</tt> if <i>any</i> of the attributes are truthy.</p>
</div>
<div class="section">
- <h3 id="operator-format">format</h3>
+ <h3 id="filter-format">format</h3>
<code>data-bind="text:format('$1 of $2',dataAttribute,dataAttribute)"</code>
<p>Formats a string with a series of data attribute replacements. Operates the same as string replacement for <tt>RegExp</tt> capture groups, where field insertions are denoted as <tt>"$1,&nbsp;$2,&nbsp;...&nbsp;$n"</tt>. Escape intentional "$n" patterns within the template string as "\$n". Note that the first value insertion index is 1, not 0.</p>
</div>
<div class="section">
- <h3 id="operator-length">length</h3>
+ <h3 id="filter-length">length</h3>
<code>data-bind="toggle:length(dataAttribute)"</code>
<p>Returns the length of an <tt>Array</tt> or <tt>Collection</tt> data attribute, or <tt>0</tt> by default. Useful for loosely-typed assessments of a collection's content status.</p>
</div>
<div class="section">
- <h3 id="operator-none">none</h3>
+ <h3 id="filter-none">none</h3>
<code>data-bind="toggle:none(dataAttribute,[...])"</code>
<p>Assesses one or more data attributes as <tt>true</tt> if <i>none</i> of the attributes are truthy.</p>
</div>
<div class="section">
- <h3 id="operator-not">not</h3>
+ <h3 id="filter-not">not</h3>
<code>data-bind="toggle:not(dataAttribute)"</code>
<p>Inverts the loosely-typed boolean value of a data attribute.</p>
</div>
<div class="section">
- <h3 id="operator-select">select</h3>
+ <h3 id="filter-select">select</h3>
<code>data-bind="text:select(conditionalValue,truthyValue,falseyValue)"</code>
<p>Performs a ternary operation using JavaScript's <tt>?:</tt> operator. The <b>select</b> operation receives three arguments: the first argument is assessed as a loosely-typed conditional; if truthy, the second argument is returned; if falsey, the third argument is returned.</p>
</div>
+ <div class="section">
+ <h2 id="binding">Epoxy.binding</h2>
+ <code>Backbone.Epoxy.binding</code>
+ <p>The <tt>Epoxy.binding</tt> API allows customization of Epoxy's default binding capabilities and configuration of binding settings.</p>
+ </div>
+
+ <div class="section">
+ <h3 id="binding-add-filter">addFilter</h3>
+ <code>Backbone.Epoxy.binding.addFilter( name, filter )</code>
+ <p>Adds a <a href="#binding-filters">binding filter</a> to Epoxy's table of internal defaults. Filters added with this method become available to all Epoxy views. Filters are added with a name string and a filter function, and return a modified binding value. Filters create read-only values, so should only be applied to one-way binding handlers. All filter arguments are pre-processed by the filter, so you may safely use conditional logic in filters without breaking automated dependency mapping:</p>
+
+<pre><code class="js">Backbone.Epoxy.binding.addFilter("select", function(condition, truthy, falsey) {
+ return condition ? truthy : falsey;
+});</code></pre>
+ </div>
+
+ <div class="section">
+ <h3 id="binding-add-handler">addHandler</h3>
+ <code>Backbone.Epoxy.binding.addHandler( name, handler )</code>
+ <p>Adds a <a href="#binding-handlers">binding handler</a> to Epoxy's table of internal defaults. Handlers added with this method become available to all Epoxy views. Binding handlers are added with a name string and a handler function or methods object. A simple one-way binding handler (writes to the DOM) may be defined as:</p>
+
+<pre><code class="js">Backbone.Epoxy.binding.addHandler("readonly", function( $element, value ) {
+ $element.prop( "readonly", !!value );
+});</code></pre>
+
+ <p>Binding handlers may also be defined as a methods object that provides functions for getting/setting the element value, and initialization/cleanup of the handler:</p>
+
+<pre><code class="js">Backbone.Epoxy.binding.addHandler("handler_name", {
+ init: function( $element, value, bindings, context ) {
+ // Initialize the binding handler...
+ },
+ get: function( $element, value ) {
+ // Get data from the bound element...
+ return $element.val();
+ },
+ set: function( $element, value ) {
+ // Set data into the bound element...
+ $element.val( value );
+ },
+ clean: function() {
+ // Cleanup the binding handler...
+ }
+});</code></pre>
+
+ <ul>
+ <li><tt>handler.init( $element, value, bindings, context )</tt> : <i>optional</i>. A handler's <b>init</b> method is called immediately to configure the handler. This is generally only needed for complex handlers that store operational data on themselves. The method receives the bound element (as jQuery) and the bound data value as arguments, as well as the hash of parsed bindings being applied to the element, and a reference to the full binding context of all available view data. The <tt>bindings</tt> and <tt>context</tt> arguments are handy for pulling additional data into the handler if needed. The <b>init</b> method may also return a reformatted version of its managed value, although this is only recommended for developers with insight into Epoxy internals. If you're not sure, don't return anything.</li>
+
+ <li><tt>handler.get( $element, value )</tt> : <i>required for two-way bindings</i>. Reads and returns the bound element's value. This should poll data from the DOM element, and then return the value formatted for the model. The existing model value is provided for comparative reference.</li>
+
+ <li><tt>handler.set( $element, value )</tt> : <i>required</i>. Writes a data value into the DOM element. Receives the element (as jQuery) and the value to set, and returns nothing.</li>
+
+ <li><tt>handler.clean()</tt> : <i>optional</i>. Called during handler disposal; offers a cleanup hook to remove any custom binding configuration as the handler is deprecated.</li>
+ </ul>
+ </div>
+
+ <div class="section">
+ <h3 id="binding-config">config</h3>
+ <code>Backbone.Epoxy.binding.config( settings )</code>
+ <p>Merges a settings object into the bindings API configuration. The following settings may be passed:</p>
+
+ <ul>
+ <li><tt>optionText</tt> : configures the <b>options</b> binding. Specifies an attribute name to pick from objects and/or models as the text written into <tt>option</tt> elements. Default is <tt>"label"</tt>.</li>
+ <li><tt>optionValue</tt> : configures the <b>options</b> binding. Specifies an attribute name to pick from objects and/or models as the value written into <tt>option</tt> elements. Default is <tt>"value"</tt>.</li>
+ </ul>
+
+<pre><code class="js">Backbone.Epoxy.binding.config({
+ optionText: "optText",
+ optionValue: "optValue"
+});</code></pre>
+ </div>
+
<div class="footer" role="contentinfo">
<p>Epoxy.js for Backbone is available on <a href="https://github.com/gmac/backbone.epoxy">GitHub</a>.<br>
&copy; 2013 Greg MacWilliam (<a href="https://twitter.com/gmacwilliam">@gmacwilliam</a>), a project of <a href="http://www.threespot.com">Threespot</a>.</p>
View
6 www/index.html
@@ -33,8 +33,8 @@
<div class="banner clearfix" role="banner">
<p class="title"><span><b>Epoxy</b>.js</span> Elegant Data Binding for Backbone</p>
<div class="download">
- <a href="js/backbone.epoxy.min.js" class="download-button">Download Epoxy 0.10.0</a>
- <p class="download-info">8k min, 2k gzip <i>|</i> <a href="https://github.com/gmac/backbone.epoxy">GitHub Full Source</a></p>
+ <a href="js/backbone.epoxy.min.js" class="download-button">Download Epoxy 0.11.0</a>
+ <p class="download-info">9k min, 2k gzip <i>|</i> <a href="https://github.com/gmac/backbone.epoxy">GitHub Full Source</a></p>
</div>
</div>
<div class="navigation" role="navigation">
@@ -49,7 +49,7 @@
<h2>Two Components, One Tough Bond</h2>
- <p>Epoxy is an elegant and extensible data binding library for <a href="http://backbonejs.org">Backbone.js</a>; it provides feature-rich extensions of Backbone's <tt>Model</tt> and <tt>View</tt> components designed to hook view elements directly to data models. Epoxy captures some great aspects of <a href="http://knockoutjs.com">Knockout.js</a> and <a href="http://emberjs.com">Ember.js</a> in a familiar API that feels tastefully like Backbone, with minimal additional file size (8k min, 3k gzip). Some key features in Epoxy include:</p>
+ <p>Epoxy is an elegant and extensible data binding library for <a href="http://backbonejs.org">Backbone.js</a>; it provides feature-rich extensions of Backbone's <tt>Model</tt> and <tt>View</tt> components designed to hook view elements directly to data models. Epoxy captures some great aspects of <a href="http://knockoutjs.com">Knockout.js</a> and <a href="http://emberjs.com">Ember.js</a> in a familiar API that feels tastefully like Backbone, with minimal additional file size (9k min, 2k gzip). Some key features in Epoxy include:</p>
<ul class="features">
<li class="computed">Computed Model Attributes</li>
View
4 www/js/backbone.epoxy.min.js
@@ -1,5 +1,5 @@
-// Backbone.Epoxy 0.10.0
+// Backbone.Epoxy 0.11.0
// (c) 2013 Greg MacWilliam
// Freely distributed under the MIT license
// http://epoxyjs.org
-(function(){function t(t){return g(t)?t.slice():v(t)?c.clone(t):t}function e(t){return function(e,n,i){return t.prototype[n].apply(e,i)}}function n(t,e,i,s){for(var o in e)if(e.hasOwnProperty(o)){var r=e[o];if(t.hasObservable(o)){if(s.length&&!(0>c.indexOf(s,o)))throw"Recursive setter: "+s.join(" > ");r=t.obs[o].set(r),r&&v(r)&&(i=n(t,r,i,s.slice().concat([o])))}else i[o]=r}return i}function i(t,e,n){if(t){if(d(t)&&(t=t()),p(t)){var i=n?n+"_":"",s=c.extend({},t.attributes,t.obs||{});e["$"+(n||"model")]=function(){return w&&w.push([t,"change"]),t},c.each(s,function(n,s){e[i+s]=function(e){return w&&w.push([t,"change:"+s]),f(e)?t.get(s):v(e)&&!g(e)?t.set(e):t.set(s,e)}})}else b(t)&&(e["$"+(n||"collection")]=function(){return w&&w.push([t,"reset add remove sort update"]),t});return t}}function s(t){return d(t)?t():(v(t)&&(t=g(t)?t.slice():c.clone(t),c.each(t,function(e,n){t[n]=s(e)})),t)}function o(t){var e=t,n=t;return v(t)&&(e=p(t)?t.get("label"):t.label,n=p(t)?t.get("value"):t.value),"<option value='"+n+"'>"+e+"</option>"}var r,a=this,h=a.Backbone,c=a._,l=h.Epoxy={},u=Array.prototype,f=c.isUndefined,d=c.isFunction,v=c.isObject,g=c.isArray,p=function(t){return t instanceof h.Model},b=function(t){return t instanceof h.Collection},m=e(h.Model);l.Model=h.Model.extend({constructor:function(){this.obs={},m(this,"constructor",arguments),this._init=!0,this.observableDefaults&&c.each(this.observableDefaults,function(e,n){this.addObservable(n,d(e)?e():t(e))},this),this.computeds&&c.each(this.computeds,function(t,e){this.addComputed(e,t)},this),c.each(this.obs,function(t){t.init()}),delete this._init},get:function(t){return r&&r.push([t,this]),this.hasObservable(t)?this.obs[t].get():m(this,"get",arguments)},getCopy:function(e){return t(this.get(e))},set:function(t,e,i){var s=t;return s&&!v(s)?(s={},s[t]=e):i=e,i=i||{},i.unset||(s=n(this,s,{},[])),m(this,"set",[s,i])},destroy:function(){return this.clearObservables(),m(this,"destroy",arguments)},addObservable:function(t,e){return this.removeObservable(t),this.obs[t]=new y(this,t,{value:e}),this},addComputed:function(t,e,n){this.removeObservable(t);var i=e;if(d(e)){var s=2;i={},i._get=e,d(n)&&(i._set=n,s++),i.deps=u.slice.call(arguments,s)}return this.obs[t]=new y(this,t,i),this},hasObservable:function(t){return this.obs.hasOwnProperty(t)},removeObservable:function(t){return this.hasObservable(t)&&(this.obs[t].dispose(),delete this.obs[t]),this},clearObservables:function(){for(var t in this.obs)this.removeObservable(t);return this},modifyArray:function(t,e){var n=this.get(t);if(g(n)&&d(u[e])){var i=u.slice.call(arguments,2),s=u[e].apply(n,i);return this.trigger("change change:"+t),s}return null},modifyObject:function(t,e,n){var i=this.get(t),s=!1;return v(i)?(f(n)&&i.hasOwnProperty(e)?(delete i[e],s=!0):i[e]!==n&&(i[e]=n,s=!0),s&&this.trigger("change change:"+t),i):null}});var y=function(t,e,n){n=n||{},n.get&&d(n.get)&&(n._get=n.get),n.set&&d(n.set)&&(n._set=n.set),delete n.get,delete n.set,c.extend(this,n),this.model=t,this.name=e,this.deps=this.deps||[],t._init||this.init()};c.extend(y.prototype,h.Events,{init:function(){if(r=this.deps,this.get(!0),r=null,this.deps.length){var t={};c.each(this.deps,function(e){var n=this.model;g(e)&&(n=e[1],e=e[0]),e.indexOf("change:")&&(e="change:"+e),t.hasOwnProperty(e)?c.contains(t[e],n)||t[e].push(n):t[e]=[n]},this),c.each(t,function(t,e){for(var n=0,i=t.length;i>n;n++)this.listenTo(t[n],e,c.bind(this.get,this,!0))},this)}},get:function(t){if(t===!0&&this._get){var e=this._get.call(this.model);this.change(e)}return this.value},set:function(t){if(this._get){if(this._set)return this._set.apply(this.model,arguments);throw"Cannot set read-only computed observable."}return this.change(t),null},fire:function(){this.model.trigger("change change:"+this.name)},change:function(t){c.isEqual(t,this.value)||(this.value=t,this.fire())},dispose:function(){this.stopListening(),this.off(),this.model=this.value=null}});var w,O=e(h.View);l.View=h.View.extend({constructor:function(){this._bind=[],O(this,"constructor",arguments),this.applyBindings()},bindings:"data-bind",applyBindings:function(){function t(t,n,i){try{e._bind.push(new C(t,n,r,o))}catch(s){throw'Error parsing bindings for "'+i+'" >> '+s}}if(this.removeBindings(),this.model||this.collection||this.bindingSources){var e=this,n=e.bindingSources,s=e.bindings,o=c.clone(_),r={};if(c.each(e.bindingHandlers||{},function(t,e){o[e]=d(t)?{set:t}:t}),e.model=i(e.model,r),e.collection=i(e.collection,r),c.each(n,function(t,e){n[e]=i(t,r,e)}),v(s))c.each(s,function(e,n){var i=this.$(n);this.$el.is(n)&&(i=i.add(this.$el)),i.length&&t(i,e,n)},this);else{var a="["+s+"]",h=this.$(a);this.$el.is(a)&&(h=h.add(this.$el)),h.each(function(e){e=$(this),t(e,e.attr(s),a)})}}},removeBindings:function(){for(;this._bind.length;)this._bind.pop().dispose()},remove:function(){this.removeBindings(),O(this,"remove")}});var _={attr:{set:function(t,e){t.attr(e)}},checked:{get:function(t,e){var n=!!t.prop("checked"),i=t.val();if(t.is(":radio"))return i;if(g(e)){e=e.slice();var s=c.indexOf(e,i);return n&&0>s?e.push(i):!n&&s>-1&&e.splice(s,1),e}return n},set:function(t,e){var n=!!e;t.is(":radio")?n=e==t.val():g(e)&&(n=c.contains(e,t.val())),t.prop("checked",n)}},classes:{set:function(t,e){c.each(e,function(e,n){t.toggleClass(n,!!e)})}},collection:{set:function(t,e,n){if(!b(e)||!d(e.view))throw"Binding 'collection' requires a Collection with a 'view' constructor.";var i,s=e.models,o=this.v;if(n=n||e,p(n))if(o.hasOwnProperty(n.cid))i=o[n.cid],i.remove(),delete o[n.cid];else{o[n.cid]=i=new e.view({model:n});var r=c.indexOf(s,n);r>t.children().length?t.eq(r).before(i.$el):t.append(i.$el)}else if(b(n)){var a=s.length&&c.every(s,function(t){return o.hasOwnProperty(t.cid)});t.hide(),a?e.each(function(e){t.append(o[e.cid].$el)}):(this.empty(),e.each(function(n){o[n.cid]=i=new e.view({model:n}),t.append(i.$el)})),t.show()}}},css:{set:function(t,e){t.css(e)}},disabled:{set:function(t,e){t.prop("disabled",!!e)}},enabled:{set:function(t,e){t.prop("disabled",!e)}},html:{set:function(t,e){t.html(e)}},options:{set:function(t,e){var n=s(this.optionsEmpty),i=s(this.optionsDefault),r=b(e)?e.models:e;if(!g(r))throw"Binding 'options' requires a list value.";var a=t.val(),h=!0,l="";r.length||i||!n?(i&&(l+=o(i)),c.each(r,function(t){l+=o(t)})):(l+=o(n),h=!1),t.html(l).prop("disabled",!h).val(a),c.isEqual(t.val(),a)||t.trigger("change")}},text:{set:function(t,e){t.text(e)}},toggle:{set:function(t,e){t.toggle(!!e)}},value:{get:function(t){return t.val()},set:function(t,e){t.val()!=e&&t.val(e)}}},x=function(t){return function(){var e=arguments;return function(){return t(e)}}},E={all:x(function(t){for(var e=!0,n=0,i=t.length;i>n;n++)s(t[n])||(e=!1);return e}),any:x(function(t){for(var e=!1,n=0,i=t.length;i>n;n++)s(t[n])&&(e=!0);return e}),length:x(function(t){return s(t[0]).length||0}),none:x(function(t){for(var e=!0,n=0,i=t.length;i>n;n++)s(t[n])&&(e=!1);return e}),not:x(function(t){return!s(t[0])}),format:x(function(t){for(var e=s(t[0]),n=1,i=t.length;i>n;n++)e=e.replace(RegExp("\\$"+n,"g"),s(t[n]));return e}),select:x(function(t){var e=s(t[0]),n=s(t[1]),i=s(t[2]);return e?n:i})},B=["events","optionsDefault","optionsEmpty"],C=function(t,e,n,i){this.$el=t,this.v={};var o=this,r=t[0].tagName.toLowerCase(),a="input"==r||"select"==r||"textarea"==r,h=Function("$o","$c","with($o){with($c){return{"+e+"}}}");e=h(E,n),c.each(B,function(t){o[t]=e[t],delete e[t]}),this.events=c.map(c.union(this.events||[],["change"]),function(t){return t+".epoxy"}).join(" "),c.each(e,function(t,e){if(!i.hasOwnProperty(e))throw"invalid binding => "+e;var n=i[e],r=[],h=function(e){n.set.call(o,o.$el,s(t),e)};if(w=r,h(),w=null,a&&n.get&&d(t)&&o.$el.on(o.events,function(){t(n.get.call(o,o.$el,s(t)))}),r.length)for(var c=0,l=r.length;l>c;c++)o.listenTo(r[c][0],r[c][1],h)})};c.extend(C.prototype,h.Events,{empty:function(){for(var t in this.v)this.v.hasOwnProperty(t)&&(this.v[t].remove(),delete this.v[t])},dispose:function(){this.empty(),this.stopListening(),this.$el.off(this.events),this.$el=null}})}).call(this);
+(function(t,e){var n="backbone",i="underscore";"undefined"!=typeof exports?module.exports=e(require(i),require(n)):"function"==typeof define&&define.amd?define([i,n],e):e(t._,t.Backbone)})(this,function(t,e){function n(t){return function(e,n,i){return t.prototype[n].apply(e,i)}}function i(e,n,s,o){for(var r in n)if(n.hasOwnProperty(r)){var c=n[r];if(e.hasObservable(r)){if(o.length&&!(0>t.indexOf(o,r)))throw"Recursive setter: "+o.join(" > ");c=e.obs[r].set(c),c&&y(c)&&(s=i(e,c,s,o.slice().concat([r])))}else s[r]=c}return s}function s(e){return w(e)?e.slice():y(e)?t.clone(e):e}function o(e,n,i){i=i||{},i.get&&m(i.get)&&(i._get=i.get),i.set&&m(i.set)&&(i._set=i.set),delete i.get,delete i.set,t.extend(this,i),this.model=e,this.name=n,this.deps=this.deps||[],e._init||this.init()}function r(e){return m(e)?e():(y(e)&&(e=w(e)?e.slice():t.clone(e),t.each(e,function(t,n){e[n]=r(t)})),e)}function c(t){return function(){var e=arguments;return function(n){return t.apply(this,u(e,n))}}}function u(t,e){if(e)throw"binding error: filtered values are read-only.";for(var n=[],i=0,s=t.length;s>i;i++)n.push(r(t[i]));return n}function a(e,n,i){if(e){if(m(e)&&(e=e()),O(e)){var s=i?i+"_":"";n["$"+(i||"model")]=function(){return k&&k.push([e,"change"]),e},t.each(e.toJSON({obs:!0}),function(t,i){n[s+i]=function(t){return h(e,i,t)}})}else x(e)&&(n["$"+(i||"collection")]=function(){return k&&k.push([e,"reset add remove sort update"]),e});return e}}function h(t,e,n){return k&&k.push([t,"change:"+e]),b(n)?t.get(e):y(n)&&!w(n)?t.set(n):t.set(e,n)}function l(t,e){var n=t.$(e);return t.$el.is(e)&&(n=n.add(t.$el)),n}function f(e,n,i,s,o,r){try{var c=Function("$f","$c","with($f){with($c){return{"+i+"}}}")(r,s)}catch(u){throw"Error parsing bindings: "+i}var a=t.map(t.union(c.events||[],["change"]),function(t){return t+".epoxy"}).join(" ");t.each(c,function(t,i){o.hasOwnProperty(i)&&e._bind.push(new d(n,o[i],t,a,s,c))})}function d(e,n,i,s,o,c){var u=this,a=e[0].tagName.toLowerCase(),h="input"==a||"select"==a||"textarea"==a,l=[],f=function(t){u.set(u.$el,r(i),t)};if(u.$el=e,u.evt=s,t.extend(u,n),i=u.init(u.$el,r(i),o,c)||i,k=l,f(),k=null,h&&n.get&&m(i)&&u.$el.on(s,function(){i(u.get(u.$el,r(i)))}),l.length)for(var d=0,v=l.length;v>d;d++)u.listenTo(l[d][0],l[d][1],f)}var v,g=e.Epoxy={},p=Array.prototype,b=t.isUndefined,m=t.isFunction,y=t.isObject,w=t.isArray,O=function(t){return t instanceof e.Model},x=function(t){return t instanceof e.Collection},_=function(){},C=n(e.Model);g.Model=e.Model.extend({constructor:function(){this.obs={},C(this,"constructor",arguments),this._init=!0,this.observableDefaults&&t.each(this.observableDefaults,function(t,e){this.addObservable(e,m(t)?t():s(t))},this),this.computeds&&t.each(this.computeds,function(t,e){this.addComputed(e,t)},this),t.each(this.obs,function(t){t.init()}),delete this._init},get:function(t){return v&&v.push([t,this]),this.hasObservable(t)?this.obs[t].get():C(this,"get",arguments)},getCopy:function(t){return s(this.get(t))},set:function(t,e,n){var s=t;return s&&!y(s)?(s={},s[t]=e):n=e,n=n||{},n.unset||(s=i(this,s,{},[])),C(this,"set",[s,n])},toJSON:function(e){var n=C(this,"toJSON",arguments);return e&&e.obs&&t.each(this.obs,function(t,e){n[e]=t.value}),n},destroy:function(){return this.clearObservables(),C(this,"destroy",arguments)},addObservable:function(t,e){return this.removeObservable(t),this.obs[t]=new o(this,t,{value:e}),this},addComputed:function(t,e,n){this.removeObservable(t);var i=e;if(m(e)){var s=2;i={},i._get=e,m(n)&&(i._set=n,s++),i.deps=p.slice.call(arguments,s)}return this.obs[t]=new o(this,t,i),this},hasObservable:function(t){return this.obs.hasOwnProperty(t)},removeObservable:function(t){return this.hasObservable(t)&&(this.obs[t].dispose(),delete this.obs[t]),this},clearObservables:function(){for(var t in this.obs)this.removeObservable(t);return this},modifyArray:function(t,e){var n=this.get(t);if(w(n)&&m(p[e])){var i=p.slice.call(arguments,2),s=p[e].apply(n,i);return this.trigger("change change:"+t),s}return null},modifyObject:function(t,e,n){var i=this.get(t),s=!1;return y(i)?(b(n)&&i.hasOwnProperty(e)?(delete i[e],s=!0):i[e]!==n&&(i[e]=n,s=!0),s&&this.trigger("change change:"+t),i):null}}),t.extend(o.prototype,e.Events,{init:function(){if(v=this.deps,this.get(!0),v=null,this.deps.length){var e={};t.each(this.deps,function(n){var i=this.model;w(n)&&(i=n[1],n=n[0]),n.indexOf("change:")&&(n="change:"+n),e.hasOwnProperty(n)?t.contains(e[n],i)||e[n].push(i):e[n]=[i]},this),t.each(e,function(e,n){for(var i=0,s=e.length;s>i;i++)this.listenTo(e[i],n,t.bind(this.get,this,!0))},this)}},get:function(t){if(t===!0&&this._get){var e=this._get.call(this.model);this.change(e)}return this.value},set:function(t){if(this._get){if(this._set)return this._set.apply(this.model,arguments);throw"Cannot set read-only computed observable."}return this.change(t),null},fire:function(){this.model.trigger("change change:"+this.name)},change:function(e){t.isEqual(e,this.value)||(this.value=e,this.fire())},dispose:function(){this.stopListening(),this.off(),this.model=this.value=null}});var E={optionText:"label",optionValue:"value"},P={attr:{set:function(t,e){t.attr(e)}},checked:{get:function(e,n){var i=!!e.prop("checked"),s=e.val();if(e.is(":radio"))return s;if(w(n)){n=n.slice();var o=t.indexOf(n,s);return i&&0>o?n.push(s):!i&&o>-1&&n.splice(o,1),n}return i},set:function(e,n){var i=!!n;e.is(":radio")?i=n==e.val():w(n)&&(i=t.contains(n,e.val())),e.prop("checked",i)}},classes:{set:function(e,n){t.each(n,function(t,n){e.toggleClass(n,!!t)})}},collection:{init:function(t,e){if(!x(e)||!m(e.view))throw"Binding 'collection' requires a Collection with a 'view' constructor.";this.v={}},set:function(e,n,i){var s,o=n.models,r=this.v;if(i=i||n,O(i))if(r.hasOwnProperty(i.cid))s=r[i.cid],s.remove(),delete r[i.cid];else{r[i.cid]=s=new n.view({model:i});var c=t.indexOf(o,i);c>e.children().length?e.eq(c).before(s.$el):e.append(s.$el)}else if(x(i)){var u=o.length&&t.every(o,function(t){return r.hasOwnProperty(t.cid)});e.hide(),u?n.each(function(t){e.append(r[t.cid].$el)}):(this.clean(),n.each(function(t){r[t.cid]=s=new n.view({model:t}),e.append(s.$el)})),e.show()}},clean:function(){for(var t in this.v)this.v.hasOwnProperty(t)&&(this.v[t].remove(),delete this.v[t])}},css:{set:function(t,e){t.css(e)}},disabled:{set:function(t,e){t.prop("disabled",!!e)}},enabled:{set:function(t,e){t.prop("disabled",!e)}},html:{set:function(t,e){t.html(e)}},options:{init:function(t,e,n,i){this.e=i.optionsEmpty,this.d=i.optionsDefault,this.v=i.value},set:function(e,n){var i=this,s=r(i.e),o=r(i.d),c=r(i.v),u=w(c)?c:[c],a=x(n)?n.models:n,h=a.length,l=!0,f="";h||o||!s?(o&&(a=[o].concat(a)),t.each(a,function(t){f+=i.opt(t,h,u)})):(f+=i.opt(s,h,u),l=!1),e.html(f).prop("disabled",!l);var d=e.val();i.v&&!t.isEqual(c,d)&&i.v(d)},opt:function(e,n,i){var s=e,o=e,r=E.optionText,c=E.optionValue;y(e)&&(s=O(e)?e.get(r):e[r],o=O(e)?e.get(c):e[c]);var u=!n||t.contains(i,o)?"' selected='selected'>":"'>";return"<option value='"+o+u+s+"</option>"},clean:function(){this.d=this.e=this.v=null}},template:{init:function(e,n,i){var s=e.find("script,template");return this.tmpl=t.template(s.length?s.html():e.html()),w(n)?t.pick(i,n):void 0},set:function(t,e){e=O(e)?e.toJSON({obs:!0}):e,t.html(this.tmpl(e))},clean:function(){this.tmpl=null}},text:{set:function(t,e){t.text(e)}},toggle:{set:function(t,e){t.toggle(!!e)}},value:{get:function(t){return t.val()},set:function(t,e){try{t.val()!=e&&t.val(e)}catch(n){}}}},B={all:c(function(){for(var t=arguments,e=0,n=t.length;n>e;e++)if(!t[e])return!1;return!0}),any:c(function(){for(var t=arguments,e=0,n=t.length;n>e;e++)if(t[e])return!0;return!1}),length:c(function(t){return t.length||0}),none:c(function(){for(var t=arguments,e=0,n=t.length;n>e;e++)if(t[e])return!1;return!0}),not:c(function(t){return!t}),format:c(function(t){for(var e=arguments,n=1,i=e.length;i>n;n++)t=t.replace(RegExp("\\$"+n,"g"),e[n]);return t}),select:c(function(t,e,n){return t?e:n})};g.binding={addHandler:function(t,e){P[t]=m(e)?{set:e}:e},addFilter:function(t,e){B[t]=c(e)},config:function(e){t.extend(E,e)}};var k,q=n(e.View);return g.View=e.View.extend({constructor:function(){this._bind=[],q(this,"constructor",arguments),this.applyBindings()},bindings:"data-bind",applyBindings:function(){if(this.removeBindings(),this.model||this.collection||this.bindingSources){var e=this,n=e.bindingSources,i=e.bindings,s=t.clone(P),o=t.clone(B),r={};t.each(e.bindingHandlers||{},function(t,e){s[e]=m(t)?{set:t}:t}),t.each(e.bindingFilters||{},function(t,e){o[e]=c(t)}),e.model=a(e.model,r),e.collection=a(e.collection,r),t.each(n,function(t,e){n[e]=a(t,r,e)}),y(i)?t.each(i,function(t,n){var i=l(e,n);i.length&&f(e,i,t,r,s,o)}):l(e,"["+i+"]").each(function(){var t=$(this);f(e,t,t.attr(i),r,s,o)})}},removeBindings:function(){for(;this._bind.length;)this._bind.pop().dispose()},remove:function(){this.removeBindings(),q(this,"remove")}}),t.extend(d.prototype,e.Events,{init:_,get:_,set:_,clean:_,dispose:function(){this.clean(),this.stopListening(),this.$el.off(this.evt),this.$el=null}}),g});
View
39 www/js/backbone.js
@@ -1,43 +1,8 @@
-// Backbone.js 0.9.10
+// Backbone.js 1.0.0
// (c) 2010-2012 Jeremy Ashkenas, DocumentCloud Inc.
// Backbone may be freely distributed under the MIT license.
// For all details and documentation:
// http://backbonejs.org
-(function(){var n=this,B=n.Backbone,h=[],C=h.push,u=h.slice,D=h.splice,g;g="undefined"!==typeof exports?exports:n.Backbone={};g.VERSION="0.9.10";var f=n._;!f&&"undefined"!==typeof require&&(f=require("underscore"));g.$=n.jQuery||n.Zepto||n.ender;g.noConflict=function(){n.Backbone=B;return this};g.emulateHTTP=!1;g.emulateJSON=!1;var v=/\s+/,q=function(a,b,c,d){if(!c)return!0;if("object"===typeof c)for(var e in c)a[b].apply(a,[e,c[e]].concat(d));else if(v.test(c)){c=c.split(v);e=0;for(var f=c.length;e<
-f;e++)a[b].apply(a,[c[e]].concat(d))}else return!0},w=function(a,b){var c,d=-1,e=a.length;switch(b.length){case 0:for(;++d<e;)(c=a[d]).callback.call(c.ctx);break;case 1:for(;++d<e;)(c=a[d]).callback.call(c.ctx,b[0]);break;case 2:for(;++d<e;)(c=a[d]).callback.call(c.ctx,b[0],b[1]);break;case 3:for(;++d<e;)(c=a[d]).callback.call(c.ctx,b[0],b[1],b[2]);break;default:for(;++d<e;)(c=a[d]).callback.apply(c.ctx,b)}},h=g.Events={on:function(a,b,c){if(!q(this,"on",a,[b,c])||!b)return this;this._events||(this._events=
-{});(this._events[a]||(this._events[a]=[])).push({callback:b,context:c,ctx:c||this});return this},once:function(a,b,c){if(!q(this,"once",a,[b,c])||!b)return this;var d=this,e=f.once(function(){d.off(a,e);b.apply(this,arguments)});e._callback=b;this.on(a,e,c);return this},off:function(a,b,c){var d,e,t,g,j,l,k,h;if(!this._events||!q(this,"off",a,[b,c]))return this;if(!a&&!b&&!c)return this._events={},this;g=a?[a]:f.keys(this._events);j=0;for(l=g.length;j<l;j++)if(a=g[j],d=this._events[a]){t=[];if(b||
-c){k=0;for(h=d.length;k<h;k++)e=d[k],(b&&b!==e.callback&&b!==e.callback._callback||c&&c!==e.context)&&t.push(e)}this._events[a]=t}return this},trigger:function(a){if(!this._events)return this;var b=u.call(arguments,1);if(!q(this,"trigger",a,b))return this;var c=this._events[a],d=this._events.all;c&&w(c,b);d&&w(d,arguments);return this},listenTo:function(a,b,c){var d=this._listeners||(this._listeners={}),e=a._listenerId||(a._listenerId=f.uniqueId("l"));d[e]=a;a.on(b,"object"===typeof b?this:c,this);
-return this},stopListening:function(a,b,c){var d=this._listeners;if(d){if(a)a.off(b,"object"===typeof b?this:c,this),!b&&!c&&delete d[a._listenerId];else{"object"===typeof b&&(c=this);for(var e in d)d[e].off(b,c,this);this._listeners={}}return this}}};h.bind=h.on;h.unbind=h.off;f.extend(g,h);var r=g.Model=function(a,b){var c,d=a||{};this.cid=f.uniqueId("c");this.attributes={};b&&b.collection&&(this.collection=b.collection);b&&b.parse&&(d=this.parse(d,b)||{});if(c=f.result(this,"defaults"))d=f.defaults({},
-d,c);this.set(d,b);this.changed={};this.initialize.apply(this,arguments)};f.extend(r.prototype,h,{changed:null,idAttribute:"id",initialize:function(){},toJSON:function(){return f.clone(this.attributes)},sync:function(){return g.sync.apply(this,arguments)},get:function(a){return this.attributes[a]},escape:function(a){return f.escape(this.get(a))},has:function(a){return null!=this.get(a)},set:function(a,b,c){var d,e,g,p,j,l,k;if(null==a)return this;"object"===typeof a?(e=a,c=b):(e={})[a]=b;c||(c={});
-if(!this._validate(e,c))return!1;g=c.unset;p=c.silent;a=[];j=this._changing;this._changing=!0;j||(this._previousAttributes=f.clone(this.attributes),this.changed={});k=this.attributes;l=this._previousAttributes;this.idAttribute in e&&(this.id=e[this.idAttribute]);for(d in e)b=e[d],f.isEqual(k[d],b)||a.push(d),f.isEqual(l[d],b)?delete this.changed[d]:this.changed[d]=b,g?delete k[d]:k[d]=b;if(!p){a.length&&(this._pending=!0);b=0;for(d=a.length;b<d;b++)this.trigger("change:"+a[b],this,k[a[b]],c)}if(j)return this;
-if(!p)for(;this._pending;)this._pending=!1,this.trigger("change",this,c);this._changing=this._pending=!1;return this},unset:function(a,b){return this.set(a,void 0,f.extend({},b,{unset:!0}))},clear:function(a){var b={},c;for(c in this.attributes)b[c]=void 0;return this.set(b,f.extend({},a,{unset:!0}))},hasChanged:function(a){return null==a?!f.isEmpty(this.changed):f.has(this.changed,a)},changedAttributes:function(a){if(!a)return this.hasChanged()?f.clone(this.changed):!1;var b,c=!1,d=this._changing?
-this._previousAttributes:this.attributes,e;for(e in a)if(!f.isEqual(d[e],b=a[e]))(c||(c={}))[e]=b;return c},previous:function(a){return null==a||!this._previousAttributes?null:this._previousAttributes[a]},previousAttributes:function(){return f.clone(this._previousAttributes)},fetch:function(a){a=a?f.clone(a):{};void 0===a.parse&&(a.parse=!0);var b=a.success;a.success=function(a,d,e){if(!a.set(a.parse(d,e),e))return!1;b&&b(a,d,e)};return this.sync("read",this,a)},save:function(a,b,c){var d,e,g=this.attributes;
-null==a||"object"===typeof a?(d=a,c=b):(d={})[a]=b;if(d&&(!c||!c.wait)&&!this.set(d,c))return!1;c=f.extend({validate:!0},c);if(!this._validate(d,c))return!1;d&&c.wait&&(this.attributes=f.extend({},g,d));void 0===c.parse&&(c.parse=!0);e=c.success;c.success=function(a,b,c){a.attributes=g;var k=a.parse(b,c);c.wait&&(k=f.extend(d||{},k));if(f.isObject(k)&&!a.set(k,c))return!1;e&&e(a,b,c)};a=this.isNew()?"create":c.patch?"patch":"update";"patch"===a&&(c.attrs=d);a=this.sync(a,this,c);d&&c.wait&&(this.attributes=
-g);return a},destroy:function(a){a=a?f.clone(a):{};var b=this,c=a.success,d=function(){b.trigger("destroy",b,b.collection,a)};a.success=function(a,b,e){(e.wait||a.isNew())&&d();c&&c(a,b,e)};if(this.isNew())return a.success(this,null,a),!1;var e=this.sync("delete",this,a);a.wait||d();return e},url:function(){var a=f.result(this,"urlRoot")||f.result(this.collection,"url")||x();return this.isNew()?a:a+("/"===a.charAt(a.length-1)?"":"/")+encodeURIComponent(this.id)},parse:function(a){return a},clone:function(){return new this.constructor(this.attributes)},
-isNew:function(){return null==this.id},isValid:function(a){return!this.validate||!this.validate(this.attributes,a)},_validate:function(a,b){if(!b.validate||!this.validate)return!0;a=f.extend({},this.attributes,a);var c=this.validationError=this.validate(a,b)||null;if(!c)return!0;this.trigger("invalid",this,c,b||{});return!1}});var s=g.Collection=function(a,b){b||(b={});b.model&&(this.model=b.model);void 0!==b.comparator&&(this.comparator=b.comparator);this.models=[];this._reset();this.initialize.apply(this,
-arguments);a&&this.reset(a,f.extend({silent:!0},b))};f.extend(s.prototype,h,{model:r,initialize:function(){},toJSON:function(a){return this.map(function(b){return b.toJSON(a)})},sync:function(){return g.sync.apply(this,arguments)},add:function(a,b){a=f.isArray(a)?a.slice():[a];b||(b={});var c,d,e,g,p,j,l,k,h,m;l=[];k=b.at;h=this.comparator&&null==k&&!1!=b.sort;m=f.isString(this.comparator)?this.comparator:null;c=0;for(d=a.length;c<d;c++)(e=this._prepareModel(g=a[c],b))?(p=this.get(e))?b.merge&&(p.set(g===
-e?e.attributes:g,b),h&&(!j&&p.hasChanged(m))&&(j=!0)):(l.push(e),e.on("all",this._onModelEvent,this),this._byId[e.cid]=e,null!=e.id&&(this._byId[e.id]=e)):this.trigger("invalid",this,g,b);l.length&&(h&&(j=!0),this.length+=l.length,null!=k?D.apply(this.models,[k,0].concat(l)):C.apply(this.models,l));j&&this.sort({silent:!0});if(b.silent)return this;c=0;for(d=l.length;c<d;c++)(e=l[c]).trigger("add",e,this,b);j&&this.trigger("sort",this,b);return this},remove:function(a,b){a=f.isArray(a)?a.slice():[a];
-b||(b={});var c,d,e,g;c=0;for(d=a.length;c<d;c++)if(g=this.get(a[c]))delete this._byId[g.id],delete this._byId[g.cid],e=this.indexOf(g),this.models.splice(e,1),this.length--,b.silent||(b.index=e,g.trigger("remove",g,this,b)),this._removeReference(g);return this},push:function(a,b){a=this._prepareModel(a,b);this.add(a,f.extend({at:this.length},b));return a},pop:function(a){var b=this.at(this.length-1);this.remove(b,a);return b},unshift:function(a,b){a=this._prepareModel(a,b);this.add(a,f.extend({at:0},
-b));return a},shift:function(a){var b=this.at(0);this.remove(b,a);return b},slice:function(a,b){return this.models.slice(a,b)},get:function(a){if(null!=a)return this._idAttr||(this._idAttr=this.model.prototype.idAttribute),this._byId[a.id||a.cid||a[this._idAttr]||a]},at:function(a){return this.models[a]},where:function(a){return f.isEmpty(a)?[]:this.filter(function(b){for(var c in a)if(a[c]!==b.get(c))return!1;return!0})},sort:function(a){if(!this.comparator)throw Error("Cannot sort a set without a comparator");
-a||(a={});f.isString(this.comparator)||1===this.comparator.length?this.models=this.sortBy(this.comparator,this):this.models.sort(f.bind(this.comparator,this));a.silent||this.trigger("sort",this,a);return this},pluck:function(a){return f.invoke(this.models,"get",a)},update:function(a,b){b=f.extend({add:!0,merge:!0,remove:!0},b);b.parse&&(a=this.parse(a,b));var c,d,e,g,h=[],j=[],l={};f.isArray(a)||(a=a?[a]:[]);if(b.add&&!b.remove)return this.add(a,b);d=0;for(e=a.length;d<e;d++)c=a[d],g=this.get(c),
-b.remove&&g&&(l[g.cid]=!0),(b.add&&!g||b.merge&&g)&&h.push(c);if(b.remove){d=0;for(e=this.models.length;d<e;d++)c=this.models[d],l[c.cid]||j.push(c)}j.length&&this.remove(j,b);h.length&&this.add(h,b);return this},reset:function(a,b){b||(b={});b.parse&&(a=this.parse(a,b));for(var c=0,d=this.models.length;c<d;c++)this._removeReference(this.models[c]);b.previousModels=this.models.slice();this._reset();a&&this.add(a,f.extend({silent:!0},b));b.silent||this.trigger("reset",this,b);return this},fetch:function(a){a=
-a?f.clone(a):{};void 0===a.parse&&(a.parse=!0);var b=a.success;a.success=function(a,d,e){a[e.update?"update":"reset"](d,e);b&&b(a,d,e)};return this.sync("read",this,a)},create:function(a,b){b=b?f.clone(b):{};if(!(a=this._prepareModel(a,b)))return!1;b.wait||this.add(a,b);var c=this,d=b.success;b.success=function(a,b,f){f.wait&&c.add(a,f);d&&d(a,b,f)};a.save(null,b);return a},parse:function(a){return a},clone:function(){return new this.constructor(this.models)},_reset:function(){this.length=0;this.models.length=
-0;this._byId={}},_prepareModel:function(a,b){if(a instanceof r)return a.collection||(a.collection=this),a;b||(b={});b.collection=this;var c=new this.model(a,b);return!c._validate(a,b)?!1:c},_removeReference:function(a){this===a.collection&&delete a.collection;a.off("all",this._onModelEvent,this)},_onModelEvent:function(a,b,c,d){("add"===a||"remove"===a)&&c!==this||("destroy"===a&&this.remove(b,d),b&&a==="change:"+b.idAttribute&&(delete this._byId[b.previous(b.idAttribute)],null!=b.id&&(this._byId[b.id]=
-b)),this.trigger.apply(this,arguments))},sortedIndex:function(a,b,c){b||(b=this.comparator);var d=f.isFunction(b)?b:function(a){return a.get(b)};return f.sortedIndex(this.models,a,d,c)}});f.each("forEach each map collect reduce foldl inject reduceRight foldr find detect filter select reject every all some any include contains invoke max min toArray size first head take initial rest tail drop last without indexOf shuffle lastIndexOf isEmpty chain".split(" "),function(a){s.prototype[a]=function(){var b=
-u.call(arguments);b.unshift(this.models);return f[a].apply(f,b)}});f.each(["groupBy","countBy","sortBy"],function(a){s.prototype[a]=function(b,c){var d=f.isFunction(b)?b:function(a){return a.get(b)};return f[a](this.models,d,c)}});var y=g.Router=function(a){a||(a={});a.routes&&(this.routes=a.routes);this._bindRoutes();this.initialize.apply(this,arguments)},E=/\((.*?)\)/g,F=/(\(\?)?:\w+/g,G=/\*\w+/g,H=/[\-{}\[\]+?.,\\\^$|#\s]/g;f.extend(y.prototype,h,{initialize:function(){},route:function(a,b,c){f.isRegExp(a)||
-(a=this._routeToRegExp(a));c||(c=this[b]);g.history.route(a,f.bind(function(d){d=this._extractParameters(a,d);c&&c.apply(this,d);this.trigger.apply(this,["route:"+b].concat(d));this.trigger("route",b,d);g.history.trigger("route",this,b,d)},this));return this},navigate:function(a,b){g.history.navigate(a,b);return this},_bindRoutes:function(){if(this.routes)for(var a,b=f.keys(this.routes);null!=(a=b.pop());)this.route(a,this.routes[a])},_routeToRegExp:function(a){a=a.replace(H,"\\$&").replace(E,"(?:$1)?").replace(F,
-function(a,c){return c?a:"([^/]+)"}).replace(G,"(.*?)");return RegExp("^"+a+"$")},_extractParameters:function(a,b){return a.exec(b).slice(1)}});var m=g.History=function(){this.handlers=[];f.bindAll(this,"checkUrl");"undefined"!==typeof window&&(this.location=window.location,this.history=window.history)},z=/^[#\/]|\s+$/g,I=/^\/+|\/+$/g,J=/msie [\w.]+/,K=/\/$/;m.started=!1;f.extend(m.prototype,h,{interval:50,getHash:function(a){return(a=(a||this).location.href.match(/#(.*)$/))?a[1]:""},getFragment:function(a,
-b){if(null==a)if(this._hasPushState||!this._wantsHashChange||b){a=this.location.pathname;var c=this.root.replace(K,"");a.indexOf(c)||(a=a.substr(c.length))}else a=this.getHash();return a.replace(z,"")},start:function(a){if(m.started)throw Error("Backbone.history has already been started");m.started=!0;this.options=f.extend({},{root:"/"},this.options,a);this.root=this.options.root;this._wantsHashChange=!1!==this.options.hashChange;this._wantsPushState=!!this.options.pushState;this._hasPushState=!(!this.options.pushState||
-!this.history||!this.history.pushState);a=this.getFragment();var b=document.documentMode,b=J.exec(navigator.userAgent.toLowerCase())&&(!b||7>=b);this.root=("/"+this.root+"/").replace(I,"/");b&&this._wantsHashChange&&(this.iframe=g.$('<iframe src="javascript:0" tabindex="-1" />').hide().appendTo("body")[0].contentWindow,this.navigate(a));if(this._hasPushState)g.$(window).on("popstate",this.checkUrl);else if(this._wantsHashChange&&"onhashchange"in window&&!b)g.$(window).on("hashchange",this.checkUrl);
-else this._wantsHashChange&&(this._checkUrlInterval=setInterval(this.checkUrl,this.interval));this.fragment=a;a=this.location;b=a.pathname.replace(/[^\/]$/,"$&/")===this.root;if(this._wantsHashChange&&this._wantsPushState&&!this._hasPushState&&!b)return this.fragment=this.getFragment(null,!0),this.location.replace(this.root+this.location.search+"#"+this.fragment),!0;this._wantsPushState&&(this._hasPushState&&b&&a.hash)&&(this.fragment=this.getHash().replace(z,""),this.history.replaceState({},document.title,
-this.root+this.fragment+a.search));if(!this.options.silent)return this.loadUrl()},stop:function(){g.$(window).off("popstate",this.checkUrl).off("hashchange",this.checkUrl);clearInterval(this._checkUrlInterval);m.started=!1},route:function(a,b){this.handlers.unshift({route:a,callback:b})},checkUrl:function(){var a=this.getFragment();a===this.fragment&&this.iframe&&(a=this.getFragment(this.getHash(this.iframe)));if(a===this.fragment)return!1;this.iframe&&this.navigate(a);this.loadUrl()||this.loadUrl(this.getHash())},
-loadUrl:function(a){var b=this.fragment=this.getFragment(a);return f.any(this.handlers,function(a){if(a.route.test(b))return a.callback(b),!0})},navigate:function(a,b){if(!m.started)return!1;if(!b||!0===b)b={trigger:b};a=this.getFragment(a||"");if(this.fragment!==a){this.fragment=a;var c=this.root+a;if(this._hasPushState)this.history[b.replace?"replaceState":"pushState"]({},document.title,c);else if(this._wantsHashChange)this._updateHash(this.location,a,b.replace),this.iframe&&a!==this.getFragment(this.getHash(this.iframe))&&
-(b.replace||this.iframe.document.open().close(),this._updateHash(this.iframe.location,a,b.replace));else return this.location.assign(c);b.trigger&&this.loadUrl(a)}},_updateHash:function(a,b,c){c?(c=a.href.replace(/(javascript:|#).*$/,""),a.replace(c+"#"+b)):a.hash="#"+b}});g.history=new m;var A=g.View=function(a){this.cid=f.uniqueId("view");this._configure(a||{});this._ensureElement();this.initialize.apply(this,arguments);this.delegateEvents()},L=/^(\S+)\s*(.*)$/,M="model collection el id attributes className tagName events".split(" ");
-f.extend(A.prototype,h,{tagName:"div",$:function(a){return this.$el.find(a)},initialize:function(){},render:function(){return this},remove:function(){this.$el.remove();this.stopListening();return this},setElement:function(a,b){this.$el&&this.undelegateEvents();this.$el=a instanceof g.$?a:g.$(a);this.el=this.$el[0];!1!==b&&this.delegateEvents();return this},delegateEvents:function(a){if(a||(a=f.result(this,"events"))){this.undelegateEvents();for(var b in a){var c=a[b];f.isFunction(c)||(c=this[a[b]]);
-if(!c)throw Error('Method "'+a[b]+'" does not exist');var d=b.match(L),e=d[1],d=d[2],c=f.bind(c,this),e=e+(".delegateEvents"+this.cid);if(""===d)this.$el.on(e,c);else this.$el.on(e,d,c)}}},undelegateEvents:function(){this.$el.off(".delegateEvents"+this.cid)},_configure:function(a){this.options&&(a=f.extend({},f.result(this,"options"),a));f.extend(this,f.pick(a,M));this.options=a},_ensureElement:function(){if(this.el)this.setElement(f.result(this,"el"),!1);else{var a=f.extend({},f.result(this,"attributes"));
-this.id&&(a.id=f.result(this,"id"));this.className&&(a["class"]=f.result(this,"className"));a=g.$("<"+f.result(this,"tagName")+">").attr(a);this.setElement(a,!1)}}});var N={create:"POST",update:"PUT",patch:"PATCH","delete":"DELETE",read:"GET"};g.sync=function(a,b,c){var d=N[a];f.defaults(c||(c={}),{emulateHTTP:g.emulateHTTP,emulateJSON:g.emulateJSON});var e={type:d,dataType:"json"};c.url||(e.url=f.result(b,"url")||x());if(null==c.data&&b&&("create"===a||"update"===a||"patch"===a))e.contentType="application/json",
-e.data=JSON.stringify(c.attrs||b.toJSON(c));c.emulateJSON&&(e.contentType="application/x-www-form-urlencoded",e.data=e.data?{model:e.data}:{});if(c.emulateHTTP&&("PUT"===d||"DELETE"===d||"PATCH"===d)){e.type="POST";c.emulateJSON&&(e.data._method=d);var h=c.beforeSend;c.beforeSend=function(a){a.setRequestHeader("X-HTTP-Method-Override",d);if(h)return h.apply(this,arguments)}}"GET"!==e.type&&!c.emulateJSON&&(e.processData=!1);var m=c.success;c.success=function(a){m&&m(b,a,c);b.trigger("sync",b,a,c)};
-var j=c.error;c.error=function(a){j&&j(b,a,c);b.trigger("error",b,a,c)};a=c.xhr=g.ajax(f.extend(e,c));b.trigger("request",b,a,c);return a};g.ajax=function(){return g.$.ajax.apply(g.$,arguments)};r.extend=s.extend=y.extend=A.extend=m.extend=function(a,b){var c=this,d;d=a&&f.has(a,"constructor")?a.constructor:function(){return c.apply(this,arguments)};f.extend(d,c,b);var e=function(){this.constructor=d};e.prototype=c.prototype;d.prototype=new e;a&&f.extend(d.prototype,a);d.__super__=c.prototype;return d};
-var x=function(){throw Error('A "url" property or function must be specified');}}).call(this);
+(function(){var t=this;var e=t.Backbone;var i=[];var r=i.push;var s=i.slice;var n=i.splice;var a;if(typeof exports!=="undefined"){a=exports}else{a=t.Backbone={}}a.VERSION="1.0.0";var h=t._;if(!h&&typeof require!=="undefined")h=require("underscore");a.$=t.jQuery||t.Zepto||t.ender||t.$;a.noConflict=function(){t.Backbone=e;return this};a.emulateHTTP=false;a.emulateJSON=false;var o=a.Events={on:function(t,e,i){if(!l(this,"on",t,[e,i])||!e)return this;this._events||(this._events={});var r=this._events[t]||(this._events[t]=[]);r.push({callback:e,context:i,ctx:i||this});return this},once:function(t,e,i){if(!l(this,"once",t,[e,i])||!e)return this;var r=this;var s=h.once(function(){r.off(t,s);e.apply(this,arguments)});s._callback=e;return this.on(t,s,i)},off:function(t,e,i){var r,s,n,a,o,u,c,f;if(!this._events||!l(this,"off",t,[e,i]))return this;if(!t&&!e&&!i){this._events={};return this}a=t?[t]:h.keys(this._events);for(o=0,u=a.length;o<u;o++){t=a[o];if(n=this._events[t]){this._events[t]=r=[];if(e||i){for(c=0,f=n.length;c<f;c++){s=n[c];if(e&&e!==s.callback&&e!==s.callback._callback||i&&i!==s.context){r.push(s)}}}if(!r.length)delete this._events[t]}}return this},trigger:function(t){if(!this._events)return this;var e=s.call(arguments,1);if(!l(this,"trigger",t,e))return this;var i=this._events[t];var r=this._events.all;if(i)c(i,e);if(r)c(r,arguments);return this},stopListening:function(t,e,i){var r=this._listeners;if(!r)return this;var s=!e&&!i;if(typeof e==="object")i=this;if(t)(r={})[t._listenerId]=t;for(var n in r){r[n].off(e,i,this);if(s)delete this._listeners[n]}return this}};var u=/\s+/;var l=function(t,e,i,r){if(!i)return true;if(typeof i==="object"){for(var s in i){t[e].apply(t,[s,i[s]].concat(r))}return false}if(u.test(i)){var n=i.split(u);for(var a=0,h=n.length;a<h;a++){t[e].apply(t,[n[a]].concat(r))}return false}return true};var c=function(t,e){var i,r=-1,s=t.length,n=e[0],a=e[1],h=e[2];switch(e.length){case 0:while(++r<s)(i=t[r]).callback.call(i.ctx);return;case 1:while(++r<s)(i=t[r]).callback.call(i.ctx,n);return;case 2:while(++r<s)(i=t[r]).callback.call(i.ctx,n,a);return;case 3:while(++r<s)(i=t[r]).callback.call(i.ctx,n,a,h);return;default:while(++r<s)(i=t[r]).callback.apply(i.ctx,e)}};var f={listenTo:"on",listenToOnce:"once"};h.each(f,function(t,e){o[e]=function(e,i,r){var s=this._listeners||(this._listeners={});var n=e._listenerId||(e._listenerId=h.uniqueId("l"));s[n]=e;if(typeof i==="object")r=this;e[t](i,r,this);return this}});o.bind=o.on;o.unbind=o.off;h.extend(a,o);var d=a.Model=function(t,e){var i;var r=t||{};e||(e={});this.cid=h.uniqueId("c");this.attributes={};h.extend(this,h.pick(e,p));if(e.parse)r=this.parse(r,e)||{};if(i=h.result(this,"defaults")){r=h.defaults({},r,i)}this.set(r,e);this.changed={};this.initialize.apply(this,arguments)};var p=["url","urlRoot","collection"];h.extend(d.prototype,o,{changed:null,validationError:null,idAttribute:"id",initialize:function(){},toJSON:function(t){return h.clone(this.attributes)},sync:function(){return a.sync.apply(this,arguments)},get:function(t){return this.attributes[t]},escape:function(t){return h.escape(this.get(t))},has:function(t){return this.get(t)!=null},set:function(t,e,i){var r,s,n,a,o,u,l,c;if(t==null)return this;if(typeof t==="object"){s=t;i=e}else{(s={})[t]=e}i||(i={});if(!this._validate(s,i))return false;n=i.unset;o=i.silent;a=[];u=this._changing;this._changing=true;if(!u){this._previousAttributes=h.clone(this.attributes);this.changed={}}c=this.attributes,l=this._previousAttributes;if(this.idAttribute in s)this.id=s[this.idAttribute];for(r in s){e=s[r];if(!h.isEqual(c[r],e))a.push(r);if(!h.isEqual(l[r],e)){this.changed[r]=e}else{delete this.changed[r]}n?delete c[r]:c[r]=e}if(!o){if(a.length)this._pending=true;for(var f=0,d=a.length;f<d;f++){this.trigger("change:"+a[f],this,c[a[f]],i)}}if(u)return this;if(!o){while(this._pending){this._pending=false;this.trigger("change",this,i)}}this._pending=false;this._changing=false;return this},unset:function(t,e){return this.set(t,void 0,h.extend({},e,{unset:true}))},clear:function(t){var e={};for(var i in this.attributes)e[i]=void 0;return this.set(e,h.extend({},t,{unset:true}))},hasChanged:function(t){if(t==null)return!h.isEmpty(this.changed);return h.has(this.changed,t)},changedAttributes:function(t){if(!t)return this.hasChanged()?h.clone(this.changed):false;var e,i=false;var r=this._changing?this._previousAttributes:this.attributes;for(var s in t){if(h.isEqual(r[s],e=t[s]))continue;(i||(i={}))[s]=e}return i},previous:function(t){if(t==null||!this._previousAttributes)return null;return this._previousAttributes[t]},previousAttributes:function(){return h.clone(this._previousAttributes)},fetch:function(t){t=t?h.clone(t):{};if(t.parse===void 0)t.parse=true;var e=this;var i=t.success;t.success=function(r){if(!e.set(e.parse(r,t),t))return false;if(i)i(e,r,t);e.trigger("sync",e,r,t)};R(this,t);return this.sync("read",this,t)},save:function(t,e,i){var r,s,n,a=this.attributes;if(t==null||typeof t==="object"){r=t;i=e}else{(r={})[t]=e}if(r&&(!i||!i.wait)&&!this.set(r,i))return false;i=h.extend({validate:true},i);if(!this._validate(r,i))return false;if(r&&i.wait){this.attributes=h.extend({},a,r)}if(i.parse===void 0)i.parse=true;var o=this;var u=i.success;i.success=function(t){o.attributes=a;var e=o.parse(t,i);if(i.wait)e=h.extend(r||{},e);if(h.isObject(e)&&!o.set(e,i)){return false}if(u)u(o,t,i);o.trigger("sync",o,t,i)};R(this,i);s=this.isNew()?"create":i.patch?"patch":"update";if(s==="patch")i.attrs=r;n=this.sync(s,this,i);if(r&&i.wait)this.attributes=a;return n},destroy:function(t){t=t?h.clone(t):{};var e=this;var i=t.success;var r=function(){e.trigger("destroy",e,e.collection,t)};t.success=function(s){if(t.wait||e.isNew())r();if(i)i(e,s,t);if(!e.isNew())e.trigger("sync",e,s,t)};if(this.isNew()){t.success();return false}R(this,t);var s=this.sync("delete",this,t);if(!t.wait)r();return s},url:function(){var t=h.result(this,"urlRoot")||h.result(this.collection,"url")||U();if(this.isNew())return t;return t+(t.charAt(t.length-1)==="/"?"":"/")+encodeURIComponent(this.id)},parse:function(t,e){return t},clone:function(){return new this.constructor(this.attributes)},isNew:function(){return this.id==null},isValid:function(t){return this._validate({},h.extend(t||{},{validate:true}))},_validate:function(t,e){if(!e.validate||!this.validate)return true;t=h.extend({},this.attributes,t);var i=this.validationError=this.validate(t,e)||null;if(!i)return true;this.trigger("invalid",this,i,h.extend(e||{},{validationError:i}));return false}});var v=["keys","values","pairs","invert","pick","omit"];h.each(v,function(t){d.prototype[t]=function(){var e=s.call(arguments);e.unshift(this.attributes);return h[t].apply(h,e)}});var g=a.Collection=function(t,e){e||(e={});if(e.url)this.url=e.url;if(e.model)this.model=e.model;if(e.comparator!==void 0)this.comparator=e.comparator;this._reset();this.initialize.apply(this,arguments);if(t)this.reset(t,h.extend({silent:true},e))};var m={add:true,remove:true,merge:true};var y={add:true,merge:false,remove:false};h.extend(g.prototype,o,{model:d,initialize:function(){},toJSON:function(t){return this.map(function(e){return e.toJSON(t)})},sync:function(){return a.sync.apply(this,arguments)},add:function(t,e){return this.set(t,h.defaults(e||{},y))},remove:function(t,e){t=h.isArray(t)?t.slice():[t];e||(e={});var i,r,s,n;for(i=0,r=t.length;i<r;i++){n=this.get(t[i]);if(!n)continue;delete this._byId[n.id];delete this._byId[n.cid];s=this.indexOf(n);this.models.splice(s,1);this.length--;if(!e.silent){e.index=s;n.trigger("remove",n,this,e)}this._removeReference(n)}return this},set:function(t,e){e=h.defaults(e||{},m);if(e.parse)t=this.parse(t,e);if(!h.isArray(t))t=t?[t]:[];var i,s,a,o,u,l;var c=e.at;var f=this.comparator&&c==null&&e.sort!==false;var d=h.isString(this.comparator)?this.comparator:null;var p=[],v=[],g={};for(i=0,s=t.length;i<s;i++){if(!(a=this._prepareModel(t[i],e)))continue;if(u=this.get(a)){if(e.remove)g[u.cid]=true;if(e.merge){u.set(a.attributes,e);if(f&&!l&&u.hasChanged(d))l=true}}else if(e.add){p.push(a);a.on("all",this._onModelEvent,this);this._byId[a.cid]=a;if(a.id!=null)this._byId[a.id]=a}}if(e.remove){for(i=0,s=this.length;i<s;++i){if(!g[(a=this.models[i]).cid])v.push(a)}if(v.length)this.remove(v,e)}if(p.length){if(f)l=true;this.length+=p.length;if(c!=null){n.apply(this.models,[c,0].concat(p))}else{r.apply(this.models,p)}}if(l)this.sort({silent:true});if(e.silent)return this;for(i=0,s=p.length;i<s;i++){(a=p[i]).trigger("add",a,this,e)}if(l)this.trigger("sort",this,e);return this},reset:function(t,e){e||(e={});for(var i=0,r=this.models.length;i<r;i++){this._removeReference(this.models[i])}e.previousModels=this.models;this._reset();this.add(t,h.extend({silent:true},e));if(!e.silent)this.trigger("reset",this,e);return this},push:function(t,e){t=this._prepareModel(t,e);this.add(t,h.extend({at:this.length},e));return t},pop:function(t){var e=this.at(this.length-1);this.remove(e,t);return e},unshift:function(t,e){t=this._prepareModel(t,e);this.add(t,h.extend({at:0},e));return t},shift:function(t){var e=this.at(0);this.remove(e,t);return e},slice:function(t,e){return this.models.slice(t,e)},get:function(t){if(t==null)return void 0;return this._byId[t.id!=null?t.id:t.cid||t]},at:function(t){return this.models[t]},where:function(t,e){if(h.isEmpty(t))return e?void 0:[];return this[e?"find":"filter"](function(e){for(var i in t){if(t[i]!==e.get(i))return false}return true})},findWhere:function(t){return this.where(t,true)},sort:function(t){if(!this.comparator)throw new Error("Cannot sort a set without a comparator");t||(t={});if(h.isString(this.comparator)||this.comparator.length===1){this.models=this.sortBy(this.comparator,this)}else{this.models.sort(h.bind(this.comparator,this))}if(!t.silent)this.trigger("sort",this,t);return this},sortedIndex:function(t,e,i){e||(e=this.comparator);var r=h.isFunction(e)?e:function(t){return t.get(e)};return h.sortedIndex(this.models,t,r,i)},pluck:function(t){return h.invoke(this.models,"get",t)},fetch:function(t){t=t?h.clone(t):{};if(t.parse===void 0)t.parse=true;var e=t.success;var i=this;t.success=function(r){var s=t.reset?"reset":"set";i[s](r,t);if(e)e(i,r,t);i.trigger("sync",i,r,t)};R(this,t);return this.sync("read",this,t)},create:function(t,e){e=e?h.clone(e):{};if(!(t=this._prepareModel(t,e)))return false;if(!e.wait)this.add(t,e);var i=this;var r=e.success;e.success=function(s){if(e.wait)i.add(t,e);if(r)r(t,s,e)};t.save(null,e);return t},parse:function(t,e){return t},clone:function(){return new this.constructor(this.models)},_reset:function(){this.length=0;this.models=[];this._byId={}},_prepareModel:function(t,e){if(t instanceof d){if(!t.collection)t.collection=this;return t}e||(e={});e.collection=this;var i=new this.model(t,e);if(!i._validate(t,e)){this.trigger("invalid",this,t,e);return false}return i},_removeReference:function(t){if(this===t.collection)delete t.collection;t.off("all",this._onModelEvent,this)},_onModelEvent:function(t,e,i,r){if((t==="add"||t==="remove")&&i!==this)return;if(t==="destroy")this.remove(e,r);if(e&&t==="change:"+e.idAttribute){delete this._byId[e.previous(e.idAttribute)];if(e.id!=null)this._byId[e.id]=e}this.trigger.apply(this,arguments)}});var _=["forEach","each","map","collect","reduce","foldl","inject","reduceRight","foldr","find","detect","filter","select","reject","every","all","some","any","include","contains","invoke","max","min","toArray","size","first","head","take","initial","rest","tail","drop","last","without","indexOf","shuffle","lastIndexOf","isEmpty","chain"];h.each(_,function(t){g.prototype[t]=function(){var e=s.call(arguments);e.unshift(this.models);return h[t].apply(h,e)}});var w=["groupBy","countBy","sortBy"];h.each(w,function(t){g.prototype[t]=function(e,i){var r=h.isFunction(e)?e:function(t){return t.get(e)};return h[t](this.models,r,i)}});var b=a.View=function(t){this.cid=h.uniqueId("view");this._configure(t||{});this._ensureElement();this.initialize.apply(this,arguments);this.delegateEvents()};var x=/^(\S+)\s*(.*)$/;var E=["model","collection","el","id","attributes","className","tagName","events"];h.extend(b.prototype,o,{tagName:"div",$:function(t){return this.$el.find(t)},initialize:function(){},render:function(){return this},remove:function(){this.$el.remove();this.stopListening();return this},setElement:function(t,e){if(this.$el)this.undelegateEvents();this.$el=t instanceof a.$?t:a.$(t);this.el=this.$el[0];if(e!==false)this.delegateEvents();return this},delegateEvents:function(t){if(!(t||(t=h.result(this,"events"))))return this;this.undelegateEvents();for(var e in t){var i=t[e];if(!h.isFunction(i))i=this[t[e]];if(!i)continue;var r=e.match(x);var s=r[1],n=r[2];i=h.bind(i,this);s+=".delegateEvents"+this.cid;if(n===""){this.$el.on(s,i)}else{this.$el.on(s,n,i)}}return this},undelegateEvents:function(){this.$el.off(".delegateEvents"+this.cid);return this},_configure:function(t){if(this.options)t=h.extend({},h.result(this,"options"),t);h.extend(this,h.pick(t,E));this.options=t},_ensureElement:function(){if(!this.el){var t=h.extend({},h.result(this,"attributes"));if(this.id)t.id=h.result(this,"id");if(this.className)t["class"]=h.result(this,"className");var e=a.$("<"+h.result(this,"tagName")+">").attr(t);this.setElement(e,false)}else{this.setElement(h.result(this,"el"),false)}}});a.sync=function(t,e,i){var r=k[t];h.defaults(i||(i={}),{emulateHTTP:a.emulateHTTP,emulateJSON:a.emulateJSON});var s={type:r,dataType:"json"};if(!i.url){s.url=h.result(e,"url")||U()}if(i.data==null&&e&&(t==="create"||t==="update"||t==="patch")){s.contentType="application/json";s.data=JSON.stringify(i.attrs||e.toJSON(i))}if(i.emulateJSON){s.contentType="application/x-www-form-urlencoded";s.data=s.data?{model:s.data}:{}}if(i.emulateHTTP&&(r==="PUT"||r==="DELETE"||r==="PATCH")){s.type="POST";if(i.emulateJSON)s.data._method=r;var n=i.beforeSend;i.beforeSend=function(t){t.setRequestHeader("X-HTTP-Method-Override",r);if(n)return n.apply(this,arguments)}}if(s.type!=="GET"&&!i.emulateJSON){s.processData=false}if(s.type==="PATCH"&&window.ActiveXObject&&!(window.external&&window.external.msActiveXFilteringEnabled)){s.xhr=function(){return new ActiveXObject("Microsoft.XMLHTTP")}}var o=i.xhr=a.ajax(h.extend(s,i));e.trigger("request",e,o,i);return o};var k={create:"POST",update:"PUT",patch:"PATCH","delete":"DELETE",read:"GET"};a.ajax=function(){return a.$.ajax.apply(a.$,arguments)};var S=a.Router=function(t){t||(t={});if(t.routes)this.routes=t.routes;this._bindRoutes();this.initialize.apply(this,arguments)};var $=/\((.*?)\)/g;var T=/(\(\?)?:\w+/g;var H=/\*\w+/g;var A=/[\-{}\[\]+?.,\\\^$|#\s]/g;h.extend(S.prototype,o,{initialize:function(){},route:function(t,e,i){if(!h.isRegExp(t))t=this._routeToRegExp(t);if(h.isFunction(e)){i=e;e=""}if(!i)i=this[e];var r=this;a.history.route(t,function(s){var n=r._extractParameters(t,s);i&&i.apply(r,n);r.trigger.apply(r,["route:"+e].concat(n));r.trigger("route",e,n);a.history.trigger("route",r,e,n)});return this},navigate:function(t,e){a.history.navigate(t,e);return this},_bindRoutes:function(){if(!this.routes)return;this.routes=h.result(this,"routes");var t,e=h.keys(this.routes);while((t=e.pop())!=null){this.route(t,this.routes[t])}},_routeToRegExp:function(t){t=t.replace(A,"\\$&").replace($,"(?:$1)?").replace(T,function(t,e){return e?t:"([^/]+)"}).replace(H,"(.*?)");return new RegExp("^"+t+"$")},_extractParameters:function(t,e){var i=t.exec(e).slice(1);return h.map(i,function(t){return t?decodeURIComponent(t):null})}});var I=a.History=function(){this.handlers=[];h.bindAll(this,"checkUrl");if(typeof window!=="undefined"){this.location=window.location;this.history=window.history}};var N=/^[#\/]|\s+$/g;var P=/^\/+|\/+$/g;var O=/msie [\w.]+/;var C=/\/$/;I.started=false;h.extend(I.prototype,o,{interval:50,getHash:function(t){var e=(t||this).location.href.match(/#(.*)$/);return e?e[1]:""},getFragment:function(t,e){if(t==null){if(this._hasPushState||!this._wantsHashChange||e){t=this.location.pathname;var i=this.root.replace(C,"");if(!t.indexOf(i))t=t.substr(i.length)}else{t=this.getHash()}}return t.replace(N,"")},start:function(t){if(I.started)throw new Error("Backbone.history has already been started");I.started=true;this.options=h.extend({},{root:"/"},this.options,t);this.root=this.options.root;this._wantsHashChange=this.options.hashChange!==false;this._wantsPushState=!!this.options.pushState;this._hasPushState=!!(this.options.pushState&&this.history&&this.history.pushState);var e=this.getFragment();var i=document.documentMode;var r=O.exec(navigator.userAgent.toLowerCase())&&(!i||i<=7);this.root=("/"+this.root+"/").replace(P,"/");if(r&&this._wantsHashChange){this.iframe=a.$('<iframe src="javascript:0" tabindex="-1" />').hide().appendTo("body")[0].contentWindow;this.navigate(e)}if(this._hasPushState){a.$(window).on("popstate",this.checkUrl)}else if(this._wantsHashChange&&"onhashchange"in window&&!r){a.$(window).on("hashchange",this.checkUrl)}else if(this._wantsHashChange){this._checkUrlInterval=setInterval(this.checkUrl,this.interval)}this.fragment=e;var s=this.location;var n=s.pathname.replace(/[^\/]$/,"$&/")===this.root;if(this._wantsHashChange&&this._wantsPushState&&!this._hasPushState&&!n){this.fragment=this.getFragment(null,true);this.location.replace(this.root+this.location.search+"#"+this.fragment);return true}else if(this._wantsPushState&&this._hasPushState&&n&&s.hash){this.fragment=this.getHash().replace(N,"");this.history.replaceState({},document.title,this.root+this.fragment+s.search)}if(!this.options.silent)return this.loadUrl()},stop:function(){a.$(window).off("popstate",this.checkUrl).off("hashchange",this.checkUrl);clearInterval(this._checkUrlInterval);I.started=false},route:function(t,e){this.handlers.unshift({route:t,callback:e})},checkUrl:function(t){var e=this.getFragment();if(e===this.fragment&&this.iframe){e=this.getFragment(this.getHash(this.iframe))}if(e===this.fragment)return false;if(this.iframe)this.navigate(e);this.loadUrl()||this.loadUrl(this.getHash())},loadUrl:function(t){var e=this.fragment=this.getFragment(t);var i=h.any(this.handlers,function(t){if(t.route.test(e)){t.callback(e);return true}});return i},navigate:function(t,e){if(!I.started)return false;if(!e||e===true)e={trigger:e};t=this.getFragment(t||"");if(this.fragment===t)return;this.fragment=t;var i=this.root+t;if(this._hasPushState){this.history[e.replace?"replaceState":"pushState"]({},document.title,i)}else if(this._wantsHashChange){this._updateHash(this.location,t,e.replace);if(this.iframe&&t!==this.getFragment(this.getHash(this.iframe))){if(!e.replace)this.iframe.document.open().close();this._updateHash(this.iframe.location,t,e.replace)}}else{return this.location.assign(i)}if(e.trigger)this.loadUrl(t)},_updateHash:function(t,e,i){if(i){var r=t.href.replace(/(javascript:|#).*$/,"");t.replace(r+"#"+e)}else{t.hash="#"+e}}});a.history=new I;var j=function(t,e){var i=this;var r;if(t&&h.has(t,"constructor")){r=t.constructor}else{r=function(){return i.apply(this,arguments)}}h.extend(r,i,e);var s=function(){this.constructor=r};s.prototype=i.prototype;r.prototype=new s;if(t)h.extend(r.prototype,t);r.__super__=i.prototype;return r};d.extend=g.extend=S.extend=b.extend=I.extend=j;var U=function(){throw new Error('A "url" property or function must be specified')};var R=function(t,e){var i=e.error;e.error=function(r){if(i)i(t,r,e);t.trigger("error",t,r,e)}}}).call(this);
View
486 www/js/json2.js
@@ -0,0 +1,486 @@
+/*
+ json2.js
+ 2012-10-08
+
+ Public Domain.
+
+ NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.
+
+ See http://www.JSON.org/js.html
+
+
+ This code should be minified before deployment.
+ See http://javascript.crockford.com/jsmin.html
+
+ USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO
+ NOT CONTROL.
+
+
+ This file creates a global JSON object containing two methods: stringify
+ and parse.
+
+ JSON.stringify(value, replacer, space)
+ value any JavaScript value, usually an object or array.
+
+ replacer an optional parameter that determines how object
+ values are stringified for objects. It can be a
+ function or an array of strings.
+
+ space an optional parameter that specifies the indentation
+ of nested structures. If it is omitted, the text will
+ be packed without extra whitespace. If it is a number,
+ it will specify the number of spaces to indent at each
+ level. If it is a string (such as '\t' or '&nbsp;'),
+ it contains the characters used to indent at each level.
+
+ This method produces a JSON text from a JavaScript value.
+
+ When an object value is found, if the object contains a toJSON
+ method, its toJSON method will be called and the result will be
+ stringified. A toJSON method does not serialize: it returns the
+ value represented by the name/value pair that should be serialized,
+ or undefined if nothing should be serialized. The toJSON method
+ will be passed the key associated with the value, and this will be
+ bound to the value
+
+ For example, this would serialize Dates as ISO strings.
+
+ Date.prototype.toJSON = function (key) {
+ function f(n) {
+ // Format integers to have at least two digits.
+ return n < 10 ? '0' + n : n;
+ }
+
+ return this.getUTCFullYear() + '-' +
+ f(this.getUTCMonth() + 1) + '-' +
+ f(this.getUTCDate()) + 'T' +
+ f(this.getUTCHours()) + ':' +
+ f(this.getUTCMinutes()) + ':' +
+ f(this.getUTCSeconds()) + 'Z';
+ };
+
+ You can provide an optional replacer method. It will be passed the
+ key and value of each member, with this bound to the containing
+ object. The value that is returned from your method will be
+ serialized. If your method returns undefined, then the member will
+ be excluded from the serialization.
+
+ If the replacer parameter is an array of strings, then it will be
+ used to select the members to be serialized. It filters the results
+ such that only members with keys listed in the replacer array are
+ stringified.
+
+ Values that do not have JSON representations, such as undefined or
+ functions, will not be serialized. Such values in objects will be
+ dropped; in arrays they will be replaced with null. You can use
+ a replacer function to replace those with JSON values.
+ JSON.stringify(undefined) returns undefined.
+
+ The optional space parameter produces a stringification of the
+ value that is filled with line breaks and indentation to make it
+ easier to read.
+
+ If the space parameter is a non-empty string, then that string will
+ be used for indentation. If the space parameter is a number, then
+ the indentation will be that many spaces.
+
+ Example:
+
+ text = JSON.stringify(['e', {pluribus: 'unum'}]);
+ // text is '["e",{"pluribus":"unum"}]'
+
+
+ text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t');
+ // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]'
+
+ text = JSON.stringify([new Date()], function (key, value) {
+ return this[key] instanceof Date ?
+ 'Date(' + this[key] + ')' : value;
+ });
+ // text is '["Date(---current time---)"]'
+
+
+ JSON.parse(text, reviver)
+ This method parses a JSON text to produce an object or array.
+ It can throw a SyntaxError exception.
+
+ The optional reviver parameter is a function that can filter and
+ transform the results. It receives each of the keys and values,
+ and its return value is used instead of the original value.
+ If it returns what it received, then the structure is not modified.
+ If it returns undefined then the member is deleted.
+
+ Example:
+
+ // Parse the text. Values that look like ISO date strings will
+ // be converted to Date objects.
+
+ myData = JSON.parse(text, function (key, value) {
+ var a;
+ if (typeof value === 'string') {
+ a =
+/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value);
+ if (a) {
+ return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4],
+ +a[5], +a[6]));
+ }
+ }
+ return value;
+ });
+
+ myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) {
+ var d;
+ if (typeof value === 'string' &&
+ value.slice(0, 5) === 'Date(' &&
+ value.slice(-1) === ')') {
+ d = new Date(value.slice(5, -1));
+ if (d) {
+ return d;
+ }
+ }
+ return value;
+ });
+
+
+ This is a reference implementation. You are free to copy, modify, or
+ redistribute.
+*/
+
+/*jslint evil: true, regexp: true */
+
+/*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply,
+ call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours,
+ getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join,
+ lastIndex, length, parse, prototype, push, replace, slice, stringify,
+ test, toJSON, toString, valueOf
+*/
+
+
+// Create a JSON object only if one does not already exist. We create the
+// methods in a closure to avoid creating global variables.
+
+if (typeof JSON !== 'object') {
+ JSON = {};
+}
+
+(function () {
+ 'use strict';
+
+ function f(n) {
+ // Format integers to have at least two digits.
+ return n < 10 ? '0' + n : n;
+ }
+
+ if (typeof Date.prototype.toJSON !== 'function') {
+
+ Date.prototype.toJSON = function (key) {
+
+ return isFinite(this.valueOf())
+ ? this.getUTCFullYear() + '-' +
+ f(this.getUTCMonth() + 1) + '-' +
+ f(this.getUTCDate()) + 'T' +
+ f(this.getUTCHours()) + ':' +
+ f(this.getUTCMinutes()) + ':' +
+ f(this.getUTCSeconds()) + 'Z'
+ : null;
+ };
+
+ String.prototype.toJSON =
+ Number.prototype.toJSON =
+ Boolean.prototype.toJSON = function (key) {
+ return this.valueOf();
+ };
+ }
+
+ var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
+ escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
+ gap,
+ indent,
+ meta = { // table of character substitutions
+ '\b': '\\b',
+ '\t': '\\t',
+ '\n': '\\n',
+ '\f': '\\f',
+ '\r': '\\r',
+ '"' : '\\"',