initial commit, add all the things
This commit is contained in:
commit
bde3536505
|
@ -0,0 +1,5 @@
|
||||||
|
*.gem
|
||||||
|
.bundle
|
||||||
|
Gemfile.lock
|
||||||
|
pkg/*
|
||||||
|
tmp/*
|
|
@ -0,0 +1,4 @@
|
||||||
|
source "http://rubygems.org"
|
||||||
|
|
||||||
|
# Specify your gem's dependencies in vendor-all-the-javascripts.gemspec
|
||||||
|
gemspec
|
|
@ -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/)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
|
@ -0,0 +1,9 @@
|
||||||
|
module Vendor
|
||||||
|
module All
|
||||||
|
module The
|
||||||
|
module Javascripts
|
||||||
|
VERSION = "0.0.1"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -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
|
|
@ -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
|
@ -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);
|
|
@ -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;
|
||||||
|
};
|
|
@ -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,'&').replace(/ {2}/g, ' ').replace(/<|>/g, '>').replace(/\n/g, '<br />');
|
||||||
|
|
||||||
|
// Compare curated content with curated twin.
|
||||||
|
var twinContent = $twin.html().replace(/<br>/ig,'<br />');
|
||||||
|
|
||||||
|
if(forced || textareaContent+' ' !== twinContent){
|
||||||
|
|
||||||
|
// Add an extra white space so new rows are added when you are at the end of a row.
|
||||||
|
$twin.html(textareaContent+' ');
|
||||||
|
|
||||||
|
// 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);
|
|
@ -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);
|
|
@ -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);
|
|
@ -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;
|
||||||
|
}
|
Loading…
Reference in New Issue