Created
April 24, 2009 23:42
-
-
Save phillipadsmith/101405 to your computer and use it in GitHub Desktop.
Search-as-you-type interface for Bricolage categories.
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
// ==UserScript== | |
// @name Bricolage category search | |
// @namespace http://wiki.bricolage.cc/greasemonkey | |
// @description Search-as-you-type interface for Bricolage categories. Works for "New Story", "New Media", and "New Category" pages. | |
// @include */workflow/profile/*/new/* | |
// @include */admin/profile/category | |
// ==/UserScript== | |
// Create an object, used below | |
var CatSearch = { | |
lastIndex : 0, | |
lastText : "", | |
get catSelect() { | |
if (! this._catSelect) { | |
var isStoryProf = location.pathname.indexOf('story') !== -1; | |
var isCatProf = location.pathname.indexOf('category') !== -1; | |
var ename = isStoryProf ? 'story_prof|new_category_id' : isCatProf ? 'parent_id' : 'media_prof|category__id'; | |
var selectList = document.getElementsByName(ename); | |
if (selectList.length) { | |
this._catSelect = selectList.item(0); | |
} | |
} | |
return this._catSelect; | |
}, | |
get searchBox() { | |
return this._searchBox; | |
}, | |
addSearchBox : function() { | |
if (this._searchBox) { | |
return; | |
} | |
var textBox = document.createElement('input'); | |
textBox.setAttribute('type', 'text'); | |
textBox.setAttribute('id', 'catsearch'); | |
textBox.setAttribute('class', 'textInput'); | |
textBox.setAttribute('name', 'catsearch'); | |
textBox.setAttribute('size', '50'); | |
textBox.setAttribute('maxlength', '256'); | |
this._searchBox = textBox; | |
var sel = this.catSelect; | |
sel.parentNode.insertBefore(this._searchBox, sel); | |
this.cacheIndex("", 0); | |
}, | |
cacheIndex : function(str, i) { | |
this._lookup[str] = i; | |
}, | |
lookupIndex : function(str) { | |
if (this._lookup[str] !== undefined) { | |
return this._lookup[str]; | |
} | |
return -1; | |
}, | |
searchAsYouType : function(event) { | |
var searchBox = event.target; | |
var key = event.keyCode; | |
var isEnterKey = key === event.DOM_VK_ENTER || key === event.DOM_VK_RETURN; | |
var newText = searchBox.value; | |
var newIndex = this.lookupIndex(newText); | |
if (newIndex !== -1 && !isEnterKey) { | |
this.catSelect.options[newIndex].selected = true; | |
} else { | |
var oldText = this.lastText; | |
var d = newText.length - oldText.length; | |
var newIndex = -1; | |
if (d > 0 && newText.indexOf(oldText) == 0) { | |
// The new text is longer, with the beginning being | |
// the old text, so the user added onto the end; | |
// therefore we can search forward from where we stopped before. | |
d = 1; | |
newIndex = this.lastIndex; | |
} else if (d < 0 && oldText.indexOf(newText) == 0) { | |
// The old text is longer, with the beginning being | |
// the new text, so the user deleted from the end; | |
// therefore we can search backward from where we stopped before. | |
d = -1; | |
newIndex = this.lastIndex; | |
} else if (d === 0 && isEnterKey) { | |
// The user hit Enter, so we continue searching forward. | |
d = 1; | |
newIndex = newText.length ? this.lastIndex + 1 : this.lastIndex; | |
} else { | |
// The user: added/deleted text somewhere besides the end, | |
// or selected a character and replaced with something else; | |
// in all these cases, we start the search over. | |
d = 1; | |
newIndex = 0; | |
} | |
// Search unless the text is unchanged or empty | |
var searchMatched = 0; | |
if (isEnterKey || newText.length > 0 || oldText !== newText) { | |
var opts = this.catSelect.options; | |
var optsLen = opts.length; | |
for (var i = newIndex; i >= 0 && i < optsLen; i += d) { | |
var opt = opts[i]; | |
var cat = opt.text; | |
if (cat.indexOf(newText) !== -1) { | |
newIndex = i; | |
opt.selected = true; | |
searchMatched = 1; | |
this.cacheIndex(cat, newIndex); | |
if (! isEnterKey) { | |
// don't save for Enter, otherwise for example | |
// category /new will get cached for /new2, /new3, etc. | |
this.cacheIndex(newText, newIndex); | |
} | |
break; | |
} | |
} | |
} | |
if (newText.length === 0 || !searchMatched) { | |
newIndex = 0; | |
this.catSelect.options[0].selected = true; | |
} | |
} | |
// Save stuff for next time | |
this.lastText = newText; | |
this.lastIndex = newIndex; | |
// Prevent form submit with Enter (Bricolage form validation) | |
if (isEnterKey) { | |
event.preventBubble(); | |
event.preventDefault(); | |
} | |
// Make sure focus is back in the search box | |
searchBox.focus(); | |
}, | |
init : function(event) { | |
this.lastIndex = 0; | |
this.lastText = ""; | |
this._lookup = {}; | |
}, | |
_catSelect: null, | |
_searchBox: null, | |
_lookup: {} | |
}; | |
// Entry point of the greasemonkey script, wrapped in an anonymous | |
// function to avoid polluting the original page's namespace | |
(function() { | |
// Check that we're on the right page, that there's a select list | |
var catSelect = CatSearch.catSelect; | |
if (catSelect) { | |
var opts = catSelect.options; | |
if (! opts.length) { | |
return; | |
} | |
} else { | |
return; | |
} | |
// Add the search box above the category select list | |
CatSearch.addSearchBox(); | |
var searchBox = CatSearch.searchBox; | |
// Add the search-as-you-type callback | |
searchBox.addEventListener('keyup', function(event) { | |
CatSearch.searchAsYouType(event); | |
}, true); | |
// Prevent Bricolage from doing onsubmit validation for Enter during search | |
searchBox.addEventListener('keypress', function(event) { | |
if (event.keyCode === event.DOM_VK_ENTER || event.keyCode === event.DOM_VK_RETURN) { | |
event.preventBubble(); | |
event.preventDefault(); | |
} | |
}, true); | |
searchBox.addEventListener('keydown', function(event) { | |
if (event.keyCode === event.DOM_VK_ENTER || event.keyCode === event.DOM_VK_RETURN) { | |
event.preventBubble(); | |
event.preventDefault(); | |
} | |
}, true); | |
// Initialize on load, in case categories change and to clear the cache | |
window.addEventListener('load', function(event) { | |
CatSearch.init(); | |
}, true); | |
})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment