diff --git a/NzbDrone.Web/Cassette.targets b/NzbDrone.Web/Cassette.targets
deleted file mode 100644
index 7f2ab6b26..000000000
--- a/NzbDrone.Web/Cassette.targets
+++ /dev/null
@@ -1,21 +0,0 @@
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/NzbDrone.Web/Web.config b/NzbDrone.Web/Web.config
index aeb634253..d9354b396 100644
--- a/NzbDrone.Web/Web.config
+++ b/NzbDrone.Web/Web.config
@@ -6,7 +6,7 @@
-
+
diff --git a/NzbDrone.Web/_backboneApp/JsLibraries/backbone.collectionbinder.js b/NzbDrone.Web/_backboneApp/JsLibraries/backbone.collectionbinder.js
new file mode 100644
index 000000000..7b6f19e50
--- /dev/null
+++ b/NzbDrone.Web/_backboneApp/JsLibraries/backbone.collectionbinder.js
@@ -0,0 +1,284 @@
+// Backbone.CollectionBinder v0.1.1
+// (c) 2012 Bart Wood
+// Distributed Under MIT License
+
+(function () {
+
+ if (!Backbone) {
+ throw 'Please include Backbone.js before Backbone.ModelBinder.js';
+ }
+
+ if (!Backbone.ModelBinder) {
+ throw 'Please include Backbone.ModelBinder.js before Backbone.CollectionBinder.js';
+ }
+
+ Backbone.CollectionBinder = function (elManagerFactory, options) {
+ _.bindAll(this);
+ this._elManagers = {};
+
+ this._elManagerFactory = elManagerFactory;
+ if (!this._elManagerFactory) throw 'elManagerFactory must be defined.';
+
+ // Let the factory just use the trigger function on the view binder
+ this._elManagerFactory.trigger = this.trigger;
+
+ this._options = options || {};
+ };
+
+ Backbone.CollectionBinder.VERSION = '0.1.1';
+
+ _.extend(Backbone.CollectionBinder.prototype, Backbone.Events, {
+ bind: function (collection, parentEl) {
+ this.unbind();
+
+ if (!collection) throw 'collection must be defined';
+ if (!parentEl) throw 'parentEl must be defined';
+
+ this._collection = collection;
+ this._elManagerFactory.setParentEl(parentEl);
+
+ this._onCollectionReset();
+
+ this._collection.on('add', this._onCollectionAdd, this);
+ this._collection.on('remove', this._onCollectionRemove, this);
+ this._collection.on('reset', this._onCollectionReset, this);
+
+ },
+
+ unbind: function () {
+ if (this._collection !== undefined) {
+ this._collection.off('add', this._onCollectionAdd);
+ this._collection.off('remove', this._onCollectionRemove);
+ this._collection.off('reset', this._onCollectionReset);
+ }
+
+ this._removeAllElManagers();
+ },
+
+ getManagerForEl: function (el) {
+ var i, elManager, elManagers = _.values(this._elManagers);
+
+ for (i = 0; i < elManagers.length; i++) {
+ elManager = elManagers[i];
+
+ if (elManager.isElContained(el)) {
+ return elManager;
+ }
+ }
+
+ return undefined;
+ },
+
+ getManagerForModel: function (model) {
+ var i, elManager, elManagers = _.values(this._elManagers);
+
+ for (i = 0; i < elManagers.length; i++) {
+ elManager = elManagers[i];
+
+ if (elManager.getModel() === model) {
+ return elManager;
+ }
+ }
+
+ return undefined;
+ },
+
+ _onCollectionAdd: function (model) {
+ this._elManagers[model.cid] = this._elManagerFactory.makeElManager(model);
+ this._elManagers[model.cid].createEl();
+
+ if (this._options['autoSort']) {
+ this.sortRootEls();
+ }
+ },
+
+ _onCollectionRemove: function (model) {
+ this._removeElManager(model);
+ },
+
+ _onCollectionReset: function () {
+ this._removeAllElManagers();
+
+ this._collection.each(function (model) {
+ this._onCollectionAdd(model);
+ }, this);
+
+ this.trigger('elsReset', this._collection);
+ },
+
+ _removeAllElManagers: function () {
+ _.each(this._elManagers, function (elManager) {
+ elManager.removeEl();
+ delete this._elManagers[elManager._model.cid];
+ }, this);
+
+ delete this._elManagers;
+ this._elManagers = {};
+ },
+
+ _removeElManager: function (model) {
+ if (this._elManagers[model.cid] !== undefined) {
+ this._elManagers[model.cid].removeEl();
+ delete this._elManagers[model.cid];
+ }
+ },
+
+ sortRootEls: function () {
+ this._collection.each(function (model, modelIndex) {
+ var modelElManager = this.getManagerForModel(model);
+ if (modelElManager) {
+ var modelEl = modelElManager.getEl();
+ var currentRootEls = this._elManagerFactory.getParentEl().children();
+
+ if (currentRootEls[modelIndex] !== modelEl[0]) {
+ modelEl.detach();
+ modelEl.insertBefore(currentRootEls[modelIndex]);
+ }
+ }
+ }, this);
+ }
+ });
+
+ // The ElManagerFactory is used for els that are just html templates
+ // elHtml - how the model's html will be rendered. Must have a single root element (div,span).
+ // bindings (optional) - either a string which is the binding attribute (name, id, data-name, etc.) or a normal bindings hash
+ Backbone.CollectionBinder.ElManagerFactory = function (elHtml, bindings) {
+ _.bindAll(this);
+
+ this._elHtml = elHtml;
+ this._bindings = bindings;
+
+ if (!_.isString(this._elHtml)) throw 'elHtml must be a valid html string';
+ };
+
+ _.extend(Backbone.CollectionBinder.ElManagerFactory.prototype, {
+ setParentEl: function (parentEl) {
+ this._parentEl = parentEl;
+ },
+
+ getParentEl: function () {
+ return this._parentEl;
+ },
+
+ makeElManager: function (model) {
+
+ var elManager = {
+ _model: model,
+
+ createEl: function () {
+
+ this._el = $(this._elHtml);
+ $(this._parentEl).append(this._el);
+
+ if (this._bindings) {
+ if (_.isString(this._bindings)) {
+ this._modelBinder = new Backbone.ModelBinder();
+ this._modelBinder.bind(this._model, this._el, Backbone.ModelBinder.createDefaultBindings(this._el, this._bindings));
+ }
+ else if (_.isObject(this._bindings)) {
+ this._modelBinder = new Backbone.ModelBinder();
+ this._modelBinder.bind(this._model, this._el, this._bindings);
+ }
+ else {
+ throw 'Unsupported bindings type, please use a boolean or a bindings hash';
+ }
+ }
+
+ this.trigger('elCreated', this._model, this._el);
+ },
+
+ removeEl: function () {
+ if (this._modelBinder !== undefined) {
+ this._modelBinder.unbind();
+ }
+
+ this._el.remove();
+ this.trigger('elRemoved', this._model, this._el);
+ },
+
+ isElContained: function (findEl) {
+ return this._el === findEl || $(this._el).has(findEl).length > 0;
+ },
+
+ getModel: function () {
+ return this._model;
+ },
+
+ getEl: function () {
+ return this._el;
+ }
+ };
+
+ _.extend(elManager, this);
+ return elManager;
+ }
+ });
+
+
+ // The ViewManagerFactory is used for els that are created and owned by backbone views.
+ // There is no bindings option because the view made by the viewCreator should take care of any binding
+ // viewCreator - a callback that will create backbone view instances for a model passed to the callback
+ Backbone.CollectionBinder.ViewManagerFactory = function (viewCreator) {
+ _.bindAll(this);
+ this._viewCreator = viewCreator;
+
+ if (!_.isFunction(this._viewCreator)) throw 'viewCreator must be a valid function that accepts a model and returns a backbone view';
+ };
+
+ _.extend(Backbone.CollectionBinder.ViewManagerFactory.prototype, {
+ setParentEl: function (parentEl) {
+ this._parentEl = parentEl;
+ },
+
+ getParentEl: function () {
+ return this._parentEl;
+ },
+
+ makeElManager: function (model) {
+ var elManager = {
+
+ _model: model,
+
+ createEl: function () {
+ this._view = this._viewCreator(model);
+ $(this._parentEl).append(this._view.render(this._model).el);
+
+ this.trigger('elCreated', this._model, this._view);
+ },
+
+ removeEl: function () {
+ if (this._view.close !== undefined) {
+ this._view.close();
+ }
+ else {
+ this._view.$el.remove();
+ console.log('warning, you should implement a close() function for your view, you might end up with zombies');
+ }
+
+ this.trigger('elRemoved', this._model, this._view);
+ },
+
+ isElContained: function (findEl) {
+ return this._view.el === findEl || this._view.$el.has(findEl).length > 0;
+ },
+
+ getModel: function () {
+ return this._model;
+ },
+
+ getView: function () {
+ return this._view;
+ },
+
+ getEl: function () {
+ return this._view.$el;
+ }
+ };
+
+ _.extend(elManager, this);
+
+ return elManager;
+ }
+ });
+
+}).call(this);
\ No newline at end of file
diff --git a/NzbDrone.Web/_backboneApp/JsLibraries/backbone.marionette.extend.js b/NzbDrone.Web/_backboneApp/JsLibraries/backbone.marionette.extend.js
index cf6c38841..31d9f4499 100644
--- a/NzbDrone.Web/_backboneApp/JsLibraries/backbone.marionette.extend.js
+++ b/NzbDrone.Web/_backboneApp/JsLibraries/backbone.marionette.extend.js
@@ -21,4 +21,11 @@
return template;
}
+});
+
+
+_.extend(Marionette.View.prototype, {
+
+
+
});
\ No newline at end of file
diff --git a/NzbDrone.Web/_backboneApp/JsLibraries/backbone.modelbinder.js b/NzbDrone.Web/_backboneApp/JsLibraries/backbone.modelbinder.js
new file mode 100644
index 000000000..d9694638c
--- /dev/null
+++ b/NzbDrone.Web/_backboneApp/JsLibraries/backbone.modelbinder.js
@@ -0,0 +1,482 @@
+// Backbone.ModelBinder v0.1.6
+// (c) 2012 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(_, $, Backbone);
+ }
+}(function (_, $, Backbone) {
+
+ if (!Backbone) {
+ throw 'Please include Backbone.js before Backbone.ModelBinder.js';
+ }
+
+ Backbone.ModelBinder = function (modelSetOptions) {
+ _.bindAll(this);
+ this._modelSetOptions = modelSetOptions || {};
+ };
+
+ // Current version of the library.
+ Backbone.ModelBinder.VERSION = '0.1.6';
+ Backbone.ModelBinder.Constants = {};
+ Backbone.ModelBinder.Constants.ModelToView = 'ModelToView';
+ Backbone.ModelBinder.Constants.ViewToModel = 'ViewToModel';
+
+ _.extend(Backbone.ModelBinder.prototype, {
+
+ bind: function (model, rootEl, attributeBindings, modelSetOptions) {
+ this.unbind();
+
+ this._model = model;
+ this._rootEl = rootEl;
+ this._modelSetOptions = _.extend({}, this._modelSetOptions, modelSetOptions);
+
+ if (!this._model) throw 'model must be specified';
+ if (!this._rootEl) throw '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;
+ }
+ },
+
+ // 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 {
+ throw '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 name attribute
+ _initializeDefaultBindings: function () {
+ var elCount, namedEls, namedEl, name, attributeBinding;
+ this._attributeBindings = {};
+ namedEls = $('[name]', this._rootEl);
+
+ for (elCount = 0; elCount < namedEls.length; elCount++) {
+ namedEl = namedEls[elCount];
+ name = $(namedEl).attr('name');
+
+ // 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: [namedEl] }];
+ this._attributeBindings[name] = attributeBinding;
+ }
+ else {
+ this._attributeBindings[name].elementBindings.push({ attributeBinding: this._attributeBindings[name], boundEls: [namedEl] });
+ }
+ }
+ },
+
+ _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) {
+ throw '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);
+
+ 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);
+ }
+ }
+ },
+
+ _unbindModelToView: function () {
+ if (this._model) {
+ this._model.off('change', this._onModelChange);
+ this._model = undefined;
+ }
+ },
+
+ _bindViewToModel: function () {
+ if (this._triggers) {
+ _.each(this._triggers, function (event, selector) {
+ $(this._rootEl).delegate(selector, event, this._onElChanged);
+ }, this);
+ }
+ else {
+ $(this._rootEl).delegate('', 'change', this._onElChanged);
+ // The change event doesn't work properly for contenteditable elements - but blur does
+ $(this._rootEl).delegate('[contenteditable]', 'blur', this._onElChanged);
+ }
+ },
+
+ _unbindViewToModel: function () {
+ if (this._rootEl) {
+ if (this._triggers) {
+ _.each(this._triggers, function (event, selector) {
+ $(this._rootEl).undelegate(selector, event, this._onElChanged);
+ }, this);
+ }
+ else {
+ $(this._rootEl).undelegate('', 'change', this._onElChanged);
+ $(this._rootEl).undelegate('[contenteditable]', 'blur', this._onElChanged);
+ }
+ }
+ },
+
+ _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';
+ },
+
+ _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.attr('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);
+ if (!_.isUndefined(previousValue)) {
+ 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':
+ if (el.val() === convertedValue) {
+ el.attr('checked', 'checked');
+ }
+ break;
+ case 'checkbox':
+ if (convertedValue) {
+ el.attr('checked', 'checked');
+ }
+ else {
+ el.removeAttr('checked');
+ }
+ break;
+ default:
+ el.val(convertedValue);
+ }
+ }
+ else if (el.is('input') || el.is('select') || el.is('textarea')) {
+ el.val(convertedValue);
+ }
+ else {
+ el.text(convertedValue);
+ }
+ },
+
+ _copyViewToModel: function (elementBinding, el) {
+ var value, convertedValue;
+
+ if (!el._isSetting) {
+
+ el._isSetting = true;
+ this._setModel(elementBinding, $(el));
+ el._isSetting = false;
+
+ if (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;
+ var opts = _.extend({}, this._modelSetOptions, { changeSource: 'ModelBinder' });
+ this._model.set(data, opts);
+ },
+
+ _getConvertedValue: function (direction, elementBinding, value) {
+ if (elementBinding.converter) {
+ value = elementBinding.converter(direction, value, elementBinding.attributeBinding.attributeName, this._model);
+ }
+
+ return value;
+ }
+ });
+
+ 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;
+
+}));
\ No newline at end of file
diff --git a/NzbDrone.Web/packages.config b/NzbDrone.Web/packages.config
index 84015841a..d0c44136e 100644
--- a/NzbDrone.Web/packages.config
+++ b/NzbDrone.Web/packages.config
@@ -7,7 +7,6 @@
-