/*
* @file Manages all library initialization, jQuery callbacks, query entry
* callbacks, server querying, and results diplay for `index.html`.
*/
var advancedSearchDiv = $("div#advanced-search");
var advancedSearchButton = $("button#advanced-search");
FINISH_TYPING_INTERVAL = 650;
var searchBar = $("form#search-bar input[type='text']")[0];
var resultsDiv = $("div#results")[0];
var typingTimer, scrollTimer, lastValue;
var searchResultsPage = 1;
/*
* Set all page callbacks.
*/
(function setHomePageCallbabacks(){
var results = $('#results').get(0);
// Enable infinite scrolling down the results page.
$(window).scroll(function(){
if($(window).scrollTop() + $(window).height() == $(document).height() &&
resultsDiv.querySelectorAll(".result").length > 0)
loadMoreResults();
clearTimeout(scrollTimer);
if (!results.classList.contains('disable-hover'))
results.classList.add('disable-hover')
scrollTimer = setTimeout(function(){
if (results.classList.contains('disable-hover'))
results.classList.remove('disable-hover');
}, 200);
});
// Toggle the advanced-search form's visibility.
advancedSearchButton.click(function(){
var searchField = $("div#search-field");
if(!advancedSearchDiv.hasClass("visible")){
searchField.addClass("partly-visible");
advancedSearchDiv.fadeIn(500).addClass("visible");
advancedSearchButton.addClass("clicked");
}
else {
advancedSearchDiv.hide().removeClass("visible");
advancedSearchButton.removeClass("clicked");
if($("div#results .result").length == 0)
searchField.removeClass("partly-visible");
clearResults();
}
});
// Enable capturing the `enter` key.
$("form#search-bar").submit(function(event){
event.preventDefault();
return false;
});
searchBar.onkeyup = typingTimer;
}());
/*
* Set keyboard shortcut mappings.
*/
(function resultsHotkeys(){
/*
* If the currently viewed result is not the first, scroll to the previous
* result.
*/
var previousResult = function(){
var currResult = $(".display-all");
if(currResult.length) {
currResult.removeClass("display-all");
currResult = currResult.closest(".result").prev(".result");
} else {
currResult = $(document.querySelectorAll(".result")[0]);
}
currResult.addClass("display-all");
currResult.each(function(){
$('html,body').stop().animate({
scrollTop: $(this).offset().top - (
$(window).height() - $(this).outerHeight(true)) / 2
}, 140);
});
};
/*
* If the currently viewed result is not the last, scroll to the next
* result.
*/
var nextResult = function(){
var currResult = $(".display-all");
if(currResult.length) {
currResult.removeClass("display-all");
currResult = currResult.closest(".result").next(".result");
} else {
currResult = $(document.querySelectorAll(".result")[0]);
}
currResult.addClass('display-all');
currResult.each(function(){
$('html,body').stop().animate({
scrollTop: $(this).offset().top - (
$(window).height() - $(this).outerHeight(true)) / 2
}, 140);
});
};
var displayHotkeyHelp = function(){
var help = $("div#hotkey-help");
if(help.hasClass("hidden"))
help.fadeIn(420);
else
help.fadeOut(420);
$("div#body").toggleClass("faded");
help.toggleClass("hidden");
}
var hotkeyActions = {
"k" : previousResult,
"j" : nextResult,
"h" : previousSymbolMatch,
"l" : nextSymbolMatch,
"?" : displayHotkeyHelp
};
$(window).keypress(function(key){
for(var hotkey in hotkeyActions){
var keyChar = String.fromCharCode(key.keyCode);
if(keyChar == hotkey &&
!($(key.target).is("textarea") || $(key.target).is("input")))
hotkeyActions[keyChar]();
}
});
}());
// Enable infinite scrolling down the results page.
$(window).scroll(function() {
var searchField = $("div#search-field");
if($(window).scrollTop() + $(window).height() == $(document).height() &&
searchField.hasClass('partly-visible')){
loadMoreResults();
}
});
/*
* Clear the existing timer and set a new one the the user types text into the
* search bar.
*/
function typingTimer(event){
clearTimeout(typingTimer);
var enterKeyCode = 13;
if(event.keyCode != enterKeyCode){
if(lastValue != searchBar.value)
typingTimer = setTimeout(finishedTyping, FINISH_TYPING_INTERVAL);
}
else {
event.preventDefault();
finishedTyping();
return false;
}
};
/*
* Callback which queries the server whenver the user stops typing.
*
* Whenever the user doesn't type for a `FINISH_TYPING_INTERVAL` after having
* entered new text in the search bar, send the current query request to the
* server.
*/
function finishedTyping(){
lastValue = searchBar.value;
var searchField = $("div#search-field");
clearResults();
if(searchBar.value){
searchField.addClass("partly-visible");
populateResults();
}
else {
searchField.removeClass("partly-visible");
$("div#advanced-search").fadeOut(50);
advancedSearchButton.removeClass("clicked");
clearResults();
}
}
/*
* Removes any child elements of `div#results`.
*/
function clearResults(){
while(resultsDiv.firstChild)
resultsDiv.removeChild(resultsDiv.firstChild);
}
/*
* Create a result element based upon a codelet instance.
*
* @return {Element} The result element.
*/
function createResult(codelet) {
var maxAttributeLength = 20;
//Level 1
var newDiv = document.createElement("div"),
table = document.createElement("table"),
row = document.createElement("tr");
//Level 2
var displayInfo = document.createElement("div"),
codeElt = document.createElement("td"),
hiddenInfoContainer = document.createElement("td"),
hiddenInfo = document.createElement("div"),
cycle = document.createElement("div");
//Level 3
var title = document.createElement("span"),
site = document.createElement("span"),
nextMatch = document.createElement("a"),
prevMatch = document.createElement("a"),
dateModified = document.createElement("div"),
language = document.createElement("div"),
dateCreated = document.createElement("div"),
authors = document.createElement("div");
//Classes and ID's
newDiv.classList.add('result');
displayInfo.id = 'display-info';
codeElt.id = 'code';
hiddenInfo.id = 'hidden-info';
cycle.id = 'cycle-matches'
title.id = 'title';
site.id = 'site';
nextMatch.id = 'next-match';
nextMatch.href = '#';
prevMatch.id = 'prev-match';
prevMatch.href = '#';
dateModified.id = 'date-modified';
language.id = 'language';
dateCreated.id = 'date-created';
authors.id = 'authors';
//Add the bulk of the html
title.innerHTML = ' » '
+ codelet.name + '';
site.innerHTML = '' +
codelet.origin[0] +'';
nextMatch.innerHTML = 'next match';
prevMatch.innerHTML = 'prev match';
language.innerHTML = 'Language: ' + codelet.lang + '';
dateModified.innerHTML = 'Last modified: ' + codelet.modified +
'';
// Needs to be changed from int to string on the server
dateCreated.innerHTML = 'Created: ' +
codelet.created.substring(0, maxAttributeLength) + '';
var authorsHtml = 'Authors: ';
var currLength = 0;
var authorsList = [];
for(var auth = 0; auth < codelet.authors.length; auth++){
currLength += codelet.authors[auth].length;
if(maxAttributeLength < currLength){
authorsList.push("...");
break;
}
else
authorsList.push('' + codelet.authors[auth] + '');
}
authors.innerHTML = "Authors: " + authorsList.join(", ") + "";
// Needs to be processed on the server
codeElt.innerHTML = '' + codelet.code + '
';
//Event binding
$(newDiv).on('mousemove', function(e) {
var holdCondition = $('.disable-hover');
if(holdCondition.length == 0) {
$(this).siblings().removeClass('display-all');
$(this).addClass('display-all');
}
});
$(newDiv).on('mouseleave', function(e) {
var holdCondition = $('.disable-hover');
if(holdCondition.length == 0)
$(this).removeClass('display-all');
});
$(nextMatch).click(function(e) {
e.stopPropagation();
e.preventDefault();
nextSymbolMatch();
});
$(prevMatch).click(function(e) {
e.stopPropagation();
e.preventDefault();
previousSymbolMatch();
});
//Finish and append elements to parent elements
hiddenInfo.appendChild(dateCreated);
hiddenInfo.appendChild(dateModified);
hiddenInfo.appendChild(language);
hiddenInfo.appendChild(authors);
hiddenInfoContainer.appendChild(hiddenInfo);
row.appendChild(codeElt);
row.appendChild(hiddenInfoContainer);
table.appendChild(row);
displayInfo.appendChild(site);
displayInfo.appendChild(title);
cycle.appendChild(prevMatch);
cycle.appendChild(nextMatch);
newDiv.appendChild(displayInfo);
newDiv.appendChild(table);
return newDiv;
}
function previousSymbolMatch() {
var currResult = $(".display-all"),
currMatch = currResult.find(".hll.current"),
matches = currResult.find(".hll"),
scrollDiv = currResult.find("#tablecontainer");
if (currMatch.length == 0)
currMatch = matches[0];
else
currMatch.removeClass('current');
var index = matches.index(currMatch.get(0)) - 1;
index = index <= 0 ? matches.length - 1 : index;
var newMatch = $(matches[index]);
scrollDiv.scrollTop(scrollDiv.scrollTop()
- scrollDiv.height() / 2
+ newMatch.position().top + newMatch.height() / 2);
newMatch.effect("highlight", {color: '#FFF'}, 750)
newMatch.addClass('current');
};
function nextSymbolMatch() {
var currResult = $(".display-all"),
currMatch = currResult.find(".hll.current"),
matches = currResult.find(".hll"),
scrollDiv = currResult.find("#tablecontainer");
if (currMatch.length == 0)
currMatch = $(matches[0]);
else
currMatch.removeClass("current");
var index = matches.index(currMatch.get(0)) + 1;
index = index >= matches.length ? 0 : index;
var newMatch = $(matches[index]);
scrollDiv.scrollTop(scrollDiv.scrollTop()
- scrollDiv.height() / 2
+ newMatch.position().top + newMatch.height() / 2);
newMatch.effect("highlight", {color: "#FFF"}, 750)
newMatch.addClass("current");
};
/*
* AJAX the current query string to the server, and return its response.
*
* @return {Array} The server's response in the form of `div.result` DOM
* elements, to fill `div#results`.
*/
function queryServer(){
var queryUrl = document.URL + "search.json?" + $.param({
"q" : searchBar.value,
"p" : searchResultsPage++,
"hl": 1
});
var results = $.Deferred();
$.getJSON(queryUrl, function(result){
var resultDivs = [];
if("error" in result)
insertErrorMessage(result["error"]);
else if(result["results"].length == 0 && searchResultsPage == 1)
insertErrorMessage("No search results.");
else
for(var codelet = 0; codelet < result["results"].length; codelet++)
resultDivs.push(createResult(result["results"][codelet]));
results.resolve(resultDivs);
});
return results;
}
/*
* Query the server with the current search string, and populate `div#results`
* with its response.
*/
function populateResults(){
searchResultsPage = 1;
loadMoreResults();
}
/*
* Query the server for the next results page, and add its codelets to
* `div#results`.
*/
function loadMoreResults(){
queryServer().done(function(results){
for(var result = 0; result < results.length; result++){
var newDiv = results[result];
resultsDiv.appendChild(newDiv);
setTimeout(
(function(divReference){
return function(){
divReference.classList.add("cascade");
};
}(newDiv)),
result * 20);
}
});
}
/*
* Displays a warning message in the UI.
*
* @param msg (str) The message string.
*/
function insertErrorMessage(msg){
var error = $(
[
"Error ",
"»
"
].join(""));
error.append(msg);
resultsDiv.appendChild(error[0]);
}