You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tapestry.apache.org by hl...@apache.org on 2014/12/30 01:50:24 UTC
[3/4] tapestry-5 git commit: Update to typeahead 0.10.5
Update to typeahead 0.10.5
Project: http://git-wip-us.apache.org/repos/asf/tapestry-5/repo
Commit: http://git-wip-us.apache.org/repos/asf/tapestry-5/commit/4ede8bec
Tree: http://git-wip-us.apache.org/repos/asf/tapestry-5/tree/4ede8bec
Diff: http://git-wip-us.apache.org/repos/asf/tapestry-5/diff/4ede8bec
Branch: refs/heads/master
Commit: 4ede8becd35a57d452b722ee3223f54ab2dda72c
Parents: aa836f3
Author: Howard M. Lewis Ship <hl...@apache.org>
Authored: Mon Dec 29 16:12:46 2014 -0800
Committer: Howard M. Lewis Ship <hl...@apache.org>
Committed: Mon Dec 29 16:13:28 2014 -0800
----------------------------------------------------------------------
54_RELEASE_NOTES.md | 2 +-
.../modules/t5/core/autocomplete.coffee | 25 +-
.../org/apache/tapestry5/EventConstants.java | 7 +-
.../tapestry5/corelib/mixins/Autocomplete.java | 5 +-
.../tapestry5/modules/JavaScriptModule.java | 2 +-
.../META-INF/assets/core/Autocomplete.css | 4 +-
.../assets/tapestry5/typeahead-0.9.3.js | 1139 -----------
.../META-INF/assets/tapestry5/typeahead.js | 1782 ++++++++++++++++++
.../src/test/app1/AutocompleteDemo.tml | 2 +-
9 files changed, 1809 insertions(+), 1159 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/4ede8bec/54_RELEASE_NOTES.md
----------------------------------------------------------------------
diff --git a/54_RELEASE_NOTES.md b/54_RELEASE_NOTES.md
index 9efdcef..b3186bd 100644
--- a/54_RELEASE_NOTES.md
+++ b/54_RELEASE_NOTES.md
@@ -378,7 +378,7 @@ need to provide a "prepare" event handler to initialize the property before it i
## Autocomplete Mixin
-The Autocomplete mixin has been rewritten to use Twitter typeahead.js; this implies it will also force jQuery onto the page,
+The Autocomplete mixin has been rewritten to use Twitter typeahead.js (0.10.5); this implies it will also force jQuery onto the page,
to support the typeahead.js library. In addition, typeahead.js does not support multiple
tokens, so this behavior (available in prior releases) has been removed.
http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/4ede8bec/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/autocomplete.coffee
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/autocomplete.coffee b/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/autocomplete.coffee
index 1a33e94..332e945 100644
--- a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/autocomplete.coffee
+++ b/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/autocomplete.coffee
@@ -1,5 +1,3 @@
-# Copyright 2012, 2013 The Apache Software Foundation
-#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
@@ -14,19 +12,32 @@
# ## t5/core/autocomplete
#
-# Support for the core/Autocomplete Tapestry mixin.
-define ["./dom", "./ajax", "jquery", "./utils", "./typeahead"],
- (dom, ajax, $, {extendURL}) ->
+# Support for the core/Autocomplete Tapestry mixin, a wrapper around
+# the Twitter autocomplete.js library.
+define ["./dom", "./ajax", "underscore", "jquery", "./utils", "./typeahead"],
+ (dom, ajax, _, $, {extendURL}) ->
init = (spec) ->
$field = $ document.getElementById spec.id
- $field.typeahead
- minLength: spec.minChars
+ engine = new Bloodhound
+ datumTokenizer: Bloodhound.tokenizers.whitespace
+ queryTokenizer: Bloodhound.tokenizers.whitespace
limit: spec.limit
remote:
url: spec.url
replace: (uri, query) -> extendURL uri, "t:input": query
filter: (response) -> response.matches
+ engine.initialize()
+
+ dataset =
+ name: spec.id
+ displayKey: _.identity
+ source: engine.ttAdapter()
+
+ $field.typeahead
+ minLength: spec.minChars
+ dataset
+
exports = init
http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/4ede8bec/tapestry-core/src/main/java/org/apache/tapestry5/EventConstants.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/EventConstants.java b/tapestry-core/src/main/java/org/apache/tapestry5/EventConstants.java
index e4df372..223f4c8 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/EventConstants.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/EventConstants.java
@@ -1,5 +1,3 @@
-// Copyright 2008, 2009, 2010, 2011 The Apache Software Foundation
-//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
@@ -14,7 +12,6 @@
package org.apache.tapestry5;
-import org.apache.tapestry5.corelib.components.BeanEditForm;
import org.apache.tapestry5.ioc.util.IdAllocator;
import org.apache.tapestry5.services.ComponentEventRequestParameters;
import org.apache.tapestry5.services.ComponentSource;
@@ -169,7 +166,9 @@ public class EventConstants
/**
* Event triggered by an {@link org.apache.tapestry5.corelib.mixins.Autocomplete} mixin to
* request completions of
- * the current input. The context is the partial string provided by the client.
+ * the current input. The first value in the context is the input string; additional values
+ * in the context parameter of the {@link org.apache.tapestry5.corelib.mixins.Autocomplete}
+ * component.
*
* @since 5.1.0.4
*/
http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/4ede8bec/tapestry-core/src/main/java/org/apache/tapestry5/corelib/mixins/Autocomplete.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/corelib/mixins/Autocomplete.java b/tapestry-core/src/main/java/org/apache/tapestry5/corelib/mixins/Autocomplete.java
index 074ce88..c2688ea 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/corelib/mixins/Autocomplete.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/corelib/mixins/Autocomplete.java
@@ -1,5 +1,3 @@
-// Copyright 2007-2013 The Apache Software Foundation
-//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
@@ -24,13 +22,12 @@ import org.apache.tapestry5.json.JSONObject;
import org.apache.tapestry5.services.compatibility.DeprecationWarning;
import org.apache.tapestry5.services.javascript.JavaScriptSupport;
-import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* A mixin for a text field that allows for autocompletion of text fields. This is based on
- * Twttter <a href="http://twitter.github.io/typeahead.js/">typeahead.js</a> version 0.9.3.
+ * Twttter <a href="http://twitter.github.io/typeahead.js/">typeahead.js</a> version 0.10.5.
* <p/>
* The container is responsible for providing an event handler for event "providecompletions". The context will be the
* partial input string sent from the client. The return value should be an array or list of completions, in
http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/4ede8bec/tapestry-core/src/main/java/org/apache/tapestry5/modules/JavaScriptModule.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/modules/JavaScriptModule.java b/tapestry-core/src/main/java/org/apache/tapestry5/modules/JavaScriptModule.java
index 4e096fb..8ab07b6 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/modules/JavaScriptModule.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/modules/JavaScriptModule.java
@@ -343,7 +343,7 @@ public class JavaScriptModule
@Path("${tapestry.asset.root}/jquery-shim.js")
Resource jqueryShim,
- @Path("${tapestry.asset.root}/typeahead-0.9.3.js")
+ @Path("${tapestry.asset.root}/typeahead.js")
Resource typeahead,
@Path("${tapestry.asset.root}/moment-2.8.4.js")
http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/4ede8bec/tapestry-core/src/main/resources/META-INF/assets/core/Autocomplete.css
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/resources/META-INF/assets/core/Autocomplete.css b/tapestry-core/src/main/resources/META-INF/assets/core/Autocomplete.css
index 684e6b4..72ef7f6 100644
--- a/tapestry-core/src/main/resources/META-INF/assets/core/Autocomplete.css
+++ b/tapestry-core/src/main/resources/META-INF/assets/core/Autocomplete.css
@@ -42,12 +42,12 @@
padding: 3px 20px;
}
-.tt-suggestion.tt-is-under-cursor {
+.tt-suggestion.tt-cursor {
color: #fff;
background-color: #428bca;
}
-.tt-suggestion.tt-is-under-cursor a {
+.tt-suggestion.tt-cursor a {
color: #fff;
}
http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/4ede8bec/tapestry-core/src/main/resources/META-INF/assets/tapestry5/typeahead-0.9.3.js
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/resources/META-INF/assets/tapestry5/typeahead-0.9.3.js b/tapestry-core/src/main/resources/META-INF/assets/tapestry5/typeahead-0.9.3.js
deleted file mode 100644
index 9365bd6..0000000
--- a/tapestry-core/src/main/resources/META-INF/assets/tapestry5/typeahead-0.9.3.js
+++ /dev/null
@@ -1,1139 +0,0 @@
-/*!
- * typeahead.js 0.9.3
- * https://github.com/twitter/typeahead
- * Copyright 2013 Twitter, Inc. and other contributors; Licensed MIT
- */
-
-(function($) {
- var VERSION = "0.9.3";
- var utils = {
- isMsie: function() {
- var match = /(msie) ([\w.]+)/i.exec(navigator.userAgent);
- return match ? parseInt(match[2], 10) : false;
- },
- isBlankString: function(str) {
- return !str || /^\s*$/.test(str);
- },
- escapeRegExChars: function(str) {
- return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
- },
- isString: function(obj) {
- return typeof obj === "string";
- },
- isNumber: function(obj) {
- return typeof obj === "number";
- },
- isArray: $.isArray,
- isFunction: $.isFunction,
- isObject: $.isPlainObject,
- isUndefined: function(obj) {
- return typeof obj === "undefined";
- },
- bind: $.proxy,
- bindAll: function(obj) {
- var val;
- for (var key in obj) {
- $.isFunction(val = obj[key]) && (obj[key] = $.proxy(val, obj));
- }
- },
- indexOf: function(haystack, needle) {
- for (var i = 0; i < haystack.length; i++) {
- if (haystack[i] === needle) {
- return i;
- }
- }
- return -1;
- },
- each: $.each,
- map: $.map,
- filter: $.grep,
- every: function(obj, test) {
- var result = true;
- if (!obj) {
- return result;
- }
- $.each(obj, function(key, val) {
- if (!(result = test.call(null, val, key, obj))) {
- return false;
- }
- });
- return !!result;
- },
- some: function(obj, test) {
- var result = false;
- if (!obj) {
- return result;
- }
- $.each(obj, function(key, val) {
- if (result = test.call(null, val, key, obj)) {
- return false;
- }
- });
- return !!result;
- },
- mixin: $.extend,
- getUniqueId: function() {
- var counter = 0;
- return function() {
- return counter++;
- };
- }(),
- defer: function(fn) {
- setTimeout(fn, 0);
- },
- debounce: function(func, wait, immediate) {
- var timeout, result;
- return function() {
- var context = this, args = arguments, later, callNow;
- later = function() {
- timeout = null;
- if (!immediate) {
- result = func.apply(context, args);
- }
- };
- callNow = immediate && !timeout;
- clearTimeout(timeout);
- timeout = setTimeout(later, wait);
- if (callNow) {
- result = func.apply(context, args);
- }
- return result;
- };
- },
- throttle: function(func, wait) {
- var context, args, timeout, result, previous, later;
- previous = 0;
- later = function() {
- previous = new Date();
- timeout = null;
- result = func.apply(context, args);
- };
- return function() {
- var now = new Date(), remaining = wait - (now - previous);
- context = this;
- args = arguments;
- if (remaining <= 0) {
- clearTimeout(timeout);
- timeout = null;
- previous = now;
- result = func.apply(context, args);
- } else if (!timeout) {
- timeout = setTimeout(later, remaining);
- }
- return result;
- };
- },
- tokenizeQuery: function(str) {
- return $.trim(str).toLowerCase().split(/[\s]+/);
- },
- tokenizeText: function(str) {
- return $.trim(str).toLowerCase().split(/[\s\-_]+/);
- },
- getProtocol: function() {
- return location.protocol;
- },
- noop: function() {}
- };
- var EventTarget = function() {
- var eventSplitter = /\s+/;
- return {
- on: function(events, callback) {
- var event;
- if (!callback) {
- return this;
- }
- this._callbacks = this._callbacks || {};
- events = events.split(eventSplitter);
- while (event = events.shift()) {
- this._callbacks[event] = this._callbacks[event] || [];
- this._callbacks[event].push(callback);
- }
- return this;
- },
- trigger: function(events, data) {
- var event, callbacks;
- if (!this._callbacks) {
- return this;
- }
- events = events.split(eventSplitter);
- while (event = events.shift()) {
- if (callbacks = this._callbacks[event]) {
- for (var i = 0; i < callbacks.length; i += 1) {
- callbacks[i].call(this, {
- type: event,
- data: data
- });
- }
- }
- }
- return this;
- }
- };
- }();
- var EventBus = function() {
- var namespace = "typeahead:";
- function EventBus(o) {
- if (!o || !o.el) {
- $.error("EventBus initialized without el");
- }
- this.$el = $(o.el);
- }
- utils.mixin(EventBus.prototype, {
- trigger: function(type) {
- var args = [].slice.call(arguments, 1);
- this.$el.trigger(namespace + type, args);
- }
- });
- return EventBus;
- }();
- var PersistentStorage = function() {
- var ls, methods;
- try {
- ls = window.localStorage;
- ls.setItem("~~~", "!");
- ls.removeItem("~~~");
- } catch (err) {
- ls = null;
- }
- function PersistentStorage(namespace) {
- this.prefix = [ "__", namespace, "__" ].join("");
- this.ttlKey = "__ttl__";
- this.keyMatcher = new RegExp("^" + this.prefix);
- }
- if (ls && window.JSON) {
- methods = {
- _prefix: function(key) {
- return this.prefix + key;
- },
- _ttlKey: function(key) {
- return this._prefix(key) + this.ttlKey;
- },
- get: function(key) {
- if (this.isExpired(key)) {
- this.remove(key);
- }
- return decode(ls.getItem(this._prefix(key)));
- },
- set: function(key, val, ttl) {
- if (utils.isNumber(ttl)) {
- ls.setItem(this._ttlKey(key), encode(now() + ttl));
- } else {
- ls.removeItem(this._ttlKey(key));
- }
- return ls.setItem(this._prefix(key), encode(val));
- },
- remove: function(key) {
- ls.removeItem(this._ttlKey(key));
- ls.removeItem(this._prefix(key));
- return this;
- },
- clear: function() {
- var i, key, keys = [], len = ls.length;
- for (i = 0; i < len; i++) {
- if ((key = ls.key(i)).match(this.keyMatcher)) {
- keys.push(key.replace(this.keyMatcher, ""));
- }
- }
- for (i = keys.length; i--; ) {
- this.remove(keys[i]);
- }
- return this;
- },
- isExpired: function(key) {
- var ttl = decode(ls.getItem(this._ttlKey(key)));
- return utils.isNumber(ttl) && now() > ttl ? true : false;
- }
- };
- } else {
- methods = {
- get: utils.noop,
- set: utils.noop,
- remove: utils.noop,
- clear: utils.noop,
- isExpired: utils.noop
- };
- }
- utils.mixin(PersistentStorage.prototype, methods);
- return PersistentStorage;
- function now() {
- return new Date().getTime();
- }
- function encode(val) {
- return JSON.stringify(utils.isUndefined(val) ? null : val);
- }
- function decode(val) {
- return JSON.parse(val);
- }
- }();
- var RequestCache = function() {
- function RequestCache(o) {
- utils.bindAll(this);
- o = o || {};
- this.sizeLimit = o.sizeLimit || 10;
- this.cache = {};
- this.cachedKeysByAge = [];
- }
- utils.mixin(RequestCache.prototype, {
- get: function(url) {
- return this.cache[url];
- },
- set: function(url, resp) {
- var requestToEvict;
- if (this.cachedKeysByAge.length === this.sizeLimit) {
- requestToEvict = this.cachedKeysByAge.shift();
- delete this.cache[requestToEvict];
- }
- this.cache[url] = resp;
- this.cachedKeysByAge.push(url);
- }
- });
- return RequestCache;
- }();
- var Transport = function() {
- var pendingRequestsCount = 0, pendingRequests = {}, maxPendingRequests, requestCache;
- function Transport(o) {
- utils.bindAll(this);
- o = utils.isString(o) ? {
- url: o
- } : o;
- requestCache = requestCache || new RequestCache();
- maxPendingRequests = utils.isNumber(o.maxParallelRequests) ? o.maxParallelRequests : maxPendingRequests || 6;
- this.url = o.url;
- this.wildcard = o.wildcard || "%QUERY";
- this.filter = o.filter;
- this.replace = o.replace;
- this.ajaxSettings = {
- type: "get",
- cache: o.cache,
- timeout: o.timeout,
- dataType: o.dataType || "json",
- beforeSend: o.beforeSend
- };
- this._get = (/^throttle$/i.test(o.rateLimitFn) ? utils.throttle : utils.debounce)(this._get, o.rateLimitWait || 300);
- }
- utils.mixin(Transport.prototype, {
- _get: function(url, cb) {
- var that = this;
- if (belowPendingRequestsThreshold()) {
- this._sendRequest(url).done(done);
- } else {
- this.onDeckRequestArgs = [].slice.call(arguments, 0);
- }
- function done(resp) {
- var data = that.filter ? that.filter(resp) : resp;
- cb && cb(data);
- requestCache.set(url, resp);
- }
- },
- _sendRequest: function(url) {
- var that = this, jqXhr = pendingRequests[url];
- if (!jqXhr) {
- incrementPendingRequests();
- jqXhr = pendingRequests[url] = $.ajax(url, this.ajaxSettings).always(always);
- }
- return jqXhr;
- function always() {
- decrementPendingRequests();
- pendingRequests[url] = null;
- if (that.onDeckRequestArgs) {
- that._get.apply(that, that.onDeckRequestArgs);
- that.onDeckRequestArgs = null;
- }
- }
- },
- get: function(query, cb) {
- var that = this, encodedQuery = encodeURIComponent(query || ""), url, resp;
- cb = cb || utils.noop;
- url = this.replace ? this.replace(this.url, encodedQuery) : this.url.replace(this.wildcard, encodedQuery);
- if (resp = requestCache.get(url)) {
- utils.defer(function() {
- cb(that.filter ? that.filter(resp) : resp);
- });
- } else {
- this._get(url, cb);
- }
- return !!resp;
- }
- });
- return Transport;
- function incrementPendingRequests() {
- pendingRequestsCount++;
- }
- function decrementPendingRequests() {
- pendingRequestsCount--;
- }
- function belowPendingRequestsThreshold() {
- return pendingRequestsCount < maxPendingRequests;
- }
- }();
- var Dataset = function() {
- var keys = {
- thumbprint: "thumbprint",
- protocol: "protocol",
- itemHash: "itemHash",
- adjacencyList: "adjacencyList"
- };
- function Dataset(o) {
- utils.bindAll(this);
- if (utils.isString(o.template) && !o.engine) {
- $.error("no template engine specified");
- }
- if (!o.local && !o.prefetch && !o.remote) {
- $.error("one of local, prefetch, or remote is required");
- }
- this.name = o.name || utils.getUniqueId();
- this.limit = o.limit || 5;
- this.minLength = o.minLength || 1;
- this.header = o.header;
- this.footer = o.footer;
- this.valueKey = o.valueKey || "value";
- this.template = compileTemplate(o.template, o.engine, this.valueKey);
- this.local = o.local;
- this.prefetch = o.prefetch;
- this.remote = o.remote;
- this.itemHash = {};
- this.adjacencyList = {};
- this.storage = o.name ? new PersistentStorage(o.name) : null;
- }
- utils.mixin(Dataset.prototype, {
- _processLocalData: function(data) {
- this._mergeProcessedData(this._processData(data));
- },
- _loadPrefetchData: function(o) {
- var that = this, thumbprint = VERSION + (o.thumbprint || ""), storedThumbprint, storedProtocol, storedItemHash, storedAdjacencyList, isExpired, deferred;
- if (this.storage) {
- storedThumbprint = this.storage.get(keys.thumbprint);
- storedProtocol = this.storage.get(keys.protocol);
- storedItemHash = this.storage.get(keys.itemHash);
- storedAdjacencyList = this.storage.get(keys.adjacencyList);
- }
- isExpired = storedThumbprint !== thumbprint || storedProtocol !== utils.getProtocol();
- o = utils.isString(o) ? {
- url: o
- } : o;
- o.ttl = utils.isNumber(o.ttl) ? o.ttl : 24 * 60 * 60 * 1e3;
- if (storedItemHash && storedAdjacencyList && !isExpired) {
- this._mergeProcessedData({
- itemHash: storedItemHash,
- adjacencyList: storedAdjacencyList
- });
- deferred = $.Deferred().resolve();
- } else {
- deferred = $.getJSON(o.url).done(processPrefetchData);
- }
- return deferred;
- function processPrefetchData(data) {
- var filteredData = o.filter ? o.filter(data) : data, processedData = that._processData(filteredData), itemHash = processedData.itemHash, adjacencyList = processedData.adjacencyList;
- if (that.storage) {
- that.storage.set(keys.itemHash, itemHash, o.ttl);
- that.storage.set(keys.adjacencyList, adjacencyList, o.ttl);
- that.storage.set(keys.thumbprint, thumbprint, o.ttl);
- that.storage.set(keys.protocol, utils.getProtocol(), o.ttl);
- }
- that._mergeProcessedData(processedData);
- }
- },
- _transformDatum: function(datum) {
- var value = utils.isString(datum) ? datum : datum[this.valueKey], tokens = datum.tokens || utils.tokenizeText(value), item = {
- value: value,
- tokens: tokens
- };
- if (utils.isString(datum)) {
- item.datum = {};
- item.datum[this.valueKey] = datum;
- } else {
- item.datum = datum;
- }
- item.tokens = utils.filter(item.tokens, function(token) {
- return !utils.isBlankString(token);
- });
- item.tokens = utils.map(item.tokens, function(token) {
- return token.toLowerCase();
- });
- return item;
- },
- _processData: function(data) {
- var that = this, itemHash = {}, adjacencyList = {};
- utils.each(data, function(i, datum) {
- var item = that._transformDatum(datum), id = utils.getUniqueId(item.value);
- itemHash[id] = item;
- utils.each(item.tokens, function(i, token) {
- var character = token.charAt(0), adjacency = adjacencyList[character] || (adjacencyList[character] = [ id ]);
- !~utils.indexOf(adjacency, id) && adjacency.push(id);
- });
- });
- return {
- itemHash: itemHash,
- adjacencyList: adjacencyList
- };
- },
- _mergeProcessedData: function(processedData) {
- var that = this;
- utils.mixin(this.itemHash, processedData.itemHash);
- utils.each(processedData.adjacencyList, function(character, adjacency) {
- var masterAdjacency = that.adjacencyList[character];
- that.adjacencyList[character] = masterAdjacency ? masterAdjacency.concat(adjacency) : adjacency;
- });
- },
- _getLocalSuggestions: function(terms) {
- var that = this, firstChars = [], lists = [], shortestList, suggestions = [];
- utils.each(terms, function(i, term) {
- var firstChar = term.charAt(0);
- !~utils.indexOf(firstChars, firstChar) && firstChars.push(firstChar);
- });
- utils.each(firstChars, function(i, firstChar) {
- var list = that.adjacencyList[firstChar];
- if (!list) {
- return false;
- }
- lists.push(list);
- if (!shortestList || list.length < shortestList.length) {
- shortestList = list;
- }
- });
- if (lists.length < firstChars.length) {
- return [];
- }
- utils.each(shortestList, function(i, id) {
- var item = that.itemHash[id], isCandidate, isMatch;
- isCandidate = utils.every(lists, function(list) {
- return ~utils.indexOf(list, id);
- });
- isMatch = isCandidate && utils.every(terms, function(term) {
- return utils.some(item.tokens, function(token) {
- return token.indexOf(term) === 0;
- });
- });
- isMatch && suggestions.push(item);
- });
- return suggestions;
- },
- initialize: function() {
- var deferred;
- this.local && this._processLocalData(this.local);
- this.transport = this.remote ? new Transport(this.remote) : null;
- deferred = this.prefetch ? this._loadPrefetchData(this.prefetch) : $.Deferred().resolve();
- this.local = this.prefetch = this.remote = null;
- this.initialize = function() {
- return deferred;
- };
- return deferred;
- },
- getSuggestions: function(query, cb) {
- var that = this, terms, suggestions, cacheHit = false;
- if (query.length < this.minLength) {
- return;
- }
- terms = utils.tokenizeQuery(query);
- suggestions = this._getLocalSuggestions(terms).slice(0, this.limit);
- if (suggestions.length < this.limit && this.transport) {
- cacheHit = this.transport.get(query, processRemoteData);
- }
- !cacheHit && cb && cb(suggestions);
- function processRemoteData(data) {
- suggestions = suggestions.slice(0);
- utils.each(data, function(i, datum) {
- var item = that._transformDatum(datum), isDuplicate;
- isDuplicate = utils.some(suggestions, function(suggestion) {
- return item.value === suggestion.value;
- });
- !isDuplicate && suggestions.push(item);
- return suggestions.length < that.limit;
- });
- cb && cb(suggestions);
- }
- }
- });
- return Dataset;
- function compileTemplate(template, engine, valueKey) {
- var renderFn, compiledTemplate;
- if (utils.isFunction(template)) {
- renderFn = template;
- } else if (utils.isString(template)) {
- compiledTemplate = engine.compile(template);
- renderFn = utils.bind(compiledTemplate.render, compiledTemplate);
- } else {
- renderFn = function(context) {
- return "<p>" + context[valueKey] + "</p>";
- };
- }
- return renderFn;
- }
- }();
- var InputView = function() {
- function InputView(o) {
- var that = this;
- utils.bindAll(this);
- this.specialKeyCodeMap = {
- 9: "tab",
- 27: "esc",
- 37: "left",
- 39: "right",
- 13: "enter",
- 38: "up",
- 40: "down"
- };
- this.$hint = $(o.hint);
- this.$input = $(o.input).on("blur.tt", this._handleBlur).on("focus.tt", this._handleFocus).on("keydown.tt", this._handleSpecialKeyEvent);
- if (!utils.isMsie()) {
- this.$input.on("input.tt", this._compareQueryToInputValue);
- } else {
- this.$input.on("keydown.tt keypress.tt cut.tt paste.tt", function($e) {
- if (that.specialKeyCodeMap[$e.which || $e.keyCode]) {
- return;
- }
- utils.defer(that._compareQueryToInputValue);
- });
- }
- this.query = this.$input.val();
- this.$overflowHelper = buildOverflowHelper(this.$input);
- }
- utils.mixin(InputView.prototype, EventTarget, {
- _handleFocus: function() {
- this.trigger("focused");
- },
- _handleBlur: function() {
- this.trigger("blured");
- },
- _handleSpecialKeyEvent: function($e) {
- var keyName = this.specialKeyCodeMap[$e.which || $e.keyCode];
- keyName && this.trigger(keyName + "Keyed", $e);
- },
- _compareQueryToInputValue: function() {
- var inputValue = this.getInputValue(), isSameQuery = compareQueries(this.query, inputValue), isSameQueryExceptWhitespace = isSameQuery ? this.query.length !== inputValue.length : false;
- if (isSameQueryExceptWhitespace) {
- this.trigger("whitespaceChanged", {
- value: this.query
- });
- } else if (!isSameQuery) {
- this.trigger("queryChanged", {
- value: this.query = inputValue
- });
- }
- },
- destroy: function() {
- this.$hint.off(".tt");
- this.$input.off(".tt");
- this.$hint = this.$input = this.$overflowHelper = null;
- },
- focus: function() {
- this.$input.focus();
- },
- blur: function() {
- this.$input.blur();
- },
- getQuery: function() {
- return this.query;
- },
- setQuery: function(query) {
- this.query = query;
- },
- getInputValue: function() {
- return this.$input.val();
- },
- setInputValue: function(value, silent) {
- this.$input.val(value);
- !silent && this._compareQueryToInputValue();
- },
- getHintValue: function() {
- return this.$hint.val();
- },
- setHintValue: function(value) {
- this.$hint.val(value);
- },
- getLanguageDirection: function() {
- return (this.$input.css("direction") || "ltr").toLowerCase();
- },
- isOverflow: function() {
- this.$overflowHelper.text(this.getInputValue());
- return this.$overflowHelper.width() > this.$input.width();
- },
- isCursorAtEnd: function() {
- var valueLength = this.$input.val().length, selectionStart = this.$input[0].selectionStart, range;
- if (utils.isNumber(selectionStart)) {
- return selectionStart === valueLength;
- } else if (document.selection) {
- range = document.selection.createRange();
- range.moveStart("character", -valueLength);
- return valueLength === range.text.length;
- }
- return true;
- }
- });
- return InputView;
- function buildOverflowHelper($input) {
- return $("<span></span>").css({
- position: "absolute",
- left: "-9999px",
- visibility: "hidden",
- whiteSpace: "nowrap",
- fontFamily: $input.css("font-family"),
- fontSize: $input.css("font-size"),
- fontStyle: $input.css("font-style"),
- fontVariant: $input.css("font-variant"),
- fontWeight: $input.css("font-weight"),
- wordSpacing: $input.css("word-spacing"),
- letterSpacing: $input.css("letter-spacing"),
- textIndent: $input.css("text-indent"),
- textRendering: $input.css("text-rendering"),
- textTransform: $input.css("text-transform")
- }).insertAfter($input);
- }
- function compareQueries(a, b) {
- a = (a || "").replace(/^\s*/g, "").replace(/\s{2,}/g, " ");
- b = (b || "").replace(/^\s*/g, "").replace(/\s{2,}/g, " ");
- return a === b;
- }
- }();
- var DropdownView = function() {
- var html = {
- suggestionsList: '<span class="tt-suggestions"></span>'
- }, css = {
- suggestionsList: {
- display: "block"
- },
- suggestion: {
- whiteSpace: "nowrap",
- cursor: "pointer"
- },
- suggestionChild: {
- whiteSpace: "normal"
- }
- };
- function DropdownView(o) {
- utils.bindAll(this);
- this.isOpen = false;
- this.isEmpty = true;
- this.isMouseOverDropdown = false;
- this.$menu = $(o.menu).on("mouseenter.tt", this._handleMouseenter).on("mouseleave.tt", this._handleMouseleave).on("click.tt", ".tt-suggestion", this._handleSelection).on("mouseover.tt", ".tt-suggestion", this._handleMouseover);
- }
- utils.mixin(DropdownView.prototype, EventTarget, {
- _handleMouseenter: function() {
- this.isMouseOverDropdown = true;
- },
- _handleMouseleave: function() {
- this.isMouseOverDropdown = false;
- },
- _handleMouseover: function($e) {
- var $suggestion = $($e.currentTarget);
- this._getSuggestions().removeClass("tt-is-under-cursor");
- $suggestion.addClass("tt-is-under-cursor");
- },
- _handleSelection: function($e) {
- var $suggestion = $($e.currentTarget);
- this.trigger("suggestionSelected", extractSuggestion($suggestion));
- },
- _show: function() {
- this.$menu.css("display", "block");
- },
- _hide: function() {
- this.$menu.hide();
- },
- _moveCursor: function(increment) {
- var $suggestions, $cur, nextIndex, $underCursor;
- if (!this.isVisible()) {
- return;
- }
- $suggestions = this._getSuggestions();
- $cur = $suggestions.filter(".tt-is-under-cursor");
- $cur.removeClass("tt-is-under-cursor");
- nextIndex = $suggestions.index($cur) + increment;
- nextIndex = (nextIndex + 1) % ($suggestions.length + 1) - 1;
- if (nextIndex === -1) {
- this.trigger("cursorRemoved");
- return;
- } else if (nextIndex < -1) {
- nextIndex = $suggestions.length - 1;
- }
- $underCursor = $suggestions.eq(nextIndex).addClass("tt-is-under-cursor");
- this._ensureVisibility($underCursor);
- this.trigger("cursorMoved", extractSuggestion($underCursor));
- },
- _getSuggestions: function() {
- return this.$menu.find(".tt-suggestions > .tt-suggestion");
- },
- _ensureVisibility: function($el) {
- var menuHeight = this.$menu.height() + parseInt(this.$menu.css("paddingTop"), 10) + parseInt(this.$menu.css("paddingBottom"), 10), menuScrollTop = this.$menu.scrollTop(), elTop = $el.position().top, elBottom = elTop + $el.outerHeight(true);
- if (elTop < 0) {
- this.$menu.scrollTop(menuScrollTop + elTop);
- } else if (menuHeight < elBottom) {
- this.$menu.scrollTop(menuScrollTop + (elBottom - menuHeight));
- }
- },
- destroy: function() {
- this.$menu.off(".tt");
- this.$menu = null;
- },
- isVisible: function() {
- return this.isOpen && !this.isEmpty;
- },
- closeUnlessMouseIsOverDropdown: function() {
- if (!this.isMouseOverDropdown) {
- this.close();
- }
- },
- close: function() {
- if (this.isOpen) {
- this.isOpen = false;
- this.isMouseOverDropdown = false;
- this._hide();
- this.$menu.find(".tt-suggestions > .tt-suggestion").removeClass("tt-is-under-cursor");
- this.trigger("closed");
- }
- },
- open: function() {
- if (!this.isOpen) {
- this.isOpen = true;
- !this.isEmpty && this._show();
- this.trigger("opened");
- }
- },
- setLanguageDirection: function(dir) {
- var ltrCss = {
- left: "0",
- right: "auto"
- }, rtlCss = {
- left: "auto",
- right: " 0"
- };
- dir === "ltr" ? this.$menu.css(ltrCss) : this.$menu.css(rtlCss);
- },
- moveCursorUp: function() {
- this._moveCursor(-1);
- },
- moveCursorDown: function() {
- this._moveCursor(+1);
- },
- getSuggestionUnderCursor: function() {
- var $suggestion = this._getSuggestions().filter(".tt-is-under-cursor").first();
- return $suggestion.length > 0 ? extractSuggestion($suggestion) : null;
- },
- getFirstSuggestion: function() {
- var $suggestion = this._getSuggestions().first();
- return $suggestion.length > 0 ? extractSuggestion($suggestion) : null;
- },
- renderSuggestions: function(dataset, suggestions) {
- var datasetClassName = "tt-dataset-" + dataset.name, wrapper = '<div class="tt-suggestion">%body</div>', compiledHtml, $suggestionsList, $dataset = this.$menu.find("." + datasetClassName), elBuilder, fragment, $el;
- if ($dataset.length === 0) {
- $suggestionsList = $(html.suggestionsList).css(css.suggestionsList);
- $dataset = $("<div></div>").addClass(datasetClassName).append(dataset.header).append($suggestionsList).append(dataset.footer).appendTo(this.$menu);
- }
- if (suggestions.length > 0) {
- this.isEmpty = false;
- this.isOpen && this._show();
- elBuilder = document.createElement("div");
- fragment = document.createDocumentFragment();
- utils.each(suggestions, function(i, suggestion) {
- suggestion.dataset = dataset.name;
- compiledHtml = dataset.template(suggestion.datum);
- elBuilder.innerHTML = wrapper.replace("%body", compiledHtml);
- $el = $(elBuilder.firstChild).css(css.suggestion).data("suggestion", suggestion);
- $el.children().each(function() {
- $(this).css(css.suggestionChild);
- });
- fragment.appendChild($el[0]);
- });
- $dataset.show().find(".tt-suggestions").html(fragment);
- } else {
- this.clearSuggestions(dataset.name);
- }
- this.trigger("suggestionsRendered");
- },
- clearSuggestions: function(datasetName) {
- var $datasets = datasetName ? this.$menu.find(".tt-dataset-" + datasetName) : this.$menu.find('[class^="tt-dataset-"]'), $suggestions = $datasets.find(".tt-suggestions");
- $datasets.hide();
- $suggestions.empty();
- if (this._getSuggestions().length === 0) {
- this.isEmpty = true;
- this._hide();
- }
- }
- });
- return DropdownView;
- function extractSuggestion($el) {
- return $el.data("suggestion");
- }
- }();
- var TypeaheadView = function() {
- var html = {
- wrapper: '<span class="twitter-typeahead"></span>',
- hint: '<input class="tt-hint" type="text" autocomplete="off" spellcheck="off" disabled>',
- dropdown: '<span class="tt-dropdown-menu"></span>'
- }, css = {
- wrapper: {
- position: "relative",
- display: "inline-block"
- },
- hint: {
- position: "absolute",
- top: "0",
- left: "0",
- borderColor: "transparent",
- boxShadow: "none"
- },
- query: {
- position: "relative",
- verticalAlign: "top",
- backgroundColor: "transparent"
- },
- dropdown: {
- position: "absolute",
- top: "100%",
- left: "0",
- zIndex: "100",
- display: "none"
- }
- };
- if (utils.isMsie()) {
- utils.mixin(css.query, {
- backgroundImage: "url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)"
- });
- }
- if (utils.isMsie() && utils.isMsie() <= 7) {
- utils.mixin(css.wrapper, {
- display: "inline",
- zoom: "1"
- });
- utils.mixin(css.query, {
- marginTop: "-1px"
- });
- }
- function TypeaheadView(o) {
- var $menu, $input, $hint;
- utils.bindAll(this);
- this.$node = buildDomStructure(o.input);
- this.datasets = o.datasets;
- this.dir = null;
- this.eventBus = o.eventBus;
- $menu = this.$node.find(".tt-dropdown-menu");
- $input = this.$node.find(".tt-query");
- $hint = this.$node.find(".tt-hint");
- this.dropdownView = new DropdownView({
- menu: $menu
- }).on("suggestionSelected", this._handleSelection).on("cursorMoved", this._clearHint).on("cursorMoved", this._setInputValueToSuggestionUnderCursor).on("cursorRemoved", this._setInputValueToQuery).on("cursorRemoved", this._updateHint).on("suggestionsRendered", this._updateHint).on("opened", this._updateHint).on("closed", this._clearHint).on("opened closed", this._propagateEvent);
- this.inputView = new InputView({
- input: $input,
- hint: $hint
- }).on("focused", this._openDropdown).on("blured", this._closeDropdown).on("blured", this._setInputValueToQuery).on("enterKeyed tabKeyed", this._handleSelection).on("queryChanged", this._clearHint).on("queryChanged", this._clearSuggestions).on("queryChanged", this._getSuggestions).on("whitespaceChanged", this._updateHint).on("queryChanged whitespaceChanged", this._openDropdown).on("queryChanged whitespaceChanged", this._setLanguageDirection).on("escKeyed", this._closeDropdown).on("escKeyed", this._setInputValueToQuery).on("tabKeyed upKeyed downKeyed", this._managePreventDefault).on("upKeyed downKeyed", this._moveDropdownCursor).on("upKeyed downKeyed", this._openDropdown).on("tabKeyed leftKeyed rightKeyed", this._autocomplete);
- }
- utils.mixin(TypeaheadView.prototype, EventTarget, {
- _managePreventDefault: function(e) {
- var $e = e.data, hint, inputValue, preventDefault = false;
- switch (e.type) {
- case "tabKeyed":
- hint = this.inputView.getHintValue();
- inputValue = this.inputView.getInputValue();
- preventDefault = hint && hint !== inputValue;
- break;
-
- case "upKeyed":
- case "downKeyed":
- preventDefault = !$e.shiftKey && !$e.ctrlKey && !$e.metaKey;
- break;
- }
- preventDefault && $e.preventDefault();
- },
- _setLanguageDirection: function() {
- var dir = this.inputView.getLanguageDirection();
- if (dir !== this.dir) {
- this.dir = dir;
- this.$node.css("direction", dir);
- this.dropdownView.setLanguageDirection(dir);
- }
- },
- _updateHint: function() {
- var suggestion = this.dropdownView.getFirstSuggestion(), hint = suggestion ? suggestion.value : null, dropdownIsVisible = this.dropdownView.isVisible(), inputHasOverflow = this.inputView.isOverflow(), inputValue, query, escapedQuery, beginsWithQuery, match;
- if (hint && dropdownIsVisible && !inputHasOverflow) {
- inputValue = this.inputView.getInputValue();
- query = inputValue.replace(/\s{2,}/g, " ").replace(/^\s+/g, "");
- escapedQuery = utils.escapeRegExChars(query);
- beginsWithQuery = new RegExp("^(?:" + escapedQuery + ")(.*$)", "i");
- match = beginsWithQuery.exec(hint);
- this.inputView.setHintValue(inputValue + (match ? match[1] : ""));
- }
- },
- _clearHint: function() {
- this.inputView.setHintValue("");
- },
- _clearSuggestions: function() {
- this.dropdownView.clearSuggestions();
- },
- _setInputValueToQuery: function() {
- this.inputView.setInputValue(this.inputView.getQuery());
- },
- _setInputValueToSuggestionUnderCursor: function(e) {
- var suggestion = e.data;
- this.inputView.setInputValue(suggestion.value, true);
- },
- _openDropdown: function() {
- this.dropdownView.open();
- },
- _closeDropdown: function(e) {
- this.dropdownView[e.type === "blured" ? "closeUnlessMouseIsOverDropdown" : "close"]();
- },
- _moveDropdownCursor: function(e) {
- var $e = e.data;
- if (!$e.shiftKey && !$e.ctrlKey && !$e.metaKey) {
- this.dropdownView[e.type === "upKeyed" ? "moveCursorUp" : "moveCursorDown"]();
- }
- },
- _handleSelection: function(e) {
- var byClick = e.type === "suggestionSelected", suggestion = byClick ? e.data : this.dropdownView.getSuggestionUnderCursor();
- if (suggestion) {
- this.inputView.setInputValue(suggestion.value);
- byClick ? this.inputView.focus() : e.data.preventDefault();
- byClick && utils.isMsie() ? utils.defer(this.dropdownView.close) : this.dropdownView.close();
- this.eventBus.trigger("selected", suggestion.datum, suggestion.dataset);
- }
- },
- _getSuggestions: function() {
- var that = this, query = this.inputView.getQuery();
- if (utils.isBlankString(query)) {
- return;
- }
- utils.each(this.datasets, function(i, dataset) {
- dataset.getSuggestions(query, function(suggestions) {
- if (query === that.inputView.getQuery()) {
- that.dropdownView.renderSuggestions(dataset, suggestions);
- }
- });
- });
- },
- _autocomplete: function(e) {
- var isCursorAtEnd, ignoreEvent, query, hint, suggestion;
- if (e.type === "rightKeyed" || e.type === "leftKeyed") {
- isCursorAtEnd = this.inputView.isCursorAtEnd();
- ignoreEvent = this.inputView.getLanguageDirection() === "ltr" ? e.type === "leftKeyed" : e.type === "rightKeyed";
- if (!isCursorAtEnd || ignoreEvent) {
- return;
- }
- }
- query = this.inputView.getQuery();
- hint = this.inputView.getHintValue();
- if (hint !== "" && query !== hint) {
- suggestion = this.dropdownView.getFirstSuggestion();
- this.inputView.setInputValue(suggestion.value);
- this.eventBus.trigger("autocompleted", suggestion.datum, suggestion.dataset);
- }
- },
- _propagateEvent: function(e) {
- this.eventBus.trigger(e.type);
- },
- destroy: function() {
- this.inputView.destroy();
- this.dropdownView.destroy();
- destroyDomStructure(this.$node);
- this.$node = null;
- },
- setQuery: function(query) {
- this.inputView.setQuery(query);
- this.inputView.setInputValue(query);
- this._clearHint();
- this._clearSuggestions();
- this._getSuggestions();
- }
- });
- return TypeaheadView;
- function buildDomStructure(input) {
- var $wrapper = $(html.wrapper), $dropdown = $(html.dropdown), $input = $(input), $hint = $(html.hint);
- $wrapper = $wrapper.css(css.wrapper);
- $dropdown = $dropdown.css(css.dropdown);
- $hint.css(css.hint).css({
- backgroundAttachment: $input.css("background-attachment"),
- backgroundClip: $input.css("background-clip"),
- backgroundColor: $input.css("background-color"),
- backgroundImage: $input.css("background-image"),
- backgroundOrigin: $input.css("background-origin"),
- backgroundPosition: $input.css("background-position"),
- backgroundRepeat: $input.css("background-repeat"),
- backgroundSize: $input.css("background-size")
- });
- $input.data("ttAttrs", {
- dir: $input.attr("dir"),
- autocomplete: $input.attr("autocomplete"),
- spellcheck: $input.attr("spellcheck"),
- style: $input.attr("style")
- });
- $input.addClass("tt-query").attr({
- autocomplete: "off",
- spellcheck: false
- }).css(css.query);
- try {
- !$input.attr("dir") && $input.attr("dir", "auto");
- } catch (e) {}
- return $input.wrap($wrapper).parent().prepend($hint).append($dropdown);
- }
- function destroyDomStructure($node) {
- var $input = $node.find(".tt-query");
- utils.each($input.data("ttAttrs"), function(key, val) {
- utils.isUndefined(val) ? $input.removeAttr(key) : $input.attr(key, val);
- });
- $input.detach().removeData("ttAttrs").removeClass("tt-query").insertAfter($node);
- $node.remove();
- }
- }();
- (function() {
- var cache = {}, viewKey = "ttView", methods;
- methods = {
- initialize: function(datasetDefs) {
- var datasets;
- datasetDefs = utils.isArray(datasetDefs) ? datasetDefs : [ datasetDefs ];
- if (datasetDefs.length === 0) {
- $.error("no datasets provided");
- }
- datasets = utils.map(datasetDefs, function(o) {
- var dataset = cache[o.name] ? cache[o.name] : new Dataset(o);
- if (o.name) {
- cache[o.name] = dataset;
- }
- return dataset;
- });
- return this.each(initialize);
- function initialize() {
- var $input = $(this), deferreds, eventBus = new EventBus({
- el: $input
- });
- deferreds = utils.map(datasets, function(dataset) {
- return dataset.initialize();
- });
- $input.data(viewKey, new TypeaheadView({
- input: $input,
- eventBus: eventBus = new EventBus({
- el: $input
- }),
- datasets: datasets
- }));
- $.when.apply($, deferreds).always(function() {
- utils.defer(function() {
- eventBus.trigger("initialized");
- });
- });
- }
- },
- destroy: function() {
- return this.each(destroy);
- function destroy() {
- var $this = $(this), view = $this.data(viewKey);
- if (view) {
- view.destroy();
- $this.removeData(viewKey);
- }
- }
- },
- setQuery: function(query) {
- return this.each(setQuery);
- function setQuery() {
- var view = $(this).data(viewKey);
- view && view.setQuery(query);
- }
- }
- };
- jQuery.fn.typeahead = function(method) {
- if (methods[method]) {
- return methods[method].apply(this, [].slice.call(arguments, 1));
- } else {
- return methods.initialize.apply(this, arguments);
- }
- };
- })();
-})(window.jQuery);
\ No newline at end of file