lightlink
Version:
Client package for lightlink.io allowing progressive loading (display the first page of data that is transmitted already while the rest AJAX request is still in loading)
157 lines (118 loc) • 5.64 kB
JavaScript
;
const PROGRESSIVE_KEY = "\n\t \t\n";
function callCallBack(callback, res, isPartial, xmlhttp) {
if (callback && typeof callback === "function") {
callback(res, isPartial, xmlhttp);
}
}
/**
* Each partial section is delimited by "\n\t \t\n" token preceded by the number of \t equals to the
* number of "}" after "]" needed to complete the JSON into correct form
*/
function completeJSON(responseText, options) {
var lastIndex = responseText.lastIndexOf(PROGRESSIVE_KEY);
if (lastIndex !== -1) {
if (options.prevLastIndex !== lastIndex) {
options.prevLastIndex = lastIndex;
responseText = responseText.substring(0, lastIndex);
lastIndex--;
responseText += "]";
while (lastIndex > 0 && responseText[lastIndex] === "\t") {
lastIndex--;
responseText += "}";
}
return responseText;
}
}
return null; // did not receive enough data for a (new) slice
}
function onReadyStateChange(xmlhttp, url, params, options) {
var res;
if (xmlhttp.readyState === 3 && (options.partial || options.successOrPartial)) {
//todo check interval
const prevTime = options.prevTime || 0;
var now = new Date().getTime();
// skip accessing to xmlhttp.responseText if min interval has not been reached
if (prevTime + options.partialMinInterval < now) {
options.prevTime = now;
var completedData = completeJSON(xmlhttp.responseText, options);
if (completedData === null)
return;
res = JSON.parse(completedData);
if (res.success === false)
return;
callCallBack(options.partial, res, true, xmlhttp);
callCallBack(options.successOrPartial, res, true, xmlhttp);
}
} else if (xmlhttp.readyState === 4) {
if (xmlhttp.status === 200) {
const responseText = xmlhttp.responseText;
try {
res = JSON.parse(responseText);
} catch (e) {
if (options.error){
options.error(xmlhttp);
}
return;
}
if (res.success === false) {
if (options.exception)
options.exception(res.error, res,xmlhttp);
} else {
callCallBack(options.success, res, false, xmlhttp);
callCallBack(options.successOrPartial, res, false, xmlhttp);
}
} else {
if (options.error)
options.error(xmlhttp);
}
}
}
const getXmlHttp = window.ActiveXObject
? function () {
return new window.ActiveXObject("MSXML2.XMLHTTP.6.0")
} // Must be "MSXML2.XMLHTTP.6.0" to support partial response
: function () {
return new window.XMLHttpRequest()
};
/**
* Calls lightlink.io service allowing progressive loading (intermediate callbacks on partial data loaded)
* @param {string} url The URL to call
* @param {object} [params] an object containing request parameters
* @param {object} [options] callbacks and progressive loading options
* <br/>
* Valid options are :<ul>
* <li><code>partial</code> callback on partial response. Parameters: (data, isPartial /*=true * /, xmlhttp)</li>
* <li><code>success</code> callback on successful response. Parameters: (data, isPartial /*=false * /, xmlhttp)</li>
* <li><code>successOrPartial</code> callback on successful or partial response. Parameters: (data, isPartial, xmlhttp)</li>
* <li><code>error</code> callback on HTTP error ou incorrect JSON response. Parameters: (xmlhttp)</li>
* <li><code>exception</code> callback on caught server side exception: Parameters: (data, xmlhttp)</li>
* <li><code>partialSlices</code> The size of partial response slices. Default to :[100,100,100,100,100,500]
* meaning 5 times each 100 rows, then each 500 rows until loading ended. Slices should not be too small to avoid too frequent updates that might be not needed. Lower CPU usage</li>
* <li><code>partialMinInterval</code> The minimum interval between partial responses. Default to 200 millis.
* Allows to reduce client CPU by avoiding too frequent updates, and less frequent xmlhttp.responseText access</li>
* <li><code>onBeforeSend</code> callback allowing to configure xmlhttp before the request is sent. Useful to add request
* headers or set xmlhttp properties like <code>xmlhttp.withCredentials=true</code>. Parameters: (xmlhttp)</li>
* </ul>
*/
function lightlink(url, params, options) {
if (!params)
params = {};
const finalOptions = {
partialMinInterval: 200,
partialSlices: [100, 100, 100, 100, 100, 500],
};
Object.assign(finalOptions, options)
const xmlhttp = getXmlHttp();
xmlhttp.onreadystatechange = function () {
onReadyStateChange(xmlhttp, url, params, finalOptions);
};
xmlhttp.open("POST", url, true);
finalOptions.onBeforeSend && finalOptions.onBeforeSend(xmlhttp, url, params, options);
if ((finalOptions.partial ||finalOptions.successOrPartial) && finalOptions.partialSlices) {
params["$lightlink-progressive"] = finalOptions.partialSlices;
}
xmlhttp.send(JSON.stringify(params));
return xmlhttp;
}
module.exports = lightlink;