text: // Backbone.ModelBinder v1.0.5
  // (c) 2013 Bart Wood
  // Distributed Under MIT License
  
  (function (factory) {
      if (typeof define === 'function' && define.amd) {
          // AMD. Register as an anonymous module.
          define(['underscore', 'jquery', 'backbone'], factory);
      } else {
          // Browser globals
          factory(_, jQuery, Backbone);
      }
  }(function(_, $, Backbone){
  
      if(!Backbone){
          throw 'Please include Backbone.js before Backbone.ModelBinder.js';
      }
  
      Backbone.ModelBinder = function(){
          _.bindAll.apply(_, [this].concat(_.functions(this)));
      };
  
      // Static setter for class level options
      Backbone.ModelBinder.SetOptions = function(options){
          Backbone.ModelBinder.options = options;
      };
  
      // Current version of the library.
      Backbone.ModelBinder.VERSION = '1.0.5';
      Backbone.ModelBinder.Constants = {};
      Backbone.ModelBinder.Constants.ModelToView = 'ModelToView';
      Backbone.ModelBinder.Constants.ViewToModel = 'ViewToModel';
  
      _.extend(Backbone.ModelBinder.prototype, {
  
          bind:function (model, rootEl, attributeBindings, options) {
              this.unbind();
  
              this._model = model;
              this._rootEl = rootEl;
              this._setOptions(options);
  
              if (!this._model) this._throwException('model must be specified');
              if (!this._rootEl) this._throwException('rootEl must be specified');
  
              if(attributeBindings){
                  // Create a deep clone of the attribute bindings
                  this._attributeBindings = $.extend(true, {}, attributeBindings);
  
                  this._initializeAttributeBindings();
                  this._initializeElBindings();
              }
              else {
                  this._initializeDefaultBindings();
              }
  
              this._bindModelToView();
              this._bindViewToModel();
          },
  
          bindCustomTriggers: function (model, rootEl, triggers, attributeBindings, modelSetOptions) {
              this._triggers = triggers;
              this.bind(model, rootEl, attributeBindings, modelSetOptions)
          },
  
          unbind:function () {
              this._unbindModelToView();
              this._unbindViewToModel();
  
              if(this._attributeBindings){
                  delete this._attributeBindings;
                  this._attributeBindings = undefined;
              }
          },
  
          _setOptions: function(options){
              this._options = _.extend({
                  boundAttribute: 'name'
              }, Backbone.ModelBinder.options, options);
  
              // initialize default options
              if(!this._options['modelSetOptions']){
                  this._options['modelSetOptions'] = {};
              }
              this._options['modelSetOptions'].changeSource = 'ModelBinder';
  
              if(!this._options['changeTriggers']){
                  this._options['changeTriggers'] = {'': 'change', '[contenteditable]': 'blur'};
              }
  
              if(!this._options['initialCopyDirection']){
                  this._options['initialCopyDirection'] = Backbone.ModelBinder.Constants.ModelToView;
              }
          },
  
          // Converts the input bindings, which might just be empty or strings, to binding objects
          _initializeAttributeBindings:function () {
              var attributeBindingKey, inputBinding, attributeBinding, elementBindingCount, elementBinding;
  
              for (attributeBindingKey in this._attributeBindings) {
                  inputBinding = this._attributeBindings[attributeBindingKey];
  
                  if (_.isString(inputBinding)) {
                      attributeBinding = {elementBindings: [{selector: inputBinding}]};
                  }
                  else if (_.isArray(inputBinding)) {
                      attributeBinding = {elementBindings: inputBinding};
                  }
                  else if(_.isObject(inputBinding)){
                      attributeBinding = {elementBindings: [inputBinding]};
                  }
                  else {
                      this._throwException('Unsupported type passed to Model Binder ' + attributeBinding);
                  }
  
                  // Add a linkage from the element binding back to the attribute binding
                  for(elementBindingCount = 0; elementBindingCount < attributeBinding.elementBindings.length; elementBindingCount++){
                      elementBinding = attributeBinding.elementBindings[elementBindingCount];
                      elementBinding.attributeBinding = attributeBinding;
                  }
  
                  attributeBinding.attributeName = attributeBindingKey;
                  this._attributeBindings[attributeBindingKey] = attributeBinding;
              }
          },
  
          // If the bindings are not specified, the default binding is performed on the specified attribute, name by default
          _initializeDefaultBindings: function(){
              var elCount, elsWithAttribute, matchedEl, name, attributeBinding;
  
              this._attributeBindings = {};
              elsWithAttribute = $('[' + this._options['boundAttribute'] + ']', this._rootEl);
  
              for(elCount = 0; elCount < elsWithAttribute.length; elCount++){
                  matchedEl = elsWithAttribute[elCount];
                  name = $(matchedEl).attr(this._options['boundAttribute']);
  
                  // For elements like radio buttons we only want a single attribute binding with possibly multiple element bindings
                  if(!this._attributeBindings[name]){
                      attributeBinding =  {attributeName: name};
                      attributeBinding.elementBindings = [{attributeBinding: attributeBinding, boundEls: [matchedEl]}];
                      this._attributeBindings[name] = attributeBinding;
                  }
                  else{
                      this._attributeBindings[name].elementBindings.push({attributeBinding: this._attributeBindings[name], boundEls: [matchedEl]});
                  }
              }
          },
  
          _initializeElBindings:function () {
              var bindingKey, attributeBinding, bindingCount, elementBinding, foundEls, elCount, el;
              for (bindingKey in this._attributeBindings) {
                  attributeBinding = this._attributeBindings[bindingKey];
  
                  for (bindingCount = 0; bindingCount < attributeBinding.elementBindings.length; bindingCount++) {
                      elementBinding = attributeBinding.elementBindings[bindingCount];
                      if (elementBinding.selector === '') {
                          foundEls = $(this._rootEl);
                      }
                      else {
                          foundEls = $(elementBinding.selector, this._rootEl);
                      }
  
                      if (foundEls.length === 0) {
                          this._throwException('Bad binding found. No elements returned for binding selector ' + elementBinding.selector);
                      }
                      else {
                          elementBinding.boundEls = [];
                          for (elCount = 0; elCount < foundEls.length; elCount++) {
                              el = foundEls[elCount];
                              elementBinding.boundEls.push(el);
                          }
                      }
                  }
              }
          },
  
          _bindModelToView: function () {
              this._model.on('change', this._onModelChange, this);
  
              if(this._options['initialCopyDirection'] === Backbone.ModelBinder.Constants.ModelToView){
                  this.copyModelAttributesToView();
              }
          },
  
          // attributesToCopy is an optional parameter - if empty, all attributes
          // that are bound will be copied.  Otherwise, only attributeBindings specified
          // in the attributesToCopy are copied.
          copyModelAttributesToView: function(attributesToCopy){
              var attributeName, attributeBinding;
  
              for (attributeName in this._attributeBindings) {
                  if(attributesToCopy === undefined || _.indexOf(attributesToCopy, attributeName) !== -1){
                      attributeBinding = this._attributeBindings[attributeName];
                      this._copyModelToView(attributeBinding);
                  }
              }
          },
  
          copyViewValuesToModel: function(){
              var bindingKey, attributeBinding, bindingCount, elementBinding, elCount, el;
              for (bindingKey in this._attributeBindings) {
                  attributeBinding = this._attributeBindings[bindingKey];
  
                  for (bindingCount = 0; bindingCount < attributeBinding.elementBindings.length; bindingCount++) {
                      elementBinding = attributeBinding.elementBindings[bindingCount];
  
                      if(this._isBindingUserEditable(elementBinding)){
                          if(this._isBindingRadioGroup(elementBinding)){
                              el = this._getRadioButtonGroupCheckedEl(elementBinding);
                              if(el){
                                  this._copyViewToModel(elementBinding, el);
                              }
                          }
                          else {
                              for(elCount = 0; elCount < elementBinding.boundEls.length; elCount++){
                                  el = $(elementBinding.boundEls[elCount]);
                                  if(this._isElUserEditable(el)){
                                      this._copyViewToModel(elementBinding, el);
                                  }
                              }
                          }
                      }
                  }
              }
          },
  
          _unbindModelToView: function(){
              if(this._model){
                  this._model.off('change', this._onModelChange);
                  this._model = undefined;
              }
          },
  
          _bindViewToModel: function () {
              _.each(this._options['changeTriggers'], function (event, selector) {
                  $(this._rootEl).delegate(selector, event, this._onElChanged);
              }, this);
  
              if(this._options['initialCopyDirection'] === Backbone.ModelBinder.Constants.ViewToModel){
                  this.copyViewValuesToModel();
              }
          },
  
          _unbindViewToModel: function () {
              if(this._options && this._options['changeTriggers']){
                  _.each(this._options['changeTriggers'], function (event, selector) {
                      $(this._rootEl).undelegate(selector, event, this._onElChanged);
                  }, this);
              }
          },
  
          _onElChanged:function (event) {
              var el, elBindings, elBindingCount, elBinding;
  
              el = $(event.target)[0];
              elBindings = this._getElBindings(el);
  
              for(elBindingCount = 0; elBindingCount < elBindings.length; elBindingCount++){
                  elBinding = elBindings[elBindingCount];
                  if (this._isBindingUserEditable(elBinding)) {
                      this._copyViewToModel(elBinding, el);
                  }
              }
          },
  
          _isBindingUserEditable: function(elBinding){
              return elBinding.elAttribute === undefined ||
                  elBinding.elAttribute === 'text' ||
                  elBinding.elAttribute === 'html';
          },
  
          _isElUserEditable: function(el){
              var isContentEditable = el.attr('contenteditable');
              return isContentEditable || el.is('input') || el.is('select') || el.is('textarea');
          },
  
          _isBindingRadioGroup: function(elBinding){
              var elCount, el;
              var isAllRadioButtons = elBinding.boundEls.length > 0;
              for(elCount = 0; elCount < elBinding.boundEls.length; elCount++){
                  el = $(elBinding.boundEls[elCount]);
                  if(el.attr('type') !== 'radio'){
                      isAllRadioButtons = false;
                      break;
                  }
              }
  
              return isAllRadioButtons;
          },
  
          _getRadioButtonGroupCheckedEl: function(elBinding){
              var elCount, el;
              for(elCount = 0; elCount < elBinding.boundEls.length; elCount++){
                  el = $(elBinding.boundEls[elCount]);
                  if(el.attr('type') === 'radio' && el.attr('checked')){
                      return el;
                  }
              }
  
              return undefined;
          },
  
          _getElBindings:function (findEl) {
              var attributeName, attributeBinding, elementBindingCount, elementBinding, boundElCount, boundEl;
              var elBindings = [];
  
              for (attributeName in this._attributeBindings) {
                  attributeBinding = this._attributeBindings[attributeName];
  
                  for (elementBindingCount = 0; elementBindingCount < attributeBinding.elementBindings.length; elementBindingCount++) {
                      elementBinding = attributeBinding.elementBindings[elementBindingCount];
  
                      for (boundElCount = 0; boundElCount < elementBinding.boundEls.length; boundElCount++) {
                          boundEl = elementBinding.boundEls[boundElCount];
  
                          if (boundEl === findEl) {
                              elBindings.push(elementBinding);
                          }
                      }
                  }
              }
  
              return elBindings;
          },
  
          _onModelChange:function () {
              var changedAttribute, attributeBinding;
  
              for (changedAttribute in this._model.changedAttributes()) {
                  attributeBinding = this._attributeBindings[changedAttribute];
  
                  if (attributeBinding) {
                      this._copyModelToView(attributeBinding);
                  }
              }
          },
  
          _copyModelToView:function (attributeBinding) {
              var elementBindingCount, elementBinding, boundElCount, boundEl, value, convertedValue;
  
              value = this._model.get(attributeBinding.attributeName);
  
              for (elementBindingCount = 0; elementBindingCount < attributeBinding.elementBindings.length; elementBindingCount++) {
                  elementBinding = attributeBinding.elementBindings[elementBindingCount];
  
                  for (boundElCount = 0; boundElCount < elementBinding.boundEls.length; boundElCount++) {
                      boundEl = elementBinding.boundEls[boundElCount];
  
                      if(!boundEl._isSetting){
                          convertedValue = this._getConvertedValue(Backbone.ModelBinder.Constants.ModelToView, elementBinding, value);
                          this._setEl($(boundEl), elementBinding, convertedValue);
                      }
                  }
              }
          },
  
          _setEl: function (el, elementBinding, convertedValue) {
              if (elementBinding.elAttribute) {
                  this._setElAttribute(el, elementBinding, convertedValue);
              }
              else {
                  this._setElValue(el, convertedValue);
              }
          },
  
          _setElAttribute:function (el, elementBinding, convertedValue) {
              switch (elementBinding.elAttribute) {
                  case 'html':
                      el.html(convertedValue);
                      break;
                  case 'text':
                      el.text(convertedValue);
                      break;
                  case 'enabled':
                      el.prop('disabled', !convertedValue);
                      break;
                  case 'displayed':
                      el[convertedValue ? 'show' : 'hide']();
                      break;
                  case 'hidden':
                      el[convertedValue ? 'hide' : 'show']();
                      break;
                  case 'css':
                      el.css(elementBinding.cssAttribute, convertedValue);
                      break;
                  case 'class':
                      var previousValue = this._model.previous(elementBinding.attributeBinding.attributeName);
                      var currentValue = this._model.get(elementBinding.attributeBinding.attributeName);
                      // is current value is now defined then remove the class the may have been set for the undefined value
                      if(!_.isUndefined(previousValue) || !_.isUndefined(currentValue)){
                          previousValue = this._getConvertedValue(Backbone.ModelBinder.Constants.ModelToView, elementBinding, previousValue);
                          el.removeClass(previousValue);
                      }
  
                      if(convertedValue){
                          el.addClass(convertedValue);
                      }
                      break;
                  default:
                      el.attr(elementBinding.elAttribute, convertedValue);
              }
          },
  
          _setElValue:function (el, convertedValue) {
              if(el.attr('type')){
                  switch (el.attr('type')) {
                      case 'radio':
                          el.prop('checked', el.val() === convertedValue);
                          break;
                      case 'checkbox':
                           el.prop('checked', !!convertedValue);
                          break;
                      case 'file':
                          break;
                      default:
                          el.val(convertedValue);
                  }
              }
              else if(el.is('input') || el.is('select') || el.is('textarea')){
                  el.val(convertedValue || (convertedValue === 0 ? '0' : ''));
              }
              else {
                  el.text(convertedValue || (convertedValue === 0 ? '0' : ''));
              }
          },
  
          _copyViewToModel: function (elementBinding, el) {
              var result, value, convertedValue;
  
              if (!el._isSetting) {
  
                  el._isSetting = true;
                  result = this._setModel(elementBinding, $(el));
                  el._isSetting = false;
  
                  if(result && elementBinding.converter){
                      value = this._model.get(elementBinding.attributeBinding.attributeName);
                      convertedValue = this._getConvertedValue(Backbone.ModelBinder.Constants.ModelToView, elementBinding, value);
                      this._setEl($(el), elementBinding, convertedValue);
                  }
              }
          },
  
          _getElValue: function(elementBinding, el){
              switch (el.attr('type')) {
                  case 'checkbox':
                      return el.prop('checked') ? true : false;
                  default:
                      if(el.attr('contenteditable') !== undefined){
                          return el.html();
                      }
                      else {
                          return el.val();
                      }
              }
          },
  
          _setModel: function (elementBinding, el) {
              var data = {};
              var elVal = this._getElValue(elementBinding, el);
              elVal = this._getConvertedValue(Backbone.ModelBinder.Constants.ViewToModel, elementBinding, elVal);
              data[elementBinding.attributeBinding.attributeName] = elVal;
              return this._model.set(data,  this._options['modelSetOptions']);
          },
  
          _getConvertedValue: function (direction, elementBinding, value) {
  
              if (elementBinding.converter) {
                  value = elementBinding.converter(direction, value, elementBinding.attributeBinding.attributeName, this._model, elementBinding.boundEls);
              }
              else if(this._options['converter']){
                  value = this._options['converter'](direction, value, elementBinding.attributeBinding.attributeName, this._model, elementBinding.boundEls);
              }
  
              return value;
          },
  
          _throwException: function(message){
              if(this._options.suppressThrows){
                  if(console && console.error){
                      console.error(message);
                  }
              }
              else {
                  throw message;
              }
          }
      });
  
      Backbone.ModelBinder.CollectionConverter = function(collection){
          this._collection = collection;
  
          if(!this._collection){
              throw 'Collection must be defined';
          }
          _.bindAll(this, 'convert');
      };
  
      _.extend(Backbone.ModelBinder.CollectionConverter.prototype, {
          convert: function(direction, value){
              if (direction === Backbone.ModelBinder.Constants.ModelToView) {
                  return value ? value.id : undefined;
              }
              else {
                  return this._collection.get(value);
              }
          }
      });
  
      // A static helper function to create a default set of bindings that you can customize before calling the bind() function
      // rootEl - where to find all of the bound elements
      // attributeType - probably 'name' or 'id' in most cases
      // converter(optional) - the default converter you want applied to all your bindings
      // elAttribute(optional) - the default elAttribute you want applied to all your bindings
      Backbone.ModelBinder.createDefaultBindings = function(rootEl, attributeType, converter, elAttribute){
          var foundEls, elCount, foundEl, attributeName;
          var bindings = {};
  
          foundEls = $('[' + attributeType + ']', rootEl);
  
          for(elCount = 0; elCount < foundEls.length; elCount++){
              foundEl = foundEls[elCount];
              attributeName = $(foundEl).attr(attributeType);
  
              if(!bindings[attributeName]){
                  var attributeBinding =  {selector: '[' + attributeType + '="' + attributeName + '"]'};
                  bindings[attributeName] = attributeBinding;
  
                  if(converter){
                      bindings[attributeName].converter = converter;
                  }
  
                  if(elAttribute){
                      bindings[attributeName].elAttribute = elAttribute;
                  }
              }
          }
  
          return bindings;
      };
  
      // Helps you to combine 2 sets of bindings
      Backbone.ModelBinder.combineBindings = function(destination, source){
          _.each(source, function(value, key){
              var elementBinding = {selector: value.selector};
  
              if(value.converter){
                  elementBinding.converter = value.converter;
              }
  
              if(value.elAttribute){
                  elementBinding.elAttribute = value.elAttribute;
              }
  
              if(!destination[key]){
                  destination[key] = elementBinding;
              }
              else {
                  destination[key] = [destination[key], elementBinding];
              }
          });
  
          return destination;
      };
  
  
      return Backbone.ModelBinder;
  
  }));
  