Created
October 4, 2011 09:43
-
-
Save janjongboom/1261250 to your computer and use it in GitHub Desktop.
Cloud9 test
This file contains hidden or 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
// hello this is a test | |
/// <reference path="jquery-1.4.1.js" /> | |
/* | |
* jQuery plugin: zoekbox - v1.0.0 | |
* (c) 2010-2011 funda real estate <[email protected]> - http://www.funda.nl | |
* | |
* This is the core library for EVERYTHING that uses the zoekbox; | |
* either geo, nieuwbouw-projects, or even the names of all funda employees | |
* therefore NEVER EVER add anything that is implementation-specific to this code. | |
* | |
* BEFORE SUBMITTING: | |
* Always validate this file against JsLint, only the | |
* -Missing '{' before 'something'- validation errors are acceptable. | |
*/ | |
(function ($) { | |
$.fn.extend({ | |
zoekbox: function (opts) { | |
if (typeof console === 'undefined') { | |
console = { log: function () { } }; | |
} | |
return this.each(function() { | |
var options = { | |
endpoints: { | |
suggest: '', | |
alternatives: '' | |
}, | |
templates: { | |
/* el: the container object (jQuery) | |
* item: the item received from the service | |
* query: the executed query | |
* currentItems: array of previous selected items | |
* | |
* return false to cancel */ | |
item: function (el, item, query, currentItems) { | |
return true; | |
}, | |
didyoumean: function () { | |
return $('<h4/>').text('Bedoelde u...'); | |
}, | |
clickitem: function (el) { // el is the selected item | |
return ''; | |
}, | |
dataparameters: function () { // returns an hashmap with extra params for the ajax service | |
return {}; | |
}, | |
matchingkey: function (el) { | |
return ''; | |
} | |
}, | |
events: { | |
onListBound: function (el) { // el is the bound list | |
}, | |
onItemSelected: function (el, boundEle) { // the <a/> that was selected | |
}, | |
onZoekboxCleared: function (zb) { // when the zoekbox was made empty | |
}, | |
onKeyUp: function (ev) { // fires after keyup | |
}, | |
onNiveauBepaald: function (el, level) { // fires after a niveau is determined | |
} | |
}, | |
maxresults: 5, // the max number of results that will be added to the list | |
splittingChars: /[;\+&]/g, // the chars that can be used as an splitting char for multiple values | |
direction: 'bottom', // bottom of top | |
enablesubmit: true, | |
enablemultiple: true, | |
lastNiveau: null, | |
boundEle: this, | |
controls: { | |
clearInputButton: null // an element to clear the text in the zoekbox (null if not available) | |
}, | |
classnames: { | |
container: { | |
alternatives: 'alternatives', | |
suggestions: 'suggestions' | |
}, | |
item: { | |
hover: 'active' | |
}, | |
positionContainer: 'auto-suggest' | |
} | |
}; | |
$.extend(true, options, opts); // doesn't break intellisense this way | |
$(this).data('zoekbox', options); | |
var controls = { | |
zoekbox: $(this), // the textbox | |
positionContainer: null, // the object that needs positioning | |
container: null, // the container around the <ul/> | |
arrow: null, | |
suggestlist: null, // the unordered list wherein the items are rendered | |
clearinput: null, | |
iframe: null, | |
form: $(this).closest('form') // form dat we evt. kunnen submitten | |
}; | |
var state = { | |
requestCounter: 0, // keeps track of the number of requests sent by the AJAX handler | |
termBeforeKeyboardNav: '', // gives back the original term when navigating with keyboard | |
inMouseOver: false, // keeps track whether using mouse-navigation | |
inFormSubmit: false, // are we processing form submit | |
previousQuery: { // to prevent executing the same term twice in a row | |
endpoint: null, | |
term: null | |
}, | |
serviceEnabled: false, | |
itemkeys: [] // array containing the matching keys for all items returned by the service | |
}; | |
var keycodes = { | |
enter: 1, | |
up: 2, | |
down: 3, | |
seperator: 4, | |
escape: 5, | |
shift: 6 | |
}; | |
var zoekboxHelper = $.zoekboxHelper(controls.zoekbox, options.splittingChars); | |
/// Initialize controls | |
var initControls = function ($) { | |
// controls aanmaken en binden | |
controls.positionContainer = $('<div/>').addClass(options.classnames.positionContainer).hide(); | |
// voeg aan de container een iframe (IE 6) toe | |
controls.iframe = $('<iframe/>'); | |
controls.positionContainer.append(controls.iframe); | |
// en de wrappers om de suggestlist heen | |
controls.suggestlist = $('<ul/>'); | |
controls.container = $('<div/>').addClass('auto-suggest-container'); | |
controls.container.append($('<div/>').addClass('auto-suggest-arrow')) // voeg de arrow toe | |
.append( | |
$('<div/>') | |
.addClass('auto-suggest-border') // border | |
.append(options.templates.didyoumean()) // bedoelde u... | |
.append($('<div/>').addClass('auto-suggest-content').append(controls.suggestlist)) // de <ul/> | |
); | |
controls.positionContainer.append(controls.container); // container toevoegen in de main container | |
controls.arrow = controls.container.find('.auto-suggest-arrow'); | |
$('body').append(controls.positionContainer); // en toevoegen aan de body | |
//--- clear input button | |
if (options.controls.clearInputButton) { | |
controls.clearinput = $(options.controls.clearInputButton).hide(); | |
if (controls.zoekbox.width()) { | |
// pas tekstbox breedte aan, aan de clearButton; en de margin-right same same | |
controls.zoekbox.width(controls.zoekbox.width() - (controls.clearinput.width() * 2)) | |
.css({ 'margin-right': (controls.clearinput.width() * 2) + 'px' }); | |
} | |
} | |
//--- autocomplete: off zetten als die er nog niet is | |
controls.zoekbox.attr('autocomplete', 'off'); | |
// Do we need to init search | |
if (controls.zoekbox.hasClass('auto-suggest-input') | |
&& controls.zoekbox.val().length > 0) { | |
// autocomplete waarbij we aangeven dat de list niet populated moet worden | |
autocomplete(options.endpoints.suggest, null, false, controls.zoekbox[0]); | |
} | |
}; | |
/// Initialize events | |
var initEvents = function ($) { | |
// bind events aan de aangemaakte controls | |
if (controls.clearinput) { | |
controls.clearinput.click(clearInput); | |
// als de lengte > 0 dan wel clearinput tonen | |
controls.zoekbox.keyup(function () { | |
controls.clearinput.toggle(controls.zoekbox.val().length > 0); | |
}).keyup(); | |
} | |
$(window).resize(windowResized).resize(); // herpositioneren | |
// keyboard | |
controls.zoekbox.keyup(zoekboxKeyup).keydown(zoekboxKeydown); | |
controls.zoekbox.blur(zoekboxBlur); | |
// events doorgeven als leeggemaakt | |
controls.zoekbox.keyup(function () { | |
if (controls.zoekbox.val().length === 0) { | |
options.events.onZoekboxCleared(controls.zoekbox); | |
} | |
}); | |
// formulier submit overschrijven | |
// ooit ga ik hier iets netjes voor maken | |
controls.form.find('input[type=submit],input[type=button],button').click(function(ev){ | |
if(!options.enablesubmit) return; | |
state.inFormSubmit = true; // we are submitting | |
controls.zoekbox.focus(); | |
controls.suggestlist.find('li').removeClass(options.classnames.item.hover); | |
// dit is hacky ja, maar we willen ev uitbreiden met een nieuwe prop | |
// which: 13 betekent: enter-toets | |
$.extend(true, ev, { | |
which: 13 | |
}); | |
// handle keydown | |
zoekboxKeydown(ev); | |
}); | |
// mouse | |
controls.suggestlist.delegate('li', 'hover', itemHover); | |
controls.suggestlist.delegate('a', 'click', itemClick); | |
// bijhouden of we op dit moment in mousenav zitten | |
controls.positionContainer.mouseover(function () { state.inMouseOver = true; }).mouseout(function () { state.inMouseOver = false; }); | |
}; | |
/// Clear zoekbox, en geef focus | |
var clearInput = function (ev) { | |
controls.zoekbox.val('').focus().keyup(); // handle keyup voor hiden van image | |
ev.preventDefault(); // en niet wegnavigeren hea! | |
}; | |
/// Positioneer de suggestbox | |
var windowResized = function () { | |
var hasInputWrapper = true; | |
var inputWrapper = controls.zoekbox.closest('div.input-wrap'); | |
// geen wrap? | |
if(inputWrapper.length === 0) { | |
inputWrapper = controls.zoekbox; | |
hasInputWrapper = false; | |
} | |
// als :hidden, dan crasht .offset() in IE 8 in IE7 Compat mode | |
if(inputWrapper.is(':hidden')) { | |
return; | |
} | |
var offset = inputWrapper.offset(); // we gaan positioneren op basis van deze wrapper | |
var top; | |
switch (options.direction) { | |
case 'top': | |
top = offset.top - controls.positionContainer.height() ; | |
break; | |
default: | |
top = offset.top + (hasInputWrapper ? inputWrapper.height() : inputWrapper.outerHeight()); | |
break; | |
} | |
controls.positionContainer.css({ | |
position: 'absolute', | |
top: top, | |
left: offset.left, | |
width: hasInputWrapper ? inputWrapper.innerWidth() : inputWrapper.outerWidth() | |
}); | |
}; | |
/// Keystroke | |
var zoekboxKeyup = function (ev) { | |
var keycode = getKeycode(ev); | |
switch (keycode) { | |
case keycodes.enter: | |
case keycodes.up: | |
case keycodes.down: | |
case keycodes.seperator: | |
case keycodes.escape: | |
case keycodes.shift: | |
ev.preventDefault(); | |
options.events.onKeyUp(ev); | |
return; // deze events doen we al in keydown; dus moeten we preventen hier | |
default: | |
state.termBeforeKeyboardNav = zoekboxHelper.zbVal(); // we slaan de laatst handmatig ingevoerde term in | |
autocomplete(options.endpoints.suggest); // en anders doen we gewoon autocomplete | |
options.events.onKeyUp(ev); | |
break; // ach, voor de zekerheid nog even een break | |
} | |
}; | |
var zoekboxKeydown = function (ev) { | |
if (!controls.zoekbox.is(':focus')) { | |
// de focus is verloren; dit kan bv. doordat TAB is ingedrukt | |
controls.positionContainer.hide(); // hide de container | |
return; // en stop execution | |
} | |
if ($.browser.msie && zoekboxHelper.browserVersion() <= 8) { | |
zoekboxHelper.updateCaret(); | |
} | |
if (controls.positionContainer.position().left === 0) { | |
windowResized(); | |
} | |
var keycode = getKeycode(ev); | |
if (keycode === keycodes.shift) { | |
return; // hier doen we al de return; handiger met debuggen | |
} | |
var listLen = null; | |
switch (keycode) { | |
case keycodes.enter: | |
// als we al in alternatives mode zijn, dan gewoon doorgaan | |
// je kan niet 2 keer achter elkaar alternatives krijgen | |
if (state.previousQuery.endpoint === options.endpoints.alternatives) { | |
controls.positionContainer.hide(); | |
return; | |
} | |
// als we niets geselecteerd hebben en op enter rammen... | |
if (getCurrentIndex() === -1 && !handleSeperatorEnterKeyDown(keycode)) { | |
// niet goed gegaan? prevent submit! | |
ev.preventDefault(); | |
} else if (getCurrentIndex() > -1) { | |
// klik op een item! | |
itemClick.call(controls.suggestlist.find('li:nth(' + getCurrentIndex() + ')').find('a:first')); | |
ev.preventDefault(); | |
} | |
if(!options.enablesubmit) { | |
ev.preventDefault(); | |
} | |
break; | |
case keycodes.seperator: | |
if (handleSeperatorEnterKeyDown(keycode)) { | |
// goed gegaan? voeg een lege waarde toe aan de tekstbox (dus Rotterdam -> 'Rotterdam + ') | |
zoekboxHelper.zbVal([zoekboxHelper.zbVal(), '']); | |
} | |
ev.preventDefault(); | |
break; | |
case keycodes.up: | |
// see if we should wrap to last item | |
if (getCurrentIndex() === -1) { | |
listLen = controls.suggestlist.find('li:visible').length; | |
setKeyboardNavigationIndex(listLen - 1); | |
} else { | |
setKeyboardNavigationIndex(getCurrentIndex() - 1); | |
} | |
ev.preventDefault(); | |
break; | |
case keycodes.down: | |
listLen = controls.suggestlist.find('li:visible').length; | |
if (getCurrentIndex() === listLen - 1) { | |
setKeyboardNavigationIndex(-1); | |
} else { | |
setKeyboardNavigationIndex(getCurrentIndex() + 1); | |
} | |
ev.preventDefault(); | |
break; | |
case keycodes.escape: | |
setKeyboardNavigationIndex(-1); // escape gaan we terug naar initiele situatie (niets geselecteerd -> -1) | |
ev.preventDefault(); | |
break; | |
} | |
}; | |
/// Handle seperator or enter char | |
var handleSeperatorEnterKeyDown = function (keycode) { | |
// als de zoekbox niet beschikbaar is, altijd true teruggeven | |
if(!state.serviceEnabled) { | |
return true; | |
} | |
// we hebben een aantal opties | |
// 1. Item komt overeen met een item in onze lijst. Mooi. | |
var matchingElement = findMatchingElement(); | |
if (matchingElement) { | |
itemClick.call(matchingElement); // boots een klik na | |
return true; | |
} | |
// 2. Item komt niet overeen met iets in onze lijst. | |
// Nu kunnen we gaan kijken of er misschien een alternatief beschikbaar is | |
autocomplete(options.endpoints.alternatives, function (data) { | |
if (data.Results.length === 0) { // niets gevonden? | |
// als keycode enter is? dan submitten | |
if (keycode === keycodes.enter && options.enablesubmit) { | |
controls.form.submit(); | |
} | |
// anders gewoon niets doen | |
} | |
}); | |
return false; | |
}; | |
/// Keyboard navigation | |
var setKeyboardNavigationIndex = function (ix) { | |
if (ix >= controls.suggestlist.find('li:visible').length || ix < -1) { | |
return; // moet wel in de boundaries zijn | |
} | |
if (ix === -1) { | |
zoekboxHelper.zbVal(state.termBeforeKeyboardNav); // herstel originele term | |
controls.suggestlist.find('li').removeClass(options.classnames.item.hover); // weg met die hover | |
return; | |
} | |
var item = controls.suggestlist.find('li:nth(' + ix + ')'); | |
zoekboxHelper.zbVal(options.templates.clickitem(item.find('a:first'))); // in de tekstbox zetten we dit dus | |
itemHover.call(item); // roep de hover functie aan met 'item' als callee | |
}; | |
/// Grab the keycode uit het event | |
var getKeycode = function (ev) { | |
if (ev.which === 13) { return keycodes.enter; } | |
if (ev.which === 40) { return keycodes.down; } | |
if (ev.which === 38) { return keycodes.up; } | |
// er zijn verschillen tussen chrome en firefox, da's niet zo mooi; dus hebben we hier wat rare code | |
if(options.enablemultiple) { | |
if (ev.which === 59 || ev.which === 186 /* semicolon */) { | |
return keycodes.seperator; | |
} | |
if (ev.which === 107 /* ampersand */) { | |
return keycodes.seperator; | |
} | |
if ((ev.which === 55 || ev.which === 187) && ev.shiftKey /* plus */) { | |
return keycodes.seperator; | |
} | |
} | |
if (ev.which === 27) { return keycodes.escape; } | |
if (ev.which === 16) { return keycodes.shift; } | |
}; | |
/// Current item | |
var getCurrentIndex = function () { | |
// ja dit is raar; maar is workaround een bug | |
return controls.suggestlist.find('li:visible').index(controls.suggestlist.find('li:visible').filter('.' + options.classnames.item.hover)); | |
}; | |
/// Mouse events | |
var itemHover = function (ev) { | |
controls.suggestlist.find('li').removeClass(options.classnames.item.hover); | |
$(this).addClass(options.classnames.item.hover); | |
}; | |
var itemClick = function (ev) { | |
var itemtext = options.templates.clickitem($(this)); // pak de tekst | |
options.events.onItemSelected($(this), options.boundEle); | |
selectItem(itemtext); | |
return false; | |
}; | |
/// Inputdevice onafhankelijke behavior | |
/// Selecteren van een item bv. uit de suggestlijst | |
var selectItem = function (text) { | |
zoekboxHelper.zbVal(text); // zet de tekst | |
controls.zoekbox.focus(); // geef focus terug aan zoekbox | |
var hideContainer = function () { controls.positionContainer.hide(); }; // hide de container | |
// in IE 6 en 7 wordt de focus() async gedaan; en daardoor wordt de container niet gehide. Shitty dus. | |
if ($.browser.msie && zoekboxHelper.browserVersion() <= 8) { | |
setTimeout(hideContainer, 1); | |
} else { | |
hideContainer(); | |
} | |
controls.suggestlist.find('li').removeClass(options.classnames.item.hover); // verwijder de hover style | |
}; | |
var zoekboxBlur = function (ev) { | |
if (controls.positionContainer.is(':hidden')) { | |
return; | |
} | |
if (state.inMouseOver) { | |
// dit is een click in de suggestlist... die wordt apart afgehandeld. | |
} else { | |
controls.positionContainer.hide(); | |
} | |
}; | |
/// Bekijkt of de tekst in de textbox op dit moment overeenkomt met een item in de lijst, en retouneert deze | |
var findMatchingElement = function () { | |
var val = zoekboxHelper.trim(zoekboxHelper.zbVal()).toLowerCase(); | |
var foundItemIx = -1; | |
// we kijken eerst of er een match is op basis van de exacte input | |
controls.suggestlist.find('li').each(function (i, ele) { | |
if(foundItemIx !== -1) return; | |
if (options.templates.matchingkey($(ele)).toLowerCase() === val) { | |
foundItemIx = i; | |
} | |
}); | |
// dan kijken we of de service vond dat iets een exacte match was | |
if (foundItemIx === -1) { | |
var ix; | |
for (ix = 0; ix < state.itemkeys.length; ix++) { | |
if (state.itemkeys[ix] === true) { | |
foundItemIx = ix; | |
break; | |
} | |
} | |
} | |
// iets gevonden? return obj. | |
if(foundItemIx > -1) { | |
return controls.suggestlist.find('li:nth(' + foundItemIx + ')').find('a:first'); | |
} | |
return false; | |
}; | |
/// Switch style in container object | |
var switchContainerStyle = function (className) { | |
var changeStyle = function (oldC, newC) { | |
if (!controls.container.hasClass(newC) && className === newC) { | |
controls.container.removeClass(oldC); | |
} | |
}; | |
changeStyle(options.classnames.container.alternatives, options.classnames.container.suggestions); | |
changeStyle(options.classnames.container.suggestions, options.classnames.container.alternatives); | |
controls.container.addClass(className); | |
// in IE 6 gaat hier iets niet goed met het weghalen van pijltje; vandaar deze hack | |
if ($.browser.msie && zoekboxHelper.browserVersion() < 7) { | |
controls.arrow.toggle(className === options.classnames.container.alternatives); | |
} | |
// toon de container | |
controls.positionContainer.show(); | |
// pas iframe hoogte aan aan container-height | |
// omdat de positionContainer nu wel zichtbaar is, is height() > 0 | |
controls.iframe.css({ | |
height: (controls.container.outerHeight() + parseInt(controls.container.css("borderBottomWidth"), 10) + 1) + 'px' | |
}); | |
return controls.positionContainer; | |
}; | |
/// Doet een request naar de service | |
var autocomplete = function (endpoint, callback, showResults, elem) { | |
// fixup optional parameter | |
if (typeof showResults === 'undefined') { | |
showResults = true; | |
} | |
// huidige zoekterm | |
var term = zoekboxHelper.zbVal().toLowerCase(); | |
if (!term) { zoekboxHelper.zbVal(term); } // als term leeg is, dan moeten we ff het veld opnieuw renderen | |
var requestCounter = ++state.requestCounter; | |
// als er niets veranderd is, sinds laatste query preventen we de execution | |
if (state.previousQuery.term === term && state.previousQuery.endpoint === endpoint) return; | |
// do een get naar de service (jsonp) | |
var postdata = $.extend({}, { query: term, max: options.maxresults }, options.templates.dataparameters()); | |
$.get(endpoint + '?callback=?', postdata, function (data) { | |
// het kan zijn dat er meerdere requests door elkaar lopen; in dat geval moeten we zeker weten dat dit de actieve is | |
if (requestCounter !== state.requestCounter) return; // in state.requestCounter zit altijd het aantal | |
// clear de suggestlist, en verberg als items is 0 | |
controls.suggestlist.empty(); | |
state.itemkeys = []; | |
// service beschikbaar; dan gaan we dat aangeven | |
state.serviceEnabled = true; | |
// in IE 6 wordt de z-index verkracht wanneer we de container niet hiden voor het opnieuw vullen... | |
if ($.browser.msie && zoekboxHelper.browserVersion() < 7) controls.positionContainer.hide(); | |
// callback functie; mits deze is meegegeven | |
if (callback && $.isFunction(callback)) { callback(data); } | |
// pak alle huidige items, behalve degene die nu geselecteerd is | |
var currentItems = zoekboxHelper.splitZbValue(); | |
currentItems.items.splice(currentItems.ix, 1); | |
var ix, item; | |
for (ix = 0, item = data.Results[ix]; ix < data.Results.length; item = data.Results[++ix]) { | |
// template vullen | |
var a = $('<a/>').attr({ href: '#', ix: ix }); | |
// de template functie vult de <a/>' | |
if (options.templates.item(a, item, data.Query, currentItems.items)) { | |
controls.suggestlist.append($('<li/>').append(a)); // append aan de lijst | |
state.itemkeys[ix] = item.Exact; // item.Exact should be used to determine whether this is an exact match | |
} | |
} | |
state.previousQuery = { term: term, endpoint: endpoint }; | |
// fire event | |
if ($.isFunction(options.events.onNiveauBepaald)) { | |
if (options.lastNiveau != data.Results[0].Niveau) { | |
options.events.onNiveauBepaald(elem, data.Results[0].Niveau); | |
lastNiveau = data.Results[0].Niveau; | |
} | |
} | |
// wanneer geen resultaten; dan hiden we de container | |
if (data.Results.length === 0 || controls.suggestlist.find('li').length === 0 || !showResults) { | |
controls.positionContainer.hide(); | |
return; | |
} | |
// verander de style van de container (met / zonder arrow enzo) | |
switch (endpoint) { | |
case options.endpoints.suggest: | |
switchContainerStyle(options.classnames.container.suggestions); break; | |
case options.endpoints.alternatives: | |
switchContainerStyle(options.classnames.container.alternatives); break; | |
} | |
// event vuren | |
if ($.isFunction(options.events.onListBound)) { | |
options.events.onListBound(controls.suggestlist); | |
// eventueel herpositioneren | |
if(options.direction === 'top') { | |
windowResized(); | |
} | |
} | |
}, 'json'); | |
}; | |
/// Initialize behavior | |
initControls($); | |
initEvents($); | |
}); | |
} | |
}); | |
})(jQuery); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment