Skip to content

Commit 0a9e717

Browse files
committed
Autosuggest added!
1 parent 32cdb9d commit 0a9e717

File tree

7 files changed

+341
-2
lines changed

7 files changed

+341
-2
lines changed

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ Arguments in queries
3434
[DATE:Some_date]
3535
[DATE:Month;type=month] for a month (will be padded like this if the input is `2012-05-13`): `2012-05-01`
3636
[DATE:Some_year;type=year,compact=true] for a year that will not be padded on the right.
37+
[STRING:SubjectName;autosuggest=subjects.name] for an autosuggest served from table 'subject' column 'name'.
3738
```
3839

3940
* Note that for comparison with the `=` you need to wrap the column name in the `DATE(columnname)` or one of the other date functions in MySQL. <http://dev.mysql.com/doc/refman/5.5/en/date-and-time-functions.html>

application.rb

+12
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,18 @@ class Application < Sinatra::Base
6666
end
6767

6868

69+
post '/autosuggestable' do
70+
results = QueryRow.autosuggest(params[:db], params[:table], params[:column], params[:query])
71+
72+
toReturn = {
73+
:results => results,
74+
:version => params[:version]
75+
}
76+
77+
toReturn.to_json
78+
end
79+
80+
6981
['/:db/:query', '/:db/:query/:order', '/:db/:query/:order/desc', '/'].each do |path|
7082
get path do
7183
@queries = QueryTable.all

models/queryrow.rb

+25-1
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ def results(params, limit = 100)
8888
queryToExecute = query['query']
8989
else
9090
queryArgs.each do |arg|
91+
p params['arg-' + arg[2]]
9192
if params['arg-' + arg[2]].nil?
9293
default = QueryRow.getArgParam(arg[3], 'default')
9394

@@ -101,7 +102,7 @@ def results(params, limit = 100)
101102

102103
queryToExecute = query['query'].gsub(pattern) { |match|
103104
match = match.scan(pattern)
104-
params['arg-' + match[0][2]]
105+
CGI::unescape(params['arg-' + match[0][2]])
105106
}
106107
end
107108

@@ -155,4 +156,27 @@ def self.getArgParam(haystack, needle)
155156
result = result[2] unless result.nil?
156157
result
157158
end
159+
160+
161+
def self.autosuggest(db, table, column, query)
162+
seconddb = Sequel.connect(
163+
:adapter => 'mysql2',
164+
:host => Config['db']['host'],
165+
:user => Config['db']['user'],
166+
:password => Config['db']['pass'],
167+
:database => db,
168+
:timeout => 30,
169+
:reconnect => true
170+
)
171+
172+
toReturn = []
173+
174+
results = seconddb[table.to_sym].filter(column.to_sym.like('%' + query + '%')).limit(14)
175+
176+
results.each do |results|
177+
toReturn.push(results[column.to_sym])
178+
end
179+
180+
toReturn
181+
end
158182
end

public/scripts/asq.js

+32-1
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ var Asq = {
4747
Asq.current.db = $('meta[name="default-db"]').attr('content');
4848

4949
Asq.recover();
50+
51+
Asq.log('Page rendered.');
5052
},
5153

5254

@@ -563,6 +565,8 @@ var Asq = {
563565
info;
564566

565567
$.each(Asq.requestedArgs, function(index, arg) {
568+
var extraAttrs = '';
569+
566570
if (arg[1] == 'DATE') {
567571
inputClass = 'date-pick';
568572

@@ -575,6 +579,11 @@ var Asq = {
575579
info = 'date';
576580
}
577581
}
582+
else if (arg[1] == 'STRING' && Asq.getArgParam(arg[3], 'autosuggest')) {
583+
inputClass = 'autosuggest';
584+
extraAttrs = ' data-autosuggest="' + Asq.getArgParam(arg[3], 'autosuggest') + '"';
585+
info = 'string';
586+
}
578587
else {
579588
inputClass = false;
580589
info = arg[1].toLowerCase();
@@ -587,7 +596,7 @@ var Asq = {
587596
arg[2] +
588597
' <small>(' + info + ')</small>' +
589598
'</label>' +
590-
'<input id="arg-' + arg[2] + '"' + (inputClass ? ' class="' + inputClass + '"' : '') + ' value="' + value + '">' +
599+
'<input id="arg-' + arg[2] + '"' + (inputClass ? ' class="' + inputClass + '"' : '') + ' value="' + value + '"' + extraAttrs + '>' +
591600
'</li>';
592601
});
593602

@@ -601,6 +610,24 @@ var Asq = {
601610
startDate: '1996-01-01'
602611
});
603612

613+
argsElm.find('.autosuggest').each(function(index, elm) {
614+
elm = $(elm);
615+
616+
var tableColumn = elm.attr('data-autosuggest').split('.');
617+
data = {
618+
db: Asq.current.db,
619+
table: tableColumn[0],
620+
column: tableColumn[1]
621+
};
622+
623+
elm.autosuggest({
624+
data: data,
625+
clickLinkCallback: function(aElm) {
626+
elm.val($(aElm).attr('data-suggestion'));
627+
}
628+
});
629+
});
630+
604631
$.each(location.search.split(/\?|&/), function(index, elm) {
605632
if (elm.indexOf('arg-') == 0) {
606633
var keyval = elm.split('=');
@@ -945,6 +972,10 @@ var Asq = {
945972

946973

947974
formatData: function(data) {
975+
if (/[0-9a-z]{40}/i.test(data)) {
976+
return data;
977+
}
978+
948979
var parsedNumber = parseFloat(data, 10),
949980
x, x1, x2;
950981

public/scripts/jquery.autosuggest.js

+228
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
/**
2+
* Curstomized autosuggest jQuery Plugin
3+
*/
4+
(function($) {
5+
var dropdownElm = null;
6+
7+
$.fn.autosuggest = function(options) {
8+
var defaults = {},
9+
elms = this,
10+
elm = null,
11+
highestIndex = 0,
12+
width = 0,
13+
mostRecent = 0;
14+
15+
16+
$.extend(defaults, options);
17+
18+
19+
elms.keyup(typeCheck);
20+
elms.attr('autocomplete', 'off');
21+
elms.mouseup(function(e){e.preventDefault()}); // for webkit
22+
elms.focus(function(e){this.select()});
23+
24+
25+
/* Called on keyup in search box; updown/moves focus in dropdown; ESC closes dropdown; others call API */
26+
function typeCheck(e) {
27+
elm = $(this);
28+
29+
switch (e.which) {
30+
case 27:
31+
if (dropdownElm)
32+
dropdownElm.addClass('hide');
33+
break;
34+
case 38:
35+
e.preventDefault();
36+
selectLink('last');
37+
break;
38+
case 40:
39+
e.preventDefault();
40+
selectLink('first');
41+
break;
42+
default:
43+
positionDrowndown($(this));
44+
break;
45+
}
46+
}
47+
48+
49+
/* Gets the position of box and places dropdown underneath it. Also does Ajax call to API. */
50+
function positionDrowndown(elm) {
51+
if (!dropdownElm) {
52+
dropdownElm = $('<div class="suggest-dropdown hide">' +
53+
'<div>' +
54+
'</div>' +
55+
'</div>');
56+
$('body').append(dropdownElm);
57+
}
58+
else {
59+
dropdownElm.addClass('hide');
60+
}
61+
62+
var terms = elm.val();
63+
64+
65+
if (terms.length === 0) {
66+
dropdownElm.addClass('hide');
67+
return;
68+
}
69+
70+
var dataToSend = {};
71+
72+
++mostRecent;
73+
74+
$.extend(dataToSend, defaults.data, {
75+
query: elm.val(),
76+
version: mostRecent
77+
});
78+
79+
$('body').addClass('loading');
80+
81+
$.ajax({
82+
url: '/autosuggestable',
83+
dataType: 'json',
84+
data: dataToSend,
85+
type: 'POST',
86+
success: ajaxCallback,
87+
error: function() {
88+
dropdownElm.addClass('hide');
89+
$('body').removeClass('loading');
90+
}
91+
});
92+
93+
/* Calculate and set positions */
94+
var offset = elm.offset(),
95+
top = offset.top + elm.outerHeight(),
96+
left = offset.left;
97+
98+
width = elm.outerWidth();
99+
100+
dropdownElm.css({
101+
left: left + 'px',
102+
top: top + 'px',
103+
width: width + 'px'
104+
});
105+
106+
/* Handle up down keystrokes */
107+
dropdownElm.undelegate('a', 'keyup keydown');
108+
dropdownElm.delegate('a', 'keyup', navigateLink);
109+
dropdownElm.delegate('a', 'keydown', function(e){
110+
if (e.which !== 38 && e.which !== 40) return;
111+
e.preventDefault();
112+
});
113+
114+
dropdownElm.undelegate('a', 'click');
115+
116+
/* Fire callback, if needed. */
117+
if (defaults.clickLinkCallback) {
118+
dropdownElm.delegate('a', 'click', function(e) {
119+
e.stopPropagation();
120+
e.preventDefault();
121+
dropdownElm.addClass('hide');
122+
defaults.clickLinkCallback(this);
123+
elm[0].focus();
124+
});
125+
}
126+
}
127+
128+
129+
/* Handles Ajax JSONP results, puts them in dropdown element */
130+
function ajaxCallback(data) {
131+
if (mostRecent != data.version) return;
132+
133+
$('body').removeClass('loading');
134+
135+
dropdownElm.removeClass('hide');
136+
137+
var html = '<ul>';
138+
139+
$.each(data.results, function(index, suggestion) {
140+
html += '<li><a href="#" data-suggestion="' + suggestion + '">' + suggestion + '</a></li>';
141+
});
142+
143+
html += '</ul>';
144+
145+
dropdownElm.find('div').html(html);
146+
147+
dropdownElm.find('a').each(function(index, aElm) {
148+
aElm = $(aElm);
149+
aElm.attr('data-index', index);
150+
highestIndex = index;
151+
});
152+
153+
elm[0].focus();
154+
}
155+
156+
157+
/* Called when using UP/DOWN keys on links */
158+
function navigateLink(e) {
159+
var aElm = $(this),
160+
upDown = e.which,
161+
currentIndex = parseInt(aElm.attr('data-index'), 10),
162+
toShow = null;
163+
164+
switch (upDown) {
165+
case 38:
166+
toShow = currentIndex - 1;
167+
if (toShow < 0) {
168+
elm[0].focus();
169+
return;
170+
}
171+
break;
172+
case 40:
173+
toShow = currentIndex + 1;
174+
if (toShow > highestIndex) {
175+
elm[0].focus();
176+
return;
177+
}
178+
break;
179+
case 27:
180+
dropdownElm.addClass('hide');
181+
elm[0].focus();
182+
return;
183+
default:
184+
return;
185+
}
186+
187+
e.preventDefault();
188+
189+
selectLink(toShow);
190+
}
191+
192+
193+
/* Gives specieif link focus */
194+
function selectLink(index) {
195+
var tabbedElm;
196+
switch(index) {
197+
case 'first':
198+
tabbedElm = dropdownElm.find('a:first');
199+
break;
200+
case 'last':
201+
tabbedElm = dropdownElm.find('a:last');
202+
break;
203+
default:
204+
tabbedElm = dropdownElm.find('a:eq(' + index + ')')
205+
}
206+
207+
tabbedElm[0].focus();
208+
}
209+
210+
211+
/* Closes dropdown when clicking outside dropdown or searchbar */
212+
function documentClick(e) {
213+
var tgt = $(e.target),
214+
found = tgt.closest('.suggest-dropdown'),
215+
same = tgt.is(elm);
216+
217+
if (found.length === 1 || !dropdownElm || same) return;
218+
219+
dropdownElm.addClass('hide');
220+
}
221+
222+
223+
$('body').click(documentClick);
224+
225+
226+
return elm;
227+
}
228+
})(jQuery);

templates/layout.haml

+1
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,5 @@
1818
%script{ :src => '/scripts/codemirror/mode/mysql/mysql.js'}
1919
%script{ :src => '/scripts/datepicker/date.js'}
2020
%script{ :src => '/scripts/datepicker/jquery.datepicker.js'}
21+
%script{ :src => '/scripts/jquery.autosuggest.js'}
2122
%script{ :src => '/scripts/asq.js'}

0 commit comments

Comments
 (0)