15 Commits

Author SHA1 Message Date
65d4f6daef bumped version to v0.1.3 2010-03-15 14:19:08 +02:00
fb543ac128 added rake file and updated .gitignore 2010-03-15 14:18:52 +02:00
1f7899e21e updated demo page 2010-03-15 13:49:00 +02:00
01e7282f2f tweaked css a bit for easier customization 2010-03-15 13:48:38 +02:00
89a146d85a improved mustache string matching 2010-03-15 13:48:21 +02:00
ae27ad890c improved redirect_to method and attached it to the
window object
2010-03-15 13:48:07 +02:00
096e625385 added position options 2010-03-15 13:46:48 +02:00
699877ec8a improved exact_match filtering 2010-03-15 13:46:24 +02:00
d1a6f8b003 cleaned up the code a bit 2010-03-15 13:46:08 +02:00
d3cf0ee007 updated todo list in readme 2010-03-10 23:11:04 +02:00
c1e25e30c1 bumped version to v0.1.2 2010-03-10 22:57:47 +02:00
d9e5bf3f22 fixed a serious bug with form submission and
return/enter key handling
2010-03-10 22:57:23 +02:00
05a0ccfe06 bumped version to v0.1.1 2010-03-10 19:45:01 +02:00
7b7ad0a285 better handling when both keyboard and mouse is
used to navigate suggested results
2010-03-10 19:44:34 +02:00
50e0711be0 fixed some typos in readme 2010-03-10 19:37:49 +02:00
7 changed files with 122 additions and 52 deletions

2
.gitignore vendored
View File

@@ -1,2 +1,2 @@
.DS_Store .DS_Store
_releases

View File

@@ -32,7 +32,7 @@ In the above example, `userFriends` is a javascript array that would look someth
{title: "Mike Smith", href: "/user/msmith", match: "msmith mikesmith@gmail.com"} {title: "Mike Smith", href: "/user/msmith", match: "msmith mikesmith@gmail.com"}
]; ];
This array needs to have two vital attributes, `title` and `href`. When filtering results based on what's been typed in the input field, the `title` and `match` attributes are used. `match` is optional, but `title` is required. `href` is the url to redirect the browser too when the result is selected. This array needs to have two vital attributes, `title` and `href`. When filtering results based on what's been typed in the input field, the `title` and `match` attributes are used. `match` is optional, but `title` is required. `href` is the url to redirect the browser to when the result is selected.
The `match` attribute is useful when you need to include extra information like a username or email and get results based on this data without having to display it. The `match` attribute is useful when you need to include extra information like a username or email and get results based on this data without having to display it.
@@ -46,20 +46,20 @@ To fetch results via an Ajax call, attach Suggest Results like this to your inpu
As a user starts typing, this will trigger a GET request to `/search_friends/json?limit=6&search=j` if the user has started typing `j`. We'll get to the `limit` option a bit later. As a user starts typing, this will trigger a GET request to `/search_friends/json?limit=6&search=j` if the user has started typing `j`. We'll get to the `limit` option a bit later.
Output from `search_friends_json.php` must be in JSON, and look like this: Output from `search_friends_json.php` must be in JSON, and look something like this:
{"results": [ {"results": [
{"title": "John Doe", "href": "/user/johndoe"}, {"title": "John Doe", "href": "/user/johndoe"},
{"title": "Mike Smith", "href": "/user/msmith"} {"title": "Mike Smith", "href": "/user/msmith"}
]} ]}
Notice how the `match` attribute is not included, as it's not supported for server-side result fetching. No filtering is done in javascript of the results returned, hence `match` is useless. You should do all filtering and order server-side if going ajax-style. Notice how the `match` attribute is not included, as it's not supported for server-side result fetching. No filtering is done client-side of the results returned, hence `match` is useless. You should do all filtering and ordering server-side if going ajax-style.
Also, a query cache is used so a specific search term is only requested once per page load. Otherwise new ajax calls would be triggered every for each time a user hits the backspace key to remove a letter. Also, a query cache is used so a specific search term is only requested once per page load. Otherwise new ajax calls would be triggered each time a user hits the backspace key to remove a letter for example.
## Options ## Options
There's a number of options you can pass `$.suggest_results`. There's a number of options you can pass `$.suggest_results()`.
* **data:** Javascript array with available results. * **data:** Javascript array with available results.
* **url:** URL to send ajax request to for results. Either `url` or `data` are required for Suggest Results to work at all. * **url:** URL to send ajax request to for results. Either `url` or `data` are required for Suggest Results to work at all.
@@ -109,6 +109,7 @@ There are some more options available too for customization, so I recommend you
* Better documentation and readme. * Better documentation and readme.
* Handle mouse hovering and keyboard navigation a bit better when used at the same time on a suggest box. * Handle mouse hovering and keyboard navigation a bit better when used at the same time on a suggest box.
* Support suggesting search terms in addition to currently only supporting results. * Support suggesting search terms in addition to currently only supporting results.
* Add callbacks for most things.
## Notice ## Notice

40
Rakefile Normal file
View File

@@ -0,0 +1,40 @@
require 'fileutils'
RLS_PATH = "_releases"
RLS_IGNORE = ["#{RLS_PATH}/*", ".git*", "*.DS_Store", "Rakefile"]
desc "Build a release package"
task :release do
FileUtils.mkdir_p(RLS_PATH)
file = File.read("suggest_results/jquery.suggest_results.js")
if file =~ /\* Suggest Results v([0-9\.]+)\n/
version = $1
target = "#{RLS_PATH}/jquery.suggest_results-#{version}.zip"
if File.exist?(target)
puts "ERROR: #{target} already exists."
else
ignore = RLS_IGNORE.map { |i| "-x \"#{i}\"" }.join(" ")
system("zip #{ignore} -r #{target} .")
puts "packaged #{target}"
end
end
end
desc "Update demo page."
task :demo do
rsync(".", "jimeh@jimeh.me:jimeh.me/files/projects/suggest_results", ["--exclude='#{RLS_PATH}'", "--delete"])
end
def rsync(source, dest, options = [])
if source.is_a?(Array)
source.map! { |dir, i| "\"#{dir}\"" }
source = source.join(" ")
end
options << "--exclude='.DS_Store'"
options << "--exclude='.git*'"
system "rsync -vr #{options.join(" ")} #{source} #{dest}"
end

View File

@@ -1,4 +1,4 @@
var data = [ var exampleData = [
{"title": "Ädams, Egbert", "info": "Bedfordshire", "href": "/demo/user/1", "match": "zzzz"}, {"title": "Ädams, Egbert", "info": "Bedfordshire", "href": "/demo/user/1", "match": "zzzz"},
{"title": "Altman, Alisha", "info": "Buckinghamshire", "href": "/demo/user/2", "match": "zzzz"}, {"title": "Altman, Alisha", "info": "Buckinghamshire", "href": "/demo/user/2", "match": "zzzz"},
{"title": "Archibald, Janna", "info": "Cambridgeshire", "href": "/demo/user/3", "match": "zzzz"}, {"title": "Archibald, Janna", "info": "Cambridgeshire", "href": "/demo/user/3", "match": "zzzz"},

View File

@@ -19,19 +19,26 @@
$(document).ready(function(){ $(document).ready(function(){
$("#example1").suggest_results({ $("#example1").suggest_results({
data: data data: exampleData
}); });
$("#example2").suggest_results({ $("#example2").suggest_results({
url: "server-side.php" url: "server-side.php",
}); });
$("#example3").suggest_results({ $("#example3").suggest_results({
url: "server-side.php", url: "server-side.php",
name: "example3", name: "example3",
empty: false, no_results: false,
limit: 4, limit: 4,
tpl_result_body: '<span class="title">{{title}}</span><span class="info">{{info}}</span>' tpl_result_body: '<span class="title">{{title}}</span><span class="info">{{ info }}</span>'
});
$("#example4").suggest_results({
data: exampleData,
pos_top: 0,
pos_left: 20,
width: 168
}); });
}); });
@@ -68,6 +75,10 @@
<strong>Customized results from ajax call:</strong><br /> <strong>Customized results from ajax call:</strong><br />
<input type="text" name="example3" value="" id="example3" /> <input type="text" name="example3" value="" id="example3" />
</p> </p>
<p>
<strong>Customized position with results from local array:</strong><br />
<input type="text" name="example4" value="" id="example4" />
</p>
<p> <p>
<a href="http://github.com/jimeh/suggest_results">GitHub Project Page</a> <a href="http://github.com/jimeh/suggest_results">GitHub Project Page</a>
</p> </p>

View File

@@ -9,7 +9,7 @@
#suggest_results ol { #suggest_results ol {
background: #fbfbfb; background: #fbfbfb;
border: 1px solid #bbb; border: 1px solid #bbb;
border-top: none; border-top-style: none;
margin: 0px; margin: 0px;
padding: 0px; padding: 0px;
list-style: none; list-style: none;
@@ -47,7 +47,6 @@
display: block; display: block;
} }
#suggest_results li.result a:hover,
#suggest_results li.result.selected a { #suggest_results li.result.selected a {
background: #698DE5; background: #698DE5;
color: #fff; color: #fff;

View File

@@ -1,5 +1,5 @@
/*! /*!
* Suggest Results v0.1.0 * Suggest Results v0.1.3
* http://github.com/jimeh/suggest_results * http://github.com/jimeh/suggest_results
* *
* Copyright (c) 2010 Jim Myhrberg. * Copyright (c) 2010 Jim Myhrberg.
@@ -24,7 +24,7 @@
$e.focus(function(){ $e.focus(function(){
self.attach($e, $options); self.attach($e, $options);
if ($e.val().length > 0) { if ($e.val().length > 0) {
self.search_timeout = self.setTimeout(function(){ self.setTimeout(function(){
self.search($e, $options); self.search($e, $options);
}, $options.delay); }, $options.delay);
}; };
@@ -32,11 +32,21 @@
self.hide(); self.hide();
}).keydown(function(e){ }).keydown(function(e){
switch(e.keyCode) { switch(e.keyCode) {
case ARRUP: self.select_prev($e, $options); return false; case ARRUP:
case ARRDN: self.select_next($e, $options); return false; self.select_prev($e, $options);
case ESC: self.clear($e, $options); break; return false;
case RETURN: self.activate_selected($options); return false; case ARRDN:
default: self.clearTimeout(); self.search($e, $options); self.select_next($e, $options);
return false;
case ESC:
self.clear($e, $options);
break;
case RETURN:
if (self.selected_result !== null) {
self.activate_selected($options);
return false;
}
break;
} }
}).keyup(function(e){ }).keyup(function(e){
if (e.keyCode > SPECIALS_END || e.keyCode == BACKSPACE) { if (e.keyCode > SPECIALS_END || e.keyCode == BACKSPACE) {
@@ -61,13 +71,13 @@
if (self.box.length == 0) { if (self.box.length == 0) {
$("body").append(self.mustache(options.tpl_container, {id: options.tpl_container_id})); $("body").append(self.mustache(options.tpl_container, {id: options.tpl_container_id}));
self.box = $("#" + options.tpl_container_id); self.box = $("#" + options.tpl_container_id);
self.list = $("ol", self.box); self.list = self.box.children("ol");
}; };
}; };
$.fn.suggest_results.search = function(elm, options){ $.fn.suggest_results.search = function(elm, options){
var self = $.fn.suggest_results; var self = $.fn.suggest_results;
var terms = (options.exact_match) ? $.trim(elm.val()) : elm.val().split(/\s/); var terms = (options.exact_match) ? $.trim(elm.val()) : $.trim(elm.val()).split(/\s+/);
if (typeof(options.url) === "string" && options.url !== "") { if (typeof(options.url) === "string" && options.url !== "") {
self.query_for_data(elm, options); self.query_for_data(elm, options);
} else { } else {
@@ -118,7 +128,12 @@
}; };
self.list.html(html); self.list.html(html);
$(".result", self.list).click(function(){ $(".result", self.list).click(function(){
self.redirect_to($("a", $(this)).attr("href")); window.redirect_to($("a", $(this)).attr("href"));
}).hover(function(){
$(".selected", self.list).removeClass("selected");
$(this).addClass("selected");
},function(){
$(this).removeClass("selected");
}); });
}; };
@@ -130,12 +145,13 @@
var offset = elm.offset(); var offset = elm.offset();
// left offset // left offset
self.box.css("left", offset.left + "px"); self.box.css("left", (offset.left + options.pos_left));
// top offset // top offset
var top = offset.top + elm.innerHeight(); var top = offset.top + elm.innerHeight();
top += parseInt(elm.css("border-top-width"), 10) + parseInt(elm.css("border-bottom-width"), 10); top += parseInt(elm.css("border-top-width"), 10) + parseInt(elm.css("border-bottom-width"), 10);
self.box.css("top", top + "px"); top += options.pos_top;
self.box.css("top", top);
// width // width
if (typeof(options.width) === "number" || (typeof(options.width) === "string" && options.width != "")) { if (typeof(options.width) === "number" || (typeof(options.width) === "string" && options.width != "")) {
@@ -144,7 +160,7 @@
var width = elm.innerWidth(); var width = elm.innerWidth();
width += parseInt(elm.css("border-left-width"), 10) + parseInt(elm.css("border-right-width"), 10); width += parseInt(elm.css("border-left-width"), 10) + parseInt(elm.css("border-right-width"), 10);
width -= parseInt(self.box.css("border-left-width"), 10) + parseInt(self.box.css("border-right-width"), 10); width -= parseInt(self.box.css("border-left-width"), 10) + parseInt(self.box.css("border-right-width"), 10);
self.box.css("width", width + "px"); self.box.css("width", width);
}; };
self.attached_to = elm_uid; self.attached_to = elm_uid;
}; };
@@ -169,14 +185,15 @@
var limit = self.current_results.length; var limit = self.current_results.length;
if (limit > 0) { if (limit > 0) {
if (self.selected_result === null) { if (self.selected_result === null) {
$(".selected", self.list).removeClass("selected");
self.selected_result = 0; self.selected_result = 0;
$("#suggested_result_" + self.selected_result, self.box).addClass("selected"); $("#suggested_result_" + self.selected_result, self.box).addClass("selected");
} else if (self.selected_result + 1 < limit) { } else if (self.selected_result + 1 < limit) {
$(".selected", self.box).removeClass("selected"); $(".selected", self.list).removeClass("selected");
self.selected_result++; self.selected_result++;
$("#suggested_result_" + self.selected_result, self.box).addClass("selected"); $("#suggested_result_" + self.selected_result, self.box).addClass("selected");
} else { } else {
$(".selected", self.box).removeClass("selected"); $(".selected", self.list).removeClass("selected");
self.selected_result = null; self.selected_result = null;
elm.putCursorAtEnd(); elm.putCursorAtEnd();
}; };
@@ -189,14 +206,15 @@
var limit = self.current_results.length; var limit = self.current_results.length;
if (limit > 0) { if (limit > 0) {
if (self.selected_result === null) { if (self.selected_result === null) {
$(".selected", self.list).removeClass("selected");
self.selected_result = limit - 1; self.selected_result = limit - 1;
$("#suggested_result_" + self.selected_result, self.box).addClass("selected"); $("#suggested_result_" + self.selected_result, self.box).addClass("selected");
} else if (self.selected_result > 0) { } else if (self.selected_result > 0) {
$(".selected", self.box).removeClass("selected"); $(".selected", self.list).removeClass("selected");
self.selected_result--; self.selected_result--;
$("#suggested_result_" + self.selected_result, self.box).addClass("selected"); $("#suggested_result_" + self.selected_result, self.box).addClass("selected");
} else { } else {
$(".selected", self.box).removeClass("selected"); $(".selected", self.list).removeClass("selected");
self.selected_result = null; self.selected_result = null;
elm.putCursorAtEnd(); elm.putCursorAtEnd();
}; };
@@ -207,7 +225,7 @@
$.fn.suggest_results.activate_selected = function(options){ $.fn.suggest_results.activate_selected = function(options){
var self = $.fn.suggest_results; var self = $.fn.suggest_results;
if (self.selected_result !== null) { if (self.selected_result !== null) {
self.redirect_to(self.current_results[self.selected_result].href); window.redirect_to(self.current_results[self.selected_result].href);
}; };
}; };
@@ -215,7 +233,8 @@
if (typeof(terms) === "string") { terms = [terms]; }; if (typeof(terms) === "string") { terms = [terms]; };
var matched = ""; var matched = "";
var results = []; var results = [];
for (var i = terms.length - 1; i >= 0; i--){ var terms_length = terms.length;
for (var i=0; i < terms_length; i++) {
var term = terms[i]; var term = terms[i];
term = term.toLowerCase(); term = term.toLowerCase();
if (data !== null && typeof(term) !== "undefined" && term !== "") { if (data !== null && typeof(term) !== "undefined" && term !== "") {
@@ -264,7 +283,6 @@
} else { } else {
self.no_results(elm, options); self.no_results(elm, options);
}; };
return [];
}; };
$.fn.suggest_results.elm_uid = function(elm){ $.fn.suggest_results.elm_uid = function(elm){
@@ -281,30 +299,12 @@
$.fn.suggest_results.mustache = function(string, data){ $.fn.suggest_results.mustache = function(string, data){
if (typeof(string) === "string" && typeof(data) === "object") { if (typeof(string) === "string" && typeof(data) === "object") {
for (var key in data) { for (var key in data) {
string = string.replace(new RegExp("{{" + key + "}}", "g"), data[key]); string = string.replace(new RegExp("{{\\s*" + key + "\\s*}}", "g"), data[key]);
} }
}; };
return string; return string;
}; };
$.fn.suggest_results.redirect_to = function(url, location){
if (typeof(location) == "undefined") {
location = window.location;
};
var redirect_to = "";
if (url.match(/.+\:\/\/.+/) === null) {
redirect_to += location.protocol + "//";
redirect_to += location.hostname;
if (location.port != "") { redirect_to += ":" + location.port; };
if (url.charAt(0) !== "/") {
redirect_to += location.pathname.substr(0, location.pathname.lastIndexOf("/")+1);
};
window.location.href = redirect_to + url;
} else {
window.location.href = url;
};
};
$.fn.suggest_results.setTimeout = function(callback, delay){ $.fn.suggest_results.setTimeout = function(callback, delay){
var self = $.fn.suggest_results; var self = $.fn.suggest_results;
self.clearTimeout(); self.clearTimeout();
@@ -342,6 +342,8 @@
url: null, url: null,
url_method: "get", url_method: "get",
url_query_var: "search", url_query_var: "search",
pos_top: 0,
pos_left: 0,
delay: 100, delay: 100,
data: null, data: null,
tpl_container_id: "suggest_results", tpl_container_id: "suggest_results",
@@ -355,6 +357,23 @@
})(jQuery); })(jQuery);
/*
redirect_to method from: http://gist.github.com/327227
*/
window.redirect_to = function(url, location){
var redirect_to = "";
if (typeof(location) == "undefined") location = window.location;
if (url.match(/^[a-zA-Z]+\:\/\/.+/) === null) {
redirect_to += location.protocol + "//" + location.hostname;
if (location.port != "") redirect_to += ":" + location.port;
if (url.charAt(0) !== "/") redirect_to += location.pathname.substr(0, location.pathname.lastIndexOf("/")+1);
window.location.href = redirect_to + url;
} else {
window.location.href = url;
};
};
/* /*
Crossbrowser hasOwnProperty solution, based on answers from: Crossbrowser hasOwnProperty solution, based on answers from:
http://stackoverflow.com/questions/135448/how-do-i-check-to-see-if-an-object-has-an-attribute-in-javascript http://stackoverflow.com/questions/135448/how-do-i-check-to-see-if-an-object-has-an-attribute-in-javascript