text: (function($) {
  
    // Backbone.Stickit Namespace
    // --------------------------
  
    Backbone.Stickit = {
    
      _handlers: [],
  
      addHandler: function(handlers) {
        // Fill-in default values.
        handlers = _.map(_.flatten([handlers]), function(handler) {
          return _.extend({
            updateModel: true,
            updateView: true,
            updateMethod: 'text'
          }, handler);
        });
        this._handlers = this._handlers.concat(handlers);
      }
    };
  
    // Backbone.View Mixins
    // --------------------
  
    _.extend(Backbone.View.prototype, {
  
      // Collection of model event bindings.
      //   [{model,event,fn}, ...]
      _modelBindings: null,
  
      // Unbind the model and event bindings from `this._modelBindings` and
      // `this.$el`. If the optional `model` parameter is defined, then only
      // delete bindings for the given `model` and its corresponding view events.
      unstickit: function(model) {
        _.each(this._modelBindings, _.bind(function(binding, i) {
          if (model && binding.model !== model) return false;
          binding.model.off(binding.event, binding.fn);
          delete this._modelBindings[i];
        }, this));
        this._modelBindings = _.compact(this._modelBindings);
  
        this.$el.off('.stickit' + (model ? '.' + model.cid : ''));
      },
  
      // Using `this.bindings` configuration or the `optionalBindingsConfig`, binds `this.model`
      // or the `optionalModel` to elements in the view.
      stickit: function(optionalModel, optionalBindingsConfig) {
        var self = this,
          model = optionalModel || this.model,
          namespace = '.stickit.' + model.cid,
          bindings = optionalBindingsConfig || this.bindings || {};
  
        this._modelBindings || (this._modelBindings = []);
        this.unstickit(model);
  
        // Iterate through the selectors in the bindings configuration and configure
        // the various options for each field.
        _.each(_.keys(bindings), function(selector) {
          var $el, options, modelAttr, visibleCb, config,
            binding = bindings[selector] || {},
            bindKey = _.uniqueId();
          
          // Support ':el' selector - special case selector for the view managed delegate.
          if (selector != ':el') $el = self.$(selector);
          else {
            $el = self.$el;
            selector = '';
          }
       
          // Fail fast if the selector didn't match an element.
          if (!$el.length) return false;
  
          // Allow shorthand setting of model attributes - `'selector':'observe'`.
          if (_.isString(binding)) binding = {observe:binding};
  
          config = getConfiguration($el, binding);
  
          // Keep backward-compatibility for `modelAttr` which was renamed `observe`.
          modelAttr = config.observe || config.modelAttr;
  
          // Keep backward-compatibility for `format` which was renamed `onGet`.
          if (config.format && !config.onGet) config.onGet = config.format;
  
          // Create the model set options with a unique `bindKey` so that we
          // can avoid double-binding in the `change:attribute` event handler.
          options = _.extend({bindKey:bindKey}, config.setOptions || {});
  
          initializeAttributes(self, $el, config, model, modelAttr);
  
          initializeVisible(self, $el, config, model, modelAttr);
  
          if (modelAttr) {
            // Setup one-way, form element to model, bindings.
            _.each(config.events || [], function(type) {
              var event = type + namespace;
              var method = function(event) {
                var val = config.getVal.call(self, $el, event, config);
                // Don't update the model if false is returned from the `updateModel` configuration.
                if (evaluateBoolean(self, config.updateModel, val, config))
                  setAttr(model, modelAttr, val, options, self, config);
              };
              if (selector === '') self.$el.on(event, method);
              else self.$el.on(event, selector, method);
            });
  
            // Setup a `change:modelAttr` observer to keep the view element in sync.
            // `modelAttr` may be an array of attributes or a single string value.
            _.each(_.flatten([modelAttr]), function(attr) {
              observeModelEvent(model, self, 'change:'+attr, function(model, val, options) {
                if (options == null || options.bindKey != bindKey)
                  updateViewBindEl(self, $el, config, getAttr(model, modelAttr, config, self), model);
              });
            });
  
            updateViewBindEl(self, $el, config, getAttr(model, modelAttr, config, self), model, true);
          }
        });
  
        // Wrap `view.remove` to unbind stickit model and dom events.
        this.remove = _.wrap(this.remove, function(oldRemove) {
          self.unstickit();
          if (oldRemove) oldRemove.call(self);
        });
      }
    });
  
    // Helpers
    // -------
  
    // Evaluates the given `path` (in object/dot-notation) relative to the given
    // `obj`. If the path is null/undefined, then the given `obj` is returned.
    var evaluatePath = function(obj, path) {
      var parts = (path || '').split('.');
      var result = _.reduce(parts, function(memo, i) { return memo[i]; }, obj);
      return result == null ? obj : result;
    };
  
    // If the given `fn` is a string, then view[fn] is called, otherwise it is
    // a function that should be executed.
    var applyViewFn = function(view, fn) {
      if (fn) return (_.isString(fn) ? view[fn] : fn).apply(view, _.toArray(arguments).slice(2));
    };
  
    var getSelectedOption = function($select) { return $select.find('option').not(function(){ return !this.selected; }); };
  
    // Given a function, string (view function reference), or a boolean
    // value, returns the truthy result. Any other types evaluate as false.
    var evaluateBoolean = function(view, reference) {
      if (_.isBoolean(reference)) return reference;
      else if (_.isFunction(reference) || _.isString(reference))
        return applyViewFn.apply(this, _.toArray(arguments));
      return false;
    };
  
    // Setup a model event binding with the given function, and track the event
    // in the view's _modelBindings.
    var observeModelEvent = function(model, view, event, fn) {
      model.on(event, fn, view);
      view._modelBindings.push({model:model, event:event, fn:fn});
    };
  
    // Prepares the given `val`ue and sets it into the `model`.
    var setAttr = function(model, attr, val, options, context, config) {
      if (config.onSet) val = applyViewFn(context, config.onSet, val, config);
      model.set(attr, val, options);
    };
  
    // Returns the given `attr`'s value from the `model`, escaping and
    // formatting if necessary. If `attr` is an array, then an array of
    // respective values will be returned.
    var getAttr = function(model, attr, config, context) {
      var val, retrieveVal = function(field) {
        var retrieved = config.escape ? model.escape(field) : model.get(field);
        return _.isUndefined(retrieved) ? '' : retrieved;
      };
      val = _.isArray(attr) ? _.map(attr, retrieveVal) : retrieveVal(attr);
      return config.onGet ? applyViewFn(context, config.onGet, val, config) : val;
    };
  
    // Find handlers in `Backbone.Stickit._handlers` with selectors that match
    // `$el` and generate a configuration by mixing them in the order that they
    // were found with the with the givne `binding`.
    var getConfiguration = function($el, binding) {
      var handlers = [{
        updateModel: false,
        updateView: true,
        updateMethod: 'text',
        update: function($el, val, m, opts) { $el[opts.updateMethod](val); },
        getVal: function($el, e, opts) { return $el[opts.updateMethod](); }
      }];
      _.each(Backbone.Stickit._handlers, function(handler) {
        if ($el.is(handler.selector)) handlers.push(handler);
      });
      handlers.push(binding);
      var config = _.extend.apply(_, handlers);
      delete config.selector;
      return config;
    };
  
    // Setup the attributes configuration - a list that maps an attribute or
    // property `name`, to an `observe`d model attribute, using an optional
    // `onGet` formatter.
    //
    //     attributes: [{
    //       name: 'attributeOrPropertyName',
    //       observe: 'modelAttrName'
    //       onGet: function(modelAttrVal, modelAttrName) { ... }
    //     }, ...]
    //
    var initializeAttributes = function(view, $el, config, model, modelAttr) {
      var props = ['autofocus', 'autoplay', 'async', 'checked', 'controls', 'defer', 'disabled', 'hidden', 'loop', 'multiple', 'open', 'readonly', 'required', 'scoped', 'selected'];
  
      _.each(config.attributes || [], function(attrConfig) {
        var lastClass = '',
          observed = attrConfig.observe || (attrConfig.observe = modelAttr),
          updateAttr = function() {
            var updateType = _.indexOf(props, attrConfig.name, true) > -1 ? 'prop' : 'attr',
              val = getAttr(model, observed, attrConfig, view);
            // If it is a class then we need to remove the last value and add the new.
            if (attrConfig.name == 'class') {
              $el.removeClass(lastClass).addClass(val);
              lastClass = val;
            }
            else $el[updateType](attrConfig.name, val);
          };
        // Keep backward-compatibility for `format` which is now `onGet`.
        if (attrConfig.format && !attrConfig.onGet) attrConfig.onGet = attrConfig.format;
        _.each(_.flatten([observed]), function(attr) {
          observeModelEvent(model, view, 'change:' + attr, updateAttr);
        });
        updateAttr();
      });
    };
  
    // If `visible` is configured, then the view element will be shown/hidden
    // based on the truthiness of the modelattr's value or the result of the
    // given callback. If a `visibleFn` is also supplied, then that callback
    // will be executed to manually handle showing/hiding the view element.
    //
    //     observe: 'isRight',
    //     visible: true, // or function(val, options) {}
    //     visibleFn: function($el, isVisible, options) {} // optional handler
    //
    var initializeVisible = function(view, $el, config, model, modelAttr) {
      if (config.visible == null) return;
  	var visibleCb = function() {
        var visible = config.visible,
            visibleFn = config.visibleFn,
            val = getAttr(model, modelAttr, config, view),
            isVisible = !!val;
        // If `visible` is a function then it should return a boolean result to show/hide.
        if (_.isFunction(visible) || _.isString(visible)) isVisible = applyViewFn(view, visible, val, config);
        // Either use the custom `visibleFn`, if provided, or execute the standard show/hide.
        if (visibleFn) applyViewFn(view, visibleFn, $el, isVisible, config);
        else {
          if (isVisible) $el.show();
          else $el.hide();
        }
  	};
  	observeModelEvent(model, view, 'change:' + modelAttr, visibleCb);
  	visibleCb();
    };
  
    // Update the value of `$el` using the given configuration and trigger the
    // `afterUpdate` callback. This action may be blocked by `config.updateView`.
    //
    //     update: function($el, val, model, options) {},  // handler for updating
    //     updateView: true, // defaults to true
    //     afterUpdate: function($el, val, options) {} // optional callback
    //
    var updateViewBindEl = function(view, $el, config, val, model, isInitializing) {
      if (!evaluateBoolean(view, config.updateView, val, config)) return;
      config.update.call(view, $el, val, model, config);
      if (!isInitializing) applyViewFn(view, config.afterUpdate, $el, val, config);
    };
  
    // Default Handlers
    // ----------------
  
    Backbone.Stickit.addHandler([{
      selector: '[contenteditable="true"]',
      updateMethod: 'html',
      events: ['keyup', 'change', 'paste', 'cut']
    }, {
      selector: 'input',
      events: ['keyup', 'change', 'paste', 'cut'],
      update: function($el, val) { $el.val(val); },
      getVal: function($el) {
        var val = $el.val();
        if ($el.is('[type="number"]')) return val == null ? val : Number(val);
        else return val;
      }
    }, {
      selector: 'textarea',
      events: ['keyup', 'change', 'paste', 'cut'],
      update: function($el, val) { $el.val(val); },
      getVal: function($el) { return $el.val(); }
    }, {
      selector: 'input[type="radio"]',
      events: ['change'],
      update: function($el, val) {
        $el.filter('[value="'+val+'"]').prop('checked', true);
      },
      getVal: function($el) {
        return $el.filter(':checked').val();
      }
    }, {
      selector: 'input[type="checkbox"]',
      events: ['change'],
      update: function($el, val, model, options) {
        if ($el.length > 1) {
          // There are multiple checkboxes so we need to go through them and check
          // any that have value attributes that match what's in the array of `val`s.
          val || (val = []);
          _.each($el, function(el) {
            if (_.indexOf(val, $(el).val()) > -1) $(el).prop('checked', true);
            else $(el).prop('checked', false);
          });
        } else {
          if (_.isBoolean(val)) $el.prop('checked', val);
          else $el.prop('checked', val == $el.val());
        }
      },
      getVal: function($el) {
        var val;
        if ($el.length > 1) {
          val = _.reduce($el, function(memo, el) {
            if ($(el).prop('checked')) memo.push($(el).val());
            return memo;
          }, []);
        } else {
          val = $el.prop('checked');
          // If the checkbox has a value attribute defined, then
          // use that value. Most browsers use "on" as a default.
          var boxval = $el.val();
          if (boxval != 'on' && boxval != null) {
            if (val) val = $el.val();
            else val = null;
          }
        }
        return val;
      }
    }, {
      selector: 'select',
      events: ['change'],
      update: function($el, val, model, options) {
        var optList,
          selectConfig = options.selectOptions,
          list = selectConfig.collection,
          isMultiple = $el.prop('multiple');
  
        var addSelectOptions = function(optList, $el, selectConfig, fieldVal, isMultiple) {
          _.each(optList, function(obj) {
            var option = $('<option/>'), optionVal = obj;
  
            // If the list contains a null/undefined value, then an empty option should
            // be appended in the list; otherwise, fill the option with text and value.
            if (obj != null) {
              option.text(evaluatePath(obj, selectConfig.labelPath || "label"));
              optionVal = evaluatePath(obj, selectConfig.valuePath || "value");
            } else if ($el.find('option').length && $el.find('option').eq(0).data('stickit_bind_val') == null) return false;
  
            // Save the option value so that we can reference it later.
            option.data('stickit_bind_val', optionVal);
            if (!_.isArray(optionVal) && !_.isObject(optionVal)) option.val(optionVal);
  
            // Determine if this option is selected.
            if (!isMultiple && optionVal != null && fieldVal != null && optionVal == fieldVal || (_.isObject(fieldVal) && _.isEqual(optionVal, fieldVal)))
              option.prop('selected', true);
            else if (isMultiple && _.isArray(fieldVal)) {
              _.each(fieldVal, function(val) {
                if (_.isObject(val)) val = evaluatePath(val, selectConfig.valuePath);
                if (val == optionVal || (_.isObject(val) && _.isEqual(optionVal, val)))
                  option.prop('selected', true);
              });
            }
  
            $el.append(option);
          });
        };
  
        $el.html('');
  
        // The `list` configuration is a function that returns the options list or a string
        // which represents the path to the list relative to `window` or the view/`this`.
        var evaluate = function(view, list) {
          var context = window;
          if (list.indexOf('this.') === 0) context = view;
          list = list.replace(/^[a-z]*\.(.+)$/, '$1');
          return evaluatePath(context, list);
        };
        optList = _.isFunction(list) ? applyViewFn(this, list) : evaluate(this, list);
  
        // Add an empty default option if the current model attribute isn't defined.
        if (val == null)
          $el.append('<option/>').find('option').prop('selected', true).data('stickit_bind_val', val);
  
        if (_.isArray(optList)) {
          addSelectOptions(optList, $el, selectConfig, val, isMultiple);
        } else if (optList instanceof Backbone.Collection) {
          addSelectOptions(optList.toJSON(), $el, selectConfig, val, isMultiple);
        } else {
          // If the optList is an object, then it should be used to define an optgroup. An
          // optgroup object configuration looks like the following:
          //
          //     {
          //       'opt_labels': ['Looney Tunes', 'Three Stooges'],
          //       'Looney Tunes': [{id: 1, name: 'Bugs Bunny'}, {id: 2, name: 'Donald Duck'}],
          //       'Three Stooges': [{id: 3, name : 'moe'}, {id: 4, name : 'larry'}, {id: 5, name : 'curly'}]
          //     }
          //
          _.each(optList.opt_labels, function(label) {
            var $group = $('<optgroup/>').attr('label', label);
            addSelectOptions(optList[label], $group, selectConfig, val, isMultiple);
            $el.append($group);
          });
        }
      },
      getVal: function($el) {
        var val;
        if ($el.prop('multiple')) {
          val = $(getSelectedOption($el).map(function() {
            return $(this).data('stickit_bind_val');
          })).get();
        } else {
          val = getSelectedOption($el).data('stickit_bind_val');
        }
        return val;
      }
    }]);
  
  })(window.jQuery || window.Zepto);
  