initial commit, add all the things

This commit is contained in:
John Bintz 2011-11-25 10:06:17 -05:00
commit bde3536505
15 changed files with 3515 additions and 0 deletions

5
.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
*.gem
.bundle
Gemfile.lock
pkg/*
tmp/*

4
Gemfile Normal file
View File

@ -0,0 +1,4 @@
source "http://rubygems.org"
# Specify your gem's dependencies in vendor-all-the-javascripts.gemspec
gemspec

12
README.md Normal file
View File

@ -0,0 +1,12 @@
Everything that I use JavaScript-wise goes in here, so I can pull it in to all my apps with ease.
Every other project is owned by the original owner, I'm just aggregating for my own convenience.
* [jquery.cookie.js](https://github.com/carhartl/jquery-cookie)
* [jquery.elastic.js](http://unwrongest.com/projects/elastic/)
* [jquery.viewport.js](https://github.com/borbit/jquery.viewport)
* [Better-Autocomplete](https://github.com/betamos/Better-Autocomplete)
* [Moment.js](https://github.com/timrwood/moment)
* [Ajax File Upload](http://phpletter.com/Our-Projects/AjaxFileUpload/)
* [jQuery UI Timepicker Addon](http://trentrichardson.com/examples/timepicker/)

74
Rakefile Normal file
View File

@ -0,0 +1,74 @@
require "bundler/gem_tasks"
require 'httparty'
require 'zip/zip'
def process_zip_url(url, &block)
mkdir_p 'tmp'
response = HTTParty.get(url)
File.open(target = 'tmp/elastic.zip', 'wb') { |fh| fh.print response.body }
Zip::ZipFile.foreach(target, &block)
end
sources = {
'jquery.cookie' => [
'https://raw.github.com/carhartl/jquery-cookie/master/jquery.cookie.js'
],
'jquery-elastic' => lambda {
process_zip_url('http://jquery-elastic.googlecode.com/files/jquery.elastic-1.6.11.zip') do |entry|
if entry.name['jquery.elastic.source.js']
entry.extract('vendor/assets/javascripts/jquery.elastic.js') { true }
end
end
},
'jquery-viewport' => [
'https://raw.github.com/borbit/jquery.viewport/master/jquery.viewport.js'
],
'better-autocomplete' => [
'https://raw.github.com/betamos/Better-Autocomplete/develop/src/jquery.better-autocomplete.js',
'https://raw.github.com/betamos/Better-Autocomplete/develop/src/better-autocomplete.css'
],
'moment' => [
'https://raw.github.com/timrwood/moment/master/moment.js'
],
'ajaxfileuploader' => lambda {
process_zip_url('http://phpletter.com/download_project_version.php?version_id=34') do |entry|
if entry.name['ajaxfileupload.js']
entry.extract('vendor/assets/javascripts/ajaxfileupload.js') { true }
end
end
},
'jquery-ui-timepicker' => [
'http://trentrichardson.com/examples/timepicker/js/jquery-ui-timepicker-addon.js'
]
}
desc 'Update verything'
task :update do
sources.each do |name, files|
case files
when Array
files.each do |url|
puts "Retrieving #{url} for #{name}..."
response = HTTParty.get(url, :format => 'application/octet-stream')
case File.extname(url)
when '.js'
target = Pathname('vendor/assets/javascripts')
when '.css'
target = Pathname('vendor/assets/stylesheets')
end
target.mkpath
target.join(File.basename(url)).open('wb') { |fh| fh.print response.body }
end
when Proc
puts "Executing code for #{name}..."
files.call
end
end
end
task :default => :update

View File

@ -0,0 +1,11 @@
require "vendor-all-the-javascripts/version"
module Vendor
module All
module The
module Javascripts
# Your code goes here...
end
end
end
end

View File

@ -0,0 +1,9 @@
module Vendor
module All
module The
module Javascripts
VERSION = "0.0.1"
end
end
end
end

View File

@ -0,0 +1,28 @@
# -*- encoding: utf-8 -*-
$:.push File.expand_path("../lib", __FILE__)
require "vendor-all-the-javascripts/version"
Gem::Specification.new do |s|
s.name = "vendor-all-the-javascripts"
s.version = Vendor::All::The::Javascripts::VERSION
s.authors = ["John Bintz"]
s.email = ["john@coswellproductions.com"]
s.homepage = ""
s.summary = %q{TODO: Write a gem summary}
s.description = %q{TODO: Write a gem description}
s.rubyforge_project = "vendor-all-the-javascripts"
s.files = `git ls-files`.split("\n")
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
s.require_paths = ["lib"]
# specify any dependencies here; for example:
# s.add_development_dependency "rspec"
# s.add_runtime_dependency "rest-client"
#
s.add_development_dependency 'httparty'
s.add_development_dependency 'rake'
s.add_development_dependency 'rubyzip'
end

View File

@ -0,0 +1,201 @@
jQuery.extend({
createUploadIframe: function(id, uri)
{
//create frame
var frameId = 'jUploadFrame' + id;
var iframeHtml = '<iframe id="' + frameId + '" name="' + frameId + '" style="position:absolute; top:-9999px; left:-9999px"';
if(window.ActiveXObject)
{
if(typeof uri== 'boolean'){
iframeHtml += ' src="' + 'javascript:false' + '"';
}
else if(typeof uri== 'string'){
iframeHtml += ' src="' + uri + '"';
}
}
iframeHtml += ' />';
jQuery(iframeHtml).appendTo(document.body);
return jQuery('#' + frameId).get(0);
},
createUploadForm: function(id, fileElementId, data)
{
//create form
var formId = 'jUploadForm' + id;
var fileId = 'jUploadFile' + id;
var form = jQuery('<form action="" method="POST" name="' + formId + '" id="' + formId + '" enctype="multipart/form-data"></form>');
if(data)
{
for(var i in data)
{
jQuery('<input type="hidden" name="' + i + '" value="' + data[i] + '" />').appendTo(form);
}
}
var oldElement = jQuery('#' + fileElementId);
var newElement = jQuery(oldElement).clone();
jQuery(oldElement).attr('id', fileId);
jQuery(oldElement).before(newElement);
jQuery(oldElement).appendTo(form);
//set attributes
jQuery(form).css('position', 'absolute');
jQuery(form).css('top', '-1200px');
jQuery(form).css('left', '-1200px');
jQuery(form).appendTo('body');
return form;
},
ajaxFileUpload: function(s) {
// TODO introduce global settings, allowing the client to modify them for all requests, not only timeout
s = jQuery.extend({}, jQuery.ajaxSettings, s);
var id = new Date().getTime()
var form = jQuery.createUploadForm(id, s.fileElementId, (typeof(s.data)=='undefined'?false:s.data));
var io = jQuery.createUploadIframe(id, s.secureuri);
var frameId = 'jUploadFrame' + id;
var formId = 'jUploadForm' + id;
// Watch for a new set of requests
if ( s.global && ! jQuery.active++ )
{
jQuery.event.trigger( "ajaxStart" );
}
var requestDone = false;
// Create the request object
var xml = {}
if ( s.global )
jQuery.event.trigger("ajaxSend", [xml, s]);
// Wait for a response to come back
var uploadCallback = function(isTimeout)
{
var io = document.getElementById(frameId);
try
{
if(io.contentWindow)
{
xml.responseText = io.contentWindow.document.body?io.contentWindow.document.body.innerHTML:null;
xml.responseXML = io.contentWindow.document.XMLDocument?io.contentWindow.document.XMLDocument:io.contentWindow.document;
}else if(io.contentDocument)
{
xml.responseText = io.contentDocument.document.body?io.contentDocument.document.body.innerHTML:null;
xml.responseXML = io.contentDocument.document.XMLDocument?io.contentDocument.document.XMLDocument:io.contentDocument.document;
}
}catch(e)
{
jQuery.handleError(s, xml, null, e);
}
if ( xml || isTimeout == "timeout")
{
requestDone = true;
var status;
try {
status = isTimeout != "timeout" ? "success" : "error";
// Make sure that the request was successful or notmodified
if ( status != "error" )
{
// process the data (runs the xml through httpData regardless of callback)
var data = jQuery.uploadHttpData( xml, s.dataType );
// If a local callback was specified, fire it and pass it the data
if ( s.success )
s.success( data, status );
// Fire the global callback
if( s.global )
jQuery.event.trigger( "ajaxSuccess", [xml, s] );
} else
jQuery.handleError(s, xml, status);
} catch(e)
{
status = "error";
jQuery.handleError(s, xml, status, e);
}
// The request was completed
if( s.global )
jQuery.event.trigger( "ajaxComplete", [xml, s] );
// Handle the global AJAX counter
if ( s.global && ! --jQuery.active )
jQuery.event.trigger( "ajaxStop" );
// Process result
if ( s.complete )
s.complete(xml, status);
jQuery(io).unbind()
setTimeout(function()
{ try
{
jQuery(io).remove();
jQuery(form).remove();
} catch(e)
{
jQuery.handleError(s, xml, null, e);
}
}, 100)
xml = null
}
}
// Timeout checker
if ( s.timeout > 0 )
{
setTimeout(function(){
// Check to see if the request is still happening
if( !requestDone ) uploadCallback( "timeout" );
}, s.timeout);
}
try
{
var form = jQuery('#' + formId);
jQuery(form).attr('action', s.url);
jQuery(form).attr('method', 'POST');
jQuery(form).attr('target', frameId);
if(form.encoding)
{
jQuery(form).attr('encoding', 'multipart/form-data');
}
else
{
jQuery(form).attr('enctype', 'multipart/form-data');
}
jQuery(form).submit();
} catch(e)
{
jQuery.handleError(s, xml, null, e);
}
jQuery('#' + frameId).load(uploadCallback );
return {abort: function () {}};
},
uploadHttpData: function( r, type ) {
var data = !type;
data = type == "xml" || data ? r.responseXML : r.responseText;
// If the type is "script", eval it in global context
if ( type == "script" )
jQuery.globalEval( data );
// Get the JavaScript object, if JSON is used.
if ( type == "json" )
eval( "data = " + data );
// evaluate scripts within html
if ( type == "html" )
jQuery("<div>").html(data).evalScripts();
return data;
}
})

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,863 @@
/**
* @fileOverview Better Autocomplete is a flexible jQuery plugin which offers
* rich text autocompletion, both from local and remote sources.
*
* @author Didrik Nordström, http://betamos.se/
*
* @version v1.0-dev
*
* @requires
* <ul><li>
* jQuery 1.4+
* </li><li>
* IE7+ or any decent webkit/gecko-based web browser
* </li></ul>
*
* @preserve Better Autocomplete v1.0-dev
* https://github.com/betamos/Better-Autocomplete
*
* Copyright 2011, Didrik Nordström, http://betamos.se/
* Dual licensed under the MIT or GPL Version 2 licenses.
*
* Requires jQuery 1.4+
* http://jquery.com/
*/
/**
* Create or alter an autocomplete object instance that belongs to
* the elements in the selection. Make sure there are only text field elements
* in the selection.
*
* @constructor
*
* @name jQuery.betterAutocomplete
*
* @param {String} method
* Should be one of the following:
* <ul><li>
* init: Initiate Better Autocomplete instances on the text input elements
* in the current jQuery selection. They are enabled by default. The other
* parameters are then required.
* </li><li>
* enable: In this jQuery selection, reenable the Better Autocomplete
* instances.
* </li><li>
* disable: In this jQuery selection, disable the Better Autocomplete
* instances.
* </li><li>
* destroy: In this jQuery selection, destroy the Better Autocomplete
* instances. It will not be possible to reenable them after this.
* </li></ul>
*
* @param {String|Object} [resource]
* If String, it will become the path for a remote resource. If not, it will
* be treated like a local resource. The path should provide JSON objects
* upon HTTP requests.
*
* @param {Object} [options]
* An object with configurable options:
* <ul><li>
* charLimit: (default=3 for remote or 1 for local resource) The minimum
* number of chars to do an AJAX call. A typical use case for this limit is
* to reduce server load.
* </li><li>
* delay: (default=350) The time in ms between last keypress and AJAX call.
* Typically used to prevent looking up irrelevant strings while the user
* is still typing. Only relevant for remote resources.
* </li><li>
* caseSensitive: (default=false) If the search should be case sensitive.
* If false, query strings will be converted to lowercase.
* </li><li>
* cacheLimit: (default=256 for remote or 0 for local resource) The maximum
* number of result objects to store in the cache. This option reduces
* server load if the user deletes characters to check back on previous
* results. To disable caching of previous results, set this option to 0.
* </li><li>
* remoteTimeout: (default=10000) The timeout for remote (AJAX) calls.
* </li><li>
* crossOrigin: (default=false) Set to true if cross origin requests will
* be performed, i.e. that the remote URL has a different domain. This will
* force Internet Explorer to use "jsonp" instead of "json" as datatype.
* </li><li>
* selectKeys: (default=[9, 13]) The key codes for keys which will select
* the current highlighted element. The defaults are tab, enter.
* </li></ul>
*
* @param {Object} [callbacks]
* An object containing optional callback functions on certain events. See
* {@link callbacks} for details. These callbacks should be used when
* customization of the default behavior of Better Autocomplete is required.
*
* @returns {Object}
* The jQuery object with the same element selection, for chaining.
*/
(function($) {
$.fn.betterAutocomplete = function(method) {
/*
* Each method expects the "this" object to be a valid DOM text input node.
* The methods "enable", "disable" and "destroy" expects an instance of a
* BetterAutocomplete object as their first argument.
*/
var methods = {
init: function(resource, options, callbacks) {
var $input = $(this),
bac = new BetterAutocomplete($input, resource, options, callbacks);
$input.data('better-autocomplete', bac);
bac.enable();
},
enable: function(bac) {
bac.enable();
},
disable: function(bac) {
bac.disable();
},
destroy: function(bac) {
bac.destroy();
}
}, args = Array.prototype.slice.call(arguments, 1);
// Method calling logic
this.each(function() {
switch (method) {
case 'init':
methods[method].apply(this, args);
break;
case 'enable':
case 'disable':
case 'destroy':
var bac = $(this).data('better-autocomplete');
if (bac instanceof BetterAutocomplete) {
methods[method].call(this, bac);
}
break;
default:
$.error(['Method', method,
'does not exist in jQuery.betterAutocomplete.'].join(' '));
}
});
// Maintain chainability
return this;
};
/**
* The BetterAutocomplete constructor function. Returns a BetterAutocomplete
* instance object.
*
* @private @constructor
* @name BetterAutocomplete
*
* @param {Object} $input
* A single input element wrapped in jQuery.
*/
var BetterAutocomplete = function($input, resource, options, callbacks) {
var lastRenderedQuery = '',
cache = {}, // Key-valued caching of search results
cacheOrder = [], // Array of query strings, in the order they are added
cacheSize = 0, // Keep count of the cache's size
timer, // Used for options.delay
activeRemoteCalls = [], // A flat array of query strings that are pending
disableMouseHighlight = false, // Suppress the autotriggered mouseover event
inputEvents = {},
isLocal = ($.type(resource) != 'string'),
$results = $('<ul />').addClass('better-autocomplete'),
hiddenResults = true, // $results are hidden
preventBlurTimer = null; // IE bug workaround, see below in code.
options = $.extend({
charLimit: isLocal ? 1 : 3,
delay: 350, // milliseconds
caseSensitive: false,
cacheLimit: isLocal ? 0 : 256, // Number of result objects
remoteTimeout: 10000, // milliseconds
crossOrigin: false,
selectKeys: [9, 13] // [tab, enter]
}, options);
callbacks = $.extend({}, defaultCallbacks, callbacks);
callbacks.insertSuggestionList($results, $input);
inputEvents.focus = function() {
// If the blur timer is active, a redraw is redundant.
preventBlurTimer || redraw(true);
};
inputEvents.blur = function() {
// If the blur prevention timer is active, refocus the input, since the
// blur event can not be cancelled.
if (preventBlurTimer) {
$input.focus();
}
else {
// The input has already lost focus, so redraw the suggestion list.
redraw();
}
};
inputEvents.keydown = function(event) {
var index = getHighlighted();
// If an arrow key is pressed and a result is highlighted
if ($.inArray(event.keyCode, [38, 40]) >= 0 && index >= 0) {
var newIndex,
size = $('.result', $results).length;
switch (event.keyCode) {
case 38: // Up arrow
newIndex = Math.max(0, index - 1);
break;
case 40: // Down arrow
newIndex = Math.min(size - 1, index + 1);
break;
}
disableMouseHighlight = true;
setHighlighted(newIndex, true);
return false;
}
// A select key has been pressed
else if ($.inArray(event.keyCode, options.selectKeys) >= 0 &&
!event.shiftKey && !event.ctrlKey && !event.altKey &&
!event.metaKey) {
select();
return event.keyCode == 9; // Never cancel tab
}
};
inputEvents.keyup = inputEvents.click = reprocess;
$('.result', $results[0]).live({
// When the user hovers a result with the mouse, highlight it.
mouseover: function() {
if (disableMouseHighlight) {
return;
}
setHighlighted($('.result', $results).index($(this)));
},
mousemove: function() {
// Enable mouseover again.
disableMouseHighlight = false;
},
mousedown: function() {
select();
return false;
}
});
// Prevent blur when clicking on group titles, scrollbars etc.,
// This event is triggered after the others because of bubbling.
$results.mousedown(function() {
// Bug in IE where clicking on scrollbar would trigger a blur event for the
// input field, despite using preventDefault() on the mousedown event.
// This workaround locks the blur event on the input for a small time.
clearTimeout(preventBlurTimer);
preventBlurTimer = setTimeout(function() {
preventBlurTimer = null;
}, 50);
return false;
});
/*
* PUBLIC METHODS
*/
/**
* Enable this instance.
*/
this.enable = function() {
// Turn off the browser's autocompletion
$input
.attr('autocomplete', 'OFF')
.attr('aria-autocomplete', 'list');
$input.bind(inputEvents);
};
/**
* Disable this instance.
*/
this.disable = function() {
$input
.removeAttr('autocomplete')
.removeAttr('aria-autocomplete');
$results.hide();
$input.unbind(inputEvents);
};
/**
* Disable and remove this instance. This instance should not be reused.
*/
this.destroy = function() {
$results.remove();
$input.unbind(inputEvents);
$input.removeData('better-autocomplete');
};
/*
* PRIVATE METHODS
*/
/**
* Add an array of results to the cache. Internal methods always reads from
* the cache, so this method must be invoked even when caching is not used,
* e.g. when using local results. This method automatically clears as much of
* the cache as required to fit within the cache limit.
*
* @param {String} query
* The query to set the results to.
*
* @param {Array[Object]} results
* The array of results for this query.
*/
var cacheResults = function(query, results) {
cacheSize += results.length;
// Now reduce size until it fits
while (cacheSize > options.cacheLimit && cacheOrder.length) {
var key = cacheOrder.shift();
cacheSize -= cache[key].length;
delete cache[key];
}
cacheOrder.push(query);
cache[query] = results;
};
/**
* Set highlight to a specific result item
*
* @param {Number} index
* The result's index, starting at 0.
*
* @param {Boolean} [autoScroll]
* (default=false) If scrolling of the results list should be automated.
*/
var setHighlighted = function(index, autoScroll) {
// Scrolling upwards
var up = index == 0 || index < getHighlighted(),
$scrollTo = $('.result', $results)
.removeClass('highlight')
.eq(index).addClass('highlight');
if (!autoScroll) {
return;
}
// Scrolling up, then make sure to show the group title
if ($scrollTo.prev().is('.group') && up) {
$scrollTo = $scrollTo.prev();
}
// Is $scrollTo partly above the visible region?
if ($scrollTo.position().top < 0) {
$results.scrollTop($scrollTo.position().top + $results.scrollTop());
}
// Or is it partly below the visible region?
else if (($scrollTo.position().top + $scrollTo.outerHeight()) >
$results.height()) {
$results.scrollTop($scrollTo.position().top + $results.scrollTop() +
$scrollTo.outerHeight() - $results.height());
}
};
/**
* Retrieve the index of the currently highlighted result item
*
* @returns {Number}
* The result's index or -1 if no result is highlighted.
*/
var getHighlighted = function() {
return $('.result', $results).index($('.result.highlight', $results));
};
/**
* Select the current highlighted element, if any.
*/
var select = function() {
var $result = $('.result', $results).eq(getHighlighted());
if (!$result.length) {
return; // No selectable element
}
var result = $result.data('result');
callbacks.select(result, $input);
// Redraw again, if the callback changed focus or content
reprocess();
};
/**
* Fetch results asynchronously via AJAX.
* Errors are ignored.
*
* @param {String} query
* The query string.
*/
var fetchResults = function(query) {
// Synchronously fetch local data
if (isLocal) {
cacheResults(query, callbacks.queryLocalResults(query, resource,
options.caseSensitive));
redraw();
}
// Asynchronously fetch remote data
else {
activeRemoteCalls.push(query);
var url = callbacks.constructURL(resource, query);
callbacks.beginFetching($input);
callbacks.fetchRemoteData(url, function(data) {
var searchResults = callbacks.processRemoteData(data);
if (!$.isArray(searchResults)) {
searchResults = [];
}
cacheResults(query, searchResults);
// Remove the query from active remote calls, since it's finished
activeRemoteCalls = $.grep(activeRemoteCalls, function(value) {
return value != query;
});
if (!activeRemoteCalls.length) {
callbacks.finishFetching($input);
}
redraw();
}, options.remoteTimeout, options.crossOrigin);
}
};
/**
* Reprocess the contents of the input field, fetch data and redraw if
* necessary.
*/
function reprocess() {
var query = callbacks.canonicalQuery($input.val(), options.caseSensitive);
clearTimeout(timer);
// Indicate that timer is inactive
timer = null;
redraw();
if (query.length >= options.charLimit && !$.isArray(cache[query]) &&
$.inArray(query, activeRemoteCalls) == -1) {
// Fetching is required
$results.empty();
if (isLocal) {
fetchResults(query);
}
else {
timer = setTimeout(function() {
fetchResults(query);
timer = null;
}, options.delay);
}
}
};
/**
* Redraws the autocomplete list based on current query and focus.
*
* @param {Boolean} [focus]
* (default=false) Force to treat the input element like it's focused.
*/
var redraw = function(focus) {
var query = callbacks.canonicalQuery($input.val(), options.caseSensitive);
// The query does not exist in db
if (!$.isArray(cache[query])) {
lastRenderedQuery = null;
$results.empty();
}
// The query exists and is not already rendered
else if (lastRenderedQuery !== query) {
lastRenderedQuery = query;
renderResults(cache[query]);
setHighlighted(0);
}
// Finally show/hide based on focus and emptiness
if (($input.is(':focus') || focus) && !$results.is(':empty')) {
$results.filter(':hidden').show() // Show if hidden
.scrollTop($results.data('scroll-top')); // Reset the lost scrolling
if (hiddenResults) {
hiddenResults = false;
callbacks.afterShow($results);
}
}
else if ($results.is(':visible')) {
// Store the scrolling position for later
$results.data('scroll-top', $results.scrollTop())
.hide(); // Hiding it resets it's scrollTop
if (!hiddenResults) {
hiddenResults = true;
callbacks.afterHide($results);
}
}
};
/**
* Regenerate the DOM content within the results list for a given set of
* results. Heavy method, use only when necessary.
*
* @param {Array[Object]} results
* An array of result objects to render.
*/
var renderResults = function(results) {
$results.empty();
var groups = {}; // Key is the group name, value is the heading element.
$.each(results, function(index, result) {
if ($.type(result) != 'object') {
return; // Continue
}
var output = callbacks.themeResult(result);
if ($.type(output) != 'string') {
return; // Continue
}
// Add the group if it doesn't exist
var group = callbacks.getGroup(result);
if ($.type(group) == 'string' && !groups[group]) {
var $groupHeading = $('<li />').addClass('group')
.append($('<h3 />').html(group))
.appendTo($results);
groups[group] = $groupHeading;
}
var $result = $('<li />').addClass('result')
.append(output)
.data('result', result) // Store the result object on this DOM element
.addClass(result.addClass);
// First groupless item
if ($.type(group) != 'string' &&
!$results.children().first().is('.result')) {
$results.prepend($result);
return; // Continue
}
var $traverseFrom = ($.type(group) == 'string') ?
groups[group] : $results.children().first();
var $target = $traverseFrom.nextUntil('.group').last();
$result.insertAfter($target.length ? $target : $traverseFrom);
});
};
};
/*
* CALLBACK METHODS
*/
/**
* These callbacks are supposed to be overridden by you when you need
* customization of the default behavior. When you are overriding a callback
* function, it is a good idea to copy the source code from the default
* callback function, as a skeleton.
*
* @name callbacks
* @namespace
*/
var defaultCallbacks = {
/**
* @lends callbacks.prototype
*/
/**
* Gets fired when the user selects a result by clicking or using the
* keyboard to select an element.
*
* <br /><br /><em>Default behavior: Inserts the result's title into the
* input field.</em>
*
* @param {Object} result
* The result object that was selected.
*
* @param {Object} $input
* The input DOM element, wrapped in jQuery.
*/
select: function(result, $input) {
$input.val(result.title);
},
/**
* Given a result object, theme it to HTML.
*
* <br /><br /><em>Default behavior: Wraps result.title in an h4 tag, and
* result.description in a p tag. Note that no sanitization of malicious
* scripts is done here. Whatever is within the title/description is just
* printed out. May contain HTML.</em>
*
* @param {Object} result
* The result object that should be rendered.
*
* @returns {String}
* HTML output, will be wrapped in a list element.
*/
themeResult: function(result) {
var output = [];
if ($.type(result.title) == 'string') {
output.push('<h4>', result.title, '</h4>');
}
if ($.type(result.description) == 'string') {
output.push('<p>', result.description, '</p>');
}
return output.join('');
},
/**
* Retrieve local results from the local resource by providing a query
* string.
*
* <br /><br /><em>Default behavior: Automatically handles arrays, if the
* data inside each element is either a plain string or a result object.
* If it is a result object, it will match the query string against the
* title and description property. Search is not case sensitive.</em>
*
* @param {String} query
* The query string, unescaped. May contain any UTF-8 character.
* If case insensitive, it already is lowercased.
*
* @param {Object} resource
* The resource provided in the {@link jQuery.betterAutocomplete} init
* constructor.
*
* @param {Boolean} caseSensitive
* From options.caseSensitive, the searching should be case sensitive.
*
* @returns {Array[Object]}
* A flat array containing pure result objects. May be an empty array.
*/
queryLocalResults: function(query, resource, caseSensitive) {
if (!$.isArray(resource)) {
// Per default Better Autocomplete only handles arrays
return [];
}
var results = [];
$.each(resource, function(i, value) {
switch ($.type(value)) {
case 'string': // Flat array of strings
if ((caseSensitive ? value : value.toLowerCase())
.indexOf(query) >= 0) {
// Match found
results.push({ title: value });
}
break;
case 'object': // Array of result objects
if ($.type(value.title) == 'string' &&
(caseSensitive ? value.title : value.title.toLowerCase())
.indexOf(query) >= 0) {
// Match found in title field
results.push(value);
}
else if ($.type(value.description) == 'string' &&
(caseSensitive ? value.description :
value.description.toLowerCase()).indexOf(query) >= 0) {
// Match found in description field
results.push(value);
}
break;
}
});
return results;
},
/**
* Fetch remote result data and return it using completeCallback when
* fetching is finished. Must be asynchronous in order to not freeze the
* Better Autocomplete instance.
*
* <br /><br /><em>Default behavior: Fetches JSON data from the url, using
* the jQuery.ajax() method. Errors are ignored.</em>
*
* @param {String} url
* The URL to fetch data from.
*
* @param {Function} completeCallback
* This function must be called, even if an error occurs. It takes zero
* or one parameter: the data that was fetched.
*
* @param {Number} timeout
* The preferred timeout for the request. This callback should respect
* the timeout.
*
* @param {Boolean} crossOrigin
* True if a cross origin request should be performed.
*/
fetchRemoteData: function(url, completeCallback, timeout, crossOrigin) {
$.ajax({
url: url,
dataType: crossOrigin && !$.support.cors ? 'jsonp' : 'json',
timeout: timeout,
success: function(data, textStatus) {
completeCallback(data);
},
error: function(jqXHR, textStatus, errorThrown) {
completeCallback();
}
});
},
/**
* Process remote fetched data by extracting an array of result objects
* from it. This callback is useful if the fetched data is not the plain
* results array, but a more complicated object which does contain results.
*
* <br /><br /><em>Default behavior: If the data is defined and is an
* array, return it. Otherwise return an empty array.</em>
*
* @param {mixed} data
* The raw data recieved from the server. Can be undefined.
*
* @returns {Array[Object]}
* A flat array containing result objects. May be an empty array.
*/
processRemoteData: function(data) {
if ($.isArray(data)) {
return data;
}
else {
return [];
}
},
/**
* From a given result object, return it's group name (if any). Used for
* grouping results together.
*
* <br /><br /><em>Default behavior: If the result has a "group" property
* defined, return it.</em>
*
* @param {Object} result
* The result object.
*
* @returns {String}
* The group name, may contain HTML. If no group, don't return anything.
*/
getGroup: function(result) {
if ($.type(result.group) == 'string') {
return result.group;
}
},
/**
* Called when remote fetching begins.
*
* <br /><br /><em>Default behavior: Adds the CSS class "fetching" to the
* input field, for styling purposes.</em>
*
* @param {Object} $input
* The input DOM element, wrapped in jQuery.
*/
beginFetching: function($input) {
$input.addClass('fetching');
},
/**
* Called when fetching is finished. All active requests must finish before
* this function is called.
*
* <br /><br /><em>Default behavior: Removes the "fetching" class.</em>
*
* @param {Object} $input
* The input DOM element, wrapped in jQuery.
*/
finishFetching: function($input) {
$input.removeClass('fetching');
},
/**
* Executed after the suggestion list has been shown.
*
* @param {Object} $results
* The suggestion list UL element, wrapped in jQuery.
*
* <br /><br /><em>Default behavior: Does nothing.</em>
*/
afterShow: function($results) {},
/**
* Executed after the suggestion list has been hidden.
*
* @param {Object} $results
* The suggestion list UL element, wrapped in jQuery.
*
* <br /><br /><em>Default behavior: Does nothing.</em>
*/
afterHide: function($results) {},
/**
* Construct the remote fetching URL.
*
* <br /><br /><em>Default behavior: Adds "?q=query" to the path. The query
* string is URL encoded.</em>
*
* @param {String} path
* The path given in the {@link jQuery.betterAutocomplete} constructor.
*
* @param {String} query
* The raw query string. Remember to URL encode this to prevent illegal
* character errors.
*
* @returns {String}
* The URL, ready for fetching.
*/
constructURL: function(path, query) {
return path + '?q=' + encodeURIComponent(query);
},
/**
* To ease up on server load, treat similar strings the same.
*
* <br /><br /><em>Default behavior: Trims the query from leading and
* trailing whitespace.</em>
*
* @param {String} rawQuery
* The user's raw input.
*
* @param {Boolean} caseSensitive
* Case sensitive. Will convert to lowercase if false.
*
* @returns {String}
* The canonical query associated with this string.
*/
canonicalQuery: function(rawQuery, caseSensitive) {
var query = $.trim(rawQuery);
if (!caseSensitive) {
query = query.toLowerCase();
}
return query;
},
/**
* Insert the results list into the DOM and position it properly.
*
* <br /><br /><em>Default behavior: Inserts suggestion list directly
* after the input element and sets an absolute position using
* jQuery.position() for determining left/top values. Also adds a nice
* looking box-shadow to the list.</em>
*
* @param {Object} $results
* The UL list element to insert, wrapped in jQuery.
*
* @param {Object} $input
* The text input element, wrapped in jQuery.
*/
insertSuggestionList: function($results, $input) {
$results.width($input.outerWidth() - 2) // Subtract border width.
.css({
position: 'absolute',
left: $input.position().left,
top: $input.position().top + $input.outerHeight(),
zIndex: 10,
maxHeight: '330px',
// Visually indicate that results are in the topmost layer
boxShadow: '0 0 15px rgba(0, 0, 0, 0.5)'
})
.hide()
.insertAfter($input);
}
};
/*
* jQuery focus selector, required by Better Autocomplete.
*
* @see http://stackoverflow.com/questions/967096/using-jquery-to-test-if-an-input-has-focus/2684561#2684561
*/
var filters = $.expr[':'];
if (!filters.focus) {
filters.focus = function(elem) {
return elem === document.activeElement && (elem.type || elem.href);
};
}
})(jQuery);

View File

@ -0,0 +1,41 @@
/**
* jQuery Cookie plugin
*
* Copyright (c) 2010 Klaus Hartl (stilbuero.de)
* Dual licensed under the MIT and GPL licenses:
* http://www.opensource.org/licenses/mit-license.php
* http://www.gnu.org/licenses/gpl.html
*
*/
jQuery.cookie = function (key, value, options) {
// key and at least value given, set cookie...
if (arguments.length > 1 && String(value) !== "[object Object]") {
options = jQuery.extend({}, options);
if (value === null || value === undefined) {
options.expires = -1;
}
if (typeof options.expires === 'number') {
var days = options.expires, t = options.expires = new Date();
t.setDate(t.getDate() + days);
}
value = String(value);
return (document.cookie = [
encodeURIComponent(key), '=',
options.raw ? value : encodeURIComponent(value),
options.expires ? '; expires=' + options.expires.toUTCString() : '', // use expires attribute, max-age is not supported by IE
options.path ? '; path=' + options.path : '',
options.domain ? '; domain=' + options.domain : '',
options.secure ? '; secure' : ''
].join(''));
}
// key and possibly options given, get cookie...
options = value || {};
var result, decode = options.raw ? function (s) { return s; } : decodeURIComponent;
return (result = new RegExp('(?:^|; )' + encodeURIComponent(key) + '=([^;]*)').exec(document.cookie)) ? decode(result[1]) : null;
};

View File

@ -0,0 +1,162 @@
/**
* @name Elastic
* @descripton Elastic is jQuery plugin that grow and shrink your textareas automatically
* @version 1.6.11
* @requires jQuery 1.2.6+
*
* @author Jan Jarfalk
* @author-email jan.jarfalk@unwrongest.com
* @author-website http://www.unwrongest.com
*
* @licence MIT License - http://www.opensource.org/licenses/mit-license.php
*/
(function($){
jQuery.fn.extend({
elastic: function() {
// We will create a div clone of the textarea
// by copying these attributes from the textarea to the div.
var mimics = [
'paddingTop',
'paddingRight',
'paddingBottom',
'paddingLeft',
'fontSize',
'lineHeight',
'fontFamily',
'width',
'fontWeight',
'border-top-width',
'border-right-width',
'border-bottom-width',
'border-left-width',
'borderTopStyle',
'borderTopColor',
'borderRightStyle',
'borderRightColor',
'borderBottomStyle',
'borderBottomColor',
'borderLeftStyle',
'borderLeftColor'
];
return this.each( function() {
// Elastic only works on textareas
if ( this.type !== 'textarea' ) {
return false;
}
var $textarea = jQuery(this),
$twin = jQuery('<div />').css({
'position' : 'absolute',
'display' : 'none',
'word-wrap' : 'break-word',
'white-space' :'pre-wrap'
}),
lineHeight = parseInt($textarea.css('line-height'),10) || parseInt($textarea.css('font-size'),'10'),
minheight = parseInt($textarea.css('height'),10) || lineHeight*3,
maxheight = parseInt($textarea.css('max-height'),10) || Number.MAX_VALUE,
goalheight = 0;
// Opera returns max-height of -1 if not set
if (maxheight < 0) { maxheight = Number.MAX_VALUE; }
// Append the twin to the DOM
// We are going to meassure the height of this, not the textarea.
$twin.appendTo($textarea.parent());
// Copy the essential styles (mimics) from the textarea to the twin
var i = mimics.length;
while(i--){
$twin.css(mimics[i].toString(),$textarea.css(mimics[i].toString()));
}
// Updates the width of the twin. (solution for textareas with widths in percent)
function setTwinWidth(){
var curatedWidth = Math.floor(parseInt($textarea.width(),10));
if($twin.width() !== curatedWidth){
$twin.css({'width': curatedWidth + 'px'});
// Update height of textarea
update(true);
}
}
// Sets a given height and overflow state on the textarea
function setHeightAndOverflow(height, overflow){
var curratedHeight = Math.floor(parseInt(height,10));
if($textarea.height() !== curratedHeight){
$textarea.css({'height': curratedHeight + 'px','overflow':overflow});
}
}
// This function will update the height of the textarea if necessary
function update(forced) {
// Get curated content from the textarea.
var textareaContent = $textarea.val().replace(/&/g,'&amp;').replace(/ {2}/g, '&nbsp;').replace(/<|>/g, '&gt;').replace(/\n/g, '<br />');
// Compare curated content with curated twin.
var twinContent = $twin.html().replace(/<br>/ig,'<br />');
if(forced || textareaContent+'&nbsp;' !== twinContent){
// Add an extra white space so new rows are added when you are at the end of a row.
$twin.html(textareaContent+'&nbsp;');
// Change textarea height if twin plus the height of one line differs more than 3 pixel from textarea height
if(Math.abs($twin.height() + lineHeight - $textarea.height()) > 3){
var goalheight = $twin.height()+lineHeight;
if(goalheight >= maxheight) {
setHeightAndOverflow(maxheight,'auto');
} else if(goalheight <= minheight) {
setHeightAndOverflow(minheight,'hidden');
} else {
setHeightAndOverflow(goalheight,'hidden');
}
}
}
}
// Hide scrollbars
$textarea.css({'overflow':'hidden'});
// Update textarea size on keyup, change, cut and paste
$textarea.bind('keyup change cut paste', function(){
update();
});
// Update width of twin if browser or textarea is resized (solution for textareas with widths in percent)
$(window).bind('resize', setTwinWidth);
$textarea.bind('resize', setTwinWidth);
$textarea.bind('update', update);
// Compact textarea on blur
$textarea.bind('blur',function(){
if($twin.height() < maxheight){
if($twin.height() > minheight) {
$textarea.height($twin.height());
} else {
$textarea.height(minheight);
}
}
});
// And this line is to catch the browser paste event
$textarea.bind('input paste',function(e){ setTimeout( update, 250); });
// Run update once when elastic is initialized
update();
});
}
});
})(jQuery);

View File

@ -0,0 +1,220 @@
(function($) {
$.widget('ui.viewport', {
options:{
binderClass: 'viewportBinder',
contentClass: 'viewportContent',
position: 'center',
content: false,
height: false,
width: false
},
_create: function() {
var content = this.options.content;
var isObject = typeof(content) == 'object';
if (isObject && content.tagName != null) {
this.options.content = $(content);
} else if (isObject && $.isArray(content)) {
this.options.content = $(content);
}
this.viewport = createViewport(this.element, this.options);
this.viewport.init();
this.viewport.adjust();
},
content: function() { return this.viewport.content; },
binder: function() { return this.viewport.binder; },
adjust: function() { this.viewport.adjust(); },
update: function() {
this.viewport.updateContentSize();
this.viewport.adjust();
},
size: function(height, width) {
if (height == null || width == null) {
return this.viewport.getContentSize();
}
this.viewport.setContentHeight(height);
this.viewport.setContentWidth(width);
},
height: function(height) {
if (height == null) {
return this.viewport.getContentSize().height;
}
this.viewport.setContentHeight(height);
},
width: function(width) {
if (width == null) {
return this.viewport.getContentSize().width;
}
this.viewport.setContentWidth(width);
}
});
function createViewport(element, options) {
var binder = $('<div class="' + options.binderClass + '"></div>');
var content = $('<div class="' + options.contentClass + '"></div>');
binder.css({position: 'absolute', overflow: 'hidden'});
element.css({position: 'relative', overflow: 'hidden'});
content.css({position: 'absolute'});
var contents = false;
if (options.content == false && element.contents().length) {
contents = element.contents().detach();
} else if (options.content != false) {
contents = options.content.detach();
}
content.append(contents);
binder.append(content);
element.append(binder);
var centerHorizontal = true,
centerVertical = true,
heightDiff = 0,
widthDiff = 0;
var contentPosition = {top: 0, left: 0};
var contentSize = {height: 0, width: 0};
var viewportSize = {height: 0, width: 0};
element.bind('dragstop', function(event, ui) {
if(contentPosition.top != ui.position.top) {
centerHorizontal = false;
}
if(contentPosition.left != ui.position.left) {
centerVertical = false;
}
contentPosition.left = ui.position.left;
contentPosition.top = ui.position.top;
});
function init() {
updateContentSize();
updateContentPosition();
}
function updateContentPosition() {
var position = options.position.split(' ');
if (position.indexOf('bottom') != -1) {
centerVertical = false;
contentPosition.top = viewportSize.height - contentSize.height;
} else if (position.indexOf('top') != -1) {
centerVertical = false;
contentPosition.top = 0;
}
if (position.indexOf('right') != -1) {
centerHorizontal = false;
contentPosition.left = viewportSize.width - contentSize.width;
} else if (position.indexOf('left') != -1) {
centerHorizontal = false;
contentPosition.left = 0;
}
}
function updateContentSize() {
if (options.width != false && options.height != false) {
content.height(options.height);
content.width(options.width);
} else if (contents != false) {
content.height(contents.height());
content.width(contents.width());
}
contentSize = {
height: content.height(),
width: content.width()
};
}
function adjust() {
viewportSize.height = element.height();
viewportSize.width = element.width();
var diff;
if (viewportSize.height > contentSize.height) {
content.css('top', 0);
binder.height(contentSize.height);
binder.css('top', Math.floor(viewportSize.height / 2) -
Math.floor(contentSize.height / 2))
} else {
diff = contentSize.height - viewportSize.height;
binder.height(viewportSize.height + diff * 2);
binder.css('top', -diff);
if (centerVertical) {
contentPosition.top = Math.floor(diff / 2);
content.css('top', contentPosition.top);
} else {
var newTop = contentPosition.top + (diff - heightDiff);
newTop = newTop >= 0 ? newTop : 0;
contentPosition.top = newTop;
content.css('top', newTop);
}
heightDiff = diff;
}
if (viewportSize.width > contentSize.width) {
content.css('left', 0);
binder.width(contentSize.width);
binder.css('left', Math.floor(viewportSize.width / 2) -
Math.floor(contentSize.width / 2));
} else {
diff = contentSize.width - viewportSize.width;
binder.width(viewportSize.width + diff * 2);
binder.css('left', -diff);
if (centerHorizontal) {
contentPosition.left = Math.floor(diff / 2);
content.css('left', contentPosition.left);
} else {
var newLeft = contentPosition.left + (diff - widthDiff);
newLeft = newLeft >= 0 ? newLeft : 0;
contentPosition.left = newLeft;
content.css('left', newLeft);
}
widthDiff = diff;
}
}
function setContentHeight(height) {
if (height != null) {
contentSize.height = height;
content.height(height);
}
}
function setContentWidth(width) {
if (width != null) {
contentSize.width = width;
content.width(width);
}
}
function getContentSize() {
return contentSize;
}
return {
init: init,
adjust: adjust,
updateContentSize: updateContentSize,
setContentHeight: setContentHeight,
setContentWidth: setContentWidth,
getContentSize: getContentSize,
content: content,
binder: binder
};
}
})(jQuery);

525
vendor/assets/javascripts/moment.js vendored Normal file
View File

@ -0,0 +1,525 @@
// Moment.js
//
// (c) 2011 Tim Wood
// Moment.js is freely distributable under the terms of the MIT license.
//
// Version 1.1.2
(function (Date, undefined) {
var moment,
round = Math.round,
languages = {},
hasModule = (typeof module !== 'undefined'),
paramsToParse = 'months|monthsShort|weekdays|weekdaysShort|longDateFormat|relativeTime|ordinal|meridiem'.split('|'),
i,
VERSION = "1.1.2",
shortcuts = 'Month|Date|Hours|Minutes|Seconds'.split('|');
// left zero fill a number
// see http://jsperf.com/left-zero-filling for performance comparison
function leftZeroFill(number, targetLength) {
var output = number + '';
while (output.length < targetLength) {
output = '0' + output;
}
return output;
}
// helper function for _.addTime and _.subtractTime
function dateAddRemove(date, _input, adding, val) {
var isString = (typeof _input === 'string'),
input = isString ? {} : _input,
ms, M, currentDate;
if (isString && val) {
input[_input] = val;
}
ms = (input.ms || input.milliseconds || 0) +
(input.s || input.seconds || 0) * 1e3 + // 1000
(input.m || input.minutes || 0) * 6e4 + // 1000 * 60
(input.h || input.hours || 0) * 36e5 + // 1000 * 60 * 60
(input.d || input.days || 0) * 864e5 + // 1000 * 60 * 60 * 24
(input.w || input.weeks || 0) * 6048e5; // 1000 * 60 * 60 * 24 * 7
M = (input.M || input.months || 0) +
(input.y || input.years || 0) * 12;
if (ms) {
date.setTime(+date + ms * adding);
}
if (M) {
currentDate = date.getDate();
date.setDate(1);
date.setMonth(date.getMonth() + M * adding);
date.setDate(Math.min(new Date(date.getFullYear(), date.getMonth() + 1, 0).getDate(), currentDate));
}
return date;
}
// check if is an array
function isArray(input) {
return Object.prototype.toString.call(input) === '[object Array]';
}
// convert an array to a date.
// the array should mirror the parameters below
// note: all values past the year are optional and will default to the lowest possible value.
// [year, month, day , hour, minute, second, millisecond]
function dateFromArray(input) {
return new Date(input[0], input[1] || 0, input[2] || 1, input[3] || 0, input[4] || 0, input[5] || 0, input[6] || 0);
}
// format date using native date object
function formatDate(date, inputString) {
var currentMonth = date.getMonth(),
currentDate = date.getDate(),
currentYear = date.getFullYear(),
currentDay = date.getDay(),
currentHours = date.getHours(),
currentMinutes = date.getMinutes(),
currentSeconds = date.getSeconds(),
charactersToReplace = /(\\)?(Mo|MM?M?M?|Do|DDDo|DD?D?D?|dddd?|do?|w[o|w]?|YYYY|YY|a|A|hh?|HH?|mm?|ss?|zz?|LL?L?L?)/g,
nonuppercaseLetters = /[^A-Z]/g,
timezoneRegex = /\([A-Za-z ]+\)|:[0-9]{2} [A-Z]{3} /g,
ordinal = moment.ordinal,
meridiem = moment.meridiem;
// check if the character is a format
// return formatted string or non string.
//
// uses switch/case instead of an object of named functions (like http://phpjs.org/functions/date:380)
// for minification and performance
// see http://jsperf.com/object-of-functions-vs-switch for performance comparison
function replaceFunction(input) {
// create a couple variables to be used later inside one of the cases.
var a, b;
switch (input) {
// MONTH
case 'M' :
return currentMonth + 1;
case 'Mo' :
return (currentMonth + 1) + ordinal(currentMonth + 1);
case 'MM' :
return leftZeroFill(currentMonth + 1, 2);
case 'MMM' :
return moment.monthsShort[currentMonth];
case 'MMMM' :
return moment.months[currentMonth];
// DAY OF MONTH
case 'D' :
return currentDate;
case 'Do' :
return currentDate + ordinal(currentDate);
case 'DD' :
return leftZeroFill(currentDate, 2);
// DAY OF YEAR
case 'DDD' :
a = new Date(currentYear, currentMonth, currentDate);
b = new Date(currentYear, 0, 1);
return ~~ (((a - b) / 864e5) + 1.5);
case 'DDDo' :
a = replaceFunction('DDD');
return a + ordinal(a);
case 'DDDD' :
return leftZeroFill(replaceFunction('DDD'), 3);
// WEEKDAY
case 'd' :
return currentDay;
case 'do' :
return currentDay + ordinal(currentDay);
case 'ddd' :
return moment.weekdaysShort[currentDay];
case 'dddd' :
return moment.weekdays[currentDay];
// WEEK OF YEAR
case 'w' :
a = new Date(currentYear, currentMonth, currentDate - currentDay + 5);
b = new Date(a.getFullYear(), 0, 4);
return ~~ ((a - b) / 864e5 / 7 + 1.5);
case 'wo' :
a = replaceFunction('w');
return a + ordinal(a);
case 'ww' :
return leftZeroFill(replaceFunction('w'), 2);
// YEAR
case 'YY' :
return leftZeroFill(currentYear % 100, 2);
case 'YYYY' :
return currentYear;
// AM / PM
case 'a' :
return currentHours > 11 ? meridiem.pm : meridiem.am;
case 'A' :
return currentHours > 11 ? meridiem.PM : meridiem.AM;
// 24 HOUR
case 'H' :
return currentHours;
case 'HH' :
return leftZeroFill(currentHours, 2);
// 12 HOUR
case 'h' :
return currentHours % 12 || 12;
case 'hh' :
return leftZeroFill(currentHours % 12 || 12, 2);
// MINUTE
case 'm' :
return currentMinutes;
case 'mm' :
return leftZeroFill(currentMinutes, 2);
// SECOND
case 's' :
return currentSeconds;
case 'ss' :
return leftZeroFill(currentSeconds, 2);
// TIMEZONE
case 'zz' :
// depreciating 'zz' fall through to 'z'
case 'z' :
return (date.toString().match(timezoneRegex) || [''])[0].replace(nonuppercaseLetters, '');
// LONG DATES
case 'L' :
case 'LL' :
case 'LLL' :
case 'LLLL' :
return formatDate(date, moment.longDateFormat[input]);
// DEFAULT
default :
return input.replace("\\", "");
}
}
return inputString.replace(charactersToReplace, replaceFunction);
}
// date from string and format string
function makeDateFromStringAndFormat(string, format) {
var inArray = [0],
tokenCharacters = /(\\)?(MM?|DD?D?D?|YYYY|YY|a|A|hh?|HH?|mm?|ss?)/g,
inputCharacters = /(\\)?([0-9]+|am|pm)/gi,
inputParts = string.match(inputCharacters),
formatParts = format.match(tokenCharacters),
i,
isPm;
// function to convert string input to date
function addTime(format, input) {
switch (format) {
// MONTH
case 'M' :
// fall through to MM
case 'MM' :
inArray[1] = ~~input - 1;
break;
// DAY OF MONTH
case 'D' :
// fall through to DDDD
case 'DD' :
// fall through to DDDD
case 'DDD' :
// fall through to DDDD
case 'DDDD' :
inArray[2] = ~~input;
break;
// YEAR
case 'YY' :
input = ~~input;
inArray[0] = input + (input > 70 ? 1900 : 2000);
break;
case 'YYYY' :
inArray[0] = ~~input;
break;
// AM / PM
case 'a' :
// fall through to A
case 'A' :
isPm = (input.toLowerCase() === 'pm');
break;
// 24 HOUR
case 'H' :
// fall through to hh
case 'HH' :
// fall through to hh
case 'h' :
// fall through to hh
case 'hh' :
inArray[3] = ~~input;
break;
// MINUTE
case 'm' :
// fall through to mm
case 'mm' :
inArray[4] = ~~input;
break;
// SECOND
case 's' :
// fall through to ss
case 'ss' :
inArray[5] = ~~input;
break;
}
}
for (i = 0; i < formatParts.length; i++) {
addTime(formatParts[i], inputParts[i]);
}
// handle am pm
if (isPm && inArray[3] < 12) {
inArray[3] += 12;
}
return dateFromArray(inArray);
}
// compare two arrays, return the number of differences
function compareArrays(array1, array2) {
var len = Math.min(array1.length, array2.length),
lengthDiff = Math.abs(array1.length - array2.length),
diffs = 0,
i;
for (i = 0; i < len; i++) {
if (~~array1[i] !== ~~array2[i]) {
diffs++;
}
}
return diffs + lengthDiff;
}
// date from string and array of format strings
function makeDateFromStringAndArray(string, formats) {
var output,
inputCharacters = /(\\)?([0-9]+|am|pm)/gi,
inputParts = string.match(inputCharacters),
scores = [],
scoreToBeat = 99,
i,
curDate,
curScore;
for (i = 0; i < formats.length; i++) {
curDate = makeDateFromStringAndFormat(string, formats[i]);
curScore = compareArrays(inputParts, formatDate(curDate, formats[i]).match(inputCharacters));
if (curScore < scoreToBeat) {
scoreToBeat = curScore;
output = curDate;
}
}
return output;
}
// Moment prototype object
function Moment(date) {
this._d = date;
}
moment = function (input, format) {
if (input === null) {
return null;
}
var date;
// parse UnderscoreDate object
if (input && input._d instanceof Date) {
date = new Date(+input._d);
// parse string and format
} else if (format) {
if (isArray(format)) {
date = makeDateFromStringAndArray(input, format);
} else {
date = makeDateFromStringAndFormat(input, format);
}
// parse everything else
} else {
date = input === undefined ? new Date() :
input instanceof Date ? input :
isArray(input) ? dateFromArray(input) :
new Date(input);
}
return new Moment(date);
};
// version number
moment.version = VERSION;
// language switching and caching
moment.lang = function (key, values) {
var i, param, req;
if (values) {
languages[key] = values;
}
if (languages[key]) {
for (i = 0; i < paramsToParse.length; i++) {
param = paramsToParse[i];
moment[param] = languages[key][param] || moment[param];
}
} else {
if (hasModule) {
req = require('./lang/' + key);
moment.lang(key, req);
}
}
};
// set default language
moment.lang('en', {
months : "January_February_March_April_May_June_July_August_September_October_November_December".split("_"),
monthsShort : "Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec".split("_"),
weekdays : "Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),
weekdaysShort : "Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),
longDateFormat : {
L : "MM/DD/YYYY",
LL : "MMMM D YYYY",
LLL : "MMMM D YYYY h:mm A",
LLLL : "dddd, MMMM D YYYY h:mm A"
},
meridiem : {
AM : 'AM',
am : 'am',
PM : 'PM',
pm : 'pm'
},
relativeTime : {
future : "in %s",
past : "%s ago",
s : "a few seconds",
m : "a minute",
mm : "%d minutes",
h : "an hour",
hh : "%d hours",
d : "a day",
dd : "%d days",
M : "a month",
MM : "%d months",
y : "a year",
yy : "%d years"
},
ordinal : function (number) {
var b = number % 10;
return (~~ (number % 100 / 10) === 1) ? 'th' :
(b === 1) ? 'st' :
(b === 2) ? 'nd' :
(b === 3) ? 'rd' : 'th';
}
});
// helper function for _date.from() and _date.fromNow()
function substituteTimeAgo(string, number, withoutSuffix) {
var rt = moment.relativeTime[string];
return (typeof rt === 'function') ?
rt(number || 1, !!withoutSuffix, string) :
rt.replace(/%d/i, number || 1);
}
function relativeTime(milliseconds, withoutSuffix) {
var seconds = round(Math.abs(milliseconds) / 1000),
minutes = round(seconds / 60),
hours = round(minutes / 60),
days = round(hours / 24),
years = round(days / 365),
args = seconds < 45 && ['s', seconds] ||
minutes === 1 && ['m'] ||
minutes < 45 && ['mm', minutes] ||
hours === 1 && ['h'] ||
hours < 22 && ['hh', hours] ||
days === 1 && ['d'] ||
days <= 25 && ['dd', days] ||
days <= 45 && ['M'] ||
days < 345 && ['MM', round(days / 30)] ||
years === 1 && ['y'] || ['yy', years];
args[2] = withoutSuffix;
return substituteTimeAgo.apply({}, args);
}
// shortcut for prototype
moment.fn = Moment.prototype = {
clone : function () {
return moment(this);
},
valueOf : function () {
return +this._d;
},
'native' : function () {
return this._d;
},
format : function (inputString) {
return formatDate(this._d, inputString);
},
add : function (input, val) {
this._d = dateAddRemove(this._d, input, 1, val);
return this;
},
subtract : function (input, val) {
this._d = dateAddRemove(this._d, input, -1, val);
return this;
},
diff : function (input, val, asFloat) {
var inputMoment = moment(input),
diff = this._d - inputMoment._d,
year = this.year() - inputMoment.year(),
month = this.month() - inputMoment.month(),
day = this.day() - inputMoment.day(),
output;
if (val === 'months') {
output = year * 12 + month + day / 30;
} else if (val === 'years') {
output = year + month / 12;
} else {
output = val === 'seconds' ? diff / 1e3 : // 1000
val === 'minutes' ? diff / 6e4 : // 1000 * 60
val === 'hours' ? diff / 36e5 : // 1000 * 60 * 60
val === 'days' ? diff / 864e5 : // 1000 * 60 * 60 * 24
val === 'weeks' ? diff / 6048e5 : // 1000 * 60 * 60 * 24 * 7
val === 'days' ? diff / 3600 : diff;
}
return asFloat ? output : round(output);
},
from : function (time, withoutSuffix) {
var difference = this.diff(time),
rel = moment.relativeTime,
output = relativeTime(difference, withoutSuffix);
return withoutSuffix ? output : (difference <= 0 ? rel.past : rel.future).replace(/%s/i, output);
},
fromNow : function (withoutSuffix) {
return this.from(moment(), withoutSuffix);
},
isLeapYear : function () {
var year = this._d.getFullYear();
return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;
}
};
// helper for adding shortcuts
function makeShortcut(name, key) {
moment.fn[name] = function (input) {
if (input != null) {
this._d['set' + key](input);
return this;
} else {
return this._d['get' + key]();
}
};
}
// loop through and add shortcuts
for (i = 0; i < shortcuts.length; i ++) {
makeShortcut(shortcuts[i].toLowerCase(), shortcuts[i]);
}
// add shortcut for year (uses different syntax than the getter/setter 'year' == 'FullYear')
makeShortcut('year', 'FullYear');
// add shortcut for day (no setter)
moment.fn.day = function () {
return this._d.getDay();
};
// CommonJS module is defined
if (hasModule) {
module.exports = moment;
}
if (typeof window !== 'undefined') {
window.moment = moment;
}
})(Date);

View File

@ -0,0 +1,84 @@
/* Stylesheet for Better Autocomplete jQuery plugin by Didrik Nordström */
/**
* Do NOT edit this file! Instead, include another stylesheet with your custom
* overrides and extensions. Copy the selectors you need to edit and place them
* in your CSS file. And remember to include that file AFTER this file.
*/
/* Reset styles for lists */
.better-autocomplete,
.better-autocomplete > .result {
list-style: none;
padding: 0;
margin: 0;
background: none;
}
.better-autocomplete {
text-align: left;
font-family: 'Lucida Grande', Helvetica, Arial, Verdana, sans-serif;
overflow: auto;
cursor: default;
background: white;
border-bottom: 1px solid #bfbfbf;
}
/* Groups */
.better-autocomplete > .group {
position: relative;
padding: 3px;
text-align: center;
background: #e7e7e7;
border-color: #bfbfbf;
border-width: 1px 1px 0 1px;
border-style: solid;
}
.better-autocomplete > .group > h3 {
font-size: 11px;
color: #555;
padding: 0;
margin: 0;
}
/* Results */
.better-autocomplete > .result {
position: relative;
padding: 5px 7px;
background: white;
border-color: #bfbfbf;
border-width: 1px 1px 0 1px;
border-style: solid;
}
.better-autocomplete > .result > h4 {
font-size: 12px;
color: #3c3c3c;
padding: 0;
margin: 0 0 2px 0;
}
.better-autocomplete > .result > p {
font-size: 11px;
line-height: 1.3;
color: #6f6f6f;
padding: 0;
margin: 0;
}
/* Highlighting */
.better-autocomplete > .result.highlight {
background: #0075ba;
border-top-color: #1f4a64;
}
.better-autocomplete > .result.highlight > h4,
.better-autocomplete > .result.highlight > p {
color: white;
}