forked from Restream/redmine_tagging
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Tag autocompletion, better search hits
- Loading branch information
friflaj
committed
Sep 25, 2010
1 parent
bd81de4
commit d49eb98
Showing
3 changed files
with
298 additions
and
26 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,267 @@ | ||
/* | ||
@author: remy sharp / http://remysharp.com | ||
@url: http://remysharp.com/2007/12/28/jquery-tag-suggestion/ | ||
@usage: setGlobalTags(['javascript', 'jquery', 'java', 'json']); // applied tags to be used for all implementations | ||
$('input.tags').tagSuggest(options); | ||
The selector is the element that the user enters their tag list | ||
@params: | ||
matchClass - class applied to the suggestions, defaults to 'tagMatches' | ||
tagContainer - the type of element uses to contain the suggestions, defaults to 'span' | ||
tagWrap - the type of element the suggestions a wrapped in, defaults to 'span' | ||
sort - boolean to force the sorted order of suggestions, defaults to false | ||
url - optional url to get suggestions if setGlobalTags isn't used. Must return array of suggested tags | ||
tags - optional array of tags specific to this instance of element matches | ||
delay - optional sets the delay between keyup and the request - can help throttle ajax requests, defaults to zero delay | ||
separator - optional separator string, defaults to ' ' (Brian J. Cardiff) | ||
@license: Creative Commons License - ShareAlike http://creativecommons.org/licenses/by-sa/3.0/ | ||
@version: 1.4 | ||
@changes: fixed filtering to ajax hits | ||
*/ | ||
|
||
(function ($) { | ||
var globalTags = []; | ||
|
||
// creates a public function within our private code. | ||
// tags can either be an array of strings OR | ||
// array of objects containing a 'tag' attribute | ||
window.setGlobalTags = function(tags /* array */) { | ||
globalTags = getTags(tags); | ||
}; | ||
|
||
function getTags(tags) { | ||
var tag, i, goodTags = []; | ||
for (i = 0; i < tags.length; i++) { | ||
tag = tags[i]; | ||
if (typeof tags[i] == 'object') { | ||
tag = tags[i].tag; | ||
} | ||
goodTags.push(tag.toLowerCase()); | ||
} | ||
|
||
return goodTags; | ||
} | ||
|
||
$.fn.tagSuggest = function (options) { | ||
var defaults = { | ||
'matchClass' : 'tagMatches', | ||
'tagContainer' : 'span', | ||
'tagWrap' : 'span', | ||
'sort' : true, | ||
'tags' : null, | ||
'url' : null, | ||
'delay' : 0, | ||
'separator' : ' ' | ||
}; | ||
|
||
var i, tag, userTags = [], settings = $.extend({}, defaults, options); | ||
|
||
if (settings.tags) { | ||
userTags = getTags(settings.tags); | ||
} else { | ||
userTags = globalTags; | ||
} | ||
|
||
return this.each(function () { | ||
var tagsElm = $(this); | ||
var elm = this; | ||
var matches, fromTab = false; | ||
var suggestionsShow = false; | ||
var workingTags = []; | ||
var currentTag = {"position": 0, tag: ""}; | ||
var tagMatches = document.createElement(settings.tagContainer); | ||
|
||
function showSuggestionsDelayed(el, key) { | ||
if (settings.delay) { | ||
if (elm.timer) clearTimeout(elm.timer); | ||
elm.timer = setTimeout(function () { | ||
showSuggestions(el, key); | ||
}, settings.delay); | ||
} else { | ||
showSuggestions(el, key); | ||
} | ||
} | ||
|
||
function showSuggestions(el, key) { | ||
workingTags = el.value.split(settings.separator); | ||
matches = []; | ||
var i, html = '', chosenTags = {}, tagSelected = false; | ||
|
||
// we're looking to complete the tag on currentTag.position (to start with) | ||
currentTag = { position: currentTags.length-1, tag: '' }; | ||
|
||
for (i = 0; i < currentTags.length && i < workingTags.length; i++) { | ||
if (!tagSelected && | ||
currentTags[i].toLowerCase() != workingTags[i].toLowerCase()) { | ||
currentTag = { position: i, tag: workingTags[i].toLowerCase() }; | ||
tagSelected = true; | ||
} | ||
// lookup for filtering out chosen tags | ||
chosenTags[currentTags[i].toLowerCase()] = true; | ||
} | ||
|
||
if (currentTag.tag) { | ||
// collect potential tags | ||
if (settings.url) { | ||
$.ajax({ | ||
'url' : settings.url, | ||
'dataType' : 'json', | ||
'data' : { 'tag' : currentTag.tag }, | ||
'async' : false, // wait until this is ajax hit is complete before continue | ||
'success' : function (m) { | ||
matches = m; | ||
} | ||
}); | ||
} else { | ||
for (i = 0; i < userTags.length; i++) { | ||
if (userTags[i].indexOf(currentTag.tag) === 0) { | ||
matches.push(userTags[i]); | ||
} | ||
} | ||
} | ||
|
||
matches = $.grep(matches, function (v, i) { | ||
return !chosenTags[v.toLowerCase()]; | ||
}); | ||
|
||
if (settings.sort) { | ||
matches = matches.sort(); | ||
} | ||
|
||
for (i = 0; i < matches.length; i++) { | ||
html += '<' + settings.tagWrap + ' class="_tag_suggestion">' + matches[i] + '</' + settings.tagWrap + '>'; | ||
} | ||
|
||
tagMatches.html(html); | ||
suggestionsShow = !!(matches.length); | ||
} else { | ||
hideSuggestions(); | ||
} | ||
} | ||
|
||
function hideSuggestions() { | ||
tagMatches.empty(); | ||
matches = []; | ||
suggestionsShow = false; | ||
} | ||
|
||
function setSelection() { | ||
var v = tagsElm.val(); | ||
|
||
// tweak for hintted elements | ||
// http://remysharp.com/2007/01/25/jquery-tutorial-text-box-hints/ | ||
if (v == tagsElm.attr('title') && tagsElm.is('.hint')) v = ''; | ||
|
||
currentTags = v.split(settings.separator); | ||
hideSuggestions(); | ||
} | ||
|
||
function chooseTag(tag) { | ||
var i, index; | ||
for (i = 0; i < currentTags.length; i++) { | ||
if (currentTags[i].toLowerCase() != workingTags[i].toLowerCase()) { | ||
index = i; | ||
break; | ||
} | ||
} | ||
|
||
if (index == workingTags.length - 1) tag = tag + settings.separator; | ||
|
||
workingTags[i] = tag; | ||
|
||
tagsElm.val(workingTags.join(settings.separator)); | ||
tagsElm.blur().focus(); | ||
setSelection(); | ||
} | ||
|
||
function handleKeys(ev) { | ||
fromTab = false; | ||
var type = ev.type; | ||
var resetSelection = false; | ||
|
||
switch (ev.keyCode) { | ||
case 37: // ignore cases (arrow keys) | ||
case 38: | ||
case 39: | ||
case 40: { | ||
hideSuggestions(); | ||
return true; | ||
} | ||
case 224: | ||
case 17: | ||
case 16: | ||
case 18: { | ||
return true; | ||
} | ||
|
||
case 8: { | ||
// delete - hide selections if we're empty | ||
if (this.value == '') { | ||
hideSuggestions(); | ||
setSelection(); | ||
return true; | ||
} else { | ||
type = 'keyup'; // allow drop through | ||
resetSelection = true; | ||
showSuggestionsDelayed(this); | ||
} | ||
break; | ||
} | ||
|
||
case 9: // return and tab | ||
case 13: { | ||
if (suggestionsShow) { | ||
// complete | ||
chooseTag(matches[0]); | ||
|
||
fromTab = true; | ||
return false; | ||
} else { | ||
return true; | ||
} | ||
} | ||
case 27: { | ||
hideSuggestions(); | ||
setSelection(); | ||
return true; | ||
} | ||
case 32: { | ||
setSelection(); | ||
return true; | ||
} | ||
} | ||
|
||
if (type == 'keyup') { | ||
switch (ev.charCode) { | ||
case 9: | ||
case 13: { | ||
return true; | ||
} | ||
} | ||
|
||
if (resetSelection) { | ||
setSelection(); | ||
} | ||
showSuggestionsDelayed(this, ev.charCode); | ||
} | ||
} | ||
|
||
tagsElm.after(tagMatches).keypress(handleKeys).keyup(handleKeys).blur(function () { | ||
if (fromTab == true || suggestionsShow) { // tweak to support tab selection for Opera & IE | ||
fromTab = false; | ||
tagsElm.focus(); | ||
} | ||
}); | ||
|
||
// replace with jQuery version | ||
tagMatches = $(tagMatches).click(function (ev) { | ||
if (ev.target.nodeName == settings.tagWrap.toUpperCase() && $(ev.target).is('._tag_suggestion')) { | ||
chooseTag(ev.target.innerHTML); | ||
} | ||
}).addClass(settings.matchClass); | ||
|
||
// initialise | ||
setSelection(); | ||
}); | ||
}; | ||
})(jQuery); |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters