let PreParedReportDataObject = null;
//////////////////////////////////
// Pre render function
//
async function ayPreRender(ayData) {
if (typeof ayPrepareData === "function") {
PreParedReportDataObject = await ayPrepareData(ayData);
} else {
PreParedReportDataObject = ayData;
}
return true;
}
//##################################### HANDLEBARS HELPERS ###################################################
///////////////////////////////////////
// Set our stock handlebars helpers
//
function ayRegisterHelpers() {
Handlebars.registerHelper("ayCaps", function (ayValue) {
return ayValue.toUpperCase();
});
Handlebars.registerHelper("ayDateTime", function (ayValue) {
return utcDateToShortDateAndTimeLocalized(ayValue);
});
Handlebars.registerHelper("ayDate", function (ayValue) {
return utcDateToShortDateLocalized(ayValue);
});
Handlebars.registerHelper("ayTime", function (ayValue) {
return utcDateToShortTimeLocalized(ayValue);
});
Handlebars.registerHelper("ayDecimal", function (ayValue) {
return decimalLocalized(ayValue);
});
Handlebars.registerHelper("ayCurrency", function (ayValue) {
return currencyLocalized(ayValue);
});
Handlebars.registerHelper("ayWiki", function (ayValue) {
if (ayValue == null) {
return "";
}
//replace attachment urls with tokenized local urls
let src = ayValue.replace(/\[ATTACH:(.*)\]/g, function (match, p1) {
return attachmentDownloadUrl(p1);
});
return new Handlebars.SafeString(
DOMPurify.sanitize(marked.parse(src, { breaks: true }))
);
});
Handlebars.registerHelper("ayJSON", function (obj) {
return JSON.stringify(obj, null, 3);
});
Handlebars.registerHelper("ayLink", function (text, url) {
var url = Handlebars.escapeExpression(url),
text = Handlebars.escapeExpression(text);
return new Handlebars.SafeString(
"" + text + ""
);
});
Handlebars.registerHelper("ayLogo", function (size) {
if (AYMETA.ayServerMetaData) {
switch (size) {
case "small":
if (!AYMETA.ayServerMetaData.HasSmallLogo) {
return "";
}
break;
case "medium":
if (!AYMETA.ayServerMetaData.HasMediumLogo) {
return "";
}
break;
case "large":
if (!AYMETA.ayServerMetaData.HasLargeLogo) {
return "";
}
break;
}
}
var url = `${Handlebars.escapeExpression(
AYMETA.ayServerMetaData.ayApiUrl
)}logo/${size}`;
return new Handlebars.SafeString("
");
});
Handlebars.registerHelper("ayT", function (translationKey) {
if (ayTranslationKeyCache[translationKey] == undefined) {
return `**Error: "${translationKey}" translation key not cached or unknown**`;
// throw `ayT reporting helper error: the key "${translationKey}" is not present in the translation cache, did you forget to include it in your call to "await ayGetTranslations(['ExampleTranslationKey1','ExampleTranslationKey2','etc']);" in ayPrepareData()\nTranslationKeyCache contains: ${JSON.stringify(
// ayTranslationKeyCache,
// null,
// 3
// )}?`;
// return translationKey;
}
return ayTranslationKeyCache[translationKey];
});
///////////////////////////////////////////
// BarCode helper using
// https://github.com/metafloor/bwip-js#browser-usage
//
Handlebars.registerHelper("ayBC", function (text, options) {
let canvas = document.getElementById("aybarcode");
if (canvas == null) {
canvas = document.createElement("canvas");
canvas.id = "aybarcode";
}
let opt = JSON.parse(options);
if (text == null) {
text = "";
} else {
text = text.toString();
}
opt.text = text;
opt.textxalign = opt.textxalign || "center";
bwipjs.toCanvas(canvas, opt);
var url = canvas.toDataURL("image/png");
return new Handlebars.SafeString("
");
});
} //eof
///////////////////////////////////////////
// Concat helper using
// https://stackoverflow.com/a/52571635/8939
//
Handlebars.registerHelper("ayConcat", function () {
arguments = [...arguments].slice(0, -1);
return arguments.join("");
});
//##################################### LOCALIZATION & TRANSLATION ###################################################
///////////////////////////////////////////
// Turn a utc date into a displayable
// short date and time
//
function utcDateToShortDateAndTimeLocalized(ayValue) {
if (!ayValue) {
return "";
}
//parse the date which is identified as utc ("2020-02-06T18:18:49.148011Z")
let parsedDate = new Date(ayValue);
//is it a valid date?
if (!(parsedDate instanceof Date && !isNaN(parsedDate))) {
return "not valid";
}
return parsedDate.toLocaleString(
AYMETA.ayClientMetaData.LanguageName || "en-US",
{
timeZone:
AYMETA.ayClientMetaData.TimeZoneName || "America/Winnipeg",
dateStyle: "short",
timeStyle: "short",
hour12: AYMETA.ayClientMetaData.Hour12
}
);
}
///////////////////////////////////////////
// Turn a utc date into a displayable
// short date
function utcDateToShortDateLocalized(ayValue) {
if (!ayValue) {
return "";
}
//parse the date which is identified as utc ("2020-02-06T18:18:49.148011Z")
let parsedDate = new Date(ayValue);
//is it a valid date?
if (!(parsedDate instanceof Date && !isNaN(parsedDate))) {
return "not valid";
}
return parsedDate.toLocaleDateString(
AYMETA.ayClientMetaData.LanguageName || "en-US",
{
timeZone:
AYMETA.ayClientMetaData.TimeZoneName || "America/Winnipeg",
dateStyle: "short"
}
);
}
///////////////////////////////////////////
// Turn a utc date into a displayable
// short time
function utcDateToShortTimeLocalized(ayValue) {
if (!ayValue) {
return "";
}
//parse the date which is identified as utc ("2020-02-06T18:18:49.148011Z")
let parsedDate = new Date(ayValue);
//is it a valid date?
if (!(parsedDate instanceof Date && !isNaN(parsedDate))) {
return "not valid";
}
return parsedDate.toLocaleTimeString(
AYMETA.ayClientMetaData.LanguageName || "en-US",
{
timeZone:
AYMETA.ayClientMetaData.TimeZoneName || "America/Winnipeg",
timeStyle: "short",
hour12: AYMETA.ayClientMetaData.Hour12
}
);
}
///////////////////////////////////////////
// CURRENCY LOCALIZATION
//
function currencyLocalized(ayValue) {
if (!ayValue) {
return "";
}
return new Intl.NumberFormat(
AYMETA.ayClientMetaData.LanguageName || "en-US",
{
style: "currency",
currency: AYMETA.ayClientMetaData.CurrencyName || "USD"
}
).format(ayValue);
}
///////////////////////////////////////////
// DECIMAL LOCALIZATION
//
function decimalLocalized(ayValue) {
if (!ayValue) {
return "";
}
return new Intl.NumberFormat(
AYMETA.ayClientMetaData.LanguageName || "en-US"
).format(ayValue);
}
//////////////////////////////////
// cache to hold translations keys
//
var ayTranslationKeyCache = {};
///////////////////////////////////
// GET TRANSLATIONS FROM API SERVER
//
async function ayGetTranslations(keys) {
if (!keys || keys.length == 0) {
return;
}
try {
let transData = await ayPostToAPI("translation/subset", keys);
transData.data.forEach(function storeFetchedTranslationItemsInCache(
item
) {
ayTranslationKeyCache[item.key] = item.value;
});
} catch (error) {
throw error;
}
}
//##################################### API UTILITIES ###################################################
///////////////////////////////////
// GET DATA FROM API SERVER
//
async function ayGetFromAPI(route, token) {
token = token || AYMETA.ayClientMetaData.Authorization;
if (route && !route.startsWith("http")) {
route = AYMETA.ayServerMetaData.ayApiUrl + route;
}
try {
let r = await fetch(route, {
method: "get",
mode: "cors",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
Authorization: token
}
});
return await extractBodyEx(r);
} catch (error) {
throw error;
}
}
///////////////////////////////////
// POST DATA TO API SERVER
//
async function ayPostToAPI(route, data, token) {
token = token || AYMETA.ayClientMetaData.Authorization;
if (route && !route.startsWith("http")) {
route = AYMETA.ayServerMetaData.ayApiUrl + route;
}
//api expects custom fields to be a string not an object
if (data && data.CustomFields && data.CustomFields.constructor === Object) {
data.CustomFields = JSON.stringify(data.CustomFields);
}
try {
fetchOptions = {
method: "post",
mode: "cors",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
Authorization: token
},
body: JSON.stringify(data)
};
let r = await fetch(route, fetchOptions);
return await extractBodyEx(r);
} catch (error) {
throw error;
}
}
///////////////////////////////////
// PUT DATA TO API SERVER
//
async function ayPutToAPI(route, data, token) {
token = token || AYMETA.ayClientMetaData.Authorization;
if (route && !route.startsWith("http")) {
route = AYMETA.ayServerMetaData.ayApiUrl + route;
}
//api expects custom fields to be a string not an object
if (data && data.CustomFields && data.CustomFields.constructor === Object) {
data.CustomFields = JSON.stringify(data.CustomFields);
}
try {
fetchOptions = {
method: "put",
mode: "cors",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
Authorization: token
},
body: JSON.stringify(data)
};
let r = await fetch(route, fetchOptions);
return await extractBodyEx(r);
} catch (error) {
throw error;
}
}
/////////////////////////////
// attachment download URL
// (INTERNAL USE NOT DOCUMENTED FOR HELPER USE)
function attachmentDownloadUrl(fileId, ctype) {
let url =
"attachment/download/" +
fileId +
"?t=" +
AYMETA.ayClientMetaData.DownloadToken;
if (ctype && ctype.includes("image")) {
url += "&i=1";
}
return AYMETA.ayServerMetaData.ayApiUrl + url;
}
//##################################### CODE UTILITIES ###################################################
async function extractBodyEx(response) {
if (response.status == 204) {
//no content, nothing to process
return response;
}
if (response.status == 202) {
//Accepted, nothing to process
return response;
}
const contentType = response.headers.get("content-type");
if (!contentType) {
return response;
}
if (contentType.includes("json")) {
return await response.json();
}
if (contentType.includes("text/plain")) {
return await response.text();
}
if (contentType.includes("application/pdf")) {
return await response.blob();
}
return response;
}
/////////////////////////////////////////////////////////
// Group by function
// reshapes input array into a new array grouped with
// a key named "group" with that group's key value
// and a key named "items" to contain all items
// for that group and also a "count" of items added
//
//
function ayGroupByKey(reportDataArray, groupByKeyName) {
//array to hold grouped data
const ret = [];
//iterate through the raw reprot data
for (let i = 0; i < reportDataArray.length; i++) {
//search the ret array for a group with this name and if found return a reference to that group object
let groupObject = ret.find(
(z) => z.group == reportDataArray[i][groupByKeyName]
);
if (groupObject != undefined) {
//there is already a matching group in the return array so just push this raw report data record into it
groupObject.items.push(reportDataArray[i]);
//update the count for this group's items
groupObject.count++;
} else {
//No group yet, so start a new one in the ret array and push this raw report data record
ret.push({
group: reportDataArray[i][groupByKeyName],
items: [reportDataArray[i]],
count: 1
});
}
}
return ret;
}