mirror of
https://github.com/Readarr/Readarr.git
synced 2026-04-18 21:34:28 -04:00
splited MVC and nancy application
backbone app is now fully served from nancy including css,js,html
This commit is contained in:
@@ -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);
|
||||
@@ -0,0 +1,131 @@
|
||||
(function () {
|
||||
var __bind = function (fn, me) { return function () { return fn.apply(me, arguments); }; };
|
||||
|
||||
window.Backbone.Debug = (function () {
|
||||
|
||||
function Debug() {
|
||||
this._hookPrototype = __bind(this._hookPrototype, this);
|
||||
this._hookMethod = __bind(this._hookMethod, this);
|
||||
this._logSync = __bind(this._logSync, this);
|
||||
this._logEvent = __bind(this._logEvent, this);
|
||||
this._saveObjects = __bind(this._saveObjects, this);
|
||||
this._hookSync = __bind(this._hookSync, this);
|
||||
this._hookEvents = __bind(this._hookEvents, this);
|
||||
this._trackObjects = __bind(this._trackObjects, this);
|
||||
this.off = __bind(this.off, this);
|
||||
this.on = __bind(this.on, this);
|
||||
this.routers = __bind(this.routers, this);
|
||||
this.views = __bind(this.views, this);
|
||||
this.models = __bind(this.models, this);
|
||||
this.collections = __bind(this.collections, this); this._options = {
|
||||
'log:events': true,
|
||||
'log:sync': true
|
||||
};
|
||||
this._objects = {
|
||||
Collection: {},
|
||||
Model: {},
|
||||
View: {},
|
||||
Router: {}
|
||||
};
|
||||
this._trackObjects();
|
||||
this._hookEvents();
|
||||
this._hookSync();
|
||||
}
|
||||
|
||||
Debug.prototype.collections = function () {
|
||||
return this._objects.Collection;
|
||||
};
|
||||
|
||||
Debug.prototype.models = function () {
|
||||
return this._objects.Model;
|
||||
};
|
||||
|
||||
Debug.prototype.views = function () {
|
||||
return this._objects.View;
|
||||
};
|
||||
|
||||
Debug.prototype.routers = function () {
|
||||
return this._objects.Router;
|
||||
};
|
||||
|
||||
Debug.prototype.on = function (option) {
|
||||
if (option != null) {
|
||||
return this._options[option] = true;
|
||||
} else {
|
||||
this._options['log:events'] = true;
|
||||
return this._options['log:sync'] = true;
|
||||
}
|
||||
};
|
||||
|
||||
Debug.prototype.off = function (option) {
|
||||
if (option != null) {
|
||||
return this._options[option] = false;
|
||||
} else {
|
||||
this._options['log:events'] = false;
|
||||
return this._options['log:sync'] = false;
|
||||
}
|
||||
};
|
||||
|
||||
Debug.prototype._trackObjects = function () {
|
||||
this._hookPrototype('Collection', 'constructor', this._saveObjects);
|
||||
this._hookPrototype('Model', 'constructor', this._saveObjects);
|
||||
this._hookPrototype('View', 'constructor', this._saveObjects);
|
||||
return this._hookPrototype('Router', 'constructor', this._saveObjects);
|
||||
};
|
||||
|
||||
Debug.prototype._hookEvents = function () {
|
||||
this._hookPrototype('Collection', 'trigger', this._logEvent);
|
||||
this._hookPrototype('Model', 'trigger', this._logEvent);
|
||||
this._hookPrototype('View', 'trigger', this._logEvent);
|
||||
return this._hookPrototype('Router', 'trigger', this._logEvent);
|
||||
};
|
||||
|
||||
Debug.prototype._hookSync = function () {
|
||||
return this._hookMethod('sync', this._logSync);
|
||||
};
|
||||
|
||||
Debug.prototype._saveObjects = function (type, method, object) {
|
||||
return this._objects[type][object.constructor.name + ':' + object.cid] = object;
|
||||
};
|
||||
|
||||
Debug.prototype._logEvent = function (parent_object, method, object, args) {
|
||||
if (this._options['log:events']) {
|
||||
return console.log("" + args[0] + " - ", object);
|
||||
}
|
||||
};
|
||||
|
||||
Debug.prototype._logSync = function (method, object, args) {
|
||||
if (this._options['log:sync'] === true) {
|
||||
return console.log("sync - " + args[0], args[1]);
|
||||
}
|
||||
};
|
||||
|
||||
Debug.prototype._hookMethod = function (method, action) {
|
||||
var original;
|
||||
original = window.Backbone[method];
|
||||
return window.Backbone[method] = function () {
|
||||
var ret;
|
||||
ret = original.apply(this, arguments);
|
||||
action(method, this, arguments);
|
||||
return ret;
|
||||
};
|
||||
};
|
||||
|
||||
Debug.prototype._hookPrototype = function (object, method, action) {
|
||||
var original;
|
||||
original = window.Backbone[object].prototype[method];
|
||||
return window.Backbone[object].prototype[method] = function () {
|
||||
var ret;
|
||||
ret = original.apply(this, arguments);
|
||||
action(object, method, this, arguments);
|
||||
return ret;
|
||||
};
|
||||
};
|
||||
|
||||
return Debug;
|
||||
|
||||
})();
|
||||
|
||||
window.Backbone.debug = new Backbone.Debug();
|
||||
|
||||
}).call(this);
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,212 @@
|
||||
https://github.com/marionettejs/backbone.marionette/blob/viewswap/docs/marionette.viewswapper.md
|
||||
|
||||
// View Swapper
|
||||
// ------------
|
||||
//
|
||||
// Switch out views based on events that are triggered
|
||||
// by the currently displayed view. Enables easy "edit in
|
||||
// place" features, "loading" screens, and more.
|
||||
|
||||
Marionette.ViewSwapper = Marionette.View.extend({
|
||||
constructor: function (options) {
|
||||
this._swapperViews = {};
|
||||
this._swapperBindings = new Marionette.EventBinder();
|
||||
this._currentViewBindings = new Marionette.EventBinder();
|
||||
|
||||
Marionette.View.prototype.constructor.apply(this, arguments);
|
||||
|
||||
this.views = Marionette.getOption(this, "views");
|
||||
this.swapOn = Marionette.getOption(this, "swapOn");
|
||||
this.initialView = Marionette.getOption(this, "initialView");
|
||||
|
||||
this._setupViewEvents("swapper", this, this._swapperBindings);
|
||||
},
|
||||
|
||||
// Render the current view. If no current view is set, it
|
||||
// will render the `initialView` that was configured.
|
||||
render: function () {
|
||||
// set up the initial view to display, on first render
|
||||
if (!this.currentView) {
|
||||
var initialViewName = Marionette.getOption(this, "initialView");
|
||||
this._swapView(initialViewName);
|
||||
}
|
||||
|
||||
// render and show the new view
|
||||
this.currentView.render();
|
||||
this.$el.append(this.currentView.$el);
|
||||
|
||||
// setup a callback for the showView call to recieve
|
||||
var done = _.bind(function () {
|
||||
// trigger show/onShow on the previous view
|
||||
if (this.currentView) {
|
||||
Marionette.triggerMethod.call(this.currentView, "show");
|
||||
Marionette.triggerMethod.call(this, "swap:in", this.currentView);
|
||||
}
|
||||
}, this);
|
||||
|
||||
// show the view, passing it the done callback
|
||||
this.showView(this.currentView, done);
|
||||
},
|
||||
|
||||
// Show a view that is being swapped in. Override this method to
|
||||
// set up your own custom fade in / show method
|
||||
showView: function (view, done) {
|
||||
view.$el.show();
|
||||
done();
|
||||
},
|
||||
|
||||
// Hide a view that is being swapped out. Override this method to
|
||||
// set up your own custom fade out / hide method
|
||||
hideView: function (view, done) {
|
||||
view.$el.hide();
|
||||
done();
|
||||
},
|
||||
|
||||
// Ensure the views that were configured for this view swapper get closed
|
||||
close: function () {
|
||||
|
||||
// Close all of the configured views that we are swapping between
|
||||
_.each(this.views, function (view, name) {
|
||||
view.close();
|
||||
});
|
||||
|
||||
// unbind all the events, and clean up any decorator views
|
||||
this._swapperViews = {};
|
||||
this._currentViewBindings.unbindAll();
|
||||
this._swapperBindings.unbindAll();
|
||||
|
||||
// Close the base view that we extended from
|
||||
Marionette.View.prototype.close.apply(this, arguments);
|
||||
},
|
||||
|
||||
// Get a view by name, throwing an exception if the view instance
|
||||
// is not found.
|
||||
_getView: function (viewName) {
|
||||
var originalView, error, views;
|
||||
var swapperView = this._swapperViews[viewName];
|
||||
|
||||
// Do not allow the name "swapper" to be used as a target view
|
||||
// or initial view. This is reserved for the ViewSwapper instance,
|
||||
// when configuring `swapOn` events
|
||||
if (viewName === "swapper") {
|
||||
error = new Error("Cannot display 'swapper' as a view.");
|
||||
error.name = "InvalidViewName";
|
||||
throw error;
|
||||
}
|
||||
|
||||
// Do we have a view with the specified name?
|
||||
if (!swapperView) {
|
||||
originalView = this.views[viewName];
|
||||
|
||||
// No view, so throw an exception
|
||||
if (!originalView) {
|
||||
error = new Error("Cannot show view in ViewSwapper. View '" + viewName + "' not found.");
|
||||
error.name = "ViewNotFoundError";
|
||||
throw error;
|
||||
}
|
||||
|
||||
// Found the view, so build a Decorator around it
|
||||
swapperView = this._buildSwapperView(originalView, viewName);
|
||||
this._swapperViews[viewName] = swapperView;
|
||||
}
|
||||
|
||||
return swapperView;
|
||||
},
|
||||
|
||||
// Decorate the configured view with information that the view swapper
|
||||
// needs, to keep track of the view's current state.
|
||||
_buildSwapperView: function (originalView, viewName) {
|
||||
var swapperView = Marionette.createObject(originalView);
|
||||
_.extend(swapperView, {
|
||||
|
||||
viewName: viewName,
|
||||
originalView: originalView,
|
||||
|
||||
// Prevent the underlying view from being rendered more than once
|
||||
render: function () {
|
||||
var value;
|
||||
|
||||
if (this._hasBeenRendered) {
|
||||
return this;
|
||||
} else {
|
||||
|
||||
// prevent any more rendering
|
||||
this._hasBeenRendered = true;
|
||||
|
||||
// do the render
|
||||
value = originalView.render.apply(originalView, arguments);
|
||||
|
||||
// trigger render/onRender
|
||||
Marionette.triggerMethod.call(this, "render");
|
||||
|
||||
// return whatever was sent back to us
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
return swapperView;
|
||||
},
|
||||
|
||||
// Set up the event handlers for the individual views, so that the
|
||||
// swapping can happen when a view event is triggered
|
||||
_setupViewEvents: function (viewName, view, bindings) {
|
||||
if (!this.swapOn || !this.swapOn[viewName]) { return; }
|
||||
var that = this;
|
||||
|
||||
// default to current view bindings, unless otherwise specified
|
||||
if (!bindings) {
|
||||
bindings = this._currentViewBindings;
|
||||
}
|
||||
|
||||
// close the previous event bindings
|
||||
bindings.unbindAll();
|
||||
|
||||
// set up the new view's event bindings
|
||||
_.each(this.swapOn[viewName], function (targetViewName, eventName) {
|
||||
|
||||
bindings.bindTo(view, eventName, function () {
|
||||
that._swapView(targetViewName);
|
||||
});
|
||||
|
||||
});
|
||||
},
|
||||
|
||||
// Do the swapping of the views to the new view, by name
|
||||
_swapView: function (viewName) {
|
||||
|
||||
// only swap views if the target view is not the same
|
||||
// as the current view
|
||||
var view = this._getView(viewName);
|
||||
if (view === this.currentView) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Provide a callback function that will switch over to
|
||||
// the new view, when called
|
||||
var done = _.bind(function () {
|
||||
|
||||
// trigger hide/onHide on the previous view
|
||||
if (this.currentView) {
|
||||
Marionette.triggerMethod.call(this.currentView, "hide");
|
||||
Marionette.triggerMethod.call(this, "swap:out", this.currentView);
|
||||
}
|
||||
|
||||
// get the next view, configure it's events and render it
|
||||
this._setupViewEvents(viewName, view);
|
||||
this.currentView = view;
|
||||
this.render();
|
||||
|
||||
}, this);
|
||||
|
||||
if (this.currentView) {
|
||||
// if we have a current view, hide it so that the new
|
||||
// view can be show in it's place
|
||||
this.hideView(this.currentView, done);
|
||||
} else {
|
||||
// no current view, so just switch to the new view
|
||||
done();
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -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;
|
||||
|
||||
}));
|
||||
@@ -0,0 +1,174 @@
|
||||
(function (root, define, require, exports, module, factory, undef) {
|
||||
'use strict';
|
||||
|
||||
if (typeof exports === 'object') {
|
||||
// Node. Does not work with strict CommonJS, but
|
||||
// only CommonJS-like enviroments that support module.exports,
|
||||
// like Node.
|
||||
module.exports = factory(require('underscore'), require('backbone'));
|
||||
} else if (typeof define === 'function' && define.amd) {
|
||||
// AMD. Register as an anonymous module.
|
||||
define(['underscore', 'backbone'], function (_, Backbone) {
|
||||
// Check if we use the AMD branch of Back
|
||||
_ = _ === undef ? root._ : _;
|
||||
Backbone = Backbone === undef ? root.Backbone : Backbone;
|
||||
return (root.returnExportsGlobal = factory(_, Backbone, root));
|
||||
});
|
||||
} else {
|
||||
// Browser globals
|
||||
root.returnExportsGlobal = factory(root._, root.Backbone);
|
||||
}
|
||||
|
||||
// Usage:
|
||||
//
|
||||
// Note: This plugin is UMD compatible, you can use it in node, amd and vanilla js envs
|
||||
//
|
||||
// Vanilla JS:
|
||||
// <script src="underscore.js"></script>
|
||||
// <script src="backbone.js"></script>
|
||||
// <script src="backbone.mutators.js"></script>
|
||||
//
|
||||
// Node:
|
||||
// var _ = require('underscore');
|
||||
// var Backbone = require('backbone');
|
||||
// var Mutators = require('backbone.mutators');
|
||||
//
|
||||
//
|
||||
// AMD:
|
||||
// define(['underscore', 'backbone', 'backbone.mutators'], function (_, Backbone, Mutators) {
|
||||
// // insert sample from below
|
||||
// return User;
|
||||
// });
|
||||
//
|
||||
// var User = Backbone.Model.extend({
|
||||
// mutators: {
|
||||
// fullname: function () {
|
||||
// return this.firstname + ' ' + this.lastname;
|
||||
// }
|
||||
// },
|
||||
//
|
||||
// defaults: {
|
||||
// firstname: 'Sebastian',
|
||||
// lastname: 'Golasch'
|
||||
// }
|
||||
// });
|
||||
//
|
||||
// var user = new User();
|
||||
// user.get('fullname') // returns 'Sebastian Golasch'
|
||||
// user.toJSON() // return '{firstname: 'Sebastian', lastname: 'Golasch', fullname: 'Sebastian Golasch'}'
|
||||
|
||||
}(this, this.define, this.require, this.exports, this.module, function (_, Backbone, root, undef) {
|
||||
'use strict';
|
||||
|
||||
// check if we use the amd branch of backbone and underscore
|
||||
Backbone = Backbone === undef ? root.Backbone : Backbone;
|
||||
_ = _ === undef ? root._ : _;
|
||||
|
||||
// extend backbones model prototype with the mutator functionality
|
||||
var Mutator = function () { },
|
||||
oldGet = Backbone.Model.prototype.get,
|
||||
oldSet = Backbone.Model.prototype.set,
|
||||
oldToJson = Backbone.Model.prototype.toJSON;
|
||||
|
||||
// This is necessary to ensure that Models declared without the mutators object do not throw and error
|
||||
Mutator.prototype.mutators = {};
|
||||
|
||||
// override get functionality to fetch the mutator props
|
||||
Mutator.prototype.get = function (attr) {
|
||||
var isMutator = this.mutators !== undef;
|
||||
|
||||
// check if we have a getter mutation
|
||||
if (isMutator === true && _.isFunction(this.mutators[attr]) === true) {
|
||||
return this.mutators[attr].call(this);
|
||||
}
|
||||
|
||||
// check if we have a deeper nested getter mutation
|
||||
if (isMutator === true && _.isObject(this.mutators[attr]) === true && _.isFunction(this.mutators[attr].get) === true) {
|
||||
return this.mutators[attr].get.call(this);
|
||||
}
|
||||
|
||||
return oldGet.call(this, attr);
|
||||
};
|
||||
|
||||
// override set functionality to set the mutator props
|
||||
Mutator.prototype.set = function (key, value, options) {
|
||||
var isMutator = this.mutators !== undef,
|
||||
ret = null,
|
||||
attrs = null,
|
||||
attr = null;
|
||||
|
||||
// seamleassly stolen from backbone core
|
||||
// check if the setter action is triggered
|
||||
// using key <-> value or object
|
||||
if (_.isObject(key) || key === null) {
|
||||
attrs = key;
|
||||
options = value;
|
||||
} else {
|
||||
attrs = {};
|
||||
attrs[key] = value;
|
||||
}
|
||||
|
||||
// check if we have a deeper nested setter mutation
|
||||
if (isMutator === true && _.isObject(this.mutators[key]) === true) {
|
||||
|
||||
// check if we need to set a single value
|
||||
if (_.isFunction(this.mutators[key].set) === true) {
|
||||
ret = this.mutators[key].set.call(this, key, attrs[key], options, _.bind(oldSet, this));
|
||||
} else if (_.isFunction(this.mutators[key])) {
|
||||
ret = this.mutators[key].call(this, key, attrs[key], options, _.bind(oldSet, this));
|
||||
}
|
||||
}
|
||||
|
||||
if (_.isObject(attrs)) {
|
||||
_.each(attrs, _.bind(function (attr, attrKey) {
|
||||
if (isMutator === true && _.isObject(this.mutators[attrKey]) === true) {
|
||||
// check if we need to set a single value
|
||||
|
||||
var meth = this.mutators[attrKey];
|
||||
if (_.isFunction(meth.set)) {
|
||||
meth = meth.set;
|
||||
}
|
||||
|
||||
if (_.isFunction(meth)) {
|
||||
if (options === undef || (_.isObject(options) === true && options.silent !== true && (options.mutators !== undef && options.mutators.silent !== true))) {
|
||||
this.trigger('mutators:set:' + attrKey);
|
||||
}
|
||||
ret = meth.call(this, attrKey, attr, options, _.bind(oldSet, this));
|
||||
}
|
||||
|
||||
}
|
||||
}, this));
|
||||
}
|
||||
|
||||
//validation purposes
|
||||
if (ret !== null) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
return oldSet.call(this, key, value, options);
|
||||
};
|
||||
|
||||
// override toJSON functionality to serialize mutator properties
|
||||
Mutator.prototype.toJSON = function () {
|
||||
// fetch ye olde values
|
||||
var attr = oldToJson.call(this);
|
||||
// iterate over all mutators (if there are some)
|
||||
_.each(this.mutators, _.bind(function (mutator, name) {
|
||||
// check if we have some getter mutations (nested or )
|
||||
if (_.isObject(this.mutators[name]) === true && _.isFunction(this.mutators[name].get)) {
|
||||
attr[name] = _.bind(this.mutators[name].get, this)();
|
||||
} else {
|
||||
attr[name] = _.bind(this.mutators[name], this)();
|
||||
}
|
||||
}, this));
|
||||
|
||||
return attr;
|
||||
};
|
||||
|
||||
// extend the models prototype
|
||||
_.extend(Backbone.Model.prototype, Mutator.prototype);
|
||||
|
||||
// make mutators globally available under the Backbone namespace
|
||||
Backbone.Mutators = Mutator;
|
||||
return Mutator;
|
||||
}));
|
||||
@@ -0,0 +1,35 @@
|
||||
(function() {
|
||||
var Shortcuts;
|
||||
|
||||
Shortcuts = function(options) {
|
||||
this.cid = _.uniqueId("backbone.shortcuts");
|
||||
this.initialize.apply(this, arguments);
|
||||
return this.delegateShortcuts();
|
||||
};
|
||||
|
||||
_.extend(Shortcuts.prototype, Backbone.Events, {
|
||||
initialize: function() {},
|
||||
delegateShortcuts: function() {
|
||||
var callback, match, method, scope, shortcut, shortcutKey, _ref, _results;
|
||||
if (!this.shortcuts) return;
|
||||
_ref = this.shortcuts;
|
||||
_results = [];
|
||||
for (shortcut in _ref) {
|
||||
callback = _ref[shortcut];
|
||||
if (!_.isFunction(callback)) method = this[callback];
|
||||
if (!method) throw new Error("Method " + callback + " does not exist");
|
||||
match = shortcut.match(/^(\S+)\s*(.*)$/);
|
||||
shortcutKey = match[1];
|
||||
scope = match[2] === "" ? "all" : match[2];
|
||||
method = _.bind(method, this);
|
||||
_results.push(key(shortcutKey, scope, method));
|
||||
}
|
||||
return _results;
|
||||
}
|
||||
});
|
||||
|
||||
Backbone.Shortcuts = Shortcuts;
|
||||
|
||||
Backbone.Shortcuts.extend = Backbone.View.extend;
|
||||
|
||||
}).call(this);
|
||||
+2159
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,94 @@
|
||||
/* Default class modification */
|
||||
$.extend($.fn.dataTableExt.oStdClasses, {
|
||||
"sWrapper": "dataTables_wrapper form-horizontal"
|
||||
});
|
||||
|
||||
/* API method to get paging information */
|
||||
$.fn.dataTableExt.oApi.fnPagingInfo = function(oSettings) {
|
||||
return {
|
||||
"iStart": oSettings._iDisplayStart,
|
||||
"iEnd": oSettings.fnDisplayEnd(),
|
||||
"iLength": oSettings._iDisplayLength,
|
||||
"iTotal": oSettings.fnRecordsTotal(),
|
||||
"iFilteredTotal": oSettings.fnRecordsDisplay(),
|
||||
"iPage": Math.ceil(oSettings._iDisplayStart / oSettings._iDisplayLength),
|
||||
"iTotalPages": Math.ceil(oSettings.fnRecordsDisplay() / oSettings._iDisplayLength)
|
||||
};
|
||||
};
|
||||
|
||||
/* Bootstrap style pagination control */
|
||||
$.extend($.fn.dataTableExt.oPagination, {
|
||||
"bootstrap": {
|
||||
"fnInit": function (oSettings, nPaging, fnDraw) {
|
||||
var oLang = oSettings.oLanguage.oPaginate;
|
||||
var fnClickHandler = function (e) {
|
||||
e.preventDefault();
|
||||
if (oSettings.oApi._fnPageChange(oSettings, e.data.action)) {
|
||||
fnDraw(oSettings);
|
||||
}
|
||||
};
|
||||
|
||||
$(nPaging).addClass('pagination').append(
|
||||
'<ul>' +
|
||||
'<li class="prev disabled"><a href="#">← ' + oLang.sPrevious + '</a></li>' +
|
||||
'<li class="next disabled"><a href="#">' + oLang.sNext + ' → </a></li>' +
|
||||
'</ul>'
|
||||
);
|
||||
var els = $('a', nPaging);
|
||||
$(els[0]).bind('click.DT', { action: "previous" }, fnClickHandler);
|
||||
$(els[1]).bind('click.DT', { action: "next" }, fnClickHandler);
|
||||
},
|
||||
|
||||
"fnUpdate": function (oSettings, fnDraw) {
|
||||
var iListLength = 5;
|
||||
var oPaging = oSettings.oInstance.fnPagingInfo();
|
||||
var an = oSettings.aanFeatures.p;
|
||||
var i, j, sClass, iStart, iEnd, iHalf = Math.floor(iListLength / 2);
|
||||
|
||||
if (oPaging.iTotalPages < iListLength) {
|
||||
iStart = 1;
|
||||
iEnd = oPaging.iTotalPages;
|
||||
}
|
||||
else if (oPaging.iPage <= iHalf) {
|
||||
iStart = 1;
|
||||
iEnd = iListLength;
|
||||
} else if (oPaging.iPage >= (oPaging.iTotalPages - iHalf)) {
|
||||
iStart = oPaging.iTotalPages - iListLength + 1;
|
||||
iEnd = oPaging.iTotalPages;
|
||||
} else {
|
||||
iStart = oPaging.iPage - iHalf + 1;
|
||||
iEnd = iStart + iListLength - 1;
|
||||
}
|
||||
|
||||
for (i = 0, iLen = an.length ; i < iLen ; i++) {
|
||||
// Remove the middle elements
|
||||
$('li:gt(0)', an[i]).filter(':not(:last)').remove();
|
||||
|
||||
// Add the new list items and their event handlers
|
||||
for (j = iStart ; j <= iEnd ; j++) {
|
||||
sClass = (j == oPaging.iPage + 1) ? 'class="active"' : '';
|
||||
$('<li ' + sClass + '><a href="#">' + j + '</a></li>')
|
||||
.insertBefore($('li:last', an[i])[0])
|
||||
.bind('click', function (e) {
|
||||
e.preventDefault();
|
||||
oSettings._iDisplayStart = (parseInt($('a', this).text(), 10) - 1) * oPaging.iLength;
|
||||
fnDraw(oSettings);
|
||||
});
|
||||
}
|
||||
|
||||
// Add / remove disabled classes from the static elements
|
||||
if (oPaging.iPage === 0) {
|
||||
$('li:first', an[i]).addClass('disabled');
|
||||
} else {
|
||||
$('li:first', an[i]).removeClass('disabled');
|
||||
}
|
||||
|
||||
if (oPaging.iPage === oPaging.iTotalPages - 1 || oPaging.iTotalPages === 0) {
|
||||
$('li:last', an[i]).addClass('disabled');
|
||||
} else {
|
||||
$('li:last', an[i]).removeClass('disabled');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,51 @@
|
||||
//Hidden title string sorting
|
||||
$.extend(jQuery.fn.dataTableExt.oSort, {
|
||||
"title-string-pre": function (a) {
|
||||
return a.match(/title="(.*?)"/)[1].toLowerCase();
|
||||
},
|
||||
|
||||
"title-string-asc": function (a, b) {
|
||||
return ((a < b) ? -1 : ((a > b) ? 1 : 0));
|
||||
},
|
||||
|
||||
"title-string-desc": function (a, b) {
|
||||
return ((a < b) ? 1 : ((a > b) ? -1 : 0));
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
//bestDateString sorting
|
||||
$.extend(jQuery.fn.dataTableExt.oSort, {
|
||||
"best-date-pre": function (a) {
|
||||
var match = a.match(/data-date="(.*?)"/)[1];
|
||||
|
||||
if (match === '')
|
||||
return Date.create().addYears(100);
|
||||
|
||||
return Date.create(match);
|
||||
},
|
||||
|
||||
"best-date-asc": function (a, b) {
|
||||
return ((a < b) ? -1 : ((a > b) ? 1 : 0));
|
||||
},
|
||||
|
||||
"best-date-desc": function (a, b) {
|
||||
return ((a < b) ? 1 : ((a > b) ? -1 : 0));
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
//Skip articles sorting
|
||||
$.extend(jQuery.fn.dataTableExt.oSort, {
|
||||
"skip-articles-pre": function (a) {
|
||||
return a.replace(/^(the|an|a|) /i, "");
|
||||
},
|
||||
|
||||
"skip-articles-asc": function (a, b) {
|
||||
return ((a < b) ? -1 : ((a > b) ? 1 : 0));
|
||||
},
|
||||
|
||||
"skip-articles-desc": function (a, b) {
|
||||
return ((a < b) ? 1 : ((a > b) ? -1 : 0));
|
||||
}
|
||||
});
|
||||
+11979
File diff suppressed because it is too large
Load Diff
+9598
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,981 @@
|
||||
/*! tableSorter 2.4+ widgets - updated 1/29/2013
|
||||
*
|
||||
* Column Styles
|
||||
* Column Filters
|
||||
* Column Resizing
|
||||
* Sticky Header
|
||||
* UI Theme (generalized)
|
||||
* Save Sort
|
||||
* ["zebra", "uitheme", "stickyHeaders", "filter", "columns"]
|
||||
*/
|
||||
/*jshint browser:true, jquery:true, unused:false, loopfunc:true */
|
||||
/*global jQuery: false, localStorage: false, navigator: false */
|
||||
;(function($){
|
||||
"use strict";
|
||||
$.tablesorter = $.tablesorter || {};
|
||||
|
||||
$.tablesorter.themes = {
|
||||
"bootstrap" : {
|
||||
table : 'table table-bordered table-striped',
|
||||
header : 'bootstrap-header', // give the header a gradient background
|
||||
footerRow : '',
|
||||
footerCells: '',
|
||||
icons : '', // add "icon-white" to make them white; this icon class is added to the <i> in the header
|
||||
sortNone : 'bootstrap-icon-unsorted',
|
||||
sortAsc : 'icon-chevron-up',
|
||||
sortDesc : 'icon-chevron-down',
|
||||
active : '', // applied when column is sorted
|
||||
hover : '', // use custom css here - bootstrap class may not override it
|
||||
filterRow : '', // filter row class
|
||||
even : '', // even row zebra striping
|
||||
odd : '' // odd row zebra striping
|
||||
},
|
||||
"jui" : {
|
||||
table : 'ui-widget ui-widget-content ui-corner-all', // table classes
|
||||
header : 'ui-widget-header ui-corner-all ui-state-default', // header classes
|
||||
footerRow : '',
|
||||
footerCells: '',
|
||||
icons : 'ui-icon', // icon class added to the <i> in the header
|
||||
sortNone : 'ui-icon-carat-2-n-s',
|
||||
sortAsc : 'ui-icon-carat-1-n',
|
||||
sortDesc : 'ui-icon-carat-1-s',
|
||||
active : 'ui-state-active', // applied when column is sorted
|
||||
hover : 'ui-state-hover', // hover class
|
||||
filterRow : '',
|
||||
even : 'ui-widget-content', // even row zebra striping
|
||||
odd : 'ui-state-default' // odd row zebra striping
|
||||
}
|
||||
};
|
||||
|
||||
// *** Store data in local storage, with a cookie fallback ***
|
||||
/* IE7 needs JSON library for JSON.stringify - (http://caniuse.com/#search=json)
|
||||
if you need it, then include https://github.com/douglascrockford/JSON-js
|
||||
|
||||
$.parseJSON is not available is jQuery versions older than 1.4.1, using older
|
||||
versions will only allow storing information for one page at a time
|
||||
|
||||
// *** Save data (JSON format only) ***
|
||||
// val must be valid JSON... use http://jsonlint.com/ to ensure it is valid
|
||||
var val = { "mywidget" : "data1" }; // valid JSON uses double quotes
|
||||
// $.tablesorter.storage(table, key, val);
|
||||
$.tablesorter.storage(table, 'tablesorter-mywidget', val);
|
||||
|
||||
// *** Get data: $.tablesorter.storage(table, key); ***
|
||||
v = $.tablesorter.storage(table, 'tablesorter-mywidget');
|
||||
// val may be empty, so also check for your data
|
||||
val = (v && v.hasOwnProperty('mywidget')) ? v.mywidget : '';
|
||||
alert(val); // "data1" if saved, or "" if not
|
||||
*/
|
||||
$.tablesorter.storage = function(table, key, val){
|
||||
var d, k, ls = false, v = {},
|
||||
id = table.id || $('.tablesorter').index( $(table) ),
|
||||
url = window.location.pathname;
|
||||
try { ls = !!(localStorage.getItem); } catch(e) {}
|
||||
// *** get val ***
|
||||
if ($.parseJSON){
|
||||
if (ls){
|
||||
v = $.parseJSON(localStorage[key]) || {};
|
||||
} else {
|
||||
k = document.cookie.split(/[;\s|=]/); // cookie
|
||||
d = $.inArray(key, k) + 1; // add one to get from the key to the value
|
||||
v = (d !== 0) ? $.parseJSON(k[d]) || {} : {};
|
||||
}
|
||||
}
|
||||
// allow val to be an empty string to
|
||||
if ((val || val === '') && window.JSON && JSON.hasOwnProperty('stringify')){
|
||||
// add unique identifiers = url pathname > table ID/index on page > data
|
||||
if (!v[url]) {
|
||||
v[url] = {};
|
||||
}
|
||||
v[url][id] = val;
|
||||
// *** set val ***
|
||||
if (ls){
|
||||
localStorage[key] = JSON.stringify(v);
|
||||
} else {
|
||||
d = new Date();
|
||||
d.setTime(d.getTime() + (31536e+6)); // 365 days
|
||||
document.cookie = key + '=' + (JSON.stringify(v)).replace(/\"/g,'\"') + '; expires=' + d.toGMTString() + '; path=/';
|
||||
}
|
||||
} else {
|
||||
return v && v[url] ? v[url][id] : {};
|
||||
}
|
||||
};
|
||||
|
||||
// Widget: General UI theme
|
||||
// "uitheme" option in "widgetOptions"
|
||||
// **************************
|
||||
$.tablesorter.addWidget({
|
||||
id: "uitheme",
|
||||
format: function(table){
|
||||
var time, klass, $el, $tar,
|
||||
t = $.tablesorter.themes,
|
||||
$t = $(table),
|
||||
c = table.config,
|
||||
wo = c.widgetOptions,
|
||||
theme = c.theme !== 'default' ? c.theme : wo.uitheme || 'jui', // default uitheme is 'jui'
|
||||
o = t[ t[theme] ? theme : t[wo.uitheme] ? wo.uitheme : 'jui'],
|
||||
$h = $(c.headerList),
|
||||
sh = 'tr.' + (wo.stickyHeaders || 'tablesorter-stickyHeader'),
|
||||
rmv = o.sortNone + ' ' + o.sortDesc + ' ' + o.sortAsc;
|
||||
if (c.debug) { time = new Date(); }
|
||||
if (!$t.hasClass('tablesorter-' + theme) || c.theme === theme || !table.hasInitialized){
|
||||
// update zebra stripes
|
||||
if (o.even !== '') { wo.zebra[0] += ' ' + o.even; }
|
||||
if (o.odd !== '') { wo.zebra[1] += ' ' + o.odd; }
|
||||
// add table/footer class names
|
||||
t = $t
|
||||
// remove other selected themes; use widgetOptions.theme_remove
|
||||
.removeClass( c.theme === '' ? '' : 'tablesorter-' + c.theme )
|
||||
.addClass('tablesorter-' + theme + ' ' + o.table) // add theme widget class name
|
||||
.find('tfoot');
|
||||
if (t.length) {
|
||||
t
|
||||
.find('tr').addClass(o.footerRow)
|
||||
.children('th, td').addClass(o.footerCells);
|
||||
}
|
||||
// update header classes
|
||||
$h
|
||||
.addClass(o.header)
|
||||
.filter(':not(.sorter-false)')
|
||||
.hover(function(){
|
||||
$(this).addClass(o.hover);
|
||||
}, function(){
|
||||
$(this).removeClass(o.hover);
|
||||
});
|
||||
if (!$h.find('.tablesorter-wrapper').length) {
|
||||
// Firefox needs this inner div to position the resizer correctly
|
||||
$h.wrapInner('<div class="tablesorter-wrapper" style="position:relative;height:100%;width:100%"></div>');
|
||||
}
|
||||
if (c.cssIcon){
|
||||
// if c.cssIcon is '', then no <i> is added to the header
|
||||
$h.find('.' + c.cssIcon).addClass(o.icons);
|
||||
}
|
||||
if ($t.hasClass('hasFilters')){
|
||||
$h.find('.tablesorter-filter-row').addClass(o.filterRow);
|
||||
}
|
||||
}
|
||||
$.each($h, function(i){
|
||||
$el = $(this);
|
||||
$tar = (c.cssIcon) ? $el.find('.' + c.cssIcon) : $el;
|
||||
if (this.sortDisabled){
|
||||
// no sort arrows for disabled columns!
|
||||
$el.removeClass(rmv);
|
||||
$tar.removeClass(rmv + ' tablesorter-icon ' + o.icons);
|
||||
} else {
|
||||
t = ($t.hasClass('hasStickyHeaders')) ? $t.find(sh).find('th').eq(i).add($el) : $el;
|
||||
klass = ($el.hasClass(c.cssAsc)) ? o.sortAsc : ($el.hasClass(c.cssDesc)) ? o.sortDesc : $el.hasClass(c.cssHeader) ? o.sortNone : '';
|
||||
$el[klass === o.sortNone ? 'removeClass' : 'addClass'](o.active);
|
||||
$tar.removeClass(rmv).addClass(klass);
|
||||
}
|
||||
});
|
||||
if (c.debug){
|
||||
$.tablesorter.benchmark("Applying " + theme + " theme", time);
|
||||
}
|
||||
},
|
||||
remove: function(table, c, wo){
|
||||
var $t = $(table),
|
||||
theme = typeof wo.uitheme === 'object' ? 'jui' : wo.uitheme || 'jui',
|
||||
o = typeof wo.uitheme === 'object' ? wo.uitheme : $.tablesorter.themes[ $.tablesorter.themes.hasOwnProperty(theme) ? theme : 'jui'],
|
||||
$h = $t.children('thead').children(),
|
||||
rmv = o.sortNone + ' ' + o.sortDesc + ' ' + o.sortAsc;
|
||||
$t
|
||||
.removeClass('tablesorter-' + theme + ' ' + o.table)
|
||||
.find(c.cssHeader).removeClass(o.header);
|
||||
$h
|
||||
.unbind('mouseenter mouseleave') // remove hover
|
||||
.removeClass(o.hover + ' ' + rmv + ' ' + o.active)
|
||||
.find('.tablesorter-filter-row').removeClass(o.filterRow);
|
||||
$h.find('.tablesorter-icon').removeClass(o.icons);
|
||||
}
|
||||
});
|
||||
|
||||
// Widget: Column styles
|
||||
// "columns", "columns_thead" (true) and
|
||||
// "columns_tfoot" (true) options in "widgetOptions"
|
||||
// **************************
|
||||
$.tablesorter.addWidget({
|
||||
id: "columns",
|
||||
format: function(table){
|
||||
var $tb, $tr, $td, $t, time, last, rmv, i, k, l,
|
||||
$tbl = $(table),
|
||||
c = table.config,
|
||||
wo = c.widgetOptions,
|
||||
b = c.$tbodies,
|
||||
list = c.sortList,
|
||||
len = list.length,
|
||||
css = [ "primary", "secondary", "tertiary" ]; // default options
|
||||
// keep backwards compatibility, for now
|
||||
css = (c.widgetColumns && c.widgetColumns.hasOwnProperty('css')) ? c.widgetColumns.css || css :
|
||||
(wo && wo.hasOwnProperty('columns')) ? wo.columns || css : css;
|
||||
last = css.length-1;
|
||||
rmv = css.join(' ');
|
||||
if (c.debug){
|
||||
time = new Date();
|
||||
}
|
||||
// check if there is a sort (on initialization there may not be one)
|
||||
for (k = 0; k < b.length; k++ ){
|
||||
$tb = $.tablesorter.processTbody(table, b.eq(k), true); // detach tbody
|
||||
$tr = $tb.children('tr');
|
||||
l = $tr.length;
|
||||
// loop through the visible rows
|
||||
$tr.each(function(){
|
||||
$t = $(this);
|
||||
if (this.style.display !== 'none'){
|
||||
// remove all columns class names
|
||||
$td = $t.children().removeClass(rmv);
|
||||
// add appropriate column class names
|
||||
if (list && list[0]){
|
||||
// primary sort column class
|
||||
$td.eq(list[0][0]).addClass(css[0]);
|
||||
if (len > 1){
|
||||
for (i = 1; i < len; i++){
|
||||
// secondary, tertiary, etc sort column classes
|
||||
$td.eq(list[i][0]).addClass( css[i] || css[last] );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
$.tablesorter.processTbody(table, $tb, false);
|
||||
}
|
||||
// add classes to thead and tfoot
|
||||
$tr = wo.columns_thead !== false ? 'thead tr' : '';
|
||||
if (wo.columns_tfoot !== false) {
|
||||
$tr += ($tr === '' ? '' : ',') + 'tfoot tr';
|
||||
}
|
||||
if ($tr.length) {
|
||||
$t = $tbl.find($tr).children().removeClass(rmv);
|
||||
if (list && list[0]){
|
||||
// primary sort column class
|
||||
$t.filter('[data-column="' + list[0][0] + '"]').addClass(css[0]);
|
||||
if (len > 1){
|
||||
for (i = 1; i < len; i++){
|
||||
// secondary, tertiary, etc sort column classes
|
||||
$t.filter('[data-column="' + list[i][0] + '"]').addClass(css[i] || css[last]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (c.debug){
|
||||
$.tablesorter.benchmark("Applying Columns widget", time);
|
||||
}
|
||||
},
|
||||
remove: function(table, c, wo){
|
||||
var k, $tb,
|
||||
b = c.$tbodies,
|
||||
rmv = (c.widgetOptions.columns || [ "primary", "secondary", "tertiary" ]).join(' ');
|
||||
c.$headers.removeClass(rmv);
|
||||
$(table).children('tfoot').children('tr').children('th, td').removeClass(rmv);
|
||||
for (k = 0; k < b.length; k++ ){
|
||||
$tb = $.tablesorter.processTbody(table, b.eq(k), true); // remove tbody
|
||||
$tb.children('tr').each(function(){
|
||||
$(this).children().removeClass(rmv);
|
||||
});
|
||||
$.tablesorter.processTbody(table, $tb, false); // restore tbody
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/* Widget: filter
|
||||
widgetOptions:
|
||||
filter_childRows : false // if true, filter includes child row content in the search
|
||||
filter_columnFilters : true // if true, a filter will be added to the top of each table column
|
||||
filter_cssFilter : 'tablesorter-filter' // css class name added to the filter row & each input in the row
|
||||
filter_functions : null // add custom filter functions using this option
|
||||
filter_hideFilters : false // collapse filter row when mouse leaves the area
|
||||
filter_ignoreCase : true // if true, make all searches case-insensitive
|
||||
filter_reset : null // jQuery selector string of an element used to reset the filters
|
||||
filter_searchDelay : 300 // typing delay in milliseconds before starting a search
|
||||
filter_startsWith : false // if true, filter start from the beginning of the cell contents
|
||||
filter_useParsedData : false // filter all data using parsed content
|
||||
filter_serversideFiltering : false // if true, server-side filtering should be performed because client-side filtering will be disabled, but the ui and events will still be used.
|
||||
**************************/
|
||||
$.tablesorter.addWidget({
|
||||
id: "filter",
|
||||
format: function(table){
|
||||
if (table.config.parsers && !$(table).hasClass('hasFilters')){
|
||||
var i, j, k, l, val, ff, x, xi, st, sel, str,
|
||||
ft, ft2, $th, rg, s, t, dis, col,
|
||||
last = '', // save last filter search
|
||||
ts = $.tablesorter,
|
||||
c = table.config,
|
||||
$ths = $(c.headerList),
|
||||
wo = c.widgetOptions,
|
||||
css = wo.filter_cssFilter || 'tablesorter-filter',
|
||||
$t = $(table).addClass('hasFilters'),
|
||||
b = c.$tbodies,
|
||||
cols = c.parsers.length,
|
||||
reg = [ // regex used in filter "check" functions
|
||||
/^\/((?:\\\/|[^\/])+)\/([mig]{0,3})?$/, // 0 = regex to test for regex
|
||||
new RegExp(c.cssChildRow), // 1 = child row
|
||||
/undefined|number/, // 2 = check type
|
||||
/(^[\"|\'|=])|([\"|\'|=]$)/, // 3 = exact match
|
||||
/[\"\'=]/g, // 4 = replace exact match flags
|
||||
/[^\w,. \-()]/g, // 5 = replace non-digits (from digit & currency parser)
|
||||
/[<>=]/g // 6 = replace operators
|
||||
],
|
||||
parsed = $ths.map(function(i){
|
||||
return (ts.getData) ? ts.getData($ths.filter('[data-column="' + i + '"]:last'), c.headers[i], 'filter') === 'parsed' : $(this).hasClass('filter-parsed');
|
||||
}).get(),
|
||||
time, timer,
|
||||
|
||||
// dig fer gold
|
||||
checkFilters = function(filter){
|
||||
var arry = $.isArray(filter),
|
||||
$inpts = $t.find('thead').eq(0).children('tr').find('select.' + css + ', input.' + css),
|
||||
v = (arry) ? filter : $inpts.map(function(){
|
||||
return $(this).val() || '';
|
||||
}).get(),
|
||||
cv = (v || []).join(''); // combined filter values
|
||||
// add filter array back into inputs
|
||||
if (arry) {
|
||||
$inpts.each(function(i,el){
|
||||
$(el).val(filter[i] || '');
|
||||
});
|
||||
}
|
||||
if (wo.filter_hideFilters === true){
|
||||
// show/hide filter row as needed
|
||||
$t.find('.tablesorter-filter-row').trigger( cv === '' ? 'mouseleave' : 'mouseenter' );
|
||||
}
|
||||
// return if the last search is the same; but filter === false when updating the search
|
||||
// see example-widget-filter.html filter toggle buttons
|
||||
if (last === cv && filter !== false) { return; }
|
||||
$t.trigger('filterStart', [v]);
|
||||
if (c.showProcessing) {
|
||||
// give it time for the processing icon to kick in
|
||||
setTimeout(function(){
|
||||
findRows(filter, v, cv);
|
||||
return false;
|
||||
}, 30);
|
||||
} else {
|
||||
findRows(filter, v, cv);
|
||||
return false;
|
||||
}
|
||||
},
|
||||
findRows = function(filter, v, cv){
|
||||
var $tb, $tr, $td, cr, r, l, ff, time, arry;
|
||||
if (c.debug) { time = new Date(); }
|
||||
|
||||
for (k = 0; k < b.length; k++ ){
|
||||
$tb = $.tablesorter.processTbody(table, b.eq(k), true);
|
||||
$tr = $tb.children('tr');
|
||||
l = $tr.length;
|
||||
if (cv === '' || wo.filter_serversideFiltering){
|
||||
$tr.show().removeClass('filtered');
|
||||
} else {
|
||||
// loop through the rows
|
||||
for (j = 0; j < l; j++){
|
||||
// skip child rows
|
||||
if (reg[1].test($tr[j].className)) { continue; }
|
||||
r = true;
|
||||
cr = $tr.eq(j).nextUntil('tr:not(.' + c.cssChildRow + ')');
|
||||
// so, if "table.config.widgetOptions.filter_childRows" is true and there is
|
||||
// a match anywhere in the child row, then it will make the row visible
|
||||
// checked here so the option can be changed dynamically
|
||||
t = (cr.length && (wo && wo.hasOwnProperty('filter_childRows') &&
|
||||
typeof wo.filter_childRows !== 'undefined' ? wo.filter_childRows : true)) ? cr.text() : '';
|
||||
t = wo.filter_ignoreCase ? t.toLocaleLowerCase() : t;
|
||||
$td = $tr.eq(j).children('td');
|
||||
for (i = 0; i < cols; i++){
|
||||
// ignore if filter is empty or disabled
|
||||
if (v[i]){
|
||||
// check if column data should be from the cell or from parsed data
|
||||
if (wo.filter_useParsedData || parsed[i]){
|
||||
x = c.cache[k].normalized[j][i];
|
||||
} else {
|
||||
// using older or original tablesorter
|
||||
x = $.trim($td.eq(i).text());
|
||||
}
|
||||
xi = !reg[2].test(typeof x) && wo.filter_ignoreCase ? x.toLocaleLowerCase() : x;
|
||||
ff = r; // if r is true, show that row
|
||||
// val = case insensitive, v[i] = case sensitive
|
||||
val = wo.filter_ignoreCase ? v[i].toLocaleLowerCase() : v[i];
|
||||
if (wo.filter_functions && wo.filter_functions[i]){
|
||||
if (wo.filter_functions[i] === true){
|
||||
// default selector; no "filter-select" class
|
||||
ff = ($ths.filter('[data-column="' + i + '"]:last').hasClass('filter-match')) ? xi.search(val) >= 0 : v[i] === x;
|
||||
} else if (typeof wo.filter_functions[i] === 'function'){
|
||||
// filter callback( exact cell content, parser normalized content, filter input value, column index )
|
||||
ff = wo.filter_functions[i](x, c.cache[k].normalized[j][i], v[i], i);
|
||||
} else if (typeof wo.filter_functions[i][v[i]] === 'function'){
|
||||
// selector option function
|
||||
ff = wo.filter_functions[i][v[i]](x, c.cache[k].normalized[j][i], v[i], i);
|
||||
}
|
||||
// Look for regex
|
||||
} else if (reg[0].test(val)){
|
||||
rg = reg[0].exec(val);
|
||||
try {
|
||||
ff = new RegExp(rg[1], rg[2]).test(xi);
|
||||
} catch (err){
|
||||
ff = false;
|
||||
}
|
||||
// Look for quotes or equals to get an exact match
|
||||
} else if (reg[3].test(val) && xi === val.replace(reg[4], '')){
|
||||
ff = true;
|
||||
// Look for a not match
|
||||
} else if (/^\!/.test(val)){
|
||||
val = val.replace('!','');
|
||||
s = xi.search($.trim(val));
|
||||
ff = val === '' ? true : !(wo.filter_startsWith ? s === 0 : s >= 0);
|
||||
// Look for operators >, >=, < or <=
|
||||
} else if (/^[<>]=?/.test(val)){
|
||||
// xi may be numeric - see issue #149
|
||||
rg = isNaN(xi) ? $.tablesorter.formatFloat(xi.replace(reg[5], ''), table) : $.tablesorter.formatFloat(xi, table);
|
||||
s = $.tablesorter.formatFloat(val.replace(reg[5], '').replace(reg[6],''), table);
|
||||
if (/>/.test(val)) { ff = />=/.test(val) ? rg >= s : rg > s; }
|
||||
if (/</.test(val)) { ff = /<=/.test(val) ? rg <= s : rg < s; }
|
||||
// Look for wild card: ? = single, or * = multiple
|
||||
} else if (/[\?|\*]/.test(val)){
|
||||
ff = new RegExp( val.replace(/\?/g, '\\S{1}').replace(/\*/g, '\\S*') ).test(xi);
|
||||
// Look for match, and add child row data for matching
|
||||
} else {
|
||||
x = (xi + t).indexOf(val);
|
||||
ff = ( (!wo.filter_startsWith && x >= 0) || (wo.filter_startsWith && x === 0) );
|
||||
}
|
||||
r = (ff) ? (r ? true : false) : false;
|
||||
}
|
||||
}
|
||||
$tr[j].style.display = (r ? '' : 'none');
|
||||
$tr.eq(j)[r ? 'removeClass' : 'addClass']('filtered');
|
||||
if (cr.length) { cr[r ? 'show' : 'hide'](); }
|
||||
}
|
||||
}
|
||||
$.tablesorter.processTbody(table, $tb, false);
|
||||
}
|
||||
|
||||
last = cv; // save last search
|
||||
if (c.debug){
|
||||
ts.benchmark("Completed filter widget search", time);
|
||||
}
|
||||
$t.trigger('applyWidgets'); // make sure zebra widget is applied
|
||||
$t.trigger('filterEnd');
|
||||
},
|
||||
buildSelect = function(i, updating){
|
||||
var o, arry = [];
|
||||
i = parseInt(i, 10);
|
||||
o = '<option value="">' + ($ths.filter('[data-column="' + i + '"]:last').attr('data-placeholder') || '') + '</option>';
|
||||
for (k = 0; k < b.length; k++ ){
|
||||
l = c.cache[k].row.length;
|
||||
// loop through the rows
|
||||
for (j = 0; j < l; j++){
|
||||
// get non-normalized cell content
|
||||
if (wo.filter_useParsedData){
|
||||
arry.push( '' + c.cache[k].normalized[j][i] );
|
||||
} else {
|
||||
t = c.cache[k].row[j][0].cells[i];
|
||||
if (t){
|
||||
arry.push( $.trim(c.supportsTextContent ? t.textContent : $(t).text()) );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// get unique elements and sort the list
|
||||
// if $.tablesorter.sortText exists (not in the original tablesorter),
|
||||
// then natural sort the list otherwise use a basic sort
|
||||
arry = $.grep(arry, function(v, k){
|
||||
return $.inArray(v ,arry) === k;
|
||||
});
|
||||
arry = (ts.sortText) ? arry.sort(function(a,b){ return ts.sortText(table, a, b, i); }) : arry.sort(true);
|
||||
|
||||
// build option list
|
||||
for (k = 0; k < arry.length; k++){
|
||||
o += '<option value="' + arry[k] + '">' + arry[k] + '</option>';
|
||||
}
|
||||
$t.find('thead').find('select.' + css + '[data-column="' + i + '"]')[ updating ? 'html' : 'append' ](o);
|
||||
},
|
||||
buildDefault = function(updating){
|
||||
// build default select dropdown
|
||||
for (i = 0; i < cols; i++){
|
||||
t = $ths.filter('[data-column="' + i + '"]:last');
|
||||
// look for the filter-select class; build/update it if found
|
||||
if ((t.hasClass('filter-select') || wo.filter_functions && wo.filter_functions[i] === true) && !t.hasClass('filter-false')){
|
||||
if (!wo.filter_functions) { wo.filter_functions = {}; }
|
||||
wo.filter_functions[i] = true; // make sure this select gets processed by filter_functions
|
||||
buildSelect(i, updating);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (c.debug){
|
||||
time = new Date();
|
||||
}
|
||||
wo.filter_ignoreCase = wo.filter_ignoreCase !== false; // set default filter_ignoreCase to true
|
||||
wo.filter_useParsedData = wo.filter_useParsedData === true; // default is false
|
||||
// don't build filter row if columnFilters is false or all columns are set to "filter-false" - issue #156
|
||||
if (wo.filter_columnFilters !== false && $ths.filter('.filter-false').length !== $ths.length){
|
||||
t = '<tr class="tablesorter-filter-row">'; // build filter row
|
||||
for (i = 0; i < cols; i++){
|
||||
dis = false;
|
||||
$th = $ths.filter('[data-column="' + i + '"]:last'); // assuming last cell of a column is the main column
|
||||
sel = (wo.filter_functions && wo.filter_functions[i] && typeof wo.filter_functions[i] !== 'function') || $th.hasClass('filter-select');
|
||||
t += '<td>';
|
||||
if (sel){
|
||||
t += '<select data-column="' + i + '" class="' + css;
|
||||
} else {
|
||||
t += '<input type="search" placeholder="' + ($th.attr('data-placeholder') || "") + '" data-column="' + i + '" class="' + css;
|
||||
}
|
||||
// use header option - headers: { 1: { filter: false } } OR add class="filter-false"
|
||||
if (ts.getData){
|
||||
dis = ts.getData($th[0], c.headers[i], 'filter') === 'false';
|
||||
// get data from jQuery data, metadata, headers option or header class name
|
||||
t += dis ? ' disabled" disabled' : '"';
|
||||
} else {
|
||||
dis = (c.headers[i] && c.headers[i].hasOwnProperty('filter') && c.headers[i].filter === false) || $th.hasClass('filter-false');
|
||||
// only class names and header options - keep this for compatibility with tablesorter v2.0.5
|
||||
t += (dis) ? ' disabled" disabled' : '"';
|
||||
}
|
||||
t += (sel ? '></select>' : '>') + '</td>';
|
||||
}
|
||||
$t.find('thead').eq(0).append(t += '</tr>');
|
||||
}
|
||||
$t
|
||||
// add .tsfilter namespace to all BUT search
|
||||
.bind('addRows updateCell update appendCache search'.split(' ').join('.tsfilter '), function(e, filter){
|
||||
if (e.type !== 'search'){
|
||||
buildDefault(true);
|
||||
}
|
||||
checkFilters(e.type === 'search' ? filter : '');
|
||||
return false;
|
||||
})
|
||||
.find('input.' + css).bind('keyup search', function(e, filter){
|
||||
// ignore arrow and meta keys; allow backspace
|
||||
if ((e.which < 32 && e.which !== 8) || (e.which >= 37 && e.which <=40)) { return; }
|
||||
// skip delay
|
||||
if (typeof filter !== 'undefined'){
|
||||
checkFilters(filter);
|
||||
return false;
|
||||
}
|
||||
// delay filtering
|
||||
clearTimeout(timer);
|
||||
timer = setTimeout(function(){
|
||||
checkFilters();
|
||||
}, wo.filter_searchDelay || 300);
|
||||
});
|
||||
|
||||
// reset button/link
|
||||
if (wo.filter_reset && $(wo.filter_reset).length){
|
||||
$(wo.filter_reset).bind('click', function(){
|
||||
$t.find('.' + css).val('');
|
||||
checkFilters();
|
||||
return false;
|
||||
});
|
||||
}
|
||||
if (wo.filter_functions){
|
||||
// i = column # (string)
|
||||
for (col in wo.filter_functions){
|
||||
if (wo.filter_functions.hasOwnProperty(col) && typeof col === 'string'){
|
||||
t = $ths.filter('[data-column="' + col + '"]:last');
|
||||
ff = '';
|
||||
if (wo.filter_functions[col] === true && !t.hasClass('filter-false')){
|
||||
buildSelect(col);
|
||||
} else if (typeof col === 'string' && !t.hasClass('filter-false')){
|
||||
// add custom drop down list
|
||||
for (str in wo.filter_functions[col]){
|
||||
if (typeof str === 'string'){
|
||||
ff += ff === '' ? '<option value="">' + (t.attr('data-placeholder') || '') + '</option>' : '';
|
||||
ff += '<option value="' + str + '">' + str + '</option>';
|
||||
}
|
||||
}
|
||||
$t.find('thead').find('select.' + css + '[data-column="' + col + '"]').append(ff);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// not really updating, but if the column has both the "filter-select" class & filter_functions set to true,
|
||||
// it would append the same options twice.
|
||||
buildDefault(true);
|
||||
|
||||
$t.find('select.' + css).bind('change search', function(){
|
||||
checkFilters();
|
||||
});
|
||||
|
||||
if (wo.filter_hideFilters === true){
|
||||
$t
|
||||
.find('.tablesorter-filter-row')
|
||||
.addClass('hideme')
|
||||
.bind('mouseenter mouseleave', function(e){
|
||||
// save event object - http://bugs.jquery.com/ticket/12140
|
||||
var all, evt = e;
|
||||
ft = $(this);
|
||||
clearTimeout(st);
|
||||
st = setTimeout(function(){
|
||||
if (/enter|over/.test(evt.type)){
|
||||
ft.removeClass('hideme');
|
||||
} else {
|
||||
// don't hide if input has focus
|
||||
// $(':focus') needs jQuery 1.6+
|
||||
if ($(document.activeElement).closest('tr')[0] !== ft[0]){
|
||||
// get all filter values
|
||||
all = $t.find('.' + (wo.filter_cssFilter || 'tablesorter-filter')).map(function(){
|
||||
return $(this).val() || '';
|
||||
}).get().join('');
|
||||
// don't hide row if any filter has a value
|
||||
if (all === ''){
|
||||
ft.addClass('hideme');
|
||||
}
|
||||
}
|
||||
}
|
||||
}, 200);
|
||||
})
|
||||
.find('input, select').bind('focus blur', function(e){
|
||||
ft2 = $(this).closest('tr');
|
||||
clearTimeout(st);
|
||||
st = setTimeout(function(){
|
||||
// don't hide row if any filter has a value
|
||||
if ($t.find('.' + (wo.filter_cssFilter || 'tablesorter-filter')).map(function(){ return $(this).val() || ''; }).get().join('') === ''){
|
||||
ft2[ e.type === 'focus' ? 'removeClass' : 'addClass']('hideme');
|
||||
}
|
||||
}, 200);
|
||||
});
|
||||
}
|
||||
|
||||
// show processing icon
|
||||
if (c.showProcessing) {
|
||||
$t.bind('filterStart filterEnd', function(e, v) {
|
||||
var fc = (v) ? $t.find('.' + c.cssHeader).filter('[data-column]').filter(function(){
|
||||
return v[$(this).data('column')] !== '';
|
||||
}) : '';
|
||||
ts.isProcessing($t[0], e.type === 'filterStart', v ? fc : '');
|
||||
});
|
||||
}
|
||||
|
||||
if (c.debug){
|
||||
ts.benchmark("Applying Filter widget", time);
|
||||
}
|
||||
// filter widget initialized
|
||||
$t.trigger('filterInit');
|
||||
}
|
||||
},
|
||||
remove: function(table, c, wo){
|
||||
var k, $tb,
|
||||
$t = $(table),
|
||||
b = c.$tbodies;
|
||||
$t
|
||||
.removeClass('hasFilters')
|
||||
// add .tsfilter namespace to all BUT search
|
||||
.unbind('addRows updateCell update appendCache search'.split(' ').join('.tsfilter'))
|
||||
.find('.tablesorter-filter-row').remove();
|
||||
for (k = 0; k < b.length; k++ ){
|
||||
$tb = $.tablesorter.processTbody(table, b.eq(k), true); // remove tbody
|
||||
$tb.children().removeClass('filtered').show();
|
||||
$.tablesorter.processTbody(table, $tb, false); // restore tbody
|
||||
}
|
||||
if (wo.filterreset) { $(wo.filter_reset).unbind('click'); }
|
||||
}
|
||||
});
|
||||
|
||||
// Widget: Sticky headers
|
||||
// based on this awesome article:
|
||||
// http://css-tricks.com/13465-persistent-headers/
|
||||
// and https://github.com/jmosbech/StickyTableHeaders by Jonas Mosbech
|
||||
// **************************
|
||||
$.tablesorter.addWidget({
|
||||
id: "stickyHeaders",
|
||||
format: function(table){
|
||||
if ($(table).hasClass('hasStickyHeaders')) { return; }
|
||||
var $table = $(table).addClass('hasStickyHeaders'),
|
||||
c = table.config,
|
||||
wo = c.widgetOptions,
|
||||
win = $(window),
|
||||
header = $(table).children('thead:first'), //.add( $(table).find('caption') ),
|
||||
hdrCells = header.children('tr:not(.sticky-false)').children(),
|
||||
css = wo.stickyHeaders || 'tablesorter-stickyHeader',
|
||||
innr = '.tablesorter-header-inner',
|
||||
firstRow = hdrCells.eq(0).parent(),
|
||||
tfoot = $table.find('tfoot'),
|
||||
t2 = wo.$sticky = $table.clone(), // clone table, but don't remove id... the table might be styled by css
|
||||
// clone the entire thead - seems to work in IE8+
|
||||
stkyHdr = t2.children('thead:first')
|
||||
.addClass(css)
|
||||
.css({
|
||||
width : header.outerWidth(true),
|
||||
position : 'fixed',
|
||||
margin : 0,
|
||||
top : 0,
|
||||
visibility : 'hidden',
|
||||
zIndex : 1
|
||||
}),
|
||||
stkyCells = stkyHdr.children('tr:not(.sticky-false)').children(), // issue #172
|
||||
laststate = '',
|
||||
spacing = 0,
|
||||
resizeHdr = function(){
|
||||
var bwsr = navigator.userAgent;
|
||||
spacing = 0;
|
||||
// yes, I dislike browser sniffing, but it really is needed here :(
|
||||
// webkit automatically compensates for border spacing
|
||||
if ($table.css('border-collapse') !== 'collapse' && !/(webkit|msie)/i.test(bwsr)) {
|
||||
// Firefox & Opera use the border-spacing
|
||||
// update border-spacing here because of demos that switch themes
|
||||
spacing = parseInt(hdrCells.eq(0).css('border-left-width'), 10) * 2;
|
||||
}
|
||||
stkyHdr.css({
|
||||
left : header.offset().left - win.scrollLeft() - spacing,
|
||||
width: header.outerWidth()
|
||||
});
|
||||
stkyCells
|
||||
.each(function(i){
|
||||
var $h = hdrCells.eq(i);
|
||||
$(this).css({
|
||||
width: $h.width() - spacing,
|
||||
height: $h.height()
|
||||
});
|
||||
})
|
||||
.find(innr).each(function(i){
|
||||
var hi = hdrCells.eq(i).find(innr),
|
||||
w = hi.width(); // - ( parseInt(hi.css('padding-left'), 10) + parseInt(hi.css('padding-right'), 10) );
|
||||
$(this).width(w);
|
||||
});
|
||||
};
|
||||
// clear out cloned table, except for sticky header
|
||||
t2.find('thead:gt(0),tr.sticky-false,tbody,tfoot,caption').remove();
|
||||
t2.css({ height:0, width:0, padding:0, margin:0, border:0 });
|
||||
// remove rows you don't want to be sticky
|
||||
stkyHdr.find('tr.sticky-false').remove();
|
||||
// remove resizable block
|
||||
stkyCells.find('.tablesorter-resizer').remove();
|
||||
// update sticky header class names to match real header after sorting
|
||||
$table
|
||||
.bind('sortEnd.tsSticky', function(){
|
||||
hdrCells.each(function(i){
|
||||
var t = stkyCells.eq(i);
|
||||
t.attr('class', $(this).attr('class'));
|
||||
if (c.cssIcon){
|
||||
t
|
||||
.find('.' + c.cssIcon)
|
||||
.attr('class', $(this).find('.' + c.cssIcon).attr('class'));
|
||||
}
|
||||
});
|
||||
})
|
||||
.bind('pagerComplete.tsSticky', function(){
|
||||
resizeHdr();
|
||||
});
|
||||
// set sticky header cell width and link clicks to real header
|
||||
hdrCells.find('*').andSelf().filter(c.selectorSort).each(function(i){
|
||||
var t = $(this);
|
||||
stkyCells.eq(i)
|
||||
// clicking on sticky will trigger sort
|
||||
.bind('mouseup', function(e){
|
||||
t.trigger(e, true); // external mouseup flag (click timer is ignored)
|
||||
})
|
||||
// prevent sticky header text selection
|
||||
.bind('mousedown', function(){
|
||||
this.onselectstart = function(){ return false; };
|
||||
return false;
|
||||
});
|
||||
});
|
||||
// add stickyheaders AFTER the table. If the table is selected by ID, the original one (first) will be returned.
|
||||
$table.after( t2 );
|
||||
// make it sticky!
|
||||
win
|
||||
.bind('scroll.tsSticky', function(){
|
||||
var offset = firstRow.offset(),
|
||||
sTop = win.scrollTop(),
|
||||
tableHt = $table.height() - (stkyHdr.height() + (tfoot.height() || 0)),
|
||||
vis = (sTop > offset.top) && (sTop < offset.top + tableHt) ? 'visible' : 'hidden';
|
||||
stkyHdr
|
||||
.css({
|
||||
// adjust when scrolling horizontally - fixes issue #143
|
||||
left : header.offset().left - win.scrollLeft() - spacing,
|
||||
visibility : vis
|
||||
});
|
||||
if (vis !== laststate){
|
||||
// make sure the column widths match
|
||||
resizeHdr();
|
||||
laststate = vis;
|
||||
}
|
||||
})
|
||||
.bind('resize.tsSticky', function(){
|
||||
resizeHdr();
|
||||
});
|
||||
},
|
||||
remove: function(table, c, wo){
|
||||
var $t = $(table),
|
||||
css = wo.stickyHeaders || 'tablesorter-stickyHeader';
|
||||
$t
|
||||
.removeClass('hasStickyHeaders')
|
||||
.unbind('sortEnd.tsSticky pagerComplete.tsSticky')
|
||||
.find('.' + css).remove();
|
||||
if (wo.$sticky) { wo.$sticky.remove(); } // remove cloned thead
|
||||
$(window).unbind('scroll.tsSticky resize.tsSticky');
|
||||
}
|
||||
});
|
||||
|
||||
// Add Column resizing widget
|
||||
// this widget saves the column widths if
|
||||
// $.tablesorter.storage function is included
|
||||
// **************************
|
||||
$.tablesorter.addWidget({
|
||||
id: "resizable",
|
||||
format: function(table){
|
||||
if ($(table).hasClass('hasResizable')) { return; }
|
||||
$(table).addClass('hasResizable');
|
||||
var $t, t, i, j, s, $c, $cols, w, tw,
|
||||
$tbl = $(table),
|
||||
c = table.config,
|
||||
wo = c.widgetOptions,
|
||||
position = 0,
|
||||
$target = null,
|
||||
$next = null,
|
||||
fullWidth = Math.abs($tbl.parent().width() - $tbl.width()) < 20,
|
||||
stopResize = function(){
|
||||
if ($.tablesorter.storage && $target){
|
||||
s[$target.index()] = $target.width();
|
||||
s[$next.index()] = $next.width();
|
||||
$target.width( s[$target.index()] );
|
||||
$next.width( s[$next.index()] );
|
||||
if (wo.resizable !== false){
|
||||
$.tablesorter.storage(table, 'tablesorter-resizable', s);
|
||||
}
|
||||
}
|
||||
position = 0;
|
||||
$target = $next = null;
|
||||
$(window).trigger('resize'); // will update stickyHeaders, just in case
|
||||
};
|
||||
s = ($.tablesorter.storage && wo.resizable !== false) ? $.tablesorter.storage(table, 'tablesorter-resizable') : {};
|
||||
// process only if table ID or url match
|
||||
if (s){
|
||||
for (j in s){
|
||||
if (!isNaN(j) && j < c.headerList.length){
|
||||
$(c.headerList[j]).width(s[j]); // set saved resizable widths
|
||||
}
|
||||
}
|
||||
}
|
||||
$t = $tbl.children('thead:first').children('tr');
|
||||
// add resizable-false class name to headers (across rows as needed)
|
||||
$t.children().each(function(){
|
||||
t = $(this);
|
||||
i = t.attr('data-column');
|
||||
j = $.tablesorter.getData( t, c.headers[i], 'resizable') === "false";
|
||||
$t.children().filter('[data-column="' + i + '"]').toggleClass('resizable-false', j);
|
||||
});
|
||||
// add wrapper inside each cell to allow for positioning of the resizable target block
|
||||
$t.each(function(){
|
||||
$c = $(this).children(':not(.resizable-false)');
|
||||
if (!$(this).find('.tablesorter-wrapper').length) {
|
||||
// Firefox needs this inner div to position the resizer correctly
|
||||
$c.wrapInner('<div class="tablesorter-wrapper" style="position:relative;height:100%;width:100%"></div>');
|
||||
}
|
||||
$c = $c.slice(0,-1); // don't include the last column of the row
|
||||
$cols = $cols ? $cols.add($c) : $c;
|
||||
});
|
||||
$cols
|
||||
.each(function(){
|
||||
$t = $(this);
|
||||
j = parseInt($t.css('padding-right'), 10) + 10; // 8 is 1/2 of the 16px wide resizer grip
|
||||
t = '<div class="tablesorter-resizer" style="cursor:w-resize;position:absolute;z-index:1;right:-' + j +
|
||||
'px;top:0;height:100%;width:20px;"></div>';
|
||||
$t
|
||||
.find('.tablesorter-wrapper')
|
||||
.append(t);
|
||||
})
|
||||
.bind('mousemove.tsresize', function(e){
|
||||
// ignore mousemove if no mousedown
|
||||
if (position === 0 || !$target) { return; }
|
||||
// resize columns
|
||||
w = e.pageX - position;
|
||||
tw = $target.width();
|
||||
$target.width( tw + w );
|
||||
if ($target.width() !== tw && fullWidth){
|
||||
$next.width( $next.width() - w );
|
||||
}
|
||||
position = e.pageX;
|
||||
})
|
||||
.bind('mouseup.tsresize', function(){
|
||||
stopResize();
|
||||
})
|
||||
.find('.tablesorter-resizer,.tablesorter-resizer-grip')
|
||||
.bind('mousedown', function(e){
|
||||
// save header cell and mouse position; closest() not supported by jQuery v1.2.6
|
||||
$target = $(e.target).closest('th');
|
||||
t = c.$headers.filter('[data-column="' + $target.attr('data-column') + '"]');
|
||||
if (t.length > 1) { $target = $target.add(t); }
|
||||
// if table is not as wide as it's parent, then resize the table
|
||||
$next = e.shiftKey ? $target.parent().find('th:not(.resizable-false)').filter(':last') : $target.nextAll(':not(.resizable-false)').eq(0);
|
||||
position = e.pageX;
|
||||
});
|
||||
$tbl.find('thead:first')
|
||||
.bind('mouseup.tsresize mouseleave.tsresize', function(e){
|
||||
stopResize();
|
||||
})
|
||||
// right click to reset columns to default widths
|
||||
.bind('contextmenu.tsresize', function(){
|
||||
$.tablesorter.resizableReset(table);
|
||||
// $.isEmptyObject() needs jQuery 1.4+
|
||||
var rtn = $.isEmptyObject ? $.isEmptyObject(s) : s === {}; // allow right click if already reset
|
||||
s = {};
|
||||
return rtn;
|
||||
});
|
||||
},
|
||||
remove: function(table, c, wo){
|
||||
$(table)
|
||||
.removeClass('hasResizable')
|
||||
.find('thead')
|
||||
.unbind('mouseup.tsresize mouseleave.tsresize contextmenu.tsresize')
|
||||
.find('tr').children()
|
||||
.unbind('mousemove.tsresize mouseup.tsresize')
|
||||
// don't remove "tablesorter-wrapper" as uitheme uses it too
|
||||
.find('.tablesorter-resizer,.tablesorter-resizer-grip').remove();
|
||||
$.tablesorter.resizableReset(table);
|
||||
}
|
||||
});
|
||||
$.tablesorter.resizableReset = function(table){
|
||||
$(table.config.headerList).filter(':not(.resizable-false)').css('width','');
|
||||
if ($.tablesorter.storage) { $.tablesorter.storage(table, 'tablesorter-resizable', {}); }
|
||||
};
|
||||
|
||||
// Save table sort widget
|
||||
// this widget saves the last sort only if the
|
||||
// saveSort widget option is true AND the
|
||||
// $.tablesorter.storage function is included
|
||||
// **************************
|
||||
$.tablesorter.addWidget({
|
||||
id: 'saveSort',
|
||||
init: function(table, thisWidget){
|
||||
// run widget format before all other widgets are applied to the table
|
||||
thisWidget.format(table, true);
|
||||
},
|
||||
format: function(table, init){
|
||||
var sl, time, c = table.config,
|
||||
wo = c.widgetOptions,
|
||||
ss = wo.saveSort !== false, // make saveSort active/inactive; default to true
|
||||
sortList = { "sortList" : c.sortList };
|
||||
if (c.debug){
|
||||
time = new Date();
|
||||
}
|
||||
if ($(table).hasClass('hasSaveSort')){
|
||||
if (ss && table.hasInitialized && $.tablesorter.storage){
|
||||
$.tablesorter.storage( table, 'tablesorter-savesort', sortList );
|
||||
if (c.debug){
|
||||
$.tablesorter.benchmark('saveSort widget: Saving last sort: ' + c.sortList, time);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// set table sort on initial run of the widget
|
||||
$(table).addClass('hasSaveSort');
|
||||
sortList = '';
|
||||
// get data
|
||||
if ($.tablesorter.storage){
|
||||
sl = $.tablesorter.storage( table, 'tablesorter-savesort' );
|
||||
sortList = (sl && sl.hasOwnProperty('sortList') && $.isArray(sl.sortList)) ? sl.sortList : '';
|
||||
if (c.debug){
|
||||
$.tablesorter.benchmark('saveSort: Last sort loaded: "' + sortList + '"', time);
|
||||
}
|
||||
}
|
||||
// init is true when widget init is run, this will run this widget before all other widgets have initialized
|
||||
// this method allows using this widget in the original tablesorter plugin; but then it will run all widgets twice.
|
||||
if (init && sortList && sortList.length > 0){
|
||||
c.sortList = sortList;
|
||||
} else if (table.hasInitialized && sortList && sortList.length > 0){
|
||||
// update sort change
|
||||
$(table).trigger('sorton', [sortList]);
|
||||
}
|
||||
}
|
||||
},
|
||||
remove: function(table, c, wo){
|
||||
// clear storage
|
||||
if ($.tablesorter.storage) { $.tablesorter.storage( table, 'tablesorter-savesort', '' ); }
|
||||
}
|
||||
});
|
||||
|
||||
})(jQuery);
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user