/**
* @module helpers/jrh_text
* @author jesse reichler <mouser@donationcoder.com>
* @copyright 5/24/19
* @description
* Collection of helper functions for text/html/views
*/
"use strict";
// modules
// see https://github.com/component/escape-html
const escapeHtml = require("escape-html");
//---------------------------------------------------------------------------
/**
* Pluralize a string based on a number (presumably the number of items being referred to)
* @example "You have eaten " + jrPluralizeCount(4, "apples", "apples")
* So it used the singular if the count is 1, otherwise the plural
*
* @param {int} number
* @param {string} singular
* @param {string} plural
* @returns string with number specified as signular or plural, and number mentioned specifically
*/
function jrPluralizeCount(number, singular, plural) {
if (number === undefined) {
number = 0;
} else if (Array.isArray(number)) {
number = number.length;
}
//
const numberStr = number.toString();
if (number === 1) {
return numberStr + " " + singular;
}
return (typeof plural === "string" ? numberStr + " " + plural : numberStr + " " + singular + "s");
}
/**
* Pluralize a string based on a number (presumably the number of items being referred to)
* @example "You have eaten " + jrPluralize(4, "one apple", "some apples")
* @example "Should we discard the " + jrPluralize(4, "apple")
* So it used the singular if the count is 1, otherwise the plural form; if no plural form is specified, use singular and add "s"
*
* @param {int} number
* @param {string} singular
* @param {string} plural
* @returns string with number specified as signular or plural
*/
function jrPluralize(number, singular, plural) {
if (number === undefined) {
number = 0;
} else if (Array.isArray(number)) {
number = number.length;
}
if (number === 1) {
return singular;
}
return (typeof plural === "string" ? plural : singular + "s");
}
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
/**
* Create an html input select (drop down) list.
*
* @param {string} name of the select input
* @param {array} choices - list of id -> description pairs
* @param {*} id - id of currently select id (if any)
* @returns html for inclusion in form
*/
function jrHtmlFormOptionListSelect(selectName, choices, id, flagShowBlank) {
const appHtmlList = jrHtmlFormOptionList(choices, id, flagShowBlank);
const rethtml = `
<select name="${selectName}">
${appHtmlList}
</select>
`;
return rethtml;
}
/**
*
* Create an html string containing <option value="val">description</option> values
*
* @param {array} choices - list of id -> description pairs
* @param {*} id - id of currently select id (if any)
* @returns html for inclusion in form (inside a select form object usually)
*/
function jrHtmlFormOptionList(choices, id, flagShowBlank) {
let rethtml = "";
let foundId = false;
// cast id to a string
id = (id === null || id === undefined) ? "" : id.toString();
if (flagShowBlank) {
rethtml += "<option value=\"\" > </option>\n";
}
// now find it in list
if (choices) {
let seltext;
for (const key in choices) {
if (key === id) {
seltext = " selected";
foundId = true;
} else {
seltext = "";
}
rethtml += "<option value=\"" + key + "\"" + seltext + ">" + choices[key] + "</option>\n";
}
}
if (id && !foundId) {
// since we couldn't find an item for selected one, and an id was specifiedi, we add a new option at top and select it
rethtml = "<option value=\"" + id + "\" selected> UNKNOWN VALUE (#" + id + ")</option>\n" + rethtml;
}
return rethtml;
}
/**
* Given a pairlist of form id=>optionlabel, return the optionlabel corresponding to specified id
* @param {object} choices
* @param {*} id
*/
function jrHtmlNiceOptionFromList(choices, id) {
if (id === undefined) {
return "[not set]";
}
if (id === null) {
return "null";
}
if (id === choices[id]) {
return ((id in choices) ? choices[id] : "unknown");
}
return ((id in choices) ? choices[id] : "unknown") + " (" + id.toString() + ")";
}
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
/**
* Helper funciton to generate some html that shows a bootstrap spoiler box with some debug information
*
* @param {string} title
* @param {string} body
* @param {string} footer
* @returns html string
*/
function jrBootstrapCollapseBox(title, body, footer) {
if (!(body instanceof String)) {
body = "<pre>" + JSON.stringify(body, null, " ") + "</pre>";
}
const rethtml = `
<div class="card" style="padding: 5px; margin: 5px;">
<div class="card-heading">
<h4 class="card-title">
<a data-toggle="collapse" href="#collapse1">> ${title}</a>
</h4>
</div>
<div id="collapse1" class="card-collapse collapse">
<div class="card-body">${body}</div>
<div class="card-footer">${footer}</div>
</div>
</div>
`;
return rethtml;
}
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
/**
* Simple wrapper for JSON.stringify that formats output for html using <pre> tags
*
* @param {*} obj
* @returns html string
*/
function jrHtmlStrigifyObject(obj) {
const rethtml = "<pre>" + JSON.stringify(obj, null, " ") + "</pre>";
return rethtml;
}
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
/**
* Create html text for a form input password, with a hint saying whether there is an existing value in the database,
* and a hint saying whether leaving the value blank will preserve existing value,
* and a hint showing how to clear the database value by using a "-" character (if this is an optional password field)
*
* @param {string} fieldName
* @param {*} obj
* @param {boolean} flagRequired
* @param {boolean} flagExistingIsNonBlank
* @returns html string
*/
function jrHtmlFormInputPassword(fieldName, obj, flagRequired, flagExistingIsNonBlank) {
let rethtml, val;
if (obj && obj[fieldName]) {
val = obj[fieldName];
} else {
val = "";
}
rethtml = `<input name="${fieldName}" type="password" value="${val}">`;
if (flagExistingIsNonBlank) {
rethtml += " (existing password in db is encrypted and non-blank; leave this field empty to preserve the existing password";
if (!flagRequired) {
rethtml += "; or specify '-' to delete existing password and leave it blank";
}
rethtml += ")";
}
return rethtml;
}
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
/**
* @see <a href="https://stackoverflow.com/questions/1026069/how-do-i-make-the-first-letter-of-a-string-uppercase-in-javascript">stackoverflow</a>
*
* @param {*} string
* @returns string with capitalized first letter
*/
function capitalizeFirstLetter(str) {
return str.charAt(0).toUpperCase() + str.slice(1);
}
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
function sanitizeUnsafeText(str, flagShowNullUndefined, flagAddConvertNewlinesToBrs) {
if (flagShowNullUndefined) {
if (str === null) {
return "[null]";
}
if (str === undefined) {
return "[undefined]";
}
} else {
if (str === null || str === undefined) {
str = "";
}
}
// escape it
let retstr = escapeHtml(str);
if (flagAddConvertNewlinesToBrs) {
// change \n to br/
retstr = retstr.replace(/\n/g, "<br/>\n");
}
// return it
return retstr;
}
function coerceToString(val, flagKeepNullUndefined) {
// ATTN:TODO catch toString conversion error
// conver val to str (but leave as undefined or null if flag set)
if (val === undefined || val === null) {
if (flagKeepNullUndefined) {
return val;
}
if (val === undefined) {
return "[undefined]";
}
if (val === null) {
return "[null]";
}
}
if (typeof val === "string") {
return val;
}
return val.toString();
}
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
function formatDateNicely(dateVal, flagCompact) {
// ATTN: TO DO -- some sanity check to make sure we are passed a valid date (or null/undefined)
if (!dateVal) {
return "[not set]";
}
if (flagCompact) {
return dateVal.toLocaleString();
}
return dateVal.toString();
}
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
function cleanIp(ipStr) {
if (!ipStr) {
return ipStr;
}
return (ipStr.length > 7 && ipStr.substr(0, 7) === "::ffff:") ? ipStr.substr(7) : ipStr;
}
//---------------------------------------------------------------------------
module.exports = {
jrPluralizeCount,
jrPluralize,
jrHtmlFormOptionListSelect,
jrHtmlFormOptionList,
jrHtmlNiceOptionFromList,
jrBootstrapCollapseBox,
jrHtmlStrigifyObject,
jrHtmlFormInputPassword,
capitalizeFirstLetter,
sanitizeUnsafeText,
coerceToString,
formatDateNicely,
cleanIp,
};