# HG changeset patch # User Sverre Rabbelier # Date 1233787953 0 # Node ID 1b64b59222270c54e6724ce40b3516f0cceec77b # Parent b0dd54907caf5e3abe9af99e72721b736eaea2b6 Added jquery thickbox, ajaxQueue, autocomplete and bgiframe Also added the soc-specific stylesheets for jquery and thickbox. Patch by: "Mario Ferraro" diff -r b0dd54907caf -r 1b64b5922227 app/jquery/jquery-thickbox.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/jquery/jquery-thickbox.js Wed Feb 04 22:52:33 2009 +0000 @@ -0,0 +1,319 @@ +/* + * Thickbox 3.1 - One Box To Rule Them All. + * By Cody Lindley (http://www.codylindley.com) + * Copyright (c) 2007 cody lindley + * Licensed under the MIT License: http://www.opensource.org/licenses/mit-license.php +*/ + +var tb_pathToImage = "../soc/content/images/loadingAnimation.gif"; + +/*!!!!!!!!!!!!!!!!! edit below this line at your own risk !!!!!!!!!!!!!!!!!!!!!!!*/ + +//on page load call tb_init +$(document).ready(function(){ + tb_init('a.thickbox, area.thickbox, input.thickbox');//pass where to apply thickbox + imgLoader = new Image();// preload image + imgLoader.src = tb_pathToImage; +}); + +//add thickbox to href & area elements that have a class of .thickbox +function tb_init(domChunk){ + $(domChunk).click(function(){ + var t = this.title || this.name || null; + var a = this.href || this.alt; + var g = this.rel || false; + tb_show(t,a,g); + this.blur(); + return false; + }); +} + +function tb_show(caption, url, imageGroup) {//function called when the user clicks on a thickbox link + + try { + if (typeof document.body.style.maxHeight === "undefined") {//if IE 6 + $("body","html").css({height: "100%", width: "100%"}); + $("html").css("overflow","hidden"); + if (document.getElementById("TB_HideSelect") === null) {//iframe to hide select elements in ie6 + $("body").append("
"); + $("#TB_overlay").click(tb_remove); + } + }else{//all others + if(document.getElementById("TB_overlay") === null){ + $("body").append("
"); + $("#TB_overlay").click(tb_remove); + } + } + + if(tb_detectMacXFF()){ + $("#TB_overlay").addClass("TB_overlayMacFFBGHack");//use png overlay so hide flash + }else{ + $("#TB_overlay").addClass("TB_overlayBG");//use background and opacity + } + + if(caption===null){caption="";} + $("body").append("
");//add loader to the page + $('#TB_load').show();//show loader + + var baseURL; + if(url.indexOf("?")!==-1){ //ff there is a query string involved + baseURL = url.substr(0, url.indexOf("?")); + }else{ + baseURL = url; + } + + var urlString = /\.jpg$|\.jpeg$|\.png$|\.gif$|\.bmp$/; + var urlType = baseURL.toLowerCase().match(urlString); + + if(urlType == '.jpg' || urlType == '.jpeg' || urlType == '.png' || urlType == '.gif' || urlType == '.bmp'){//code to show images + + TB_PrevCaption = ""; + TB_PrevURL = ""; + TB_PrevHTML = ""; + TB_NextCaption = ""; + TB_NextURL = ""; + TB_NextHTML = ""; + TB_imageCount = ""; + TB_FoundURL = false; + if(imageGroup){ + TB_TempArray = $("a[@rel="+imageGroup+"]").get(); + for (TB_Counter = 0; ((TB_Counter < TB_TempArray.length) && (TB_NextHTML === "")); TB_Counter++) { + var urlTypeTemp = TB_TempArray[TB_Counter].href.toLowerCase().match(urlString); + if (!(TB_TempArray[TB_Counter].href == url)) { + if (TB_FoundURL) { + TB_NextCaption = TB_TempArray[TB_Counter].title; + TB_NextURL = TB_TempArray[TB_Counter].href; + TB_NextHTML = "  Next >"; + } else { + TB_PrevCaption = TB_TempArray[TB_Counter].title; + TB_PrevURL = TB_TempArray[TB_Counter].href; + TB_PrevHTML = "  < Prev"; + } + } else { + TB_FoundURL = true; + TB_imageCount = "Image " + (TB_Counter + 1) +" of "+ (TB_TempArray.length); + } + } + } + + imgPreloader = new Image(); + imgPreloader.onload = function(){ + imgPreloader.onload = null; + + // Resizing large images - orginal by Christian Montoya edited by me. + var pagesize = tb_getPageSize(); + var x = pagesize[0] - 150; + var y = pagesize[1] - 150; + var imageWidth = imgPreloader.width; + var imageHeight = imgPreloader.height; + if (imageWidth > x) { + imageHeight = imageHeight * (x / imageWidth); + imageWidth = x; + if (imageHeight > y) { + imageWidth = imageWidth * (y / imageHeight); + imageHeight = y; + } + } else if (imageHeight > y) { + imageWidth = imageWidth * (y / imageHeight); + imageHeight = y; + if (imageWidth > x) { + imageHeight = imageHeight * (x / imageWidth); + imageWidth = x; + } + } + // End Resizing + + TB_WIDTH = imageWidth + 30; + TB_HEIGHT = imageHeight + 60; + $("#TB_window").append(""+caption+"" + "
"+caption+"
" + TB_imageCount + TB_PrevHTML + TB_NextHTML + "
close or Esc Key
"); + + $("#TB_closeWindowButton").click(tb_remove); + + if (!(TB_PrevHTML === "")) { + function goPrev(){ + if($(document).unbind("click",goPrev)){$(document).unbind("click",goPrev);} + $("#TB_window").remove(); + $("body").append("
"); + tb_show(TB_PrevCaption, TB_PrevURL, imageGroup); + return false; + } + $("#TB_prev").click(goPrev); + } + + if (!(TB_NextHTML === "")) { + function goNext(){ + $("#TB_window").remove(); + $("body").append("
"); + tb_show(TB_NextCaption, TB_NextURL, imageGroup); + return false; + } + $("#TB_next").click(goNext); + + } + + document.onkeydown = function(e){ + if (e == null) { // ie + keycode = event.keyCode; + } else { // mozilla + keycode = e.which; + } + if(keycode == 27){ // close + tb_remove(); + } else if(keycode == 190){ // display previous image + if(!(TB_NextHTML == "")){ + document.onkeydown = ""; + goNext(); + } + } else if(keycode == 188){ // display next image + if(!(TB_PrevHTML == "")){ + document.onkeydown = ""; + goPrev(); + } + } + }; + + tb_position(); + $("#TB_load").remove(); + $("#TB_ImageOff").click(tb_remove); + $("#TB_window").css({display:"block"}); //for safari using css instead of show + }; + + imgPreloader.src = url; + }else{//code to show html + + var queryString = url.replace(/^[^\?]+\??/,''); + var params = tb_parseQuery( queryString ); + + TB_WIDTH = (params['width']*1) + 30 || 630; //defaults to 630 if no paramaters were added to URL + TB_HEIGHT = (params['height']*1) + 40 || 440; //defaults to 440 if no paramaters were added to URL + ajaxContentW = TB_WIDTH - 30; + ajaxContentH = TB_HEIGHT - 45; + + if(url.indexOf('TB_iframe') != -1){// either iframe or ajax window + urlNoQuery = url.split('TB_'); + $("#TB_iframeContent").remove(); + if(params['modal'] != "true"){//iframe no modal + $("#TB_window").append("
"+caption+"
close or Esc Key
"); + }else{//iframe modal + $("#TB_overlay").unbind(); + $("#TB_window").append(""); + } + }else{// not an iframe, ajax + if($("#TB_window").css("display") != "block"){ + if(params['modal'] != "true"){//ajax no modal + $("#TB_window").append("
"+caption+"
close or Esc Key
"); + }else{//ajax modal + $("#TB_overlay").unbind(); + $("#TB_window").append("
"); + } + }else{//this means the window is already up, we are just loading new content via ajax + $("#TB_ajaxContent")[0].style.width = ajaxContentW +"px"; + $("#TB_ajaxContent")[0].style.height = ajaxContentH +"px"; + $("#TB_ajaxContent")[0].scrollTop = 0; + $("#TB_ajaxWindowTitle").html(caption); + } + } + + $("#TB_closeWindowButton").click(tb_remove); + + if(url.indexOf('TB_inline') != -1){ + $("#TB_ajaxContent").append($('#' + params['inlineId']).children()); + $("#TB_window").unload(function () { + $('#' + params['inlineId']).append( $("#TB_ajaxContent").children() ); // move elements back when you're finished + }); + tb_position(); + $("#TB_load").remove(); + $("#TB_window").css({display:"block"}); + }else if(url.indexOf('TB_iframe') != -1){ + tb_position(); + if($.browser.safari){//safari needs help because it will not fire iframe onload + $("#TB_load").remove(); + $("#TB_window").css({display:"block"}); + } + }else{ + $("#TB_ajaxContent").load(url += "&random=" + (new Date().getTime()),function(){//to do a post change this load method + tb_position(); + $("#TB_load").remove(); + tb_init("#TB_ajaxContent a.thickbox"); + $("#TB_window").css({display:"block"}); + }); + } + + } + + if(!params['modal']){ + document.onkeyup = function(e){ + if (e == null) { // ie + keycode = event.keyCode; + } else { // mozilla + keycode = e.which; + } + if(keycode == 27){ // close + tb_remove(); + } + }; + } + + } catch(e) { + //nothing here + } +} + +//helper functions below +function tb_showIframe(){ + $("#TB_load").remove(); + $("#TB_window").css({display:"block"}); +} + +function tb_remove() { + $("#TB_imageOff").unbind("click"); + $("#TB_closeWindowButton").unbind("click"); + $("#TB_window").fadeOut("fast",function(){$('#TB_window,#TB_overlay,#TB_HideSelect').trigger("unload").unbind().remove();}); + $("#TB_load").remove(); + if (typeof document.body.style.maxHeight == "undefined") {//if IE 6 + $("body","html").css({height: "auto", width: "auto"}); + $("html").css("overflow",""); + } + document.onkeydown = ""; + document.onkeyup = ""; + return false; +} + +function tb_position() { +$("#TB_window").css({marginLeft: '-' + parseInt((TB_WIDTH / 2),10) + 'px', width: TB_WIDTH + 'px'}); + if ( !(jQuery.browser.msie && jQuery.browser.version < 7)) { // take away IE6 + $("#TB_window").css({marginTop: '-' + parseInt((TB_HEIGHT / 2),10) + 'px'}); + } +} + +function tb_parseQuery ( query ) { + var Params = {}; + if ( ! query ) {return Params;}// return empty object + var Pairs = query.split(/[;&]/); + for ( var i = 0; i < Pairs.length; i++ ) { + var KeyVal = Pairs[i].split('='); + if ( ! KeyVal || KeyVal.length != 2 ) {continue;} + var key = unescape( KeyVal[0] ); + var val = unescape( KeyVal[1] ); + val = val.replace(/\+/g, ' '); + Params[key] = val; + } + return Params; +} + +function tb_getPageSize(){ + var de = document.documentElement; + var w = window.innerWidth || self.innerWidth || (de&&de.clientWidth) || document.body.clientWidth; + var h = window.innerHeight || self.innerHeight || (de&&de.clientHeight) || document.body.clientHeight; + arrayPageSize = [w,h]; + return arrayPageSize; +} + +function tb_detectMacXFF() { + var userAgent = navigator.userAgent.toLowerCase(); + if (userAgent.indexOf('mac') != -1 && userAgent.indexOf('firefox')!=-1) { + return true; + } +} + + diff -r b0dd54907caf -r 1b64b5922227 app/jquery/jquery.ajaxQueue.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/jquery/jquery.ajaxQueue.js Wed Feb 04 22:52:33 2009 +0000 @@ -0,0 +1,116 @@ +/** + * Ajax Queue Plugin + * + * Homepage: http://jquery.com/plugins/project/ajaxqueue + * Documentation: http://docs.jquery.com/AjaxQueue + */ + +/** + + + + + */ +/* + * Queued Ajax requests. + * A new Ajax request won't be started until the previous queued + * request has finished. + */ + +/* + * Synced Ajax requests. + * The Ajax request will happen as soon as you call this method, but + * the callbacks (success/error/complete) won't fire until all previous + * synced requests have been completed. + */ + + +(function($) { + + var ajax = $.ajax; + + var pendingRequests = {}; + + var synced = []; + var syncedData = []; + + $.ajax = function(settings) { + // create settings for compatibility with ajaxSetup + settings = jQuery.extend(settings, jQuery.extend({}, jQuery.ajaxSettings, settings)); + + var port = settings.port; + + switch(settings.mode) { + case "abort": + if ( pendingRequests[port] ) { + pendingRequests[port].abort(); + } + return pendingRequests[port] = ajax.apply(this, arguments); + case "queue": + var _old = settings.complete; + settings.complete = function(){ + if ( _old ) + _old.apply( this, arguments ); + jQuery([ajax]).dequeue("ajax" + port );; + }; + + jQuery([ ajax ]).queue("ajax" + port, function(){ + ajax( settings ); + }); + return; + case "sync": + var pos = synced.length; + + synced[ pos ] = { + error: settings.error, + success: settings.success, + complete: settings.complete, + done: false + }; + + syncedData[ pos ] = { + error: [], + success: [], + complete: [] + }; + + settings.error = function(){ syncedData[ pos ].error = arguments; }; + settings.success = function(){ syncedData[ pos ].success = arguments; }; + settings.complete = function(){ + syncedData[ pos ].complete = arguments; + synced[ pos ].done = true; + + if ( pos == 0 || !synced[ pos-1 ] ) + for ( var i = pos; i < synced.length && synced[i].done; i++ ) { + if ( synced[i].error ) synced[i].error.apply( jQuery, syncedData[i].error ); + if ( synced[i].success ) synced[i].success.apply( jQuery, syncedData[i].success ); + if ( synced[i].complete ) synced[i].complete.apply( jQuery, syncedData[i].complete ); + + synced[i] = null; + syncedData[i] = null; + } + }; + } + return ajax.apply(this, arguments); + }; + +})(jQuery); \ No newline at end of file diff -r b0dd54907caf -r 1b64b5922227 app/jquery/jquery.autocomplete.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/jquery/jquery.autocomplete.js Wed Feb 04 22:52:33 2009 +0000 @@ -0,0 +1,759 @@ +/* + * Autocomplete - jQuery plugin 1.0.2 + * + * Copyright (c) 2007 Dylan Verheul, Dan G. Switzer, Anjesh Tuladhar, Jörn Zaefferer + * + * Dual licensed under the MIT and GPL licenses: + * http://www.opensource.org/licenses/mit-license.php + * http://www.gnu.org/licenses/gpl.html + * + * Revision: $Id: jquery.autocomplete.js 5747 2008-06-25 18:30:55Z joern.zaefferer $ + * + */ + +;(function($) { + +$.fn.extend({ + autocomplete: function(urlOrData, options) { + var isUrl = typeof urlOrData == "string"; + options = $.extend({}, $.Autocompleter.defaults, { + url: isUrl ? urlOrData : null, + data: isUrl ? null : urlOrData, + delay: isUrl ? $.Autocompleter.defaults.delay : 10, + max: options && !options.scroll ? 10 : 150 + }, options); + + // if highlight is set to false, replace it with a do-nothing function + options.highlight = options.highlight || function(value) { return value; }; + + // if the formatMatch option is not specified, then use formatItem for backwards compatibility + options.formatMatch = options.formatMatch || options.formatItem; + + return this.each(function() { + new $.Autocompleter(this, options); + }); + }, + result: function(handler) { + return this.bind("result", handler); + }, + search: function(handler) { + return this.trigger("search", [handler]); + }, + flushCache: function() { + return this.trigger("flushCache"); + }, + setOptions: function(options){ + return this.trigger("setOptions", [options]); + }, + unautocomplete: function() { + return this.trigger("unautocomplete"); + } +}); + +$.Autocompleter = function(input, options) { + + var KEY = { + UP: 38, + DOWN: 40, + DEL: 46, + TAB: 9, + RETURN: 13, + ESC: 27, + COMMA: 188, + PAGEUP: 33, + PAGEDOWN: 34, + BACKSPACE: 8 + }; + + // Create $ object for input element + var $input = $(input).attr("autocomplete", "off").addClass(options.inputClass); + + var timeout; + var previousValue = ""; + var cache = $.Autocompleter.Cache(options); + var hasFocus = 0; + var lastKeyPressCode; + var config = { + mouseDownOnSelect: false + }; + var select = $.Autocompleter.Select(options, input, selectCurrent, config); + + var blockSubmit; + + // prevent form submit in opera when selecting with return key + $.browser.opera && $(input.form).bind("submit.autocomplete", function() { + if (blockSubmit) { + blockSubmit = false; + return false; + } + }); + + // only opera doesn't trigger keydown multiple times while pressed, others don't work with keypress at all + $input.bind(($.browser.opera ? "keypress" : "keydown") + ".autocomplete", function(event) { + // track last key pressed + lastKeyPressCode = event.keyCode; + switch(event.keyCode) { + + case KEY.UP: + event.preventDefault(); + if ( select.visible() ) { + select.prev(); + } else { + onChange(0, true); + } + break; + + case KEY.DOWN: + event.preventDefault(); + if ( select.visible() ) { + select.next(); + } else { + onChange(0, true); + } + break; + + case KEY.PAGEUP: + event.preventDefault(); + if ( select.visible() ) { + select.pageUp(); + } else { + onChange(0, true); + } + break; + + case KEY.PAGEDOWN: + event.preventDefault(); + if ( select.visible() ) { + select.pageDown(); + } else { + onChange(0, true); + } + break; + + // matches also semicolon + case options.multiple && $.trim(options.multipleSeparator) == "," && KEY.COMMA: + case KEY.TAB: + case KEY.RETURN: + if( selectCurrent() ) { + // stop default to prevent a form submit, Opera needs special handling + event.preventDefault(); + blockSubmit = true; + return false; + } + break; + + case KEY.ESC: + select.hide(); + break; + + default: + clearTimeout(timeout); + timeout = setTimeout(onChange, options.delay); + break; + } + }).focus(function(){ + // track whether the field has focus, we shouldn't process any + // results if the field no longer has focus + hasFocus++; + }).blur(function() { + hasFocus = 0; + if (!config.mouseDownOnSelect) { + hideResults(); + } + }).click(function() { + // show select when clicking in a focused field + if ( hasFocus++ > 1 && !select.visible() ) { + onChange(0, true); + } + }).bind("search", function() { + // TODO why not just specifying both arguments? + var fn = (arguments.length > 1) ? arguments[1] : null; + function findValueCallback(q, data) { + var result; + if( data && data.length ) { + for (var i=0; i < data.length; i++) { + if( data[i].result.toLowerCase() == q.toLowerCase() ) { + result = data[i]; + break; + } + } + } + if( typeof fn == "function" ) fn(result); + else $input.trigger("result", result && [result.data, result.value]); + } + $.each(trimWords($input.val()), function(i, value) { + request(value, findValueCallback, findValueCallback); + }); + }).bind("flushCache", function() { + cache.flush(); + }).bind("setOptions", function() { + $.extend(options, arguments[1]); + // if we've updated the data, repopulate + if ( "data" in arguments[1] ) + cache.populate(); + }).bind("unautocomplete", function() { + select.unbind(); + $input.unbind(); + $(input.form).unbind(".autocomplete"); + }); + + + function selectCurrent() { + var selected = select.selected(); + if( !selected ) + return false; + + var v = selected.result; + previousValue = v; + + if ( options.multiple ) { + var words = trimWords($input.val()); + if ( words.length > 1 ) { + v = words.slice(0, words.length - 1).join( options.multipleSeparator ) + options.multipleSeparator + v; + } + v += options.multipleSeparator; + } + + $input.val(v); + hideResultsNow(); + $input.trigger("result", [selected.data, selected.value]); + return true; + } + + function onChange(crap, skipPrevCheck) { + if( lastKeyPressCode == KEY.DEL ) { + select.hide(); + return; + } + + var currentValue = $input.val(); + + if ( !skipPrevCheck && currentValue == previousValue ) + return; + + previousValue = currentValue; + + currentValue = lastWord(currentValue); + if ( currentValue.length >= options.minChars) { + $input.addClass(options.loadingClass); + if (!options.matchCase) + currentValue = currentValue.toLowerCase(); + request(currentValue, receiveData, hideResultsNow); + } else { + stopLoading(); + select.hide(); + } + }; + + function trimWords(value) { + if ( !value ) { + return [""]; + } + var words = value.split( options.multipleSeparator ); + var result = []; + $.each(words, function(i, value) { + if ( $.trim(value) ) + result[i] = $.trim(value); + }); + return result; + } + + function lastWord(value) { + if ( !options.multiple ) + return value; + var words = trimWords(value); + return words[words.length - 1]; + } + + // fills in the input box w/the first match (assumed to be the best match) + // q: the term entered + // sValue: the first matching result + function autoFill(q, sValue){ + // autofill in the complete box w/the first match as long as the user hasn't entered in more data + // if the last user key pressed was backspace, don't autofill + if( options.autoFill && (lastWord($input.val()).toLowerCase() == q.toLowerCase()) && lastKeyPressCode != KEY.BACKSPACE ) { + // fill in the value (keep the case the user has typed) + $input.val($input.val() + sValue.substring(lastWord(previousValue).length)); + // select the portion of the value not typed by the user (so the next character will erase) + $.Autocompleter.Selection(input, previousValue.length, previousValue.length + sValue.length); + } + }; + + function hideResults() { + clearTimeout(timeout); + timeout = setTimeout(hideResultsNow, 200); + }; + + function hideResultsNow() { + var wasVisible = select.visible(); + select.hide(); + clearTimeout(timeout); + stopLoading(); + if (options.mustMatch) { + // call search and run callback + $input.search( + function (result){ + // if no value found, clear the input box + if( !result ) { + if (options.multiple) { + var words = trimWords($input.val()).slice(0, -1); + $input.val( words.join(options.multipleSeparator) + (words.length ? options.multipleSeparator : "") ); + } + else + $input.val( "" ); + } + } + ); + } + if (wasVisible) + // position cursor at end of input field + $.Autocompleter.Selection(input, input.value.length, input.value.length); + }; + + function receiveData(q, data) { + if ( data && data.length && hasFocus ) { + stopLoading(); + select.display(data, q); + autoFill(q, data[0].value); + select.show(); + } else { + hideResultsNow(); + } + }; + + function request(term, success, failure) { + if (!options.matchCase) + term = term.toLowerCase(); + var data = cache.load(term); + // recieve the cached data + if (data && data.length) { + success(term, data); + // if an AJAX url has been supplied, try loading the data now + } else if( (typeof options.url == "string") && (options.url.length > 0) ){ + + var extraParams = { + timestamp: +new Date() + }; + $.each(options.extraParams, function(key, param) { + extraParams[key] = typeof param == "function" ? param() : param; + }); + + $.ajax({ + // try to leverage ajaxQueue plugin to abort previous requests + mode: "abort", + // limit abortion to this input + port: "autocomplete" + input.name, + dataType: options.dataType, + url: options.url, + data: $.extend({ + q: lastWord(term), + limit: options.max + }, extraParams), + success: function(data) { + var parsed = options.parse && options.parse(data) || parse(data); + cache.add(term, parsed); + success(term, parsed); + } + }); + } else { + // if we have a failure, we need to empty the list -- this prevents the the [TAB] key from selecting the last successful match + select.emptyList(); + failure(term); + } + }; + + function parse(data) { + var parsed = []; + var rows = data.split("\n"); + for (var i=0; i < rows.length; i++) { + var row = $.trim(rows[i]); + if (row) { + row = row.split("|"); + parsed[parsed.length] = { + data: row, + value: row[0], + result: options.formatResult && options.formatResult(row, row[0]) || row[0] + }; + } + } + return parsed; + }; + + function stopLoading() { + $input.removeClass(options.loadingClass); + }; + +}; + +$.Autocompleter.defaults = { + inputClass: "ac_input", + resultsClass: "ac_results", + loadingClass: "ac_loading", + minChars: 1, + delay: 400, + matchCase: false, + matchSubset: true, + matchContains: false, + cacheLength: 10, + max: 100, + mustMatch: false, + extraParams: {}, + selectFirst: true, + formatItem: function(row) { return row[0]; }, + formatMatch: null, + autoFill: false, + width: 0, + multiple: false, + multipleSeparator: ", ", + highlight: function(value, term) { + return value.replace(new RegExp("(?![^&;]+;)(?!<[^<>]*)(" + term.replace(/([\^\$\(\)\[\]\{\}\*\.\+\?\|\\])/gi, "\\$1") + ")(?![^<>]*>)(?![^&;]+;)", "gi"), "$1"); + }, + scroll: true, + scrollHeight: 180 +}; + +$.Autocompleter.Cache = function(options) { + + var data = {}; + var length = 0; + + function matchSubset(s, sub) { + if (!options.matchCase) + s = s.toLowerCase(); + var i = s.indexOf(sub); + if (i == -1) return false; + return i == 0 || options.matchContains; + }; + + function add(q, value) { + if (length > options.cacheLength){ + flush(); + } + if (!data[q]){ + length++; + } + data[q] = value; + } + + function populate(){ + if( !options.data ) return false; + // track the matches + var stMatchSets = {}, + nullData = 0; + + // no url was specified, we need to adjust the cache length to make sure it fits the local data store + if( !options.url ) options.cacheLength = 1; + + // track all options for minChars = 0 + stMatchSets[""] = []; + + // loop through the array and create a lookup structure + for ( var i = 0, ol = options.data.length; i < ol; i++ ) { + var rawValue = options.data[i]; + // if rawValue is a string, make an array otherwise just reference the array + rawValue = (typeof rawValue == "string") ? [rawValue] : rawValue; + + var value = options.formatMatch(rawValue, i+1, options.data.length); + if ( value === false ) + continue; + + var firstChar = value.charAt(0).toLowerCase(); + // if no lookup array for this character exists, look it up now + if( !stMatchSets[firstChar] ) + stMatchSets[firstChar] = []; + + // if the match is a string + var row = { + value: value, + data: rawValue, + result: options.formatResult && options.formatResult(rawValue) || value + }; + + // push the current match into the set list + stMatchSets[firstChar].push(row); + + // keep track of minChars zero items + if ( nullData++ < options.max ) { + stMatchSets[""].push(row); + } + }; + + // add the data items to the cache + $.each(stMatchSets, function(i, value) { + // increase the cache size + options.cacheLength++; + // add to the cache + add(i, value); + }); + } + + // populate any existing data + setTimeout(populate, 25); + + function flush(){ + data = {}; + length = 0; + } + + return { + flush: flush, + add: add, + populate: populate, + load: function(q) { + if (!options.cacheLength || !length) + return null; + /* + * if dealing w/local data and matchContains than we must make sure + * to loop through all the data collections looking for matches + */ + if( !options.url && options.matchContains ){ + // track all matches + var csub = []; + // loop through all the data grids for matches + for( var k in data ){ + // don't search through the stMatchSets[""] (minChars: 0) cache + // this prevents duplicates + if( k.length > 0 ){ + var c = data[k]; + $.each(c, function(i, x) { + // if we've got a match, add it to the array + if (matchSubset(x.value, q)) { + csub.push(x); + } + }); + } + } + return csub; + } else + // if the exact item exists, use it + if (data[q]){ + return data[q]; + } else + if (options.matchSubset) { + for (var i = q.length - 1; i >= options.minChars; i--) { + var c = data[q.substr(0, i)]; + if (c) { + var csub = []; + $.each(c, function(i, x) { + if (matchSubset(x.value, q)) { + csub[csub.length] = x; + } + }); + return csub; + } + } + } + return null; + } + }; +}; + +$.Autocompleter.Select = function (options, input, select, config) { + var CLASSES = { + ACTIVE: "ac_over" + }; + + var listItems, + active = -1, + data, + term = "", + needsInit = true, + element, + list; + + // Create results + function init() { + if (!needsInit) + return; + element = $("
") + .hide() + .addClass(options.resultsClass) + .css("position", "absolute") + .appendTo(document.body); + + list = $("
    ").appendTo(element).mouseover( function(event) { + if(target(event).nodeName && target(event).nodeName.toUpperCase() == 'LI') { + active = $("li", list).removeClass(CLASSES.ACTIVE).index(target(event)); + $(target(event)).addClass(CLASSES.ACTIVE); + } + }).click(function(event) { + $(target(event)).addClass(CLASSES.ACTIVE); + select(); + // TODO provide option to avoid setting focus again after selection? useful for cleanup-on-focus + input.focus(); + return false; + }).mousedown(function() { + config.mouseDownOnSelect = true; + }).mouseup(function() { + config.mouseDownOnSelect = false; + }); + + if( options.width > 0 ) + element.css("width", options.width); + + needsInit = false; + } + + function target(event) { + var element = event.target; + while(element && element.tagName != "LI") + element = element.parentNode; + // more fun with IE, sometimes event.target is empty, just ignore it then + if(!element) + return []; + return element; + } + + function moveSelect(step) { + listItems.slice(active, active + 1).removeClass(CLASSES.ACTIVE); + movePosition(step); + var activeItem = listItems.slice(active, active + 1).addClass(CLASSES.ACTIVE); + if(options.scroll) { + var offset = 0; + listItems.slice(0, active).each(function() { + offset += this.offsetHeight; + }); + if((offset + activeItem[0].offsetHeight - list.scrollTop()) > list[0].clientHeight) { + list.scrollTop(offset + activeItem[0].offsetHeight - list.innerHeight()); + } else if(offset < list.scrollTop()) { + list.scrollTop(offset); + } + } + }; + + function movePosition(step) { + active += step; + if (active < 0) { + active = listItems.size() - 1; + } else if (active >= listItems.size()) { + active = 0; + } + } + + function limitNumberOfItems(available) { + return options.max && options.max < available + ? options.max + : available; + } + + function fillList() { + list.empty(); + var max = limitNumberOfItems(data.length); + for (var i=0; i < max; i++) { + if (!data[i]) + continue; + var formatted = options.formatItem(data[i].data, i+1, max, data[i].value, term); + if ( formatted === false ) + continue; + var li = $("
  • ").html( options.highlight(formatted, term) ).addClass(i%2 == 0 ? "ac_even" : "ac_odd").appendTo(list)[0]; + $.data(li, "ac_data", data[i]); + } + listItems = list.find("li"); + if ( options.selectFirst ) { + listItems.slice(0, 1).addClass(CLASSES.ACTIVE); + active = 0; + } + // apply bgiframe if available + if ( $.fn.bgiframe ) + list.bgiframe(); + } + + return { + display: function(d, q) { + init(); + data = d; + term = q; + fillList(); + }, + next: function() { + moveSelect(1); + }, + prev: function() { + moveSelect(-1); + }, + pageUp: function() { + if (active != 0 && active - 8 < 0) { + moveSelect( -active ); + } else { + moveSelect(-8); + } + }, + pageDown: function() { + if (active != listItems.size() - 1 && active + 8 > listItems.size()) { + moveSelect( listItems.size() - 1 - active ); + } else { + moveSelect(8); + } + }, + hide: function() { + element && element.hide(); + listItems && listItems.removeClass(CLASSES.ACTIVE); + active = -1; + }, + visible : function() { + return element && element.is(":visible"); + }, + current: function() { + return this.visible() && (listItems.filter("." + CLASSES.ACTIVE)[0] || options.selectFirst && listItems[0]); + }, + show: function() { + var offset = $(input).offset(); + element.css({ + width: typeof options.width == "string" || options.width > 0 ? options.width : $(input).width(), + top: offset.top + input.offsetHeight, + left: offset.left + }).show(); + if(options.scroll) { + list.scrollTop(0); + list.css({ + maxHeight: options.scrollHeight, + overflow: 'auto' + }); + + if($.browser.msie && typeof document.body.style.maxHeight === "undefined") { + var listHeight = 0; + listItems.each(function() { + listHeight += this.offsetHeight; + }); + var scrollbarsVisible = listHeight > options.scrollHeight; + list.css('height', scrollbarsVisible ? options.scrollHeight : listHeight ); + if (!scrollbarsVisible) { + // IE doesn't recalculate width when scrollbar disappears + listItems.width( list.width() - parseInt(listItems.css("padding-left")) - parseInt(listItems.css("padding-right")) ); + } + } + + } + }, + selected: function() { + var selected = listItems && listItems.filter("." + CLASSES.ACTIVE).removeClass(CLASSES.ACTIVE); + return selected && selected.length && $.data(selected[0], "ac_data"); + }, + emptyList: function (){ + list && list.empty(); + }, + unbind: function() { + element && element.remove(); + } + }; +}; + +$.Autocompleter.Selection = function(field, start, end) { + if( field.createTextRange ){ + var selRange = field.createTextRange(); + selRange.collapse(true); + selRange.moveStart("character", start); + selRange.moveEnd("character", end); + selRange.select(); + } else if( field.setSelectionRange ){ + field.setSelectionRange(start, end); + } else { + if( field.selectionStart ){ + field.selectionStart = start; + field.selectionEnd = end; + } + } + field.focus(); +}; + +})(jQuery); \ No newline at end of file diff -r b0dd54907caf -r 1b64b5922227 app/jquery/jquery.bgiframe.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/jquery/jquery.bgiframe.js Wed Feb 04 22:52:33 2009 +0000 @@ -0,0 +1,100 @@ +/* Copyright (c) 2006 Brandon Aaron (http://brandonaaron.net) + * Dual licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) + * and GPL (http://www.opensource.org/licenses/gpl-license.php) licenses. + * + * $LastChangedDate: 2007-07-21 18:44:59 -0500 (Sat, 21 Jul 2007) $ + * $Rev: 2446 $ + * + * Version 2.1.1 + */ + +(function($){ + +/** + * The bgiframe is chainable and applies the iframe hack to get + * around zIndex issues in IE6. It will only apply itself in IE6 + * and adds a class to the iframe called 'bgiframe'. The iframe + * is appeneded as the first child of the matched element(s) + * with a tabIndex and zIndex of -1. + * + * By default the plugin will take borders, sized with pixel units, + * into account. If a different unit is used for the border's width, + * then you will need to use the top and left settings as explained below. + * + * NOTICE: This plugin has been reported to cause perfromance problems + * when used on elements that change properties (like width, height and + * opacity) a lot in IE6. Most of these problems have been caused by + * the expressions used to calculate the elements width, height and + * borders. Some have reported it is due to the opacity filter. All + * these settings can be changed if needed as explained below. + * + * @example $('div').bgiframe(); + * @before

    Paragraph

    + * @result