Created
May 3, 2017 15:38
-
-
Save twobob/82e2c9a628e50d5cf81f41a9a44e27f2 to your computer and use it in GitHub Desktop.
reworked elasticlunr.js demo page (to NOT used stored example_index.json) with incremental result rendering
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
requirejs.config({waitSeconds:0}); | |
require([ | |
'./jquery.js', | |
'./handlebars.js', | |
'./elasticlunr.js', | |
'text!templates/question_view.mustache', | |
'text!templates/question_list.mustache', | |
'text!templates/word_list.mustache', | |
'text!data.json' | |
], function (_, Mustache, elasticlunr, questionView, questionList, wordList, data, indexDump) { | |
// compile templates for later | |
var WLtemplate = Mustache.compile(wordList); | |
var QLtemplate = Mustache.compile(questionList); | |
var QVtemplate = Mustache.compile(questionView); | |
//random seed for our murmurhash3_32_gc ID helper. | |
var seeder = 99999; | |
// get DOM refs to mangle cursors and do clear downs | |
var BodyRef = document.getElementById("body"); | |
var clearButton = $('#clearButton'); | |
// setup optional simple list type method | |
var renderQuestionList = function (qs) { | |
$("#question-list-container") | |
.empty() | |
.append(QLtemplate({questions: qs})) | |
} | |
// setup initial complete word list renderer | |
var renderWordList = function (qs) { | |
$("#question-list-container") | |
.empty() | |
.append(WLtemplate({list: qs})) | |
} | |
//setup single view for zoomed observation | |
var renderQuestionView = function (question) { | |
$("#question-view-container") | |
.empty() | |
.append(QVtemplate(question)) | |
} | |
// setup the search instance | |
var idx = elasticlunr(function () { | |
this.setRef('id'); | |
this.addField('title'); // image name tidied | |
this.addField('searchTerms'); // tags concatenated | |
this.addField('tags'); // HTML clickable anchors filled with tag words | |
this.saveDocument(false); // save some space should you ever wish to actually encode it via Stringify | |
}); | |
// holder for every single tag | |
var fulllist = new Array(); | |
// create the actual content by walking over example_data.json | |
var questions = JSON.parse(data).questions.map(function (raw) { | |
// mangle tags back into tidy lumps for use as TITLE tags on images | |
var holder = "" | |
for (i in raw.question.content){ | |
holder +=i +' : '+ raw.question.content[i]+'\n'; | |
} | |
var make_id = murmurhash3_32_gc(raw.img.filename, seeder); | |
//console.log(make_id); | |
// append all results to main tag array | |
for (thing in raw.question.content ) | |
{ fulllist[fulllist.length]= thing; } | |
// create clickable widgets for displaying words in that auto-launch a search with that term | |
var clickableTags = Object.keys(raw.question.content).map(function (k) { | |
var result = '<a style="cursor:pointer" onclick="searchTerm(\''+k+'\')">'+k+'</a>'; | |
return result; | |
}) | |
// return that completed Question Object (with fancy clickable thingys) | |
// you could be more stingy here possibly, some of this could be refactored away. | |
return { | |
id: make_id, | |
title: raw.img.filename.replace('.JPG',''), // tidy name | |
body: holder, // used tfor TITLE property on img | |
searchTerms: Object.keys(raw.question.content).join(' '), // actually searched on | |
tags: clickableTags, // used for displaying clickable words | |
img: raw.img.filename, // convenience placeholder | |
thumb: raw.thumb.filename // link to src file | |
} | |
}) | |
// now add the created corpus to the engine | |
questions.forEach(function (question) { | |
idx.addDoc(question); | |
}); | |
//assign it to the javascript window object | |
window.idx = idx; | |
// create a lightweight searchable index configuration refernce | |
var config = '{ "fields": { "searchTerms": {"boost": 2}, "title": {"boost": 1} }, "boolean": "OR"}'; | |
var json_config = new elasticlunr.Configuration(config, window.idx.getFields()).get(); | |
// we got this far. Remove the Header from the page - remove the "LOADING THOUSANDS OF IMAGES" message | |
document.getElementById("loader").style.display = "none" ; | |
document.getElementById("hiding_title").style.display = "none" ; | |
// put the LOWEST NAMED file up for display | |
renderQuestionView(questions[0]) | |
// compress our complete tag list to just unique terms. | |
var shortlist = fulllist.filter((v, i, a) => a.indexOf(v) === i).map(function (w) {return { x : w } }); | |
// create helper to clear down searches and create complete word list in LIST area on right | |
var emptyFunction = function emptyMe (){ | |
$('input').val(''); | |
renderWordList(shortlist); | |
} | |
// assign it to the window | |
window.emptyFunction = emptyFunction; | |
// create that list for real | |
renderWordList(shortlist); | |
// helper method to steady the reaction to typing events | |
var debounce = function (fn) { | |
var timeout | |
return function () { | |
var args = Array.prototype.slice.call(arguments), | |
ctx = this | |
clearTimeout(timeout) | |
timeout = setTimeout(function () { | |
fn.apply(ctx, args) | |
}, 100) | |
} | |
} | |
// create method that supports partial rendering ob very large rendering queues with rudimentary periodic callback support | |
var renderPartialQuestionList = function (results, startPos, endPos){ | |
var temp = results.slice(startPos, endPos); | |
$("#question-list-container") | |
.append(QLtemplate({questions: temp})) | |
} | |
// Helper that does the rendering | |
function doHeavyWork(results, start, totalResultsToRender, term) { | |
var total = totalResultsToRender; | |
var fragment = 50; | |
var end = start + fragment; | |
var left = totalResultsToRender - end ; | |
// partially render list | |
// the thing to render, the start record and the end record | |
renderPartialQuestionList(results, end-fragment, end) | |
clearButton.text(end+fragment +' of '+results.length+' for '+term); | |
if (end >= total) { | |
// If we reached the end, stop and change status | |
clearButton.removeClass('blink'); | |
// tell how many we did | |
clearButton.text(results.length+' for '+term); | |
// desist the annoying apinny cursor | |
BodyRef.style.cursor = ''; | |
} else { | |
// Otherwise, process next fragment | |
setTimeout(function() { | |
doHeavyWork(results, end, totalResultsToRender, term); | |
}, 0); | |
} | |
} | |
// do the heavy work of big rendering in the DOM | |
function dowork(results, totalResultsToRender, term) { | |
// Set "working" status | |
document.body.style.cursor = "wait"; | |
document.getElementById("clearButton").innerHTML = "working"; | |
// render the single view and set term in search bar | |
renderQuestionView(results[0]); | |
// render big view in chunks | |
doHeavyWork(results, 0, totalResultsToRender, term); | |
} | |
// create results from a search term, use the config we made earlier | |
function searchTerm(term){ | |
$('input').val(term); | |
var results = null; | |
results = window.idx.search(term, json_config).map(function (result) { | |
return questions.filter(function (q) { return q.id === parseInt(result.ref, 10) })[0] | |
}) | |
// check if the typing was non-meaningful | |
if(results.length<1) { renderWordList(shortlist); } | |
else | |
{ | |
// we have work to do | |
$("#question-list-container").empty(); | |
dowork(results, results.length, term); | |
} | |
} | |
window.searchTerm = searchTerm; // Make it available via the javascript window object rather than require.js | |
// on key up search on 3 letters or more. | |
$('input').bind('keyup', debounce(function () { | |
if ($(this).val().length < 2) return; | |
searchTerm($(this).val()); | |
})) | |
// if we click lists make them load the relevant object into the big view | |
$("#question-list-container").delegate('li', 'click', function () { | |
var li = $(this) | |
var id = li.data('question-id') | |
// show that in the big view | |
renderQuestionView(questions.filter(function (question) { | |
return (question.id == id) | |
})[0]) // there can be only one... | |
}) | |
}) | |
// Handy helper to mangle names into unique numbers :) | |
function murmurhash3_32_gc(key, seed) { | |
var remainder, bytes, h1, h1b, c1, c1b, c2, c2b, k1, i; | |
remainder = key.length & 3; // key.length % 4 | |
bytes = key.length - remainder; | |
h1 = seed; | |
c1 = 0xcc9e2d51; | |
c2 = 0x1b873593; | |
i = 0; | |
while (i < bytes) { | |
k1 = | |
((key.charCodeAt(i) & 0xff)) | | |
((key.charCodeAt(++i) & 0xff) << 8) | | |
((key.charCodeAt(++i) & 0xff) << 16) | | |
((key.charCodeAt(++i) & 0xff) << 24); | |
++i; | |
k1 = ((((k1 & 0xffff) * c1) + ((((k1 >>> 16) * c1) & 0xffff) << 16))) & 0xffffffff; | |
k1 = (k1 << 15) | (k1 >>> 17); | |
k1 = ((((k1 & 0xffff) * c2) + ((((k1 >>> 16) * c2) & 0xffff) << 16))) & 0xffffffff; | |
h1 ^= k1; | |
h1 = (h1 << 13) | (h1 >>> 19); | |
h1b = ((((h1 & 0xffff) * 5) + ((((h1 >>> 16) * 5) & 0xffff) << 16))) & 0xffffffff; | |
h1 = (((h1b & 0xffff) + 0x6b64) + ((((h1b >>> 16) + 0xe654) & 0xffff) << 16)); | |
} | |
k1 = 0; | |
switch (remainder) { | |
case 3: k1 ^= (key.charCodeAt(i + 2) & 0xff) << 16; | |
case 2: k1 ^= (key.charCodeAt(i + 1) & 0xff) << 8; | |
case 1: k1 ^= (key.charCodeAt(i) & 0xff); | |
k1 = (((k1 & 0xffff) * c1) + ((((k1 >>> 16) * c1) & 0xffff) << 16)) & 0xffffffff; | |
k1 = (k1 << 15) | (k1 >>> 17); | |
k1 = (((k1 & 0xffff) * c2) + ((((k1 >>> 16) * c2) & 0xffff) << 16)) & 0xffffffff; | |
h1 ^= k1; | |
} | |
h1 ^= key.length; | |
h1 ^= h1 >>> 16; | |
h1 = (((h1 & 0xffff) * 0x85ebca6b) + ((((h1 >>> 16) * 0x85ebca6b) & 0xffff) << 16)) & 0xffffffff; | |
h1 ^= h1 >>> 13; | |
h1 = ((((h1 & 0xffff) * 0xc2b2ae35) + ((((h1 >>> 16) * 0xc2b2ae35) & 0xffff) << 16))) & 0xffffffff; | |
h1 ^= h1 >>> 16; | |
return h1 >>> 0; | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment