/* new JsonIndex({ indexFiles: [ "index-level-1.json", "index-level-2.json" ], dataFile: "index-sorted.json", keyField: 0, compareKeys: compareTrimPrefixLowerCase(prefix, key), maxPages: 2 }); */ var JsonIndex = (function() { function compareTrimPrefixLowerCase(a, b) { if (a.trim) a = a.trim(); else if ($ && $.trim) a = $.trim(a); if (b.trim) b = b.trim(); else if ($ && $.trim) b = $.trim(b); if (a.length < b.length) b = b.substr(0, a.length); a = a.toLowerCase(); b = b.toLowerCase(); if (a < b) return -1; else if (a > b) return 1; return 0; } var JsonIndex = function(options) { if (!options.compareKeys) options.compareKeys = compareTrimPrefixLowerCase; if (!options.keyField) options.keyField = 0; if (!options.maxPages) options.maxPages = 2; this.options = options; this.indexCache = {}; this.indexCacheHistory = []; this.runRangeTest(); }; JsonIndex.prototype.runRangeTest = function() { var jsonIndex = this, url = this.options.indexFiles[0], headers = { "Range": "bytes=1-1" }; var responseHandler = function(responseText) { jsonIndex.supported = (responseText.length == 1) ? "yes" : "no"; if (jsonIndex.supported=="no" && jsonIndex.onError) jsonIndex.onError("Your browser is not smart enough to search. It does not support HTTP Range requests."); jsonIndex.continueQuery(); }; var errorHandler = function() { jsonIndex.supported = "no"; if (jsonIndex.onError) jsonIndex.onError("Request failed."); }; if (window.jQuery) { this.currentRequest = window.jQuery.ajax({ url: url, type: "GET", dataType: "text", headers: headers, success: responseHandler, error: errorHandler }); } else { this.currentRequest = new Request({ url: url, method: "GET", headers: headers, onSuccess: responseHandler, onFailure: errorHandler }); this.currentRequest.send(); } }; JsonIndex.prototype.loadJson = function(url, pageBounds) { var cacheKey = [url, pageBounds]; var index = this.indexCache[cacheKey]; if (index) return index; if (!this.supported) { return "wait"; } else if (this.supported == "no") { return "unsupported"; } if (this.currentRequest) { if (this.currentRequest.cancel) this.currentRequest.cancel(); else if (this.currentRequest.abort) this.currentRequest.abort(); } var jsonIndex = this, headers = {}; if (pageBounds) { var offsetStart = pageBounds[0][0], offsetEnd = pageBounds[pageBounds.length-1][1]; headers["Range"] = "bytes="+offsetStart+"-"+offsetEnd; } var responseHandler = function(responseText) { jsonIndex.indexCacheHistory.push(cacheKey); if (pageBounds) responseText = "[" + responseText + "]"; jsonIndex.indexCache[cacheKey] = JSON.parse(responseText); while (jsonIndex.indexCacheHistory.length > 100) { delete jsonIndex.indexCache[jsonIndex.indexCacheHistory.shift()]; } jsonIndex.continueQuery(); }; var errorHandler = function() { if (jsonIndex.onError) jsonIndex.onError("Request failed."); }; if (window.jQuery) { this.currentRequest = window.jQuery.ajax({ url: url, type: "GET", dataType: "text", headers: headers, success: responseHandler, error: errorHandler }); } else { this.currentRequest = new Request({ url: url, method: "GET", headers: headers, onSuccess: responseHandler, onFailure: errorHandler }); this.currentRequest.send(); } return "wait"; }; JsonIndex.prototype.findItemInIndex = function(item, index) { var from = 0, to, cmp = this.options.compareKeys; while (from < index.length - 1 && index[from][0] && cmp(item, index[from][0]) > 0) from++; to = from; while (to < index.length - 1 && index[to][0] && cmp(item, index[to][0]) >= 0) to++; var pageBounds = []; for (var p=from; p<=to; p++) { // calculate start and end offset for each page pageBounds.push([ (p==0) ? index[p][1] : index[p-1][1], index[p][1] + index[p][2] - 1 ]); } return pageBounds; }; JsonIndex.prototype.findItem = function(item, startAtBounds) { var index, hasMore = false, nextBounds = [], pageBounds = null, page = 0, fromBound, toBound, level = 0; if (!startAtBounds) startAtBounds = []; while (level < this.options.indexFiles.length) { index = this.loadJson(this.options.indexFiles[level], pageBounds); if (index == "wait" || index == "unsupported") { return index; } else { pageBounds = this.findItemInIndex(item, index); level++; } if (pageBounds) { if (startAtBounds[level]) { // we start at the requested position fromBound = startAtBounds[level]; // find the page number of this bound page = 0; while (page < pageBounds.length && fromBound >= pageBounds[page][1]) page++; } else { // just start at the first page fromBound = pageBounds[0][0]; page = 0; } // advance to last page to be displayed page = Math.min(pageBounds.length, page + this.options.maxPages) - 1; // set the end bound to the end bound of this page toBound = pageBounds[page][1]; // the next button should point where? if (page + 1 < pageBounds.length) { // this is not the last page of this level hasMore = true; nextBounds[level] = toBound + 2; // keep previous levels on same bounds for (var i=0; i