Lots of work on autocomplete and more refactoring code.
Autocomplete is nearly done, there is a new script to handle
autocomplete as a jquery plugin (currently uses bootstrap). The main
query.js code has been refactored to be inside an anonymous function to
permit use of "use strict" on all the code. Minimal functionality
changes other than a schema update.
Future: The actual retrieval code will need to be rewritten for the
new paging. This will actually make the code more efficient and easier
to read.
Change-Id: I1abf71469f56390ae7a3db9c4fb8889a756a40e0
diff --git a/client/query/autocomplete.js b/client/query/autocomplete.js
new file mode 100644
index 0000000..6edd6d5
--- /dev/null
+++ b/client/query/autocomplete.js
@@ -0,0 +1,136 @@
+/*
+ * The following code is a jquery extention written to add autocomplete functionality to bootstrap input groups.
+ *
+ * Usage:
+ *
+ * Then simply call $('.someClass').autoComplete(getSuggestions) on it to enable auto completion.
+ *
+ * Also add
+ * ```
+ * .autoComplete {
+ * position: absolute;
+ * top: 38px;
+ * width: 229px;
+ * }
+ *
+ * .autoComplete .list-group {
+ * margin-bottom: 0;
+ * border: none;
+ * }
+ * ```
+ * to an included css file.
+ *
+ * getSuggestions returns by calling its callback parameter with an array of valid strings.
+ *
+ */
+(function(){
+ "use strict";
+ if (!jQuery){
+ throw new Error("jQuery is required and must be loaded before this script.")
+ }
+ jQuery.fn.extend({
+ /**
+ * @param {Array<String>|getSuggestions}
+ */
+ autoComplete: function(suggestions) {
+
+ var element = $('<div></div>');
+ element.addClass('list-group')
+ .css({
+ 'border-top-left-radius': 0,
+ 'border-top-right-radius': 0,
+ 'width': this.width(),
+ 'position': 'absolute',
+ 'top': this.parent().height(),
+ 'display': 'none',
+ 'max-height': '500px',
+ 'overflow-y': 'auto'
+ });
+
+ this.focus(function(){
+ element.slideDown();
+ }).blur(function(){
+ element.slideUp();
+ }).before(element);
+
+ var getSuggestions = function(current, callback){
+ callback(suggestions.reduce(function(prev, suggestion){
+ if (current.toLowerCase().indexOf(suggestion.substr(0, current.length).toLowerCase()) === 0){
+ prev.push(suggestion);
+ }
+ return prev;
+ }, []));
+ }
+
+ var setAutoComplete = function(list){
+ element.empty();
+
+ element.html(list.reduce(function(prev, current){
+ return [prev, '<a href="#" class="list-group-item">', current, '</a>'].join("");
+ }, ""));
+
+ }
+
+ if (suggestions instanceof Function){
+ getSuggestions = suggestions;
+ }
+
+ var input = this;
+
+ element.bind('click', 'a', function(){
+ input.val($(this).text());
+ });
+
+ this.keydown(function(e){
+ switch(e.which){
+ case 38: //up
+ var active = element.find('.active');
+ if (active.length === 0){
+ element.find(':first-child').addClass('active');
+ } else {
+ if (!active.is(':first-child')){
+ active.removeClass('active').prev().addClass('active');
+ }
+ }
+ e.preventDefault();
+ break;
+
+ case 40: //down
+ var active = element.find('.active');
+ if (active.length === 0){
+ element.find(':first-child').addClass('active');
+ } else {
+ if (!active.is(':last-child')){
+ active.removeClass('active').next().addClass('active');
+ }
+ }
+ e.preventDefault();
+ break;
+
+ case 13: //Enter
+ var active = element.find('.active');
+ if (active.length === 1){
+ $(this).val(active.text());
+ e.preventDefault();
+ }
+ break;
+
+ case 9: //Tab
+ getSuggestions(input.val(), setAutoComplete);
+ e.preventDefault(); //Don't print tab
+ break;
+ }
+
+ });
+
+ return this;
+
+ }
+ });
+})();
+
+/**
+ * @callback getSuggestions
+ * @param {string} current - The current value of the input field.
+ * @param {function}
+ */
\ No newline at end of file