mirror of
https://github.com/Readarr/Readarr.git
synced 2026-04-23 22:25:09 -04:00
Moved source code under src folder - massive change
This commit is contained in:
@@ -0,0 +1,365 @@
|
||||
/*
|
||||
backgrid-filter
|
||||
http://github.com/wyuenho/backgrid
|
||||
|
||||
Copyright (c) 2013 Jimmy Yuen Ho Wong and contributors
|
||||
Licensed under the MIT @license.
|
||||
*/
|
||||
|
||||
(function ($, _, Backbone, Backgrid, lunr) {
|
||||
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
ServerSideFilter is a search form widget that submits a query to the server
|
||||
for filtering the current collection.
|
||||
|
||||
@class Backgrid.Extension.ServerSideFilter
|
||||
*/
|
||||
var ServerSideFilter = Backgrid.Extension.ServerSideFilter = Backbone.View.extend({
|
||||
|
||||
/** @property */
|
||||
tagName: "form",
|
||||
|
||||
/** @property */
|
||||
className: "backgrid-filter form-search",
|
||||
|
||||
/** @property {function(Object, ?Object=): string} template */
|
||||
template: _.template('<div class="input-prepend input-append"><span class="add-on"><i class="icon-search"></i></span><input type="text" <% if (placeholder) { %> placeholder="<%- placeholder %>" <% } %> name="<%- name %>" /><span class="add-on"><a class="close" href="#">×</a></span></div>'),
|
||||
|
||||
/** @property */
|
||||
events: {
|
||||
"click .close": "clear",
|
||||
"submit": "search"
|
||||
},
|
||||
|
||||
/** @property {string} [name='q'] Query key */
|
||||
name: "q",
|
||||
|
||||
/** @property The HTML5 placeholder to appear beneath the search box. */
|
||||
placeholder: null,
|
||||
|
||||
/**
|
||||
@param {Object} options
|
||||
@param {Backbone.Collection} options.collection
|
||||
@param {String} [options.name]
|
||||
@param {String} [options.placeholder]
|
||||
*/
|
||||
initialize: function (options) {
|
||||
Backgrid.requireOptions(options, ["collection"]);
|
||||
Backbone.View.prototype.initialize.apply(this, arguments);
|
||||
this.name = options.name || this.name;
|
||||
this.placeholder = options.placeholder || this.placeholder;
|
||||
|
||||
var collection = this.collection, self = this;
|
||||
if (Backbone.PageableCollection &&
|
||||
collection instanceof Backbone.PageableCollection &&
|
||||
collection.mode == "server") {
|
||||
collection.queryParams[this.name] = function () {
|
||||
return self.$el.find("input[type=text]").val();
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
Upon search form submission, this event handler constructs a query
|
||||
parameter object and pass it to Collection#fetch for server-side
|
||||
filtering.
|
||||
*/
|
||||
search: function (e) {
|
||||
if (e) e.preventDefault();
|
||||
var data = {};
|
||||
data[this.name] = this.$el.find("input[type=text]").val();
|
||||
this.collection.fetch({data: data});
|
||||
},
|
||||
|
||||
/**
|
||||
Event handler for the close button. Clears the search box and refetch the
|
||||
collection.
|
||||
*/
|
||||
clear: function (e) {
|
||||
if (e) e.preventDefault();
|
||||
this.$("input[type=text]").val(null);
|
||||
this.collection.fetch();
|
||||
},
|
||||
|
||||
/**
|
||||
Renders a search form with a text box, optionally with a placeholder and
|
||||
a preset value if supplied during initialization.
|
||||
*/
|
||||
render: function () {
|
||||
this.$el.empty().append(this.template({
|
||||
name: this.name,
|
||||
placeholder: this.placeholder,
|
||||
value: this.value
|
||||
}));
|
||||
this.delegateEvents();
|
||||
return this;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
/**
|
||||
ClientSideFilter is a search form widget that searches a collection for
|
||||
model matches against a query on the client side. The exact matching
|
||||
algorithm can be overriden by subclasses.
|
||||
|
||||
@class Backgrid.Extension.ClientSideFilter
|
||||
@extends Backgrid.Extension.ServerSideFilter
|
||||
*/
|
||||
var ClientSideFilter = Backgrid.Extension.ClientSideFilter = ServerSideFilter.extend({
|
||||
|
||||
/** @property */
|
||||
events: {
|
||||
"click .close": function (e) {
|
||||
e.preventDefault();
|
||||
this.clear();
|
||||
},
|
||||
"change input[type=text]": "search",
|
||||
"keyup input[type=text]": "search",
|
||||
"submit": function (e) {
|
||||
e.preventDefault();
|
||||
this.search();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
@property {?Array.<string>} A list of model field names to search
|
||||
for matches. If null, all of the fields will be searched.
|
||||
*/
|
||||
fields: null,
|
||||
|
||||
/**
|
||||
@property wait The time in milliseconds to wait since for since the last
|
||||
change to the search box's value before searching. This value can be
|
||||
adjusted depending on how often the search box is used and how large the
|
||||
search index is.
|
||||
*/
|
||||
wait: 149,
|
||||
|
||||
/**
|
||||
Debounces the #search and #clear methods and makes a copy of the given
|
||||
collection for searching.
|
||||
|
||||
@param {Object} options
|
||||
@param {Backbone.Collection} options.collection
|
||||
@param {String} [options.placeholder]
|
||||
@param {String} [options.fields]
|
||||
@param {String} [options.wait=149]
|
||||
*/
|
||||
initialize: function (options) {
|
||||
ServerSideFilter.prototype.initialize.apply(this, arguments);
|
||||
|
||||
this.fields = options.fields || this.fields;
|
||||
this.wait = options.wait || this.wait;
|
||||
|
||||
this._debounceMethods(["search", "clear"]);
|
||||
|
||||
var collection = this.collection;
|
||||
var shadowCollection = this.shadowCollection = collection.clone();
|
||||
shadowCollection.url = collection.url;
|
||||
shadowCollection.sync = collection.sync;
|
||||
shadowCollection.parse = collection.parse;
|
||||
|
||||
this.listenTo(collection, "add", function (model, collection, options) {
|
||||
shadowCollection.add(model, options);
|
||||
});
|
||||
this.listenTo(collection, "remove", function (model, collection, options) {
|
||||
shadowCollection.remove(model, options);
|
||||
});
|
||||
this.listenTo(collection, "sort reset", function (collection, options) {
|
||||
options = _.extend({reindex: true}, options || {});
|
||||
if (options.reindex) shadowCollection.reset(collection.models);
|
||||
});
|
||||
},
|
||||
|
||||
_debounceMethods: function (methodNames) {
|
||||
if (_.isString(methodNames)) methodNames = [methodNames];
|
||||
|
||||
this.undelegateEvents();
|
||||
|
||||
for (var i = 0, l = methodNames.length; i < l; i++) {
|
||||
var methodName = methodNames[i];
|
||||
var method = this[methodName];
|
||||
this[methodName] = _.debounce(method, this.wait);
|
||||
}
|
||||
|
||||
this.delegateEvents();
|
||||
},
|
||||
|
||||
/**
|
||||
This default implementation takes a query string and returns a matcher
|
||||
function that looks for matches in the model's #fields or all of its
|
||||
fields if #fields is null, for any of the words in the query
|
||||
case-insensitively.
|
||||
|
||||
Subclasses overriding this method must take care to conform to the
|
||||
signature of the matcher function. In addition, when the matcher function
|
||||
is called, its context will be bound to this ClientSideFilter object so
|
||||
it has access to the filter's attributes and methods.
|
||||
|
||||
@param {string} query The search query in the search box.
|
||||
@return {function(Backbone.Model):boolean} A matching function.
|
||||
*/
|
||||
makeMatcher: function (query) {
|
||||
var regexp = new RegExp(query.trim().split(/\W/).join("|"), "i");
|
||||
return function (model) {
|
||||
var keys = this.fields || model.keys();
|
||||
for (var i = 0, l = keys.length; i < l; i++) {
|
||||
if (regexp.test(model.get(keys[i]) + "")) return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
Takes the query from the search box, constructs a matcher with it and
|
||||
loops through collection looking for matches. Reset the given collection
|
||||
when all the matches have been found.
|
||||
*/
|
||||
search: function () {
|
||||
var matcher = _.bind(this.makeMatcher(this.$("input[type=text]").val()), this);
|
||||
this.collection.reset(this.shadowCollection.filter(matcher), {reindex: false});
|
||||
},
|
||||
|
||||
/**
|
||||
Clears the search box and reset the collection to its original.
|
||||
*/
|
||||
clear: function () {
|
||||
this.$("input[type=text]").val(null);
|
||||
this.collection.reset(this.shadowCollection.models, {reindex: false});
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
/**
|
||||
LunrFilter is a ClientSideFilter that uses [lunrjs](http://lunrjs.com/) to
|
||||
index the text fields of each model for a collection, and performs
|
||||
full-text searching.
|
||||
|
||||
@class Backgrid.Extension.LunrFilter
|
||||
@extends Backgrid.Extension.ClientSideFilter
|
||||
*/
|
||||
Backgrid.Extension.LunrFilter = ClientSideFilter.extend({
|
||||
|
||||
/**
|
||||
@property {string} [ref="id"]`lunrjs` document reference attribute name.
|
||||
*/
|
||||
ref: "id",
|
||||
|
||||
/**
|
||||
@property {Object} fields A hash of `lunrjs` index field names and boost
|
||||
value. Unlike ClientSideFilter#fields, LunrFilter#fields is _required_ to
|
||||
initialize the index.
|
||||
*/
|
||||
fields: null,
|
||||
|
||||
/**
|
||||
Indexes the underlying collection on construction. The index will refresh
|
||||
when the underlying collection is reset. If any model is added, removed
|
||||
or if any indexed fields of any models has changed, the index will be
|
||||
updated.
|
||||
|
||||
@param {Object} options
|
||||
@param {Backbone.Collection} options.collection
|
||||
@param {String} [options.placeholder]
|
||||
@param {string} [options.ref] `lunrjs` document reference attribute name.
|
||||
@param {Object} [options.fields] A hash of `lunrjs` index field names and
|
||||
boost value.
|
||||
@param {number} [options.wait]
|
||||
*/
|
||||
initialize: function (options) {
|
||||
ClientSideFilter.prototype.initialize.apply(this, arguments);
|
||||
|
||||
this.ref = options.ref || this.ref;
|
||||
|
||||
var collection = this.collection;
|
||||
this.listenTo(collection, "add", this.addToIndex);
|
||||
this.listenTo(collection, "remove", this.removeFromIndex);
|
||||
this.listenTo(collection, "reset", this.resetIndex);
|
||||
this.listenTo(collection, "change", this.updateIndex);
|
||||
|
||||
this.resetIndex(collection);
|
||||
},
|
||||
|
||||
/**
|
||||
Reindex the collection. If `options.reindex` is `false`, this method is a
|
||||
no-op.
|
||||
|
||||
@param {Backbone.Collection} collection
|
||||
@param {Object} [options]
|
||||
@param {boolean} [options.reindex=true]
|
||||
*/
|
||||
resetIndex: function (collection, options) {
|
||||
options = _.extend({reindex: true}, options || {});
|
||||
|
||||
if (options.reindex) {
|
||||
var self = this;
|
||||
this.index = lunr(function () {
|
||||
_.each(self.fields, function (boost, fieldName) {
|
||||
this.field(fieldName, boost);
|
||||
this.ref(self.ref);
|
||||
}, this);
|
||||
});
|
||||
|
||||
collection.each(function (model) {
|
||||
this.addToIndex(model);
|
||||
}, this);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
Adds the given model to the index.
|
||||
|
||||
@param {Backbone.Model} model
|
||||
*/
|
||||
addToIndex: function (model) {
|
||||
var index = this.index;
|
||||
var doc = model.toJSON();
|
||||
if (index.documentStore.has(doc[this.ref])) index.update(doc);
|
||||
else index.add(doc);
|
||||
},
|
||||
|
||||
/**
|
||||
Removes the given model from the index.
|
||||
|
||||
@param {Backbone.Model} model
|
||||
*/
|
||||
removeFromIndex: function (model) {
|
||||
var index = this.index;
|
||||
var doc = model.toJSON();
|
||||
if (index.documentStore.has(doc[this.ref])) index.remove(doc);
|
||||
},
|
||||
|
||||
/**
|
||||
Updates the index for the given model.
|
||||
|
||||
@param {Backbone.Model} model
|
||||
*/
|
||||
updateIndex: function (model) {
|
||||
var changed = model.changedAttributes();
|
||||
if (changed && !_.isEmpty(_.intersection(_.keys(this.fields),
|
||||
_.keys(changed)))) {
|
||||
this.index.update(model.toJSON());
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
Takes the query from the search box and performs a full-text search on
|
||||
the client-side. The search result is returned by resetting the
|
||||
underlying collection to the models after interrogating the index for the
|
||||
query answer.
|
||||
*/
|
||||
search: function () {
|
||||
var searchResults = this.index.search(this.$("input[type=text]").val());
|
||||
var models = [];
|
||||
for (var i = 0; i < searchResults.length; i++) {
|
||||
var result = searchResults[i];
|
||||
models.push(this.shadowCollection.get(result.ref));
|
||||
}
|
||||
this.collection.reset(models, {reindex: false});
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
}(jQuery, _, Backbone, Backgrid, lunr));
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,198 @@
|
||||
/*
|
||||
backgrid-paginator
|
||||
http://github.com/wyuenho/backgrid
|
||||
|
||||
Copyright (c) 2013 Jimmy Yuen Ho Wong and contributors
|
||||
Licensed under the MIT @license.
|
||||
*/
|
||||
|
||||
(function ($, _, Backbone, Backgrid) {
|
||||
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
Paginator is a Backgrid extension that renders a series of configurable
|
||||
pagination handles. This extension is best used for splitting a large data
|
||||
set across multiple pages. If the number of pages is larger then a
|
||||
threshold, which is set to 10 by default, the page handles are rendered
|
||||
within a sliding window, plus the fast forward, fast backward, previous and
|
||||
next page handles. The fast forward, fast backward, previous and next page
|
||||
handles can be turned off.
|
||||
|
||||
@class Backgrid.Extension.Paginator
|
||||
*/
|
||||
Backgrid.Extension.Paginator = Backbone.View.extend({
|
||||
|
||||
/** @property */
|
||||
className: "backgrid-paginator",
|
||||
|
||||
/** @property */
|
||||
windowSize: 10,
|
||||
|
||||
/**
|
||||
@property {Object} fastForwardHandleLabels You can disable specific
|
||||
handles by setting its value to `null`.
|
||||
*/
|
||||
fastForwardHandleLabels: {
|
||||
first: "《",
|
||||
prev: "〈",
|
||||
next: "〉",
|
||||
last: "》"
|
||||
},
|
||||
|
||||
/** @property */
|
||||
template: _.template('<ul><% _.each(handles, function (handle) { %><li <% if (handle.className) { %>class="<%= handle.className %>"<% } %>><a href="#" <% if (handle.title) {%> title="<%= handle.title %>"<% } %>><%= handle.label %></a></li><% }); %></ul>'),
|
||||
|
||||
/** @property */
|
||||
events: {
|
||||
"click a": "changePage"
|
||||
},
|
||||
|
||||
/**
|
||||
Initializer.
|
||||
|
||||
@param {Object} options
|
||||
@param {Backbone.Collection} options.collection
|
||||
@param {boolean} [options.fastForwardHandleLabels] Whether to render fast forward buttons.
|
||||
*/
|
||||
initialize: function (options) {
|
||||
Backgrid.requireOptions(options, ["collection"]);
|
||||
|
||||
var collection = this.collection;
|
||||
var fullCollection = collection.fullCollection;
|
||||
if (fullCollection) {
|
||||
this.listenTo(fullCollection, "add", this.render);
|
||||
this.listenTo(fullCollection, "remove", this.render);
|
||||
this.listenTo(fullCollection, "reset", this.render);
|
||||
}
|
||||
else {
|
||||
this.listenTo(collection, "add", this.render);
|
||||
this.listenTo(collection, "remove", this.render);
|
||||
this.listenTo(collection, "reset", this.render);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
jQuery event handler for the page handlers. Goes to the right page upon
|
||||
clicking.
|
||||
|
||||
@param {Event} e
|
||||
*/
|
||||
changePage: function (e) {
|
||||
e.preventDefault();
|
||||
|
||||
var $li = $(e.target).parent();
|
||||
if (!$li.hasClass("active") && !$li.hasClass("disabled")) {
|
||||
|
||||
var label = $(e.target).text();
|
||||
var ffLabels = this.fastForwardHandleLabels;
|
||||
|
||||
var collection = this.collection;
|
||||
|
||||
if (ffLabels) {
|
||||
switch (label) {
|
||||
case ffLabels.first:
|
||||
collection.getFirstPage();
|
||||
return;
|
||||
case ffLabels.prev:
|
||||
collection.getPreviousPage();
|
||||
return;
|
||||
case ffLabels.next:
|
||||
collection.getNextPage();
|
||||
return;
|
||||
case ffLabels.last:
|
||||
collection.getLastPage();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var state = collection.state;
|
||||
var pageIndex = +label;
|
||||
collection.getPage(state.firstPage === 0 ? pageIndex - 1 : pageIndex);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
Internal method to create a list of page handle objects for the template
|
||||
to render them.
|
||||
|
||||
@return {Array.<Object>} an array of page handle objects hashes
|
||||
*/
|
||||
makeHandles: function () {
|
||||
|
||||
var handles = [];
|
||||
var collection = this.collection;
|
||||
var state = collection.state;
|
||||
|
||||
// convert all indices to 0-based here
|
||||
var firstPage = state.firstPage;
|
||||
var lastPage = +state.lastPage;
|
||||
lastPage = Math.max(0, firstPage ? lastPage - 1 : lastPage);
|
||||
var currentPage = Math.max(state.currentPage, state.firstPage);
|
||||
currentPage = firstPage ? currentPage - 1 : currentPage;
|
||||
var windowStart = Math.floor(currentPage / this.windowSize) * this.windowSize;
|
||||
var windowEnd = Math.min(lastPage + 1, windowStart + this.windowSize);
|
||||
|
||||
if (collection.mode !== "infinite") {
|
||||
for (var i = windowStart; i < windowEnd; i++) {
|
||||
handles.push({
|
||||
label: i + 1,
|
||||
title: "No. " + (i + 1),
|
||||
className: currentPage === i ? "active" : undefined
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
var ffLabels = this.fastForwardHandleLabels;
|
||||
if (ffLabels) {
|
||||
|
||||
if (ffLabels.prev) {
|
||||
handles.unshift({
|
||||
label: ffLabels.prev,
|
||||
className: collection.hasPrevious() ? void 0 : "disabled"
|
||||
});
|
||||
}
|
||||
|
||||
if (ffLabels.first) {
|
||||
handles.unshift({
|
||||
label: ffLabels.first,
|
||||
className: collection.hasPrevious() ? void 0 : "disabled"
|
||||
});
|
||||
}
|
||||
|
||||
if (ffLabels.next) {
|
||||
handles.push({
|
||||
label: ffLabels.next,
|
||||
className: collection.hasNext() ? void 0 : "disabled"
|
||||
});
|
||||
}
|
||||
|
||||
if (ffLabels.last) {
|
||||
handles.push({
|
||||
label: ffLabels.last,
|
||||
className: collection.hasNext() ? void 0 : "disabled"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return handles;
|
||||
},
|
||||
|
||||
/**
|
||||
Render the paginator handles inside an unordered list.
|
||||
*/
|
||||
render: function () {
|
||||
this.$el.empty();
|
||||
|
||||
this.$el.append(this.template({
|
||||
handles: this.makeHandles()
|
||||
}));
|
||||
|
||||
this.delegateEvents();
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
}(jQuery, _, Backbone, Backgrid));
|
||||
@@ -0,0 +1,330 @@
|
||||
/**
|
||||
* Main source
|
||||
*/
|
||||
|
||||
;(function(factory) {
|
||||
if (typeof define === 'function' && define.amd) {
|
||||
// AMD
|
||||
define(['underscore', 'backbone'], factory);
|
||||
} else {
|
||||
// globals
|
||||
factory(_, Backbone);
|
||||
}
|
||||
}(function(_, Backbone) {
|
||||
|
||||
/**
|
||||
* Takes a nested object and returns a shallow object keyed with the path names
|
||||
* e.g. { "level1.level2": "value" }
|
||||
*
|
||||
* @param {Object} Nested object e.g. { level1: { level2: 'value' } }
|
||||
* @return {Object} Shallow object with path names e.g. { 'level1.level2': 'value' }
|
||||
*/
|
||||
function objToPaths(obj) {
|
||||
var ret = {},
|
||||
separator = DeepModel.keyPathSeparator;
|
||||
|
||||
for (var key in obj) {
|
||||
var val = obj[key];
|
||||
|
||||
if (val && val.constructor === Object && !_.isEmpty(val)) {
|
||||
//Recursion for embedded objects
|
||||
var obj2 = objToPaths(val);
|
||||
|
||||
for (var key2 in obj2) {
|
||||
var val2 = obj2[key2];
|
||||
|
||||
ret[key + separator + key2] = val2;
|
||||
}
|
||||
} else {
|
||||
ret[key] = val;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Object} Object to fetch attribute from
|
||||
* @param {String} Object path e.g. 'user.name'
|
||||
* @return {Mixed}
|
||||
*/
|
||||
function getNested(obj, path, return_exists) {
|
||||
var separator = DeepModel.keyPathSeparator;
|
||||
|
||||
var fields = path.split(separator);
|
||||
var result = obj;
|
||||
return_exists || (return_exists === false);
|
||||
for (var i = 0, n = fields.length; i < n; i++) {
|
||||
if (return_exists && !_.has(result, fields[i])) {
|
||||
return false;
|
||||
}
|
||||
result = result[fields[i]];
|
||||
|
||||
if (result == null && i < n - 1) {
|
||||
result = {};
|
||||
}
|
||||
|
||||
if (typeof result === 'undefined') {
|
||||
if (return_exists)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
if (return_exists)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Object} obj Object to fetch attribute from
|
||||
* @param {String} path Object path e.g. 'user.name'
|
||||
* @param {Object} [options] Options
|
||||
* @param {Boolean} [options.unset] Whether to delete the value
|
||||
* @param {Mixed} Value to set
|
||||
*/
|
||||
function setNested(obj, path, val, options) {
|
||||
options = options || {};
|
||||
|
||||
var separator = DeepModel.keyPathSeparator;
|
||||
|
||||
var fields = path.split(separator);
|
||||
var result = obj;
|
||||
for (var i = 0, n = fields.length; i < n && result !== undefined ; i++) {
|
||||
var field = fields[i];
|
||||
|
||||
//If the last in the path, set the value
|
||||
if (i === n - 1) {
|
||||
options.unset ? delete result[field] : result[field] = val;
|
||||
} else {
|
||||
//Create the child object if it doesn't exist, or isn't an object
|
||||
if (typeof result[field] === 'undefined' || ! _.isObject(result[field])) {
|
||||
result[field] = {};
|
||||
}
|
||||
|
||||
//Move onto the next part of the path
|
||||
result = result[field];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function deleteNested(obj, path) {
|
||||
setNested(obj, path, null, { unset: true });
|
||||
}
|
||||
|
||||
var DeepModel = Backbone.Model.extend({
|
||||
|
||||
// Override constructor
|
||||
// Support having nested defaults by using _.deepExtend instead of _.extend
|
||||
constructor: function(attributes, options) {
|
||||
var defaults;
|
||||
var attrs = attributes || {};
|
||||
this.cid = _.uniqueId('c');
|
||||
this.attributes = {};
|
||||
if (options && options.collection) this.collection = options.collection;
|
||||
if (options && options.parse) attrs = this.parse(attrs, options) || {};
|
||||
if (defaults = _.result(this, 'defaults')) {
|
||||
//<custom code>
|
||||
// Replaced the call to _.defaults with _.deepExtend.
|
||||
attrs = _.deepExtend({}, defaults, attrs);
|
||||
//</custom code>
|
||||
}
|
||||
this.set(attrs, options);
|
||||
this.changed = {};
|
||||
this.initialize.apply(this, arguments);
|
||||
},
|
||||
|
||||
// Return a copy of the model's `attributes` object.
|
||||
toJSON: function(options) {
|
||||
return _.deepClone(this.attributes);
|
||||
},
|
||||
|
||||
// Override get
|
||||
// Supports nested attributes via the syntax 'obj.attr' e.g. 'author.user.name'
|
||||
get: function(attr) {
|
||||
return getNested(this.attributes, attr);
|
||||
},
|
||||
|
||||
// Override set
|
||||
// Supports nested attributes via the syntax 'obj.attr' e.g. 'author.user.name'
|
||||
set: function(key, val, options) {
|
||||
var attr, attrs, unset, changes, silent, changing, prev, current;
|
||||
if (key == null) return this;
|
||||
|
||||
// Handle both `"key", value` and `{key: value}` -style arguments.
|
||||
if (typeof key === 'object') {
|
||||
attrs = key;
|
||||
options = val || {};
|
||||
} else {
|
||||
(attrs = {})[key] = val;
|
||||
}
|
||||
|
||||
options || (options = {});
|
||||
|
||||
// Run validation.
|
||||
if (!this._validate(attrs, options)) return false;
|
||||
|
||||
// Extract attributes and options.
|
||||
unset = options.unset;
|
||||
silent = options.silent;
|
||||
changes = [];
|
||||
changing = this._changing;
|
||||
this._changing = true;
|
||||
|
||||
if (!changing) {
|
||||
this._previousAttributes = _.deepClone(this.attributes); //<custom>: Replaced _.clone with _.deepClone
|
||||
this.changed = {};
|
||||
}
|
||||
current = this.attributes, prev = this._previousAttributes;
|
||||
|
||||
// Check for changes of `id`.
|
||||
if (this.idAttribute in attrs) this.id = attrs[this.idAttribute];
|
||||
|
||||
//<custom code>
|
||||
attrs = objToPaths(attrs);
|
||||
//</custom code>
|
||||
|
||||
// For each `set` attribute, update or delete the current value.
|
||||
for (attr in attrs) {
|
||||
val = attrs[attr];
|
||||
|
||||
//<custom code>: Using getNested, setNested and deleteNested
|
||||
if (!_.isEqual(getNested(current, attr), val)) changes.push(attr);
|
||||
if (!_.isEqual(getNested(prev, attr), val)) {
|
||||
setNested(this.changed, attr, val);
|
||||
} else {
|
||||
deleteNested(this.changed, attr);
|
||||
}
|
||||
unset ? deleteNested(current, attr) : setNested(current, attr, val);
|
||||
//</custom code>
|
||||
}
|
||||
|
||||
// Trigger all relevant attribute changes.
|
||||
if (!silent) {
|
||||
if (changes.length) this._pending = true;
|
||||
|
||||
//<custom code>
|
||||
var separator = DeepModel.keyPathSeparator;
|
||||
var alreadyTriggered = {}; // * @restorer
|
||||
|
||||
for (var i = 0, l = changes.length; i < l; i++) {
|
||||
var key = changes[i];
|
||||
|
||||
if (!alreadyTriggered.hasOwnProperty(key) || !alreadyTriggered[key]) { // * @restorer
|
||||
alreadyTriggered[key] = true; // * @restorer
|
||||
this.trigger('change:' + key, this, getNested(current, key), options);
|
||||
} // * @restorer
|
||||
|
||||
var fields = key.split(separator);
|
||||
|
||||
//Trigger change events for parent keys with wildcard (*) notation
|
||||
for(var n = fields.length - 1; n > 0; n--) {
|
||||
var parentKey = _.first(fields, n).join(separator),
|
||||
wildcardKey = parentKey + separator + '*';
|
||||
|
||||
if (!alreadyTriggered.hasOwnProperty(wildcardKey) || !alreadyTriggered[wildcardKey]) { // * @restorer
|
||||
alreadyTriggered[wildcardKey] = true; // * @restorer
|
||||
this.trigger('change:' + wildcardKey, this, getNested(current, parentKey), options);
|
||||
} // * @restorer
|
||||
|
||||
// + @restorer
|
||||
if (!alreadyTriggered.hasOwnProperty(parentKey) || !alreadyTriggered[parentKey]) {
|
||||
alreadyTriggered[parentKey] = true;
|
||||
this.trigger('change:' + parentKey, this, getNested(current, parentKey), options);
|
||||
}
|
||||
// - @restorer
|
||||
}
|
||||
//</custom code>
|
||||
}
|
||||
}
|
||||
|
||||
if (changing) return this;
|
||||
if (!silent) {
|
||||
while (this._pending) {
|
||||
this._pending = false;
|
||||
this.trigger('change', this, options);
|
||||
}
|
||||
}
|
||||
this._pending = false;
|
||||
this._changing = false;
|
||||
return this;
|
||||
},
|
||||
|
||||
// Clear all attributes on the model, firing `"change"` unless you choose
|
||||
// to silence it.
|
||||
clear: function(options) {
|
||||
var attrs = {};
|
||||
var shallowAttributes = objToPaths(this.attributes);
|
||||
for (var key in shallowAttributes) attrs[key] = void 0;
|
||||
return this.set(attrs, _.extend({}, options, {unset: true}));
|
||||
},
|
||||
|
||||
// Determine if the model has changed since the last `"change"` event.
|
||||
// If you specify an attribute name, determine if that attribute has changed.
|
||||
hasChanged: function(attr) {
|
||||
if (attr == null) return !_.isEmpty(this.changed);
|
||||
return getNested(this.changed, attr) !== undefined;
|
||||
},
|
||||
|
||||
// Return an object containing all the attributes that have changed, or
|
||||
// false if there are no changed attributes. Useful for determining what
|
||||
// parts of a view need to be updated and/or what attributes need to be
|
||||
// persisted to the server. Unset attributes will be set to undefined.
|
||||
// You can also pass an attributes object to diff against the model,
|
||||
// determining if there *would be* a change.
|
||||
changedAttributes: function(diff) {
|
||||
//<custom code>: objToPaths
|
||||
if (!diff) return this.hasChanged() ? objToPaths(this.changed) : false;
|
||||
//</custom code>
|
||||
|
||||
var old = this._changing ? this._previousAttributes : this.attributes;
|
||||
|
||||
//<custom code>
|
||||
diff = objToPaths(diff);
|
||||
old = objToPaths(old);
|
||||
//</custom code>
|
||||
|
||||
var val, changed = false;
|
||||
for (var attr in diff) {
|
||||
if (_.isEqual(old[attr], (val = diff[attr]))) continue;
|
||||
(changed || (changed = {}))[attr] = val;
|
||||
}
|
||||
return changed;
|
||||
},
|
||||
|
||||
// Get the previous value of an attribute, recorded at the time the last
|
||||
// `"change"` event was fired.
|
||||
previous: function(attr) {
|
||||
if (attr == null || !this._previousAttributes) return null;
|
||||
|
||||
//<custom code>
|
||||
return getNested(this._previousAttributes, attr);
|
||||
//</custom code>
|
||||
},
|
||||
|
||||
// Get all of the attributes of the model at the time of the previous
|
||||
// `"change"` event.
|
||||
previousAttributes: function() {
|
||||
//<custom code>
|
||||
return _.deepClone(this._previousAttributes);
|
||||
//</custom code>
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
//Config; override in your app to customise
|
||||
DeepModel.keyPathSeparator = '.';
|
||||
|
||||
|
||||
//Exports
|
||||
Backbone.DeepModel = DeepModel;
|
||||
|
||||
//For use in NodeJS
|
||||
if (typeof module != 'undefined') module.exports = DeepModel;
|
||||
|
||||
return Backbone;
|
||||
|
||||
}));
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,576 @@
|
||||
// Backbone.ModelBinder v1.0.2
|
||||
// (c) 2013 Bart Wood
|
||||
// Distributed Under MIT License
|
||||
|
||||
(function (factory) {
|
||||
if (typeof define === 'function' && define.amd) {
|
||||
// AMD. Register as an anonymous module.
|
||||
define(['underscore', 'jquery', 'backbone'], factory);
|
||||
} else {
|
||||
// Browser globals
|
||||
factory(_, $, Backbone);
|
||||
}
|
||||
}(function(_, $, Backbone){
|
||||
|
||||
if(!Backbone){
|
||||
throw 'Please include Backbone.js before Backbone.ModelBinder.js';
|
||||
}
|
||||
|
||||
Backbone.ModelBinder = function(){
|
||||
_.bindAll.apply(_, [this].concat(_.functions(this)));
|
||||
};
|
||||
|
||||
// Static setter for class level options
|
||||
Backbone.ModelBinder.SetOptions = function(options){
|
||||
Backbone.ModelBinder.options = options;
|
||||
};
|
||||
|
||||
// Current version of the library.
|
||||
Backbone.ModelBinder.VERSION = '1.0.2';
|
||||
Backbone.ModelBinder.Constants = {};
|
||||
Backbone.ModelBinder.Constants.ModelToView = 'ModelToView';
|
||||
Backbone.ModelBinder.Constants.ViewToModel = 'ViewToModel';
|
||||
|
||||
_.extend(Backbone.ModelBinder.prototype, {
|
||||
|
||||
bind:function (model, rootEl, attributeBindings, options) {
|
||||
this.unbind();
|
||||
|
||||
this._model = model;
|
||||
this._rootEl = rootEl;
|
||||
this._setOptions(options);
|
||||
|
||||
if (!this._model) this._throwException('model must be specified');
|
||||
if (!this._rootEl) this._throwException('rootEl must be specified');
|
||||
|
||||
if(attributeBindings){
|
||||
// Create a deep clone of the attribute bindings
|
||||
this._attributeBindings = $.extend(true, {}, attributeBindings);
|
||||
|
||||
this._initializeAttributeBindings();
|
||||
this._initializeElBindings();
|
||||
}
|
||||
else {
|
||||
this._initializeDefaultBindings();
|
||||
}
|
||||
|
||||
this._bindModelToView();
|
||||
this._bindViewToModel();
|
||||
},
|
||||
|
||||
bindCustomTriggers: function (model, rootEl, triggers, attributeBindings, modelSetOptions) {
|
||||
this._triggers = triggers;
|
||||
this.bind(model, rootEl, attributeBindings, modelSetOptions)
|
||||
},
|
||||
|
||||
unbind:function () {
|
||||
this._unbindModelToView();
|
||||
this._unbindViewToModel();
|
||||
|
||||
if(this._attributeBindings){
|
||||
delete this._attributeBindings;
|
||||
this._attributeBindings = undefined;
|
||||
}
|
||||
},
|
||||
|
||||
_setOptions: function(options){
|
||||
this._options = _.extend({
|
||||
boundAttribute: 'name'
|
||||
}, Backbone.ModelBinder.options, options);
|
||||
|
||||
// initialize default options
|
||||
if(!this._options['modelSetOptions']){
|
||||
this._options['modelSetOptions'] = {};
|
||||
}
|
||||
this._options['modelSetOptions'].changeSource = 'ModelBinder';
|
||||
|
||||
if(!this._options['changeTriggers']){
|
||||
this._options['changeTriggers'] = {'': 'change', '[contenteditable]': 'blur'};
|
||||
}
|
||||
|
||||
if(!this._options['initialCopyDirection']){
|
||||
this._options['initialCopyDirection'] = Backbone.ModelBinder.Constants.ModelToView;
|
||||
}
|
||||
},
|
||||
|
||||
// Converts the input bindings, which might just be empty or strings, to binding objects
|
||||
_initializeAttributeBindings:function () {
|
||||
var attributeBindingKey, inputBinding, attributeBinding, elementBindingCount, elementBinding;
|
||||
|
||||
for (attributeBindingKey in this._attributeBindings) {
|
||||
inputBinding = this._attributeBindings[attributeBindingKey];
|
||||
|
||||
if (_.isString(inputBinding)) {
|
||||
attributeBinding = {elementBindings: [{selector: inputBinding}]};
|
||||
}
|
||||
else if (_.isArray(inputBinding)) {
|
||||
attributeBinding = {elementBindings: inputBinding};
|
||||
}
|
||||
else if(_.isObject(inputBinding)){
|
||||
attributeBinding = {elementBindings: [inputBinding]};
|
||||
}
|
||||
else {
|
||||
this._throwException('Unsupported type passed to Model Binder ' + attributeBinding);
|
||||
}
|
||||
|
||||
// Add a linkage from the element binding back to the attribute binding
|
||||
for(elementBindingCount = 0; elementBindingCount < attributeBinding.elementBindings.length; elementBindingCount++){
|
||||
elementBinding = attributeBinding.elementBindings[elementBindingCount];
|
||||
elementBinding.attributeBinding = attributeBinding;
|
||||
}
|
||||
|
||||
attributeBinding.attributeName = attributeBindingKey;
|
||||
this._attributeBindings[attributeBindingKey] = attributeBinding;
|
||||
}
|
||||
},
|
||||
|
||||
// If the bindings are not specified, the default binding is performed on the specified attribute, name by default
|
||||
_initializeDefaultBindings: function(){
|
||||
var elCount, elsWithAttribute, matchedEl, name, attributeBinding;
|
||||
|
||||
this._attributeBindings = {};
|
||||
elsWithAttribute = $('[' + this._options['boundAttribute'] + ']', this._rootEl);
|
||||
|
||||
for(elCount = 0; elCount < elsWithAttribute.length; elCount++){
|
||||
matchedEl = elsWithAttribute[elCount];
|
||||
name = $(matchedEl).attr(this._options['boundAttribute']);
|
||||
|
||||
// For elements like radio buttons we only want a single attribute binding with possibly multiple element bindings
|
||||
if(!this._attributeBindings[name]){
|
||||
attributeBinding = {attributeName: name};
|
||||
attributeBinding.elementBindings = [{attributeBinding: attributeBinding, boundEls: [matchedEl]}];
|
||||
this._attributeBindings[name] = attributeBinding;
|
||||
}
|
||||
else{
|
||||
this._attributeBindings[name].elementBindings.push({attributeBinding: this._attributeBindings[name], boundEls: [matchedEl]});
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_initializeElBindings:function () {
|
||||
var bindingKey, attributeBinding, bindingCount, elementBinding, foundEls, elCount, el;
|
||||
for (bindingKey in this._attributeBindings) {
|
||||
attributeBinding = this._attributeBindings[bindingKey];
|
||||
|
||||
for (bindingCount = 0; bindingCount < attributeBinding.elementBindings.length; bindingCount++) {
|
||||
elementBinding = attributeBinding.elementBindings[bindingCount];
|
||||
if (elementBinding.selector === '') {
|
||||
foundEls = $(this._rootEl);
|
||||
}
|
||||
else {
|
||||
foundEls = $(elementBinding.selector, this._rootEl);
|
||||
}
|
||||
|
||||
if (foundEls.length === 0) {
|
||||
this._throwException('Bad binding found. No elements returned for binding selector ' + elementBinding.selector);
|
||||
}
|
||||
else {
|
||||
elementBinding.boundEls = [];
|
||||
for (elCount = 0; elCount < foundEls.length; elCount++) {
|
||||
el = foundEls[elCount];
|
||||
elementBinding.boundEls.push(el);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_bindModelToView: function () {
|
||||
this._model.on('change', this._onModelChange, this);
|
||||
|
||||
if(this._options['initialCopyDirection'] === Backbone.ModelBinder.Constants.ModelToView){
|
||||
this.copyModelAttributesToView();
|
||||
}
|
||||
},
|
||||
|
||||
// attributesToCopy is an optional parameter - if empty, all attributes
|
||||
// that are bound will be copied. Otherwise, only attributeBindings specified
|
||||
// in the attributesToCopy are copied.
|
||||
copyModelAttributesToView: function(attributesToCopy){
|
||||
var attributeName, attributeBinding;
|
||||
|
||||
for (attributeName in this._attributeBindings) {
|
||||
if(attributesToCopy === undefined || _.indexOf(attributesToCopy, attributeName) !== -1){
|
||||
attributeBinding = this._attributeBindings[attributeName];
|
||||
this._copyModelToView(attributeBinding);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
copyViewValuesToModel: function(){
|
||||
var bindingKey, attributeBinding, bindingCount, elementBinding, elCount, el;
|
||||
for (bindingKey in this._attributeBindings) {
|
||||
attributeBinding = this._attributeBindings[bindingKey];
|
||||
|
||||
for (bindingCount = 0; bindingCount < attributeBinding.elementBindings.length; bindingCount++) {
|
||||
elementBinding = attributeBinding.elementBindings[bindingCount];
|
||||
|
||||
if(this._isBindingUserEditable(elementBinding)){
|
||||
if(this._isBindingRadioGroup(elementBinding)){
|
||||
el = this._getRadioButtonGroupCheckedEl(elementBinding);
|
||||
if(el){
|
||||
this._copyViewToModel(elementBinding, el);
|
||||
}
|
||||
}
|
||||
else {
|
||||
for(elCount = 0; elCount < elementBinding.boundEls.length; elCount++){
|
||||
el = $(elementBinding.boundEls[elCount]);
|
||||
if(this._isElUserEditable(el)){
|
||||
this._copyViewToModel(elementBinding, el);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_unbindModelToView: function(){
|
||||
if(this._model){
|
||||
this._model.off('change', this._onModelChange);
|
||||
this._model = undefined;
|
||||
}
|
||||
},
|
||||
|
||||
_bindViewToModel: function () {
|
||||
_.each(this._options['changeTriggers'], function (event, selector) {
|
||||
$(this._rootEl).delegate(selector, event, this._onElChanged);
|
||||
}, this);
|
||||
|
||||
if(this._options['initialCopyDirection'] === Backbone.ModelBinder.Constants.ViewToModel){
|
||||
this.copyViewValuesToModel();
|
||||
}
|
||||
},
|
||||
|
||||
_unbindViewToModel: function () {
|
||||
if(this._options && this._options['changeTriggers']){
|
||||
_.each(this._options['changeTriggers'], function (event, selector) {
|
||||
$(this._rootEl).undelegate(selector, event, this._onElChanged);
|
||||
}, this);
|
||||
}
|
||||
},
|
||||
|
||||
_onElChanged:function (event) {
|
||||
var el, elBindings, elBindingCount, elBinding;
|
||||
|
||||
el = $(event.target)[0];
|
||||
elBindings = this._getElBindings(el);
|
||||
|
||||
for(elBindingCount = 0; elBindingCount < elBindings.length; elBindingCount++){
|
||||
elBinding = elBindings[elBindingCount];
|
||||
if (this._isBindingUserEditable(elBinding)) {
|
||||
this._copyViewToModel(elBinding, el);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_isBindingUserEditable: function(elBinding){
|
||||
return elBinding.elAttribute === undefined ||
|
||||
elBinding.elAttribute === 'text' ||
|
||||
elBinding.elAttribute === 'html';
|
||||
},
|
||||
|
||||
_isElUserEditable: function(el){
|
||||
var isContentEditable = el.attr('contenteditable');
|
||||
return isContentEditable || el.is('input') || el.is('select') || el.is('textarea');
|
||||
},
|
||||
|
||||
_isBindingRadioGroup: function(elBinding){
|
||||
var elCount, el;
|
||||
var isAllRadioButtons = elBinding.boundEls.length > 0;
|
||||
for(elCount = 0; elCount < elBinding.boundEls.length; elCount++){
|
||||
el = $(elBinding.boundEls[elCount]);
|
||||
if(el.attr('type') !== 'radio'){
|
||||
isAllRadioButtons = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return isAllRadioButtons;
|
||||
},
|
||||
|
||||
_getRadioButtonGroupCheckedEl: function(elBinding){
|
||||
var elCount, el;
|
||||
for(elCount = 0; elCount < elBinding.boundEls.length; elCount++){
|
||||
el = $(elBinding.boundEls[elCount]);
|
||||
if(el.attr('type') === 'radio' && el.attr('checked')){
|
||||
return el;
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
},
|
||||
|
||||
_getElBindings:function (findEl) {
|
||||
var attributeName, attributeBinding, elementBindingCount, elementBinding, boundElCount, boundEl;
|
||||
var elBindings = [];
|
||||
|
||||
for (attributeName in this._attributeBindings) {
|
||||
attributeBinding = this._attributeBindings[attributeName];
|
||||
|
||||
for (elementBindingCount = 0; elementBindingCount < attributeBinding.elementBindings.length; elementBindingCount++) {
|
||||
elementBinding = attributeBinding.elementBindings[elementBindingCount];
|
||||
|
||||
for (boundElCount = 0; boundElCount < elementBinding.boundEls.length; boundElCount++) {
|
||||
boundEl = elementBinding.boundEls[boundElCount];
|
||||
|
||||
if (boundEl === findEl) {
|
||||
elBindings.push(elementBinding);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return elBindings;
|
||||
},
|
||||
|
||||
_onModelChange:function () {
|
||||
var changedAttribute, attributeBinding;
|
||||
|
||||
for (changedAttribute in this._model.changedAttributes()) {
|
||||
attributeBinding = this._attributeBindings[changedAttribute];
|
||||
|
||||
if (attributeBinding) {
|
||||
this._copyModelToView(attributeBinding);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_copyModelToView:function (attributeBinding) {
|
||||
var elementBindingCount, elementBinding, boundElCount, boundEl, value, convertedValue;
|
||||
|
||||
value = this._model.get(attributeBinding.attributeName);
|
||||
|
||||
for (elementBindingCount = 0; elementBindingCount < attributeBinding.elementBindings.length; elementBindingCount++) {
|
||||
elementBinding = attributeBinding.elementBindings[elementBindingCount];
|
||||
|
||||
for (boundElCount = 0; boundElCount < elementBinding.boundEls.length; boundElCount++) {
|
||||
boundEl = elementBinding.boundEls[boundElCount];
|
||||
|
||||
if(!boundEl._isSetting){
|
||||
convertedValue = this._getConvertedValue(Backbone.ModelBinder.Constants.ModelToView, elementBinding, value);
|
||||
this._setEl($(boundEl), elementBinding, convertedValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_setEl: function (el, elementBinding, convertedValue) {
|
||||
if (elementBinding.elAttribute) {
|
||||
this._setElAttribute(el, elementBinding, convertedValue);
|
||||
}
|
||||
else {
|
||||
this._setElValue(el, convertedValue);
|
||||
}
|
||||
},
|
||||
|
||||
_setElAttribute:function (el, elementBinding, convertedValue) {
|
||||
switch (elementBinding.elAttribute) {
|
||||
case 'html':
|
||||
el.html(convertedValue);
|
||||
break;
|
||||
case 'text':
|
||||
el.text(convertedValue);
|
||||
break;
|
||||
case 'enabled':
|
||||
el.prop('disabled', !convertedValue);
|
||||
break;
|
||||
case 'displayed':
|
||||
el[convertedValue ? 'show' : 'hide']();
|
||||
break;
|
||||
case 'hidden':
|
||||
el[convertedValue ? 'hide' : 'show']();
|
||||
break;
|
||||
case 'css':
|
||||
el.css(elementBinding.cssAttribute, convertedValue);
|
||||
break;
|
||||
case 'class':
|
||||
var previousValue = this._model.previous(elementBinding.attributeBinding.attributeName);
|
||||
var currentValue = this._model.get(elementBinding.attributeBinding.attributeName);
|
||||
// is current value is now defined then remove the class the may have been set for the undefined value
|
||||
if(!_.isUndefined(previousValue) || !_.isUndefined(currentValue)){
|
||||
previousValue = this._getConvertedValue(Backbone.ModelBinder.Constants.ModelToView, elementBinding, previousValue);
|
||||
el.removeClass(previousValue);
|
||||
}
|
||||
|
||||
if(convertedValue){
|
||||
el.addClass(convertedValue);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
el.attr(elementBinding.elAttribute, convertedValue);
|
||||
}
|
||||
},
|
||||
|
||||
_setElValue:function (el, convertedValue) {
|
||||
if(el.attr('type')){
|
||||
switch (el.attr('type')) {
|
||||
case 'radio':
|
||||
if (el.val() === convertedValue) {
|
||||
// must defer the change trigger or the change will actually fire with the old value
|
||||
el.prop('checked') || _.defer(function() { el.trigger('change'); });
|
||||
el.prop('checked', true);
|
||||
}
|
||||
else {
|
||||
// must defer the change trigger or the change will actually fire with the old value
|
||||
el.prop('checked', false);
|
||||
}
|
||||
break;
|
||||
case 'checkbox':
|
||||
// must defer the change trigger or the change will actually fire with the old value
|
||||
el.prop('checked') === !!convertedValue || _.defer(function() { el.trigger('change') });
|
||||
el.prop('checked', !!convertedValue);
|
||||
break;
|
||||
case 'file':
|
||||
break;
|
||||
default:
|
||||
el.val(convertedValue);
|
||||
}
|
||||
}
|
||||
else if(el.is('input') || el.is('select') || el.is('textarea')){
|
||||
el.val(convertedValue || (convertedValue === 0 ? '0' : ''));
|
||||
}
|
||||
else {
|
||||
el.text(convertedValue || (convertedValue === 0 ? '0' : ''));
|
||||
}
|
||||
},
|
||||
|
||||
_copyViewToModel: function (elementBinding, el) {
|
||||
var result, value, convertedValue;
|
||||
|
||||
if (!el._isSetting) {
|
||||
|
||||
el._isSetting = true;
|
||||
result = this._setModel(elementBinding, $(el));
|
||||
el._isSetting = false;
|
||||
|
||||
if(result && elementBinding.converter){
|
||||
value = this._model.get(elementBinding.attributeBinding.attributeName);
|
||||
convertedValue = this._getConvertedValue(Backbone.ModelBinder.Constants.ModelToView, elementBinding, value);
|
||||
this._setEl($(el), elementBinding, convertedValue);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_getElValue: function(elementBinding, el){
|
||||
switch (el.attr('type')) {
|
||||
case 'checkbox':
|
||||
return el.prop('checked') ? true : false;
|
||||
default:
|
||||
if(el.attr('contenteditable') !== undefined){
|
||||
return el.html();
|
||||
}
|
||||
else {
|
||||
return el.val();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_setModel: function (elementBinding, el) {
|
||||
var data = {};
|
||||
var elVal = this._getElValue(elementBinding, el);
|
||||
elVal = this._getConvertedValue(Backbone.ModelBinder.Constants.ViewToModel, elementBinding, elVal);
|
||||
data[elementBinding.attributeBinding.attributeName] = elVal;
|
||||
return this._model.set(data, this._options['modelSetOptions']);
|
||||
},
|
||||
|
||||
_getConvertedValue: function (direction, elementBinding, value) {
|
||||
if (elementBinding.converter) {
|
||||
value = elementBinding.converter(direction, value, elementBinding.attributeBinding.attributeName, this._model, elementBinding.boundEls);
|
||||
}
|
||||
|
||||
return value;
|
||||
},
|
||||
|
||||
_throwException: function(message){
|
||||
if(this._options.suppressThrows){
|
||||
if(console && console.error){
|
||||
console.error(message);
|
||||
}
|
||||
}
|
||||
else {
|
||||
throw message;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Backbone.ModelBinder.CollectionConverter = function(collection){
|
||||
this._collection = collection;
|
||||
|
||||
if(!this._collection){
|
||||
throw 'Collection must be defined';
|
||||
}
|
||||
_.bindAll(this, 'convert');
|
||||
};
|
||||
|
||||
_.extend(Backbone.ModelBinder.CollectionConverter.prototype, {
|
||||
convert: function(direction, value){
|
||||
if (direction === Backbone.ModelBinder.Constants.ModelToView) {
|
||||
return value ? value.id : undefined;
|
||||
}
|
||||
else {
|
||||
return this._collection.get(value);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// A static helper function to create a default set of bindings that you can customize before calling the bind() function
|
||||
// rootEl - where to find all of the bound elements
|
||||
// attributeType - probably 'name' or 'id' in most cases
|
||||
// converter(optional) - the default converter you want applied to all your bindings
|
||||
// elAttribute(optional) - the default elAttribute you want applied to all your bindings
|
||||
Backbone.ModelBinder.createDefaultBindings = function(rootEl, attributeType, converter, elAttribute){
|
||||
var foundEls, elCount, foundEl, attributeName;
|
||||
var bindings = {};
|
||||
|
||||
foundEls = $('[' + attributeType + ']', rootEl);
|
||||
|
||||
for(elCount = 0; elCount < foundEls.length; elCount++){
|
||||
foundEl = foundEls[elCount];
|
||||
attributeName = $(foundEl).attr(attributeType);
|
||||
|
||||
if(!bindings[attributeName]){
|
||||
var attributeBinding = {selector: '[' + attributeType + '="' + attributeName + '"]'};
|
||||
bindings[attributeName] = attributeBinding;
|
||||
|
||||
if(converter){
|
||||
bindings[attributeName].converter = converter;
|
||||
}
|
||||
|
||||
if(elAttribute){
|
||||
bindings[attributeName].elAttribute = elAttribute;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return bindings;
|
||||
};
|
||||
|
||||
// Helps you to combine 2 sets of bindings
|
||||
Backbone.ModelBinder.combineBindings = function(destination, source){
|
||||
_.each(source, function(value, key){
|
||||
var elementBinding = {selector: value.selector};
|
||||
|
||||
if(value.converter){
|
||||
elementBinding.converter = value.converter;
|
||||
}
|
||||
|
||||
if(value.elAttribute){
|
||||
elementBinding.elAttribute = value.elAttribute;
|
||||
}
|
||||
|
||||
if(!destination[key]){
|
||||
destination[key] = elementBinding;
|
||||
}
|
||||
else {
|
||||
destination[key] = [destination[key], elementBinding];
|
||||
}
|
||||
});
|
||||
|
||||
return destination;
|
||||
};
|
||||
|
||||
|
||||
return Backbone.ModelBinder;
|
||||
|
||||
}));
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,40 @@
|
||||
(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");
|
||||
}
|
||||
else {
|
||||
method = callback;
|
||||
}
|
||||
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);
|
||||
@@ -0,0 +1,606 @@
|
||||
// Backbone.Validation v0.8.1
|
||||
//
|
||||
// Copyright (c) 2011-2013 Thomas Pedersen
|
||||
// Distributed under MIT License
|
||||
//
|
||||
// Documentation and full license available at:
|
||||
// http://thedersen.com/projects/backbone-validation
|
||||
Backbone.Validation = (function(_){
|
||||
'use strict';
|
||||
|
||||
// Default options
|
||||
// ---------------
|
||||
|
||||
var defaultOptions = {
|
||||
forceUpdate: false,
|
||||
selector: 'name',
|
||||
labelFormatter: 'sentenceCase',
|
||||
valid: Function.prototype,
|
||||
invalid: Function.prototype
|
||||
};
|
||||
|
||||
|
||||
// Helper functions
|
||||
// ----------------
|
||||
|
||||
// Formatting functions used for formatting error messages
|
||||
var formatFunctions = {
|
||||
// Uses the configured label formatter to format the attribute name
|
||||
// to make it more readable for the user
|
||||
formatLabel: function(attrName, model) {
|
||||
return defaultLabelFormatters[defaultOptions.labelFormatter](attrName, model);
|
||||
},
|
||||
|
||||
// Replaces nummeric placeholders like {0} in a string with arguments
|
||||
// passed to the function
|
||||
format: function() {
|
||||
var args = Array.prototype.slice.call(arguments),
|
||||
text = args.shift();
|
||||
return text.replace(/\{(\d+)\}/g, function(match, number) {
|
||||
return typeof args[number] !== 'undefined' ? args[number] : match;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// Flattens an object
|
||||
// eg:
|
||||
//
|
||||
// var o = {
|
||||
// address: {
|
||||
// street: 'Street',
|
||||
// zip: 1234
|
||||
// }
|
||||
// };
|
||||
//
|
||||
// becomes:
|
||||
//
|
||||
// var o = {
|
||||
// 'address.street': 'Street',
|
||||
// 'address.zip': 1234
|
||||
// };
|
||||
var flatten = function (obj, into, prefix) {
|
||||
into = into || {};
|
||||
prefix = prefix || '';
|
||||
|
||||
_.each(obj, function(val, key) {
|
||||
if(obj.hasOwnProperty(key)) {
|
||||
if (val && typeof val === 'object' && !(
|
||||
val instanceof Array ||
|
||||
val instanceof Date ||
|
||||
val instanceof RegExp ||
|
||||
val instanceof Backbone.Model ||
|
||||
val instanceof Backbone.Collection)
|
||||
) {
|
||||
flatten(val, into, prefix + key + '.');
|
||||
}
|
||||
else {
|
||||
into[prefix + key] = val;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return into;
|
||||
};
|
||||
|
||||
// Validation
|
||||
// ----------
|
||||
|
||||
var Validation = (function(){
|
||||
|
||||
// Returns an object with undefined properties for all
|
||||
// attributes on the model that has defined one or more
|
||||
// validation rules.
|
||||
var getValidatedAttrs = function(model) {
|
||||
return _.reduce(_.keys(model.validation || {}), function(memo, key) {
|
||||
memo[key] = void 0;
|
||||
return memo;
|
||||
}, {});
|
||||
};
|
||||
|
||||
// Looks on the model for validations for a specified
|
||||
// attribute. Returns an array of any validators defined,
|
||||
// or an empty array if none is defined.
|
||||
var getValidators = function(model, attr) {
|
||||
var attrValidationSet = model.validation ? model.validation[attr] || {} : {};
|
||||
|
||||
// If the validator is a function or a string, wrap it in a function validator
|
||||
if (_.isFunction(attrValidationSet) || _.isString(attrValidationSet)) {
|
||||
attrValidationSet = {
|
||||
fn: attrValidationSet
|
||||
};
|
||||
}
|
||||
|
||||
// Stick the validator object into an array
|
||||
if(!_.isArray(attrValidationSet)) {
|
||||
attrValidationSet = [attrValidationSet];
|
||||
}
|
||||
|
||||
// Reduces the array of validators into a new array with objects
|
||||
// with a validation method to call, the value to validate against
|
||||
// and the specified error message, if any
|
||||
return _.reduce(attrValidationSet, function(memo, attrValidation) {
|
||||
_.each(_.without(_.keys(attrValidation), 'msg'), function(validator) {
|
||||
memo.push({
|
||||
fn: defaultValidators[validator],
|
||||
val: attrValidation[validator],
|
||||
msg: attrValidation.msg
|
||||
});
|
||||
});
|
||||
return memo;
|
||||
}, []);
|
||||
};
|
||||
|
||||
// Validates an attribute against all validators defined
|
||||
// for that attribute. If one or more errors are found,
|
||||
// the first error message is returned.
|
||||
// If the attribute is valid, an empty string is returned.
|
||||
var validateAttr = function(model, attr, value, computed) {
|
||||
// Reduces the array of validators to an error message by
|
||||
// applying all the validators and returning the first error
|
||||
// message, if any.
|
||||
return _.reduce(getValidators(model, attr), function(memo, validator){
|
||||
// Pass the format functions plus the default
|
||||
// validators as the context to the validator
|
||||
var ctx = _.extend({}, formatFunctions, defaultValidators),
|
||||
result = validator.fn.call(ctx, value, attr, validator.val, model, computed);
|
||||
|
||||
if(result === false || memo === false) {
|
||||
return false;
|
||||
}
|
||||
if (result && !memo) {
|
||||
return validator.msg || result;
|
||||
}
|
||||
return memo;
|
||||
}, '');
|
||||
};
|
||||
|
||||
// Loops through the model's attributes and validates them all.
|
||||
// Returns and object containing names of invalid attributes
|
||||
// as well as error messages.
|
||||
var validateModel = function(model, attrs) {
|
||||
var error,
|
||||
invalidAttrs = {},
|
||||
isValid = true,
|
||||
computed = _.clone(attrs),
|
||||
flattened = flatten(attrs);
|
||||
|
||||
_.each(flattened, function(val, attr) {
|
||||
error = validateAttr(model, attr, val, computed);
|
||||
if (error) {
|
||||
invalidAttrs[attr] = error;
|
||||
isValid = false;
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
invalidAttrs: invalidAttrs,
|
||||
isValid: isValid
|
||||
};
|
||||
};
|
||||
|
||||
// Contains the methods that are mixed in on the model when binding
|
||||
var mixin = function(view, options) {
|
||||
return {
|
||||
|
||||
// Check whether or not a value passes validation
|
||||
// without updating the model
|
||||
preValidate: function(attr, value) {
|
||||
return validateAttr(this, attr, value, _.extend({}, this.attributes));
|
||||
},
|
||||
|
||||
// Check to see if an attribute, an array of attributes or the
|
||||
// entire model is valid. Passing true will force a validation
|
||||
// of the model.
|
||||
isValid: function(option) {
|
||||
var flattened = flatten(this.attributes);
|
||||
|
||||
if(_.isString(option)){
|
||||
return !validateAttr(this, option, flattened[option], _.extend({}, this.attributes));
|
||||
}
|
||||
if(_.isArray(option)){
|
||||
return _.reduce(option, function(memo, attr) {
|
||||
return memo && !validateAttr(this, attr, flattened[attr], _.extend({}, this.attributes));
|
||||
}, true, this);
|
||||
}
|
||||
if(option === true) {
|
||||
this.validate();
|
||||
}
|
||||
return this.validation ? this._isValid : true;
|
||||
},
|
||||
|
||||
// This is called by Backbone when it needs to perform validation.
|
||||
// You can call it manually without any parameters to validate the
|
||||
// entire model.
|
||||
validate: function(attrs, setOptions){
|
||||
var model = this,
|
||||
validateAll = !attrs,
|
||||
opt = _.extend({}, options, setOptions),
|
||||
validatedAttrs = getValidatedAttrs(model),
|
||||
allAttrs = _.extend({}, validatedAttrs, model.attributes, attrs),
|
||||
changedAttrs = flatten(attrs || allAttrs),
|
||||
|
||||
result = validateModel(model, allAttrs);
|
||||
|
||||
model._isValid = result.isValid;
|
||||
|
||||
// After validation is performed, loop through all changed attributes
|
||||
// and call the valid callbacks so the view is updated.
|
||||
_.each(validatedAttrs, function(val, attr){
|
||||
var invalid = result.invalidAttrs.hasOwnProperty(attr);
|
||||
if(!invalid){
|
||||
opt.valid(view, attr, opt.selector);
|
||||
}
|
||||
});
|
||||
|
||||
// After validation is performed, loop through all changed attributes
|
||||
// and call the invalid callback so the view is updated.
|
||||
_.each(validatedAttrs, function(val, attr){
|
||||
var invalid = result.invalidAttrs.hasOwnProperty(attr),
|
||||
changed = changedAttrs.hasOwnProperty(attr);
|
||||
|
||||
if(invalid && (changed || validateAll)){
|
||||
opt.invalid(view, attr, result.invalidAttrs[attr], opt.selector);
|
||||
}
|
||||
});
|
||||
|
||||
// Trigger validated events.
|
||||
// Need to defer this so the model is actually updated before
|
||||
// the event is triggered.
|
||||
_.defer(function() {
|
||||
model.trigger('validated', model._isValid, model, result.invalidAttrs);
|
||||
model.trigger('validated:' + (model._isValid ? 'valid' : 'invalid'), model, result.invalidAttrs);
|
||||
});
|
||||
|
||||
// Return any error messages to Backbone, unless the forceUpdate flag is set.
|
||||
// Then we do not return anything and fools Backbone to believe the validation was
|
||||
// a success. That way Backbone will update the model regardless.
|
||||
if (!opt.forceUpdate && _.intersection(_.keys(result.invalidAttrs), _.keys(changedAttrs)).length > 0) {
|
||||
return result.invalidAttrs;
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
// Helper to mix in validation on a model
|
||||
var bindModel = function(view, model, options) {
|
||||
_.extend(model, mixin(view, options));
|
||||
};
|
||||
|
||||
// Removes the methods added to a model
|
||||
var unbindModel = function(model) {
|
||||
delete model.validate;
|
||||
delete model.preValidate;
|
||||
delete model.isValid;
|
||||
};
|
||||
|
||||
// Mix in validation on a model whenever a model is
|
||||
// added to a collection
|
||||
var collectionAdd = function(model) {
|
||||
bindModel(this.view, model, this.options);
|
||||
};
|
||||
|
||||
// Remove validation from a model whenever a model is
|
||||
// removed from a collection
|
||||
var collectionRemove = function(model) {
|
||||
unbindModel(model);
|
||||
};
|
||||
|
||||
// Returns the public methods on Backbone.Validation
|
||||
return {
|
||||
|
||||
// Current version of the library
|
||||
version: '0.8.1',
|
||||
|
||||
// Called to configure the default options
|
||||
configure: function(options) {
|
||||
_.extend(defaultOptions, options);
|
||||
},
|
||||
|
||||
// Hooks up validation on a view with a model
|
||||
// or collection
|
||||
bind: function(view, options) {
|
||||
var model = view.model,
|
||||
collection = view.collection;
|
||||
|
||||
options = _.extend({}, defaultOptions, defaultCallbacks, options);
|
||||
|
||||
if(typeof model === 'undefined' && typeof collection === 'undefined'){
|
||||
throw 'Before you execute the binding your view must have a model or a collection.\n' +
|
||||
'See http://thedersen.com/projects/backbone-validation/#using-form-model-validation for more information.';
|
||||
}
|
||||
|
||||
if(model) {
|
||||
bindModel(view, model, options);
|
||||
}
|
||||
else if(collection) {
|
||||
collection.each(function(model){
|
||||
bindModel(view, model, options);
|
||||
});
|
||||
collection.bind('add', collectionAdd, {view: view, options: options});
|
||||
collection.bind('remove', collectionRemove);
|
||||
}
|
||||
},
|
||||
|
||||
// Removes validation from a view with a model
|
||||
// or collection
|
||||
unbind: function(view) {
|
||||
var model = view.model,
|
||||
collection = view.collection;
|
||||
|
||||
if(model) {
|
||||
unbindModel(view.model);
|
||||
}
|
||||
if(collection) {
|
||||
collection.each(function(model){
|
||||
unbindModel(model);
|
||||
});
|
||||
collection.unbind('add', collectionAdd);
|
||||
collection.unbind('remove', collectionRemove);
|
||||
}
|
||||
},
|
||||
|
||||
// Used to extend the Backbone.Model.prototype
|
||||
// with validation
|
||||
mixin: mixin(null, defaultOptions)
|
||||
};
|
||||
}());
|
||||
|
||||
|
||||
// Callbacks
|
||||
// ---------
|
||||
|
||||
var defaultCallbacks = Validation.callbacks = {
|
||||
|
||||
// Gets called when a previously invalid field in the
|
||||
// view becomes valid. Removes any error message.
|
||||
// Should be overridden with custom functionality.
|
||||
valid: function(view, attr, selector) {
|
||||
view.$('[' + selector + '~="' + attr + '"]')
|
||||
.removeClass('invalid')
|
||||
.removeAttr('data-error');
|
||||
},
|
||||
|
||||
// Gets called when a field in the view becomes invalid.
|
||||
// Adds a error message.
|
||||
// Should be overridden with custom functionality.
|
||||
invalid: function(view, attr, error, selector) {
|
||||
view.$('[' + selector + '~="' + attr + '"]')
|
||||
.addClass('invalid')
|
||||
.attr('data-error', error);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// Patterns
|
||||
// --------
|
||||
|
||||
var defaultPatterns = Validation.patterns = {
|
||||
// Matches any digit(s) (i.e. 0-9)
|
||||
digits: /^\d+$/,
|
||||
|
||||
// Matched any number (e.g. 100.000)
|
||||
number: /^-?(?:\d+|\d{1,3}(?:,\d{3})+)(?:\.\d+)?$/,
|
||||
|
||||
// Matches a valid email address (e.g. mail@example.com)
|
||||
email: /^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))$/i,
|
||||
|
||||
// Mathes any valid url (e.g. http://www.xample.com)
|
||||
url: /^(https?|ftp):\/\/(((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:)*@)?(((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]))|((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?)(:\d*)?)(\/((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/(([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)?)?(\?((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|[\uE000-\uF8FF]|\/|\?)*)?(\#((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|\/|\?)*)?$/i
|
||||
};
|
||||
|
||||
|
||||
// Error messages
|
||||
// --------------
|
||||
|
||||
// Error message for the build in validators.
|
||||
// {x} gets swapped out with arguments form the validator.
|
||||
var defaultMessages = Validation.messages = {
|
||||
required: '{0} is required',
|
||||
acceptance: '{0} must be accepted',
|
||||
min: '{0} must be greater than or equal to {1}',
|
||||
max: '{0} must be less than or equal to {1}',
|
||||
range: '{0} must be between {1} and {2}',
|
||||
length: '{0} must be {1} characters',
|
||||
minLength: '{0} must be at least {1} characters',
|
||||
maxLength: '{0} must be at most {1} characters',
|
||||
rangeLength: '{0} must be between {1} and {2} characters',
|
||||
oneOf: '{0} must be one of: {1}',
|
||||
equalTo: '{0} must be the same as {1}',
|
||||
pattern: '{0} must be a valid {1}'
|
||||
};
|
||||
|
||||
// Label formatters
|
||||
// ----------------
|
||||
|
||||
// Label formatters are used to convert the attribute name
|
||||
// to a more human friendly label when using the built in
|
||||
// error messages.
|
||||
// Configure which one to use with a call to
|
||||
//
|
||||
// Backbone.Validation.configure({
|
||||
// labelFormatter: 'label'
|
||||
// });
|
||||
var defaultLabelFormatters = Validation.labelFormatters = {
|
||||
|
||||
// Returns the attribute name with applying any formatting
|
||||
none: function(attrName) {
|
||||
return attrName;
|
||||
},
|
||||
|
||||
// Converts attributeName or attribute_name to Attribute name
|
||||
sentenceCase: function(attrName) {
|
||||
return attrName.replace(/(?:^\w|[A-Z]|\b\w)/g, function(match, index) {
|
||||
return index === 0 ? match.toUpperCase() : ' ' + match.toLowerCase();
|
||||
}).replace(/_/g, ' ');
|
||||
},
|
||||
|
||||
// Looks for a label configured on the model and returns it
|
||||
//
|
||||
// var Model = Backbone.Model.extend({
|
||||
// validation: {
|
||||
// someAttribute: {
|
||||
// required: true
|
||||
// }
|
||||
// },
|
||||
//
|
||||
// labels: {
|
||||
// someAttribute: 'Custom label'
|
||||
// }
|
||||
// });
|
||||
label: function(attrName, model) {
|
||||
return (model.labels && model.labels[attrName]) || defaultLabelFormatters.sentenceCase(attrName, model);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// Built in validators
|
||||
// -------------------
|
||||
|
||||
var defaultValidators = Validation.validators = (function(){
|
||||
// Use native trim when defined
|
||||
var trim = String.prototype.trim ?
|
||||
function(text) {
|
||||
return text === null ? '' : String.prototype.trim.call(text);
|
||||
} :
|
||||
function(text) {
|
||||
var trimLeft = /^\s+/,
|
||||
trimRight = /\s+$/;
|
||||
|
||||
return text === null ? '' : text.toString().replace(trimLeft, '').replace(trimRight, '');
|
||||
};
|
||||
|
||||
// Determines whether or not a value is a number
|
||||
var isNumber = function(value){
|
||||
return _.isNumber(value) || (_.isString(value) && value.match(defaultPatterns.number));
|
||||
};
|
||||
|
||||
// Determines whether or not a value is empty
|
||||
var hasValue = function(value) {
|
||||
return !(_.isNull(value) || _.isUndefined(value) || (_.isString(value) && trim(value) === '') || (_.isArray(value) && _.isEmpty(value)));
|
||||
};
|
||||
|
||||
return {
|
||||
// Function validator
|
||||
// Lets you implement a custom function used for validation
|
||||
fn: function(value, attr, fn, model, computed) {
|
||||
if(_.isString(fn)){
|
||||
fn = model[fn];
|
||||
}
|
||||
return fn.call(model, value, attr, computed);
|
||||
},
|
||||
|
||||
// Required validator
|
||||
// Validates if the attribute is required or not
|
||||
required: function(value, attr, required, model, computed) {
|
||||
var isRequired = _.isFunction(required) ? required.call(model, value, attr, computed) : required;
|
||||
if(!isRequired && !hasValue(value)) {
|
||||
return false; // overrides all other validators
|
||||
}
|
||||
if (isRequired && !hasValue(value)) {
|
||||
return this.format(defaultMessages.required, this.formatLabel(attr, model));
|
||||
}
|
||||
},
|
||||
|
||||
// Acceptance validator
|
||||
// Validates that something has to be accepted, e.g. terms of use
|
||||
// `true` or 'true' are valid
|
||||
acceptance: function(value, attr, accept, model) {
|
||||
if(value !== 'true' && (!_.isBoolean(value) || value === false)) {
|
||||
return this.format(defaultMessages.acceptance, this.formatLabel(attr, model));
|
||||
}
|
||||
},
|
||||
|
||||
// Min validator
|
||||
// Validates that the value has to be a number and equal to or greater than
|
||||
// the min value specified
|
||||
min: function(value, attr, minValue, model) {
|
||||
if (!isNumber(value) || value < minValue) {
|
||||
return this.format(defaultMessages.min, this.formatLabel(attr, model), minValue);
|
||||
}
|
||||
},
|
||||
|
||||
// Max validator
|
||||
// Validates that the value has to be a number and equal to or less than
|
||||
// the max value specified
|
||||
max: function(value, attr, maxValue, model) {
|
||||
if (!isNumber(value) || value > maxValue) {
|
||||
return this.format(defaultMessages.max, this.formatLabel(attr, model), maxValue);
|
||||
}
|
||||
},
|
||||
|
||||
// Range validator
|
||||
// Validates that the value has to be a number and equal to or between
|
||||
// the two numbers specified
|
||||
range: function(value, attr, range, model) {
|
||||
if(!isNumber(value) || value < range[0] || value > range[1]) {
|
||||
return this.format(defaultMessages.range, this.formatLabel(attr, model), range[0], range[1]);
|
||||
}
|
||||
},
|
||||
|
||||
// Length validator
|
||||
// Validates that the value has to be a string with length equal to
|
||||
// the length value specified
|
||||
length: function(value, attr, length, model) {
|
||||
if (!hasValue(value) || trim(value).length !== length) {
|
||||
return this.format(defaultMessages.length, this.formatLabel(attr, model), length);
|
||||
}
|
||||
},
|
||||
|
||||
// Min length validator
|
||||
// Validates that the value has to be a string with length equal to or greater than
|
||||
// the min length value specified
|
||||
minLength: function(value, attr, minLength, model) {
|
||||
if (!hasValue(value) || trim(value).length < minLength) {
|
||||
return this.format(defaultMessages.minLength, this.formatLabel(attr, model), minLength);
|
||||
}
|
||||
},
|
||||
|
||||
// Max length validator
|
||||
// Validates that the value has to be a string with length equal to or less than
|
||||
// the max length value specified
|
||||
maxLength: function(value, attr, maxLength, model) {
|
||||
if (!hasValue(value) || trim(value).length > maxLength) {
|
||||
return this.format(defaultMessages.maxLength, this.formatLabel(attr, model), maxLength);
|
||||
}
|
||||
},
|
||||
|
||||
// Range length validator
|
||||
// Validates that the value has to be a string and equal to or between
|
||||
// the two numbers specified
|
||||
rangeLength: function(value, attr, range, model) {
|
||||
if(!hasValue(value) || trim(value).length < range[0] || trim(value).length > range[1]) {
|
||||
return this.format(defaultMessages.rangeLength, this.formatLabel(attr, model), range[0], range[1]);
|
||||
}
|
||||
},
|
||||
|
||||
// One of validator
|
||||
// Validates that the value has to be equal to one of the elements in
|
||||
// the specified array. Case sensitive matching
|
||||
oneOf: function(value, attr, values, model) {
|
||||
if(!_.include(values, value)){
|
||||
return this.format(defaultMessages.oneOf, this.formatLabel(attr, model), values.join(', '));
|
||||
}
|
||||
},
|
||||
|
||||
// Equal to validator
|
||||
// Validates that the value has to be equal to the value of the attribute
|
||||
// with the name specified
|
||||
equalTo: function(value, attr, equalTo, model, computed) {
|
||||
if(value !== computed[equalTo]) {
|
||||
return this.format(defaultMessages.equalTo, this.formatLabel(attr, model), this.formatLabel(equalTo, model));
|
||||
}
|
||||
},
|
||||
|
||||
// Pattern validator
|
||||
// Validates that the value has to match the pattern specified.
|
||||
// Can be a regular expression or the name of one of the built in patterns
|
||||
pattern: function(value, attr, pattern, model) {
|
||||
if (!hasValue(value) || !value.toString().match(defaultPatterns[pattern] || pattern)) {
|
||||
return this.format(defaultMessages.pattern, this.formatLabel(attr, model), pattern);
|
||||
}
|
||||
}
|
||||
};
|
||||
}());
|
||||
|
||||
return Validation;
|
||||
}(_));
|
||||
Vendored
+2280
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,153 @@
|
||||
/**
|
||||
* filesize
|
||||
*
|
||||
* @author Jason Mulligan <jason.mulligan@avoidwork.com>
|
||||
* @copyright 2013 Jason Mulligan
|
||||
* @license BSD-3 <https://raw.github.com/avoidwork/filesize.js/master/LICENSE>
|
||||
* @link http://filesizejs.com
|
||||
* @module filesize
|
||||
* @version 1.10.0
|
||||
*/
|
||||
( function ( global ) {
|
||||
"use strict";
|
||||
|
||||
var base = 10,
|
||||
right = /\.(.*)/,
|
||||
bit = /b$/,
|
||||
bite = /^B$/,
|
||||
zero = /^0$/,
|
||||
options;
|
||||
|
||||
options = {
|
||||
all : {
|
||||
increments : [["B", 1], ["kb", 125], ["kB", 1000], ["Mb", 125000], ["MB", 1000000], ["Gb", 125000000], ["GB", 1000000000], ["Tb", 125000000000], ["TB", 1000000000000], ["Pb", 125000000000000], ["PB", 1000000000000000]],
|
||||
nth : 11
|
||||
},
|
||||
bitless : {
|
||||
increments : [["B", 1], ["kB", 1000], ["MB", 1000000], ["GB", 1000000000], ["TB", 1000000000000], ["PB", 1000000000000000]],
|
||||
nth : 6
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* filesize
|
||||
*
|
||||
* @param {Mixed} arg String, Int or Float to transform
|
||||
* @param {Mixed} pos [Optional] Position to round to, defaults to 2 if shrt is ommitted, or `true` for shrthand output
|
||||
* @param {Boolean} bits [Optional] Determines if `bit` sizes are used for result calculation, default is true
|
||||
* @return {String} Readable file size String
|
||||
*/
|
||||
function filesize ( arg) {
|
||||
var result = "",
|
||||
bits = true,
|
||||
skip = false,
|
||||
i, neg, num, pos, shrt, size, sizes, suffix, z;
|
||||
|
||||
// Determining arguments
|
||||
if (arguments[3] !== undefined) {
|
||||
pos = arguments[1];
|
||||
shrt = arguments[2];
|
||||
bits = arguments[3];
|
||||
}
|
||||
else {
|
||||
typeof arguments[1] === "boolean" ? shrt = arguments[1] : pos = arguments[1];
|
||||
|
||||
if ( typeof arguments[2] === "boolean" ) {
|
||||
bits = arguments[2];
|
||||
}
|
||||
}
|
||||
|
||||
if ( isNaN( arg ) || ( pos !== undefined && isNaN( pos ) ) ) {
|
||||
throw new Error("Invalid arguments");
|
||||
}
|
||||
|
||||
shrt = ( shrt === true );
|
||||
bits = ( bits === true );
|
||||
pos = shrt ? 1 : ( pos === undefined ? 2 : parseInt( pos, base ) );
|
||||
num = Number( arg );
|
||||
neg = ( num < 0 );
|
||||
|
||||
// Flipping a negative number to determine the size
|
||||
if ( neg ) {
|
||||
num = -num;
|
||||
}
|
||||
|
||||
// Zero is now a special case because bytes divide by 1
|
||||
if ( num === 0 ) {
|
||||
if ( shrt ) {
|
||||
result = "0";
|
||||
}
|
||||
else {
|
||||
result = "0 B";
|
||||
}
|
||||
}
|
||||
else {
|
||||
if ( bits ) {
|
||||
sizes = options.all.increments;
|
||||
i = options.all.nth;
|
||||
}
|
||||
else {
|
||||
sizes = options.bitless.increments;
|
||||
i = options.bitless.nth;
|
||||
}
|
||||
|
||||
while ( i-- ) {
|
||||
size = sizes[i][1];
|
||||
suffix = sizes[i][0];
|
||||
|
||||
if ( num >= size ) {
|
||||
// Treating bytes as cardinal
|
||||
if ( bite.test( suffix ) ) {
|
||||
skip = true;
|
||||
pos = 0;
|
||||
}
|
||||
|
||||
result = ( num / size ).toFixed( pos );
|
||||
|
||||
if ( !skip && shrt ) {
|
||||
if ( bits && bit.test( suffix ) ) {
|
||||
suffix = suffix.toLowerCase();
|
||||
}
|
||||
|
||||
suffix = suffix.charAt( 0 );
|
||||
z = right.exec( result );
|
||||
|
||||
if ( suffix === "k" ) {
|
||||
suffix = "K";
|
||||
}
|
||||
|
||||
if ( z !== null && z[1] !== undefined && zero.test( z[1] ) ) {
|
||||
result = parseInt( result, base );
|
||||
}
|
||||
|
||||
result += suffix;
|
||||
}
|
||||
else if ( !shrt ) {
|
||||
result += " " + suffix;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Decorating a 'diff'
|
||||
if ( neg ) {
|
||||
result = "-" + result;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// CommonJS, AMD, script tag
|
||||
if ( typeof exports !== "undefined" ) {
|
||||
module.exports = filesize;
|
||||
}
|
||||
else if ( typeof define === "function" ) {
|
||||
define( function () {
|
||||
return filesize;
|
||||
});
|
||||
}
|
||||
else {
|
||||
global.filesize = filesize;
|
||||
}
|
||||
})( this );
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,145 @@
|
||||
/* Handlebars Helpers - Dan Harper (http://github.com/danharper) */
|
||||
|
||||
/* This program is free software. It comes without any warranty, to
|
||||
* the extent permitted by applicable law. You can redistribute it
|
||||
* and/or modify it under the terms of the Do What The Fuck You Want
|
||||
* To Public License, Version 2, as published by Sam Hocevar. See
|
||||
* http://sam.zoy.org/wtfpl/COPYING for more details. */
|
||||
|
||||
/**
|
||||
* Following lines make Handlebars helper function to work with all
|
||||
* three such as Direct web, RequireJS AMD and Node JS.
|
||||
* This concepts derived from UMD.
|
||||
* @courtesy - https://github.com/umdjs/umd/blob/master/returnExports.js
|
||||
*/
|
||||
|
||||
(function (root, factory) {
|
||||
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('handlebars'));
|
||||
} else if (typeof define === 'function' && define.amd) {
|
||||
// AMD. Register as an anonymous module.
|
||||
define(['handlebars'], factory);
|
||||
} else {
|
||||
// Browser globals (root is window)
|
||||
root.returnExports = factory(root.Handlebars);
|
||||
}
|
||||
}(this, function (Handlebars) {
|
||||
|
||||
/**
|
||||
* If Equals
|
||||
* if_eq this compare=that
|
||||
*/
|
||||
Handlebars.registerHelper('if_eq', function(context, options) {
|
||||
if (context == options.hash.compare)
|
||||
return options.fn(this);
|
||||
return options.inverse(this);
|
||||
});
|
||||
|
||||
/**
|
||||
* Unless Equals
|
||||
* unless_eq this compare=that
|
||||
*/
|
||||
Handlebars.registerHelper('unless_eq', function(context, options) {
|
||||
if (context == options.hash.compare)
|
||||
return options.inverse(this);
|
||||
return options.fn(this);
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* If Greater Than
|
||||
* if_gt this compare=that
|
||||
*/
|
||||
Handlebars.registerHelper('if_gt', function(context, options) {
|
||||
if (context > options.hash.compare)
|
||||
return options.fn(this);
|
||||
return options.inverse(this);
|
||||
});
|
||||
|
||||
/**
|
||||
* Unless Greater Than
|
||||
* unless_gt this compare=that
|
||||
*/
|
||||
Handlebars.registerHelper('unless_gt', function(context, options) {
|
||||
if (context > options.hash.compare)
|
||||
return options.inverse(this);
|
||||
return options.fn(this);
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* If Less Than
|
||||
* if_lt this compare=that
|
||||
*/
|
||||
Handlebars.registerHelper('if_lt', function(context, options) {
|
||||
if (context < options.hash.compare)
|
||||
return options.fn(this);
|
||||
return options.inverse(this);
|
||||
});
|
||||
|
||||
/**
|
||||
* Unless Less Than
|
||||
* unless_lt this compare=that
|
||||
*/
|
||||
Handlebars.registerHelper('unless_lt', function(context, options) {
|
||||
if (context < options.hash.compare)
|
||||
return options.inverse(this);
|
||||
return options.fn(this);
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* If Greater Than or Equal To
|
||||
* if_gteq this compare=that
|
||||
*/
|
||||
Handlebars.registerHelper('if_gteq', function(context, options) {
|
||||
if (context >= options.hash.compare)
|
||||
return options.fn(this);
|
||||
return options.inverse(this);
|
||||
});
|
||||
|
||||
/**
|
||||
* Unless Greater Than or Equal To
|
||||
* unless_gteq this compare=that
|
||||
*/
|
||||
Handlebars.registerHelper('unless_gteq', function(context, options) {
|
||||
if (context >= options.hash.compare)
|
||||
return options.inverse(this);
|
||||
return options.fn(this);
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* If Less Than or Equal To
|
||||
* if_lteq this compare=that
|
||||
*/
|
||||
Handlebars.registerHelper('if_lteq', function(context, options) {
|
||||
if (context <= options.hash.compare)
|
||||
return options.fn(this);
|
||||
return options.inverse(this);
|
||||
});
|
||||
|
||||
/**
|
||||
* Unless Less Than or Equal To
|
||||
* unless_lteq this compare=that
|
||||
*/
|
||||
Handlebars.registerHelper('unless_lteq', function(context, options) {
|
||||
if (context <= options.hash.compare)
|
||||
return options.inverse(this);
|
||||
return options.fn(this);
|
||||
});
|
||||
|
||||
/**
|
||||
* Convert new line (\n\r) to <br>
|
||||
* from http://phpjs.org/functions/nl2br:480
|
||||
*/
|
||||
Handlebars.registerHelper('nl2br', function(text) {
|
||||
text = Handlebars.Utils.escapeExpression(text);
|
||||
var nl2br = (text + '').replace(/([^>\r\n]?)(\r\n|\n\r|\r|\n)/g, '$1' + '<br>' + '$2');
|
||||
return new Handlebars.SafeString(nl2br);
|
||||
});
|
||||
|
||||
}));
|
||||
@@ -0,0 +1,374 @@
|
||||
/*
|
||||
|
||||
Copyright (C) 2011 by Yehuda Katz
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
@license
|
||||
*/
|
||||
|
||||
// lib/handlebars/browser-prefix.js
|
||||
(function(undefined) {
|
||||
var Handlebars = {};
|
||||
;
|
||||
// lib/handlebars/base.js
|
||||
|
||||
Handlebars.VERSION = "1.0.0";
|
||||
Handlebars.COMPILER_REVISION = 4;
|
||||
|
||||
Handlebars.REVISION_CHANGES = {
|
||||
1: '<= 1.0.rc.2', // 1.0.rc.2 is actually rev2 but doesn't report it
|
||||
2: '== 1.0.0-rc.3',
|
||||
3: '== 1.0.0-rc.4',
|
||||
4: '>= 1.0.0'
|
||||
};
|
||||
|
||||
Handlebars.helpers = {};
|
||||
Handlebars.partials = {};
|
||||
|
||||
var toString = Object.prototype.toString,
|
||||
functionType = '[object Function]',
|
||||
objectType = '[object Object]';
|
||||
|
||||
Handlebars.registerHelper = function(name, fn, inverse) {
|
||||
if (toString.call(name) === objectType) {
|
||||
if (inverse || fn) { throw new Handlebars.Exception('Arg not supported with multiple helpers'); }
|
||||
Handlebars.Utils.extend(this.helpers, name);
|
||||
} else {
|
||||
if (inverse) { fn.not = inverse; }
|
||||
this.helpers[name] = fn;
|
||||
}
|
||||
};
|
||||
|
||||
Handlebars.registerPartial = function(name, str) {
|
||||
if (toString.call(name) === objectType) {
|
||||
Handlebars.Utils.extend(this.partials, name);
|
||||
} else {
|
||||
this.partials[name] = str;
|
||||
}
|
||||
};
|
||||
|
||||
Handlebars.registerHelper('helperMissing', function(arg) {
|
||||
if(arguments.length === 2) {
|
||||
return undefined;
|
||||
} else {
|
||||
throw new Error("Missing helper: '" + arg + "'");
|
||||
}
|
||||
});
|
||||
|
||||
Handlebars.registerHelper('blockHelperMissing', function(context, options) {
|
||||
var inverse = options.inverse || function() {}, fn = options.fn;
|
||||
|
||||
var type = toString.call(context);
|
||||
|
||||
if(type === functionType) { context = context.call(this); }
|
||||
|
||||
if(context === true) {
|
||||
return fn(this);
|
||||
} else if(context === false || context == null) {
|
||||
return inverse(this);
|
||||
} else if(type === "[object Array]") {
|
||||
if(context.length > 0) {
|
||||
return Handlebars.helpers.each(context, options);
|
||||
} else {
|
||||
return inverse(this);
|
||||
}
|
||||
} else {
|
||||
return fn(context);
|
||||
}
|
||||
});
|
||||
|
||||
Handlebars.K = function() {};
|
||||
|
||||
Handlebars.createFrame = Object.create || function(object) {
|
||||
Handlebars.K.prototype = object;
|
||||
var obj = new Handlebars.K();
|
||||
Handlebars.K.prototype = null;
|
||||
return obj;
|
||||
};
|
||||
|
||||
Handlebars.logger = {
|
||||
DEBUG: 0, INFO: 1, WARN: 2, ERROR: 3, level: 3,
|
||||
|
||||
methodMap: {0: 'debug', 1: 'info', 2: 'warn', 3: 'error'},
|
||||
|
||||
// can be overridden in the host environment
|
||||
log: function(level, obj) {
|
||||
if (Handlebars.logger.level <= level) {
|
||||
var method = Handlebars.logger.methodMap[level];
|
||||
if (typeof console !== 'undefined' && console[method]) {
|
||||
console[method].call(console, obj);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Handlebars.log = function(level, obj) { Handlebars.logger.log(level, obj); };
|
||||
|
||||
Handlebars.registerHelper('each', function(context, options) {
|
||||
var fn = options.fn, inverse = options.inverse;
|
||||
var i = 0, ret = "", data;
|
||||
|
||||
var type = toString.call(context);
|
||||
if(type === functionType) { context = context.call(this); }
|
||||
|
||||
if (options.data) {
|
||||
data = Handlebars.createFrame(options.data);
|
||||
}
|
||||
|
||||
if(context && typeof context === 'object') {
|
||||
if(context instanceof Array){
|
||||
for(var j = context.length; i<j; i++) {
|
||||
if (data) { data.index = i; }
|
||||
ret = ret + fn(context[i], { data: data });
|
||||
}
|
||||
} else {
|
||||
for(var key in context) {
|
||||
if(context.hasOwnProperty(key)) {
|
||||
if(data) { data.key = key; }
|
||||
ret = ret + fn(context[key], {data: data});
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(i === 0){
|
||||
ret = inverse(this);
|
||||
}
|
||||
|
||||
return ret;
|
||||
});
|
||||
|
||||
Handlebars.registerHelper('if', function(conditional, options) {
|
||||
var type = toString.call(conditional);
|
||||
if(type === functionType) { conditional = conditional.call(this); }
|
||||
|
||||
if(!conditional || Handlebars.Utils.isEmpty(conditional)) {
|
||||
return options.inverse(this);
|
||||
} else {
|
||||
return options.fn(this);
|
||||
}
|
||||
});
|
||||
|
||||
Handlebars.registerHelper('unless', function(conditional, options) {
|
||||
return Handlebars.helpers['if'].call(this, conditional, {fn: options.inverse, inverse: options.fn});
|
||||
});
|
||||
|
||||
Handlebars.registerHelper('with', function(context, options) {
|
||||
var type = toString.call(context);
|
||||
if(type === functionType) { context = context.call(this); }
|
||||
|
||||
if (!Handlebars.Utils.isEmpty(context)) return options.fn(context);
|
||||
});
|
||||
|
||||
Handlebars.registerHelper('log', function(context, options) {
|
||||
var level = options.data && options.data.level != null ? parseInt(options.data.level, 10) : 1;
|
||||
Handlebars.log(level, context);
|
||||
});
|
||||
;
|
||||
// lib/handlebars/utils.js
|
||||
|
||||
var errorProps = ['description', 'fileName', 'lineNumber', 'message', 'name', 'number', 'stack'];
|
||||
|
||||
Handlebars.Exception = function(message) {
|
||||
var tmp = Error.prototype.constructor.apply(this, arguments);
|
||||
|
||||
// Unfortunately errors are not enumerable in Chrome (at least), so `for prop in tmp` doesn't work.
|
||||
for (var idx = 0; idx < errorProps.length; idx++) {
|
||||
this[errorProps[idx]] = tmp[errorProps[idx]];
|
||||
}
|
||||
};
|
||||
Handlebars.Exception.prototype = new Error();
|
||||
|
||||
// Build out our basic SafeString type
|
||||
Handlebars.SafeString = function(string) {
|
||||
this.string = string;
|
||||
};
|
||||
Handlebars.SafeString.prototype.toString = function() {
|
||||
return this.string.toString();
|
||||
};
|
||||
|
||||
var escape = {
|
||||
"&": "&",
|
||||
"<": "<",
|
||||
">": ">",
|
||||
'"': """,
|
||||
"'": "'",
|
||||
"`": "`"
|
||||
};
|
||||
|
||||
var badChars = /[&<>"'`]/g;
|
||||
var possible = /[&<>"'`]/;
|
||||
|
||||
var escapeChar = function(chr) {
|
||||
return escape[chr] || "&";
|
||||
};
|
||||
|
||||
Handlebars.Utils = {
|
||||
extend: function(obj, value) {
|
||||
for(var key in value) {
|
||||
if(value.hasOwnProperty(key)) {
|
||||
obj[key] = value[key];
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
escapeExpression: function(string) {
|
||||
// don't escape SafeStrings, since they're already safe
|
||||
if (string instanceof Handlebars.SafeString) {
|
||||
return string.toString();
|
||||
} else if (string == null || string === false) {
|
||||
return "";
|
||||
}
|
||||
|
||||
// Force a string conversion as this will be done by the append regardless and
|
||||
// the regex test will do this transparently behind the scenes, causing issues if
|
||||
// an object's to string has escaped characters in it.
|
||||
string = string.toString();
|
||||
|
||||
if(!possible.test(string)) { return string; }
|
||||
return string.replace(badChars, escapeChar);
|
||||
},
|
||||
|
||||
isEmpty: function(value) {
|
||||
if (!value && value !== 0) {
|
||||
return true;
|
||||
} else if(toString.call(value) === "[object Array]" && value.length === 0) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
;
|
||||
// lib/handlebars/runtime.js
|
||||
|
||||
Handlebars.VM = {
|
||||
template: function(templateSpec) {
|
||||
// Just add water
|
||||
var container = {
|
||||
escapeExpression: Handlebars.Utils.escapeExpression,
|
||||
invokePartial: Handlebars.VM.invokePartial,
|
||||
programs: [],
|
||||
program: function(i, fn, data) {
|
||||
var programWrapper = this.programs[i];
|
||||
if(data) {
|
||||
programWrapper = Handlebars.VM.program(i, fn, data);
|
||||
} else if (!programWrapper) {
|
||||
programWrapper = this.programs[i] = Handlebars.VM.program(i, fn);
|
||||
}
|
||||
return programWrapper;
|
||||
},
|
||||
merge: function(param, common) {
|
||||
var ret = param || common;
|
||||
|
||||
if (param && common) {
|
||||
ret = {};
|
||||
Handlebars.Utils.extend(ret, common);
|
||||
Handlebars.Utils.extend(ret, param);
|
||||
}
|
||||
return ret;
|
||||
},
|
||||
programWithDepth: Handlebars.VM.programWithDepth,
|
||||
noop: Handlebars.VM.noop,
|
||||
compilerInfo: null
|
||||
};
|
||||
|
||||
return function(context, options) {
|
||||
options = options || {};
|
||||
var result = templateSpec.call(container, Handlebars, context, options.helpers, options.partials, options.data);
|
||||
|
||||
var compilerInfo = container.compilerInfo || [],
|
||||
compilerRevision = compilerInfo[0] || 1,
|
||||
currentRevision = Handlebars.COMPILER_REVISION;
|
||||
|
||||
if (compilerRevision !== currentRevision) {
|
||||
if (compilerRevision < currentRevision) {
|
||||
var runtimeVersions = Handlebars.REVISION_CHANGES[currentRevision],
|
||||
compilerVersions = Handlebars.REVISION_CHANGES[compilerRevision];
|
||||
throw "Template was precompiled with an older version of Handlebars than the current runtime. "+
|
||||
"Please update your precompiler to a newer version ("+runtimeVersions+") or downgrade your runtime to an older version ("+compilerVersions+").";
|
||||
} else {
|
||||
// Use the embedded version info since the runtime doesn't know about this revision yet
|
||||
throw "Template was precompiled with a newer version of Handlebars than the current runtime. "+
|
||||
"Please update your runtime to a newer version ("+compilerInfo[1]+").";
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
},
|
||||
|
||||
programWithDepth: function(i, fn, data /*, $depth */) {
|
||||
var args = Array.prototype.slice.call(arguments, 3);
|
||||
|
||||
var program = function(context, options) {
|
||||
options = options || {};
|
||||
|
||||
return fn.apply(this, [context, options.data || data].concat(args));
|
||||
};
|
||||
program.program = i;
|
||||
program.depth = args.length;
|
||||
return program;
|
||||
},
|
||||
program: function(i, fn, data) {
|
||||
var program = function(context, options) {
|
||||
options = options || {};
|
||||
|
||||
return fn(context, options.data || data);
|
||||
};
|
||||
program.program = i;
|
||||
program.depth = 0;
|
||||
return program;
|
||||
},
|
||||
noop: function() { return ""; },
|
||||
invokePartial: function(partial, name, context, helpers, partials, data) {
|
||||
var options = { helpers: helpers, partials: partials, data: data };
|
||||
|
||||
if(partial === undefined) {
|
||||
throw new Handlebars.Exception("The partial " + name + " could not be found");
|
||||
} else if(partial instanceof Function) {
|
||||
return partial(context, options);
|
||||
} else if (!Handlebars.compile) {
|
||||
throw new Handlebars.Exception("The partial " + name + " could not be compiled when running in runtime-only mode");
|
||||
} else {
|
||||
partials[name] = Handlebars.compile(partial, {data: data !== undefined});
|
||||
return partials[name](context, options);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Handlebars.template = Handlebars.VM.template;
|
||||
;
|
||||
// lib/handlebars/browser-suffix.js
|
||||
if (typeof module === 'object' && module.exports) {
|
||||
// CommonJS
|
||||
module.exports = Handlebars;
|
||||
|
||||
} else if (typeof define === "function" && define.amd) {
|
||||
// AMD modules
|
||||
define(function() { return Handlebars; });
|
||||
|
||||
} else {
|
||||
// other, i.e. browser
|
||||
this.Handlebars = Handlebars;
|
||||
}
|
||||
}).call(this);
|
||||
;
|
||||
@@ -0,0 +1,377 @@
|
||||
/*! Backstretch - v2.0.4 - 2013-06-19
|
||||
* http://srobbin.com/jquery-plugins/backstretch/
|
||||
* Copyright (c) 2013 Scott Robbin; Licensed MIT */
|
||||
|
||||
;(function ($, window, undefined) {
|
||||
'use strict';
|
||||
|
||||
/* PLUGIN DEFINITION
|
||||
* ========================= */
|
||||
|
||||
$.fn.backstretch = function (images, options) {
|
||||
// We need at least one image or method name
|
||||
if (images === undefined || images.length === 0) {
|
||||
$.error("No images were supplied for Backstretch");
|
||||
}
|
||||
|
||||
/*
|
||||
* Scroll the page one pixel to get the right window height on iOS
|
||||
* Pretty harmless for everyone else
|
||||
*/
|
||||
if ($(window).scrollTop() === 0 ) {
|
||||
window.scrollTo(0, 0);
|
||||
}
|
||||
|
||||
return this.each(function () {
|
||||
var $this = $(this)
|
||||
, obj = $this.data('backstretch');
|
||||
|
||||
// Do we already have an instance attached to this element?
|
||||
if (obj) {
|
||||
|
||||
// Is this a method they're trying to execute?
|
||||
if (typeof images == 'string' && typeof obj[images] == 'function') {
|
||||
// Call the method
|
||||
obj[images](options);
|
||||
|
||||
// No need to do anything further
|
||||
return;
|
||||
}
|
||||
|
||||
// Merge the old options with the new
|
||||
options = $.extend(obj.options, options);
|
||||
|
||||
// Remove the old instance
|
||||
obj.destroy(true);
|
||||
}
|
||||
|
||||
obj = new Backstretch(this, images, options);
|
||||
$this.data('backstretch', obj);
|
||||
});
|
||||
};
|
||||
|
||||
// If no element is supplied, we'll attach to body
|
||||
$.backstretch = function (images, options) {
|
||||
// Return the instance
|
||||
return $('body')
|
||||
.backstretch(images, options)
|
||||
.data('backstretch');
|
||||
};
|
||||
|
||||
// Custom selector
|
||||
$.expr[':'].backstretch = function(elem) {
|
||||
return $(elem).data('backstretch') !== undefined;
|
||||
};
|
||||
|
||||
/* DEFAULTS
|
||||
* ========================= */
|
||||
|
||||
$.fn.backstretch.defaults = {
|
||||
centeredX: true // Should we center the image on the X axis?
|
||||
, centeredY: true // Should we center the image on the Y axis?
|
||||
, duration: 5000 // Amount of time in between slides (if slideshow)
|
||||
, fade: 0 // Speed of fade transition between slides
|
||||
};
|
||||
|
||||
/* STYLES
|
||||
*
|
||||
* Baked-in styles that we'll apply to our elements.
|
||||
* In an effort to keep the plugin simple, these are not exposed as options.
|
||||
* That said, anyone can override these in their own stylesheet.
|
||||
* ========================= */
|
||||
var styles = {
|
||||
wrap: {
|
||||
left: 0
|
||||
, top: 0
|
||||
, overflow: 'hidden'
|
||||
, margin: 0
|
||||
, padding: 0
|
||||
, height: '100%'
|
||||
, width: '100%'
|
||||
, zIndex: -999999
|
||||
}
|
||||
, img: {
|
||||
position: 'absolute'
|
||||
, display: 'none'
|
||||
, margin: 0
|
||||
, padding: 0
|
||||
, border: 'none'
|
||||
, width: 'auto'
|
||||
, height: 'auto'
|
||||
, maxHeight: 'none'
|
||||
, maxWidth: 'none'
|
||||
, zIndex: -999999
|
||||
}
|
||||
};
|
||||
|
||||
/* CLASS DEFINITION
|
||||
* ========================= */
|
||||
var Backstretch = function (container, images, options) {
|
||||
this.options = $.extend({}, $.fn.backstretch.defaults, options || {});
|
||||
|
||||
/* In its simplest form, we allow Backstretch to be called on an image path.
|
||||
* e.g. $.backstretch('/path/to/image.jpg')
|
||||
* So, we need to turn this back into an array.
|
||||
*/
|
||||
this.images = $.isArray(images) ? images : [images];
|
||||
|
||||
// Preload images
|
||||
$.each(this.images, function () {
|
||||
$('<img />')[0].src = this;
|
||||
});
|
||||
|
||||
// Convenience reference to know if the container is body.
|
||||
this.isBody = container === document.body;
|
||||
|
||||
/* We're keeping track of a few different elements
|
||||
*
|
||||
* Container: the element that Backstretch was called on.
|
||||
* Wrap: a DIV that we place the image into, so we can hide the overflow.
|
||||
* Root: Convenience reference to help calculate the correct height.
|
||||
*/
|
||||
this.$container = $(container);
|
||||
this.$root = this.isBody ? supportsFixedPosition ? $(window) : $(document) : this.$container;
|
||||
|
||||
// Don't create a new wrap if one already exists (from a previous instance of Backstretch)
|
||||
var $existing = this.$container.children(".backstretch").first();
|
||||
this.$wrap = $existing.length ? $existing : $('<div class="backstretch"></div>').css(styles.wrap).appendTo(this.$container);
|
||||
|
||||
// Non-body elements need some style adjustments
|
||||
if (!this.isBody) {
|
||||
// If the container is statically positioned, we need to make it relative,
|
||||
// and if no zIndex is defined, we should set it to zero.
|
||||
var position = this.$container.css('position')
|
||||
, zIndex = this.$container.css('zIndex');
|
||||
|
||||
this.$container.css({
|
||||
position: position === 'static' ? 'relative' : position
|
||||
, zIndex: zIndex === 'auto' ? 0 : zIndex
|
||||
, background: 'none'
|
||||
});
|
||||
|
||||
// Needs a higher z-index
|
||||
this.$wrap.css({zIndex: -999998});
|
||||
}
|
||||
|
||||
// Fixed or absolute positioning?
|
||||
this.$wrap.css({
|
||||
position: this.isBody && supportsFixedPosition ? 'fixed' : 'absolute'
|
||||
});
|
||||
|
||||
// Set the first image
|
||||
this.index = 0;
|
||||
this.show(this.index);
|
||||
|
||||
// Listen for resize
|
||||
$(window).on('resize.backstretch', $.proxy(this.resize, this))
|
||||
.on('orientationchange.backstretch', $.proxy(function () {
|
||||
// Need to do this in order to get the right window height
|
||||
if (this.isBody && window.pageYOffset === 0) {
|
||||
window.scrollTo(0, 1);
|
||||
this.resize();
|
||||
}
|
||||
}, this));
|
||||
};
|
||||
|
||||
/* PUBLIC METHODS
|
||||
* ========================= */
|
||||
Backstretch.prototype = {
|
||||
resize: function () {
|
||||
try {
|
||||
var bgCSS = {left: 0, top: 0}
|
||||
, rootWidth = this.isBody ? this.$root.width() : this.$root.innerWidth()
|
||||
, bgWidth = rootWidth
|
||||
, rootHeight = this.isBody ? ( window.innerHeight ? window.innerHeight : this.$root.height() ) : this.$root.innerHeight()
|
||||
, bgHeight = bgWidth / this.$img.data('ratio')
|
||||
, bgOffset;
|
||||
|
||||
// Make adjustments based on image ratio
|
||||
if (bgHeight >= rootHeight) {
|
||||
bgOffset = (bgHeight - rootHeight) / 2;
|
||||
if(this.options.centeredY) {
|
||||
bgCSS.top = '-' + bgOffset + 'px';
|
||||
}
|
||||
} else {
|
||||
bgHeight = rootHeight;
|
||||
bgWidth = bgHeight * this.$img.data('ratio');
|
||||
bgOffset = (bgWidth - rootWidth) / 2;
|
||||
if(this.options.centeredX) {
|
||||
bgCSS.left = '-' + bgOffset + 'px';
|
||||
}
|
||||
}
|
||||
|
||||
this.$wrap.css({width: rootWidth, height: rootHeight})
|
||||
.find('img:not(.deleteable)').css({width: bgWidth, height: bgHeight}).css(bgCSS);
|
||||
} catch(err) {
|
||||
// IE7 seems to trigger resize before the image is loaded.
|
||||
// This try/catch block is a hack to let it fail gracefully.
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
// Show the slide at a certain position
|
||||
, show: function (newIndex) {
|
||||
|
||||
// Validate index
|
||||
if (Math.abs(newIndex) > this.images.length - 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Vars
|
||||
var self = this
|
||||
, oldImage = self.$wrap.find('img').addClass('deleteable')
|
||||
, evtOptions = { relatedTarget: self.$container[0] };
|
||||
|
||||
// Trigger the "before" event
|
||||
self.$container.trigger($.Event('backstretch.before', evtOptions), [self, newIndex]);
|
||||
|
||||
// Set the new index
|
||||
this.index = newIndex;
|
||||
|
||||
// Pause the slideshow
|
||||
clearInterval(self.interval);
|
||||
|
||||
// New image
|
||||
self.$img = $('<img />')
|
||||
.css(styles.img)
|
||||
.bind('load', function (e) {
|
||||
var imgWidth = this.width || $(e.target).width()
|
||||
, imgHeight = this.height || $(e.target).height();
|
||||
|
||||
// Save the ratio
|
||||
$(this).data('ratio', imgWidth / imgHeight);
|
||||
|
||||
// Show the image, then delete the old one
|
||||
// "speed" option has been deprecated, but we want backwards compatibilty
|
||||
$(this).fadeIn(self.options.speed || self.options.fade, function () {
|
||||
oldImage.remove();
|
||||
|
||||
// Resume the slideshow
|
||||
if (!self.paused) {
|
||||
self.cycle();
|
||||
}
|
||||
|
||||
// Trigger the "after" and "show" events
|
||||
// "show" is being deprecated
|
||||
$(['after', 'show']).each(function () {
|
||||
self.$container.trigger($.Event('backstretch.' + this, evtOptions), [self, newIndex]);
|
||||
});
|
||||
});
|
||||
|
||||
// Resize
|
||||
self.resize();
|
||||
})
|
||||
.appendTo(self.$wrap);
|
||||
|
||||
// Hack for IE img onload event
|
||||
self.$img.attr('src', self.images[newIndex]);
|
||||
return self;
|
||||
}
|
||||
|
||||
, next: function () {
|
||||
// Next slide
|
||||
return this.show(this.index < this.images.length - 1 ? this.index + 1 : 0);
|
||||
}
|
||||
|
||||
, prev: function () {
|
||||
// Previous slide
|
||||
return this.show(this.index === 0 ? this.images.length - 1 : this.index - 1);
|
||||
}
|
||||
|
||||
, pause: function () {
|
||||
// Pause the slideshow
|
||||
this.paused = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
, resume: function () {
|
||||
// Resume the slideshow
|
||||
this.paused = false;
|
||||
this.next();
|
||||
return this;
|
||||
}
|
||||
|
||||
, cycle: function () {
|
||||
// Start/resume the slideshow
|
||||
if(this.images.length > 1) {
|
||||
// Clear the interval, just in case
|
||||
clearInterval(this.interval);
|
||||
|
||||
this.interval = setInterval($.proxy(function () {
|
||||
// Check for paused slideshow
|
||||
if (!this.paused) {
|
||||
this.next();
|
||||
}
|
||||
}, this), this.options.duration);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
, destroy: function (preserveBackground) {
|
||||
// Stop the resize events
|
||||
$(window).off('resize.backstretch orientationchange.backstretch');
|
||||
|
||||
// Clear the interval
|
||||
clearInterval(this.interval);
|
||||
|
||||
// Remove Backstretch
|
||||
if(!preserveBackground) {
|
||||
this.$wrap.remove();
|
||||
}
|
||||
this.$container.removeData('backstretch');
|
||||
}
|
||||
};
|
||||
|
||||
/* SUPPORTS FIXED POSITION?
|
||||
*
|
||||
* Based on code from jQuery Mobile 1.1.0
|
||||
* http://jquerymobile.com/
|
||||
*
|
||||
* In a nutshell, we need to figure out if fixed positioning is supported.
|
||||
* Unfortunately, this is very difficult to do on iOS, and usually involves
|
||||
* injecting content, scrolling the page, etc.. It's ugly.
|
||||
* jQuery Mobile uses this workaround. It's not ideal, but works.
|
||||
*
|
||||
* Modified to detect IE6
|
||||
* ========================= */
|
||||
|
||||
var supportsFixedPosition = (function () {
|
||||
var ua = navigator.userAgent
|
||||
, platform = navigator.platform
|
||||
// Rendering engine is Webkit, and capture major version
|
||||
, wkmatch = ua.match( /AppleWebKit\/([0-9]+)/ )
|
||||
, wkversion = !!wkmatch && wkmatch[ 1 ]
|
||||
, ffmatch = ua.match( /Fennec\/([0-9]+)/ )
|
||||
, ffversion = !!ffmatch && ffmatch[ 1 ]
|
||||
, operammobilematch = ua.match( /Opera Mobi\/([0-9]+)/ )
|
||||
, omversion = !!operammobilematch && operammobilematch[ 1 ]
|
||||
, iematch = ua.match( /MSIE ([0-9]+)/ )
|
||||
, ieversion = !!iematch && iematch[ 1 ];
|
||||
|
||||
return !(
|
||||
// iOS 4.3 and older : Platform is iPhone/Pad/Touch and Webkit version is less than 534 (ios5)
|
||||
((platform.indexOf( "iPhone" ) > -1 || platform.indexOf( "iPad" ) > -1 || platform.indexOf( "iPod" ) > -1 ) && wkversion && wkversion < 534) ||
|
||||
|
||||
// Opera Mini
|
||||
(window.operamini && ({}).toString.call( window.operamini ) === "[object OperaMini]") ||
|
||||
(operammobilematch && omversion < 7458) ||
|
||||
|
||||
//Android lte 2.1: Platform is Android and Webkit version is less than 533 (Android 2.2)
|
||||
(ua.indexOf( "Android" ) > -1 && wkversion && wkversion < 533) ||
|
||||
|
||||
// Firefox Mobile before 6.0 -
|
||||
(ffversion && ffversion < 6) ||
|
||||
|
||||
// WebOS less than 3
|
||||
("palmGetResource" in window && wkversion && wkversion < 534) ||
|
||||
|
||||
// MeeGo
|
||||
(ua.indexOf( "MeeGo" ) > -1 && ua.indexOf( "NokiaBrowser/8.5.0" ) > -1) ||
|
||||
|
||||
// IE6
|
||||
(ieversion && ieversion <= 6)
|
||||
);
|
||||
}());
|
||||
|
||||
}(jQuery, window));
|
||||
@@ -0,0 +1,632 @@
|
||||
/*
|
||||
* jQuery dotdotdot 1.6.1
|
||||
*
|
||||
* Copyright (c) 2013 Fred Heusschen
|
||||
* www.frebsite.nl
|
||||
*
|
||||
* Plugin website:
|
||||
* dotdotdot.frebsite.nl
|
||||
*
|
||||
* Dual licensed under the MIT and GPL licenses.
|
||||
* http://en.wikipedia.org/wiki/MIT_License
|
||||
* http://en.wikipedia.org/wiki/GNU_General_Public_License
|
||||
*/
|
||||
|
||||
(function( $ )
|
||||
{
|
||||
if ( $.fn.dotdotdot )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
$.fn.dotdotdot = function( o )
|
||||
{
|
||||
if ( this.length == 0 )
|
||||
{
|
||||
if ( !o || o.debug !== false )
|
||||
{
|
||||
debug( true, 'No element found for "' + this.selector + '".' );
|
||||
}
|
||||
return this;
|
||||
}
|
||||
if ( this.length > 1 )
|
||||
{
|
||||
return this.each(
|
||||
function()
|
||||
{
|
||||
$(this).dotdotdot( o );
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
var $dot = this;
|
||||
|
||||
if ( $dot.data( 'dotdotdot' ) )
|
||||
{
|
||||
$dot.trigger( 'destroy.dot' );
|
||||
}
|
||||
|
||||
$dot.data( 'dotdotdot-style', $dot.attr( 'style' ) );
|
||||
$dot.css( 'word-wrap', 'break-word' );
|
||||
if ($dot.css( 'white-space' ) === 'nowrap')
|
||||
{
|
||||
$dot.css( 'white-space', 'normal' );
|
||||
}
|
||||
|
||||
$dot.bind_events = function()
|
||||
{
|
||||
$dot.bind(
|
||||
'update.dot',
|
||||
function( e, c )
|
||||
{
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
opts.maxHeight = ( typeof opts.height == 'number' )
|
||||
? opts.height
|
||||
: getTrueInnerHeight( $dot );
|
||||
|
||||
opts.maxHeight += opts.tolerance;
|
||||
|
||||
if ( typeof c != 'undefined' )
|
||||
{
|
||||
if ( typeof c == 'string' || c instanceof HTMLElement )
|
||||
{
|
||||
c = $('<div />').append( c ).contents();
|
||||
}
|
||||
if ( c instanceof $ )
|
||||
{
|
||||
orgContent = c;
|
||||
}
|
||||
}
|
||||
|
||||
$inr = $dot.wrapInner( '<div class="dotdotdot" />' ).children();
|
||||
$inr.empty()
|
||||
.append( orgContent.clone( true ) )
|
||||
.css({
|
||||
'height' : 'auto',
|
||||
'width' : 'auto',
|
||||
'border' : 'none',
|
||||
'padding' : 0,
|
||||
'margin' : 0
|
||||
});
|
||||
|
||||
var after = false,
|
||||
trunc = false;
|
||||
|
||||
if ( conf.afterElement )
|
||||
{
|
||||
after = conf.afterElement.clone( true );
|
||||
conf.afterElement.remove();
|
||||
}
|
||||
if ( test( $inr, opts ) )
|
||||
{
|
||||
if ( opts.wrap == 'children' )
|
||||
{
|
||||
trunc = children( $inr, opts, after );
|
||||
}
|
||||
else
|
||||
{
|
||||
trunc = ellipsis( $inr, $dot, $inr, opts, after );
|
||||
}
|
||||
}
|
||||
$inr.replaceWith( $inr.contents() );
|
||||
$inr = null;
|
||||
|
||||
if ( $.isFunction( opts.callback ) )
|
||||
{
|
||||
opts.callback.call( $dot[ 0 ], trunc, orgContent );
|
||||
}
|
||||
|
||||
conf.isTruncated = trunc;
|
||||
return trunc;
|
||||
}
|
||||
|
||||
).bind(
|
||||
'isTruncated.dot',
|
||||
function( e, fn )
|
||||
{
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
if ( typeof fn == 'function' )
|
||||
{
|
||||
fn.call( $dot[ 0 ], conf.isTruncated );
|
||||
}
|
||||
return conf.isTruncated;
|
||||
}
|
||||
|
||||
).bind(
|
||||
'originalContent.dot',
|
||||
function( e, fn )
|
||||
{
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
if ( typeof fn == 'function' )
|
||||
{
|
||||
fn.call( $dot[ 0 ], orgContent );
|
||||
}
|
||||
return orgContent;
|
||||
}
|
||||
|
||||
).bind(
|
||||
'destroy.dot',
|
||||
function( e )
|
||||
{
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
$dot.unwatch()
|
||||
.unbind_events()
|
||||
.empty()
|
||||
.append( orgContent )
|
||||
.attr( 'style', $dot.data( 'dotdotdot-style' ) )
|
||||
.data( 'dotdotdot', false );
|
||||
}
|
||||
);
|
||||
return $dot;
|
||||
}; // /bind_events
|
||||
|
||||
$dot.unbind_events = function()
|
||||
{
|
||||
$dot.unbind('.dot');
|
||||
return $dot;
|
||||
}; // /unbind_events
|
||||
|
||||
$dot.watch = function()
|
||||
{
|
||||
$dot.unwatch();
|
||||
if ( opts.watch == 'window' )
|
||||
{
|
||||
var $window = $(window),
|
||||
_wWidth = $window.width(),
|
||||
_wHeight = $window.height();
|
||||
|
||||
$window.bind(
|
||||
'resize.dot' + conf.dotId,
|
||||
function()
|
||||
{
|
||||
if ( _wWidth != $window.width() || _wHeight != $window.height() || !opts.windowResizeFix )
|
||||
{
|
||||
_wWidth = $window.width();
|
||||
_wHeight = $window.height();
|
||||
|
||||
if ( watchInt )
|
||||
{
|
||||
clearInterval( watchInt );
|
||||
}
|
||||
watchInt = setTimeout(
|
||||
function()
|
||||
{
|
||||
$dot.trigger( 'update.dot' );
|
||||
}, 10
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
watchOrg = getSizes( $dot );
|
||||
watchInt = setInterval(
|
||||
function()
|
||||
{
|
||||
var watchNew = getSizes( $dot );
|
||||
if ( watchOrg.width != watchNew.width ||
|
||||
watchOrg.height != watchNew.height )
|
||||
{
|
||||
$dot.trigger( 'update.dot' );
|
||||
watchOrg = getSizes( $dot );
|
||||
}
|
||||
}, 100
|
||||
);
|
||||
}
|
||||
return $dot;
|
||||
};
|
||||
$dot.unwatch = function()
|
||||
{
|
||||
$(window).unbind( 'resize.dot' + conf.dotId );
|
||||
if ( watchInt )
|
||||
{
|
||||
clearInterval( watchInt );
|
||||
}
|
||||
return $dot;
|
||||
};
|
||||
|
||||
var orgContent = $dot.contents(),
|
||||
opts = $.extend( true, {}, $.fn.dotdotdot.defaults, o ),
|
||||
conf = {},
|
||||
watchOrg = {},
|
||||
watchInt = null,
|
||||
$inr = null;
|
||||
|
||||
|
||||
if ( !( opts.lastCharacter.remove instanceof Array ) )
|
||||
{
|
||||
opts.lastCharacter.remove = $.fn.dotdotdot.defaultArrays.lastCharacter.remove;
|
||||
}
|
||||
if ( !( opts.lastCharacter.noEllipsis instanceof Array ) )
|
||||
{
|
||||
opts.lastCharacter.noEllipsis = $.fn.dotdotdot.defaultArrays.lastCharacter.noEllipsis;
|
||||
}
|
||||
|
||||
|
||||
conf.afterElement = getElement( opts.after, $dot );
|
||||
conf.isTruncated = false;
|
||||
conf.dotId = dotId++;
|
||||
|
||||
|
||||
$dot.data( 'dotdotdot', true )
|
||||
.bind_events()
|
||||
.trigger( 'update.dot' );
|
||||
|
||||
if ( opts.watch )
|
||||
{
|
||||
$dot.watch();
|
||||
}
|
||||
|
||||
return $dot;
|
||||
};
|
||||
|
||||
|
||||
// public
|
||||
$.fn.dotdotdot.defaults = {
|
||||
'ellipsis' : '... ',
|
||||
'wrap' : 'word',
|
||||
'fallbackToLetter' : true,
|
||||
'lastCharacter' : {},
|
||||
'tolerance' : 0,
|
||||
'callback' : null,
|
||||
'after' : null,
|
||||
'height' : null,
|
||||
'watch' : false,
|
||||
'windowResizeFix' : true,
|
||||
'debug' : false
|
||||
};
|
||||
$.fn.dotdotdot.defaultArrays = {
|
||||
'lastCharacter' : {
|
||||
'remove' : [ ' ', '\u3000', ',', ';', '.', '!', '?' ],
|
||||
'noEllipsis' : []
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// private
|
||||
var dotId = 1;
|
||||
|
||||
function children( $elem, o, after )
|
||||
{
|
||||
var $elements = $elem.children(),
|
||||
isTruncated = false;
|
||||
|
||||
$elem.empty();
|
||||
|
||||
for ( var a = 0, l = $elements.length; a < l; a++ )
|
||||
{
|
||||
var $e = $elements.eq( a );
|
||||
$elem.append( $e );
|
||||
if ( after )
|
||||
{
|
||||
$elem.append( after );
|
||||
}
|
||||
if ( test( $elem, o ) )
|
||||
{
|
||||
$e.remove();
|
||||
isTruncated = true;
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
if ( after )
|
||||
{
|
||||
after.detach();
|
||||
}
|
||||
}
|
||||
}
|
||||
return isTruncated;
|
||||
}
|
||||
function ellipsis( $elem, $d, $i, o, after )
|
||||
{
|
||||
var $elements = $elem.contents(),
|
||||
isTruncated = false;
|
||||
|
||||
$elem.empty();
|
||||
|
||||
var notx = 'table, thead, tbody, tfoot, tr, col, colgroup, object, embed, param, ol, ul, dl, blockquote, select, optgroup, option, textarea, script, style';
|
||||
for ( var a = 0, l = $elements.length; a < l; a++ )
|
||||
{
|
||||
|
||||
if ( isTruncated )
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
var e = $elements[ a ],
|
||||
$e = $(e);
|
||||
|
||||
if ( typeof e == 'undefined' )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
$elem.append( $e );
|
||||
if ( after )
|
||||
{
|
||||
$elem[ ( $elem.is( notx ) ) ? 'after' : 'append' ]( after );
|
||||
}
|
||||
if ( e.nodeType == 3 )
|
||||
{
|
||||
if ( test( $i, o ) )
|
||||
{
|
||||
isTruncated = ellipsisElement( $e, $d, $i, o, after );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
isTruncated = ellipsis( $e, $d, $i, o, after );
|
||||
}
|
||||
|
||||
if ( !isTruncated )
|
||||
{
|
||||
if ( after )
|
||||
{
|
||||
after.detach();
|
||||
}
|
||||
}
|
||||
}
|
||||
return isTruncated;
|
||||
}
|
||||
function ellipsisElement( $e, $d, $i, o, after )
|
||||
{
|
||||
var isTruncated = false,
|
||||
e = $e[ 0 ];
|
||||
|
||||
if ( typeof e == 'undefined' )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var txt = getTextContent( e ),
|
||||
space = ( txt.indexOf(' ') !== -1 ) ? ' ' : '\u3000',
|
||||
separator = ( o.wrap == 'letter' ) ? '' : space,
|
||||
textArr = txt.split( separator ),
|
||||
position = -1,
|
||||
midPos = -1,
|
||||
startPos = 0,
|
||||
endPos = textArr.length - 1;
|
||||
|
||||
while ( startPos <= endPos && !( startPos == 0 && endPos == 0 ) )
|
||||
{
|
||||
var m = Math.floor( ( startPos + endPos ) / 2 );
|
||||
if ( m == midPos )
|
||||
{
|
||||
break;
|
||||
}
|
||||
midPos = m;
|
||||
|
||||
setTextContent( e, textArr.slice( 0, midPos + 1 ).join( separator ) + o.ellipsis );
|
||||
|
||||
if ( !test( $i, o ) )
|
||||
{
|
||||
position = midPos;
|
||||
startPos = midPos;
|
||||
}
|
||||
else
|
||||
{
|
||||
endPos = midPos;
|
||||
}
|
||||
if( endPos == startPos && endPos == 0 && o.fallbackToLetter )
|
||||
{
|
||||
separator = '';
|
||||
textArr = textArr[0].split(separator);
|
||||
position = -1;
|
||||
midPos = -1;
|
||||
startPos = 0;
|
||||
endPos = textArr.length - 1;
|
||||
}
|
||||
}
|
||||
|
||||
if ( position != -1 && !( textArr.length == 1 && textArr[ 0 ].length == 0 ) )
|
||||
{
|
||||
txt = addEllipsis( textArr.slice( 0, position + 1 ).join( separator ), o );
|
||||
isTruncated = true;
|
||||
setTextContent( e, txt );
|
||||
}
|
||||
else
|
||||
{
|
||||
var $w = $e.parent();
|
||||
$e.remove();
|
||||
|
||||
var afterLength = ( after ) ? after.length : 0 ;
|
||||
|
||||
if ( $w.contents().size() > afterLength )
|
||||
{
|
||||
var $n = $w.contents().eq( -1 - afterLength );
|
||||
isTruncated = ellipsisElement( $n, $d, $i, o, after );
|
||||
}
|
||||
else
|
||||
{
|
||||
var $p = $w.prev()
|
||||
var e = $p.contents().eq( -1 )[ 0 ];
|
||||
|
||||
if ( typeof e != 'undefined' )
|
||||
{
|
||||
var txt = addEllipsis( getTextContent( e ), o );
|
||||
setTextContent( e, txt );
|
||||
if ( after )
|
||||
{
|
||||
$p.append( after );
|
||||
}
|
||||
$w.remove();
|
||||
isTruncated = true;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return isTruncated;
|
||||
}
|
||||
function test( $i, o )
|
||||
{
|
||||
return $i.innerHeight() > o.maxHeight;
|
||||
}
|
||||
function addEllipsis( txt, o )
|
||||
{
|
||||
while( $.inArray( txt.slice( -1 ), o.lastCharacter.remove ) > -1 )
|
||||
{
|
||||
txt = txt.slice( 0, -1 );
|
||||
}
|
||||
if ( $.inArray( txt.slice( -1 ), o.lastCharacter.noEllipsis ) < 0 )
|
||||
{
|
||||
txt += o.ellipsis;
|
||||
}
|
||||
return txt;
|
||||
}
|
||||
function getSizes( $d )
|
||||
{
|
||||
return {
|
||||
'width' : $d.innerWidth(),
|
||||
'height': $d.innerHeight()
|
||||
};
|
||||
}
|
||||
function setTextContent( e, content )
|
||||
{
|
||||
if ( e.innerText )
|
||||
{
|
||||
e.innerText = content;
|
||||
}
|
||||
else if ( e.nodeValue )
|
||||
{
|
||||
e.nodeValue = content;
|
||||
}
|
||||
else if (e.textContent)
|
||||
{
|
||||
e.textContent = content;
|
||||
}
|
||||
|
||||
}
|
||||
function getTextContent( e )
|
||||
{
|
||||
if ( e.innerText )
|
||||
{
|
||||
return e.innerText;
|
||||
}
|
||||
else if ( e.nodeValue )
|
||||
{
|
||||
return e.nodeValue;
|
||||
}
|
||||
else if ( e.textContent )
|
||||
{
|
||||
return e.textContent;
|
||||
}
|
||||
else
|
||||
{
|
||||
return "";
|
||||
}
|
||||
}
|
||||
function getElement( e, $i )
|
||||
{
|
||||
if ( typeof e == 'undefined' )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if ( !e )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if ( typeof e == 'string' )
|
||||
{
|
||||
e = $(e, $i);
|
||||
return ( e.length )
|
||||
? e
|
||||
: false;
|
||||
}
|
||||
if ( typeof e == 'object' )
|
||||
{
|
||||
return ( typeof e.jquery == 'undefined' )
|
||||
? false
|
||||
: e;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
function getTrueInnerHeight( $el )
|
||||
{
|
||||
var h = $el.innerHeight(),
|
||||
a = [ 'paddingTop', 'paddingBottom' ];
|
||||
|
||||
for ( var z = 0, l = a.length; z < l; z++ ) {
|
||||
var m = parseInt( $el.css( a[ z ] ), 10 );
|
||||
if ( isNaN( m ) )
|
||||
{
|
||||
m = 0;
|
||||
}
|
||||
h -= m;
|
||||
}
|
||||
return h;
|
||||
}
|
||||
function debug( d, m )
|
||||
{
|
||||
if ( !d )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if ( typeof m == 'string' )
|
||||
{
|
||||
m = 'dotdotdot: ' + m;
|
||||
}
|
||||
else
|
||||
{
|
||||
m = [ 'dotdotdot:', m ];
|
||||
}
|
||||
|
||||
if ( typeof window.console != 'undefined' )
|
||||
{
|
||||
if ( typeof window.console.log != 'undefined' )
|
||||
{
|
||||
window.console.log( m );
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// override jQuery.html
|
||||
var _orgHtml = $.fn.html;
|
||||
$.fn.html = function( str ) {
|
||||
if ( typeof str != 'undefined' )
|
||||
{
|
||||
if ( this.data( 'dotdotdot' ) )
|
||||
{
|
||||
if ( typeof str != 'function' )
|
||||
{
|
||||
return this.trigger( 'update', [ str ] );
|
||||
}
|
||||
}
|
||||
return _orgHtml.call( this, str );
|
||||
}
|
||||
return _orgHtml.call( this );
|
||||
};
|
||||
|
||||
|
||||
// override jQuery.text
|
||||
var _orgText = $.fn.text;
|
||||
$.fn.text = function( str ) {
|
||||
if ( typeof str != 'undefined' )
|
||||
{
|
||||
if ( this.data( 'dotdotdot' ) )
|
||||
{
|
||||
var temp = $( '<div />' );
|
||||
temp.text( str );
|
||||
str = temp.html();
|
||||
temp.remove();
|
||||
return this.trigger( 'update', [ str ] );
|
||||
}
|
||||
return _orgText.call( this, str );
|
||||
}
|
||||
return _orgText.call( this );
|
||||
};
|
||||
|
||||
|
||||
})( jQuery );
|
||||
Vendored
+9789
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,672 @@
|
||||
/*!jQuery Knob*/
|
||||
/**
|
||||
* Downward compatible, touchable dial
|
||||
*
|
||||
* Version: 1.2.0 (15/07/2012)
|
||||
* Requires: jQuery v1.7+
|
||||
*
|
||||
* Copyright (c) 2012 Anthony Terrien
|
||||
* Under MIT and GPL licenses:
|
||||
* http://www.opensource.org/licenses/mit-license.php
|
||||
* http://www.gnu.org/licenses/gpl.html
|
||||
*
|
||||
* Thanks to vor, eskimoblood, spiffistan, FabrizioC
|
||||
*/
|
||||
(function($) {
|
||||
|
||||
/**
|
||||
* Kontrol library
|
||||
*/
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Definition of globals and core
|
||||
*/
|
||||
var k = {}, // kontrol
|
||||
max = Math.max,
|
||||
min = Math.min;
|
||||
|
||||
k.c = {};
|
||||
k.c.d = $(document);
|
||||
k.c.t = function (e) {
|
||||
return e.originalEvent.touches.length - 1;
|
||||
};
|
||||
|
||||
/**
|
||||
* Kontrol Object
|
||||
*
|
||||
* Definition of an abstract UI control
|
||||
*
|
||||
* Each concrete component must call this one.
|
||||
* <code>
|
||||
* k.o.call(this);
|
||||
* </code>
|
||||
*/
|
||||
k.o = function () {
|
||||
var s = this;
|
||||
|
||||
this.o = null; // array of options
|
||||
this.$ = null; // jQuery wrapped element
|
||||
this.i = null; // mixed HTMLInputElement or array of HTMLInputElement
|
||||
this.g = null; // 2D graphics context for 'pre-rendering'
|
||||
this.v = null; // value ; mixed array or integer
|
||||
this.cv = null; // change value ; not commited value
|
||||
this.x = 0; // canvas x position
|
||||
this.y = 0; // canvas y position
|
||||
this.$c = null; // jQuery canvas element
|
||||
this.c = null; // rendered canvas context
|
||||
this.t = 0; // touches index
|
||||
this.isInit = false;
|
||||
this.fgColor = null; // main color
|
||||
this.pColor = null; // previous color
|
||||
this.dH = null; // draw hook
|
||||
this.cH = null; // change hook
|
||||
this.eH = null; // cancel hook
|
||||
this.rH = null; // release hook
|
||||
|
||||
this.run = function () {
|
||||
var cf = function (e, conf) {
|
||||
var k;
|
||||
for (k in conf) {
|
||||
s.o[k] = conf[k];
|
||||
}
|
||||
s.init();
|
||||
s._configure()
|
||||
._draw();
|
||||
};
|
||||
|
||||
if(this.$.data('kontroled')) return;
|
||||
this.$.data('kontroled', true);
|
||||
|
||||
this.extend();
|
||||
this.o = $.extend(
|
||||
{
|
||||
// Config
|
||||
min : this.$.data('min') || 0,
|
||||
max : this.$.data('max') || 100,
|
||||
stopper : true,
|
||||
readOnly : this.$.data('readonly'),
|
||||
|
||||
// UI
|
||||
cursor : (this.$.data('cursor') === true && 30)
|
||||
|| this.$.data('cursor')
|
||||
|| 0,
|
||||
thickness : this.$.data('thickness') || 0.35,
|
||||
lineCap : this.$.data('linecap') || 'butt',
|
||||
width : this.$.data('width') || 200,
|
||||
height : this.$.data('height') || 200,
|
||||
displayInput : this.$.data('displayinput') == null || this.$.data('displayinput'),
|
||||
displayPrevious : this.$.data('displayprevious'),
|
||||
fgColor : this.$.data('fgcolor') || '#87CEEB',
|
||||
inputColor: this.$.data('inputcolor') || this.$.data('fgcolor') || '#87CEEB',
|
||||
inline : false,
|
||||
step : this.$.data('step') || 1,
|
||||
|
||||
// Hooks
|
||||
draw : null, // function () {}
|
||||
change : null, // function (value) {}
|
||||
cancel : null, // function () {}
|
||||
release : null, // function (value) {}
|
||||
error : null // function () {}
|
||||
}, this.o
|
||||
);
|
||||
|
||||
// routing value
|
||||
if(this.$.is('fieldset')) {
|
||||
|
||||
// fieldset = array of integer
|
||||
this.v = {};
|
||||
this.i = this.$.find('input')
|
||||
this.i.each(function(k) {
|
||||
var $this = $(this);
|
||||
s.i[k] = $this;
|
||||
s.v[k] = $this.val();
|
||||
|
||||
$this.bind(
|
||||
'change'
|
||||
, function () {
|
||||
var val = {};
|
||||
val[k] = $this.val();
|
||||
s.val(val);
|
||||
}
|
||||
);
|
||||
});
|
||||
this.$.find('legend').remove();
|
||||
|
||||
} else {
|
||||
// input = integer
|
||||
this.i = this.$;
|
||||
this.v = this.$.val();
|
||||
(this.v == '') && (this.v = this.o.min);
|
||||
|
||||
this.$.bind(
|
||||
'change'
|
||||
, function () {
|
||||
s.val(s._validate(s.$.val()));
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
(!this.o.displayInput) && this.$.hide();
|
||||
|
||||
this.$c = $('<canvas width="' +
|
||||
this.o.width + 'px" height="' +
|
||||
this.o.height + 'px"></canvas>');
|
||||
|
||||
this.c = this.$c[0].getContext? this.$c[0].getContext('2d') : null;
|
||||
|
||||
if (!this.c) {
|
||||
this.o.error && this.o.error();
|
||||
return;
|
||||
}
|
||||
|
||||
this.$
|
||||
.wrap($('<div style="' + (this.o.inline ? 'display:inline;' : '') +
|
||||
'width:' + this.o.width + 'px;height:' +
|
||||
this.o.height + 'px;"></div>'))
|
||||
.before(this.$c);
|
||||
|
||||
if (this.v instanceof Object) {
|
||||
this.cv = {};
|
||||
this.copy(this.v, this.cv);
|
||||
} else {
|
||||
this.cv = this.v;
|
||||
}
|
||||
|
||||
this.$
|
||||
.bind("configure", cf)
|
||||
.parent()
|
||||
.bind("configure", cf);
|
||||
|
||||
this._listen()
|
||||
._configure()
|
||||
._xy()
|
||||
.init();
|
||||
|
||||
this.isInit = true;
|
||||
|
||||
this._draw();
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
this._draw = function () {
|
||||
|
||||
// canvas pre-rendering
|
||||
var d = true,
|
||||
c = document.createElement('canvas');
|
||||
|
||||
c.width = s.o.width;
|
||||
c.height = s.o.height;
|
||||
s.g = c.getContext('2d');
|
||||
|
||||
s.clear();
|
||||
|
||||
s.dH
|
||||
&& (d = s.dH());
|
||||
|
||||
(d !== false) && s.draw();
|
||||
|
||||
s.c.drawImage(c, 0, 0);
|
||||
c = null;
|
||||
};
|
||||
|
||||
this._touch = function (e) {
|
||||
|
||||
var touchMove = function (e) {
|
||||
|
||||
var v = s.xy2val(
|
||||
e.originalEvent.touches[s.t].pageX,
|
||||
e.originalEvent.touches[s.t].pageY
|
||||
);
|
||||
|
||||
if (v == s.cv) return;
|
||||
|
||||
if (
|
||||
s.cH
|
||||
&& (s.cH(v) === false)
|
||||
) return;
|
||||
|
||||
|
||||
s.change(s._validate(v));
|
||||
s.$.trigger('change', v);
|
||||
s._draw();
|
||||
};
|
||||
|
||||
// get touches index
|
||||
this.t = k.c.t(e);
|
||||
|
||||
// First touch
|
||||
touchMove(e);
|
||||
|
||||
// Touch events listeners
|
||||
k.c.d
|
||||
.bind("touchmove.k", touchMove)
|
||||
.bind(
|
||||
"touchend.k"
|
||||
, function () {
|
||||
k.c.d.unbind('touchmove.k touchend.k');
|
||||
|
||||
if (
|
||||
s.rH
|
||||
&& (s.rH(s.cv) === false)
|
||||
) return;
|
||||
|
||||
s.val(s.cv);
|
||||
}
|
||||
);
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
this._mouse = function (e) {
|
||||
|
||||
var mouseMove = function (e) {
|
||||
var v = s.xy2val(e.pageX, e.pageY);
|
||||
if (v == s.cv) return;
|
||||
|
||||
if (
|
||||
s.cH
|
||||
&& (s.cH(v) === false)
|
||||
) return;
|
||||
|
||||
s.change(s._validate(v));
|
||||
s.$.trigger('change', v);
|
||||
s._draw();
|
||||
};
|
||||
|
||||
// First click
|
||||
mouseMove(e);
|
||||
|
||||
// Mouse events listeners
|
||||
k.c.d
|
||||
.bind("mousemove.k", mouseMove)
|
||||
.bind(
|
||||
// Escape key cancel current change
|
||||
"keyup.k"
|
||||
, function (e) {
|
||||
if (e.keyCode === 27) {
|
||||
k.c.d.unbind("mouseup.k mousemove.k keyup.k");
|
||||
|
||||
if (
|
||||
s.eH
|
||||
&& (s.eH() === false)
|
||||
) return;
|
||||
|
||||
s.cancel();
|
||||
}
|
||||
}
|
||||
)
|
||||
.bind(
|
||||
"mouseup.k"
|
||||
, function (e) {
|
||||
k.c.d.unbind('mousemove.k mouseup.k keyup.k');
|
||||
|
||||
if (
|
||||
s.rH
|
||||
&& (s.rH(s.cv) === false)
|
||||
) return;
|
||||
|
||||
s.val(s.cv);
|
||||
}
|
||||
);
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
this._xy = function () {
|
||||
var o = this.$c.offset();
|
||||
this.x = o.left;
|
||||
this.y = o.top;
|
||||
return this;
|
||||
};
|
||||
|
||||
this._listen = function () {
|
||||
|
||||
if (!this.o.readOnly) {
|
||||
this.$c
|
||||
.bind(
|
||||
"mousedown"
|
||||
, function (e) {
|
||||
e.preventDefault();
|
||||
s._xy()._mouse(e);
|
||||
}
|
||||
)
|
||||
.bind(
|
||||
"touchstart"
|
||||
, function (e) {
|
||||
e.preventDefault();
|
||||
s._xy()._touch(e);
|
||||
}
|
||||
);
|
||||
this.listen();
|
||||
} else {
|
||||
this.$.attr('readonly', 'readonly');
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
this._configure = function () {
|
||||
|
||||
// Hooks
|
||||
if (this.o.draw) this.dH = this.o.draw;
|
||||
if (this.o.change) this.cH = this.o.change;
|
||||
if (this.o.cancel) this.eH = this.o.cancel;
|
||||
if (this.o.release) this.rH = this.o.release;
|
||||
|
||||
if (this.o.displayPrevious) {
|
||||
this.pColor = this.h2rgba(this.o.fgColor, "0.4");
|
||||
this.fgColor = this.h2rgba(this.o.fgColor, "0.6");
|
||||
} else {
|
||||
this.fgColor = this.o.fgColor;
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
this._clear = function () {
|
||||
this.$c[0].width = this.$c[0].width;
|
||||
};
|
||||
|
||||
this._validate = function(v) {
|
||||
return (~~ (((v < 0) ? -0.5 : 0.5) + (v/this.o.step))) * this.o.step;
|
||||
};
|
||||
|
||||
// Abstract methods
|
||||
this.listen = function () {}; // on start, one time
|
||||
this.extend = function () {}; // each time configure triggered
|
||||
this.init = function () {}; // each time configure triggered
|
||||
this.change = function (v) {}; // on change
|
||||
this.val = function (v) {}; // on release
|
||||
this.xy2val = function (x, y) {}; //
|
||||
this.draw = function () {}; // on change / on release
|
||||
this.clear = function () { this._clear(); };
|
||||
|
||||
// Utils
|
||||
this.h2rgba = function (h, a) {
|
||||
var rgb;
|
||||
h = h.substring(1,7)
|
||||
rgb = [parseInt(h.substring(0,2),16)
|
||||
,parseInt(h.substring(2,4),16)
|
||||
,parseInt(h.substring(4,6),16)];
|
||||
return "rgba(" + rgb[0] + "," + rgb[1] + "," + rgb[2] + "," + a + ")";
|
||||
};
|
||||
|
||||
this.copy = function (f, t) {
|
||||
for (var i in f) { t[i] = f[i]; }
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* k.Dial
|
||||
*/
|
||||
k.Dial = function () {
|
||||
k.o.call(this);
|
||||
|
||||
this.startAngle = null;
|
||||
this.xy = null;
|
||||
this.radius = null;
|
||||
this.lineWidth = null;
|
||||
this.cursorExt = null;
|
||||
this.w2 = null;
|
||||
this.PI2 = 2*Math.PI;
|
||||
|
||||
this.extend = function () {
|
||||
this.o = $.extend(
|
||||
{
|
||||
bgColor : this.$.data('bgcolor') || '#EEEEEE',
|
||||
angleOffset : this.$.data('angleoffset') || 0,
|
||||
angleArc : this.$.data('anglearc') || 360,
|
||||
inline : true
|
||||
}, this.o
|
||||
);
|
||||
};
|
||||
|
||||
this.val = function (v) {
|
||||
if (null != v) {
|
||||
this.cv = this.o.stopper ? max(min(v, this.o.max), this.o.min) : v;
|
||||
this.v = this.cv;
|
||||
this.$.val(this.v);
|
||||
this._draw();
|
||||
} else {
|
||||
return this.v;
|
||||
}
|
||||
};
|
||||
|
||||
this.xy2val = function (x, y) {
|
||||
var a, ret;
|
||||
|
||||
a = Math.atan2(
|
||||
x - (this.x + this.w2)
|
||||
, - (y - this.y - this.w2)
|
||||
) - this.angleOffset;
|
||||
|
||||
if(this.angleArc != this.PI2 && (a < 0) && (a > -0.5)) {
|
||||
// if isset angleArc option, set to min if .5 under min
|
||||
a = 0;
|
||||
} else if (a < 0) {
|
||||
a += this.PI2;
|
||||
}
|
||||
|
||||
ret = ~~ (0.5 + (a * (this.o.max - this.o.min) / this.angleArc))
|
||||
+ this.o.min;
|
||||
|
||||
this.o.stopper
|
||||
&& (ret = max(min(ret, this.o.max), this.o.min));
|
||||
|
||||
return ret;
|
||||
};
|
||||
|
||||
this.listen = function () {
|
||||
// bind MouseWheel
|
||||
var s = this,
|
||||
mw = function (e) {
|
||||
e.preventDefault();
|
||||
var ori = e.originalEvent
|
||||
,deltaX = ori.detail || ori.wheelDeltaX
|
||||
,deltaY = ori.detail || ori.wheelDeltaY
|
||||
,v = parseInt(s.$.val()) + (deltaX>0 || deltaY>0 ? s.o.step : deltaX<0 || deltaY<0 ? -s.o.step : 0);
|
||||
|
||||
if (
|
||||
s.cH
|
||||
&& (s.cH(v) === false)
|
||||
) return;
|
||||
|
||||
s.val(v);
|
||||
s.$.trigger('change', v);
|
||||
}
|
||||
, kval, to, m = 1, kv = {37:-s.o.step, 38:s.o.step, 39:s.o.step, 40:-s.o.step};
|
||||
|
||||
this.$
|
||||
.bind(
|
||||
"keydown"
|
||||
,function (e) {
|
||||
var kc = e.keyCode;
|
||||
|
||||
// numpad support
|
||||
if(kc >= 96 && kc <= 105) {
|
||||
kc = e.keyCode = kc - 48;
|
||||
}
|
||||
|
||||
kval = parseInt(String.fromCharCode(kc));
|
||||
|
||||
if (isNaN(kval)) {
|
||||
|
||||
(kc !== 13) // enter
|
||||
&& (kc !== 8) // bs
|
||||
&& (kc !== 9) // tab
|
||||
&& (kc !== 189) // -
|
||||
&& e.preventDefault();
|
||||
|
||||
// arrows
|
||||
if ($.inArray(kc,[37,38,39,40]) > -1) {
|
||||
e.preventDefault();
|
||||
|
||||
var v = parseInt(s.$.val()) + kv[kc] * m;
|
||||
|
||||
s.o.stopper
|
||||
&& (v = max(min(v, s.o.max), s.o.min));
|
||||
|
||||
s.change(v);
|
||||
s.$.trigger('change', v);
|
||||
s._draw();
|
||||
|
||||
// long time keydown speed-up
|
||||
to = window.setTimeout(
|
||||
function () { m*=2; }
|
||||
,30
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
.bind(
|
||||
"keyup"
|
||||
,function (e) {
|
||||
if (isNaN(kval)) {
|
||||
if (to) {
|
||||
window.clearTimeout(to);
|
||||
to = null;
|
||||
m = 1;
|
||||
s.val(s.$.val());
|
||||
}
|
||||
} else {
|
||||
// kval postcond
|
||||
(s.$.val() > s.o.max && s.$.val(s.o.max))
|
||||
|| (s.$.val() < s.o.min && s.$.val(s.o.min));
|
||||
}
|
||||
|
||||
}
|
||||
);
|
||||
|
||||
this.$c.bind("mousewheel DOMMouseScroll", mw);
|
||||
this.$.bind("mousewheel DOMMouseScroll", mw)
|
||||
};
|
||||
|
||||
this.init = function () {
|
||||
|
||||
if (
|
||||
this.v < this.o.min
|
||||
|| this.v > this.o.max
|
||||
) this.v = this.o.min;
|
||||
|
||||
this.$.val(this.v);
|
||||
this.w2 = this.o.width / 2;
|
||||
this.cursorExt = this.o.cursor / 100;
|
||||
this.xy = this.w2;
|
||||
this.lineWidth = this.xy * this.o.thickness;
|
||||
this.lineCap = this.o.lineCap;
|
||||
this.radius = this.xy - this.lineWidth / 2;
|
||||
|
||||
this.o.angleOffset
|
||||
&& (this.o.angleOffset = isNaN(this.o.angleOffset) ? 0 : this.o.angleOffset);
|
||||
|
||||
this.o.angleArc
|
||||
&& (this.o.angleArc = isNaN(this.o.angleArc) ? this.PI2 : this.o.angleArc);
|
||||
|
||||
// deg to rad
|
||||
this.angleOffset = this.o.angleOffset * Math.PI / 180;
|
||||
this.angleArc = this.o.angleArc * Math.PI / 180;
|
||||
|
||||
// compute start and end angles
|
||||
this.startAngle = 1.5 * Math.PI + this.angleOffset;
|
||||
this.endAngle = 1.5 * Math.PI + this.angleOffset + this.angleArc;
|
||||
|
||||
var s = max(
|
||||
String(Math.abs(this.o.max)).length
|
||||
, String(Math.abs(this.o.min)).length
|
||||
, 2
|
||||
) + 2;
|
||||
|
||||
this.o.displayInput
|
||||
&& this.i.css({
|
||||
'width' : ((this.o.width / 2 + 4) >> 0) + 'px'
|
||||
,'height' : ((this.o.width / 3) >> 0) + 'px'
|
||||
,'position' : 'absolute'
|
||||
,'vertical-align' : 'middle'
|
||||
,'margin-top' : ((this.o.width / 3) >> 0) + 'px'
|
||||
,'margin-left' : '-' + ((this.o.width * 3 / 4 + 2) >> 0) + 'px'
|
||||
,'border' : 0
|
||||
,'background' : 'none'
|
||||
,'font' : 'bold ' + ((this.o.width / s) >> 0) + 'px Arial'
|
||||
,'text-align' : 'center'
|
||||
,'color' : this.o.inputColor || this.o.fgColor
|
||||
,'padding' : '0px'
|
||||
,'-webkit-appearance': 'none'
|
||||
})
|
||||
|| this.i.css({
|
||||
'width' : '0px'
|
||||
,'visibility' : 'hidden'
|
||||
});
|
||||
};
|
||||
|
||||
this.change = function (v) {
|
||||
this.cv = v;
|
||||
this.$.val(v);
|
||||
};
|
||||
|
||||
this.angle = function (v) {
|
||||
return (v - this.o.min) * this.angleArc / (this.o.max - this.o.min);
|
||||
};
|
||||
|
||||
this.draw = function () {
|
||||
|
||||
var c = this.g, // context
|
||||
a = this.angle(this.cv) // Angle
|
||||
, sat = this.startAngle // Start angle
|
||||
, eat = sat + a // End angle
|
||||
, sa, ea // Previous angles
|
||||
, r = 1;
|
||||
|
||||
c.lineWidth = this.lineWidth;
|
||||
|
||||
c.lineCap = this.lineCap;
|
||||
|
||||
this.o.cursor
|
||||
&& (sat = eat - this.cursorExt)
|
||||
&& (eat = eat + this.cursorExt);
|
||||
|
||||
c.beginPath();
|
||||
c.strokeStyle = this.o.bgColor;
|
||||
c.arc(this.xy, this.xy, this.radius, this.endAngle, this.startAngle, true);
|
||||
c.stroke();
|
||||
|
||||
if (this.o.displayPrevious) {
|
||||
ea = this.startAngle + this.angle(this.v);
|
||||
sa = this.startAngle;
|
||||
this.o.cursor
|
||||
&& (sa = ea - this.cursorExt)
|
||||
&& (ea = ea + this.cursorExt);
|
||||
|
||||
c.beginPath();
|
||||
c.strokeStyle = this.pColor;
|
||||
c.arc(this.xy, this.xy, this.radius, sa, ea, false);
|
||||
c.stroke();
|
||||
r = (this.cv == this.v);
|
||||
}
|
||||
|
||||
c.beginPath();
|
||||
c.strokeStyle = r ? this.o.fgColor : this.fgColor ;
|
||||
c.arc(this.xy, this.xy, this.radius, sat, eat, false);
|
||||
c.stroke();
|
||||
};
|
||||
|
||||
this.cancel = function () {
|
||||
this.val(this.v);
|
||||
};
|
||||
};
|
||||
|
||||
$.fn.dial = $.fn.knob = function (o) {
|
||||
return this.each(
|
||||
function () {
|
||||
var d = new k.Dial();
|
||||
d.o = o;
|
||||
d.$ = $(this);
|
||||
d.run();
|
||||
}
|
||||
).parent();
|
||||
};
|
||||
|
||||
})(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
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user