Moved source code under src folder - massive change

This commit is contained in:
Mark McDowall
2013-10-02 18:01:32 -07:00
parent 2fc8123d6b
commit 5bf0e197ec
1499 changed files with 1054 additions and 1444 deletions
@@ -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="#">&times;</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));
+330
View File
@@ -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
+576
View File
@@ -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
+40
View File
@@ -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);
+606
View File
@@ -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;
}(_));
+2280
View File
File diff suppressed because it is too large Load Diff
+153
View File
@@ -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
+145
View File
@@ -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);
});
}));
+374
View File
@@ -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 = {
"&": "&amp;",
"<": "&lt;",
">": "&gt;",
'"': "&quot;",
"'": "&#x27;",
"`": "&#x60;"
};
var badChars = /[&<>"'`]/g;
var possible = /[&<>"'`]/;
var escapeChar = function(chr) {
return escape[chr] || "&amp;";
};
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);
;
+377
View File
@@ -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));
+632
View File
@@ -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 );
+9789
View File
File diff suppressed because it is too large Load Diff
+672
View File
@@ -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