Skip to content

Instantly share code, notes, and snippets.

@twobob
Created May 3, 2017 15:38
Show Gist options
  • Save twobob/82e2c9a628e50d5cf81f41a9a44e27f2 to your computer and use it in GitHub Desktop.
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
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