JavaScript

From Torben's Wiki

Basics

Load after html loading via defer

<script src="main.js" defer></script>

HTML Form Elements

Function of button click

<button id="submit" onclick="myFunction()">Anmelden</button>

Function on enter

<input type="number" id="threshold" name="threshold" min="50" max="500" step="1" value="300">
var input_threshold = document.getElementById("threshold");
    input_threshold.addEventListener("keydown", function (e) {
      // Enter is pressed
      if (e.keyCode === 13) {
        event.preventDefault();
        myFunction();
      }
    }, false); 

Variables

Declaration

const var = 123; // constant: no change possible
let var = 123;   // block-bound, perferred to var
var var = 123;   // old style, better use let instead

Clone/Copy Object

// from https://www.samanthaming.com/tidbits/70-3-ways-to-clone-objects/
const myObject2 = Object.assign({}, myObject);

Arrays

Loop over all elements

var rows = table.getRows();
for (var i = 0; i < rows.length; i++) {
    var row = rows[i];
    ...
}

Loop over array of objects

for (let i = 0; i < Object.keys(array).length; i++) {
  const key = Object.keys(array)[i];
  const value = array[key];
}
NOTE: do not use
for (const key in array) { ... }
as this results in this problem
"The body of a for-in should be wrapped in an if statement to filter unwanted properties from the prototype"


Element in Array

if(myArray.indexOf(myItem) > -1) {...}

Last element of array

my_data.slice(-1)[0]

or

my_data.slice(-1)[0]["myKey"]
Array of Objects

delete some object properties

// delete not needed object properties
const allowedKeys = new Set(["type", "name", "x_date", "x_url", ...Object.keys(measures)]);
data.forEach((obj) => {
  Object.entries(obj).forEach(([key]) => {
    if (!allowedKeys.has(key)) {
      delete obj[key];
    }
  });
  // extract year from x_date and add as property
  obj.year = parseInt(obj.x_date.substr(0, 4));
});

Extract distinct list/set list for property 'type' values

const act_types = [...new Set(data.map((obj) => obj.type))].sort();

Sort by object property "measure" DESC

data = data.sort((a, b) => b[measure] - a[measure]);

Async fetching via JQuery

in HTML

<script src="lib/jquery-3.5.0.min.js"></script>

in JS

// array of promises for async fetching
const promises = [];

// ref dictionary to be fetched: Country Code -> Country Name
var mapCountryNames = {};

// fetch countries-latest-all.json containing country reference data like code and continent
function fetch_mapRefCountryData(mapCountryNames) {
    const url =
        "https://entorb.net/COVID-19-coronavirus/data/int/countries-latest-all.json";
    return $.getJSON(url, function (data) {
        console.log("success: mapCountryNames");
    })
        .done(function (data) {
            console.log("done: mapCountryNames");
            $.each(data, function (key, val) {
                mapCountryNames[data[key].Code] = data[key].Country;
            });
        })
        .fail(function () {
            console.log("fail: mapCountryNames");
        });
}

// Start the async fetching 
promises.push(fetch_mapRefCountryData(mapCountryNames, mapContinentCountries));

// Wait for all async promises to be done (all data is fetched), then print message
Promise.all(promises).then(function () {
    console.log("All data fetched");
});


Date handling

// calculate date via offset
const daysOffset = 7;
const s_data_last_date = "2020-04-01"
const ts_last_date = Date.parse(s_data_last_date)
var minDate = new Date(ts_last_date);
minDate.setDate(minDate.getDate() + daysOffset);

Helper Functions

from [https://love2dev.com/blog/javascript-remove-from-array/

function arrayRemove(arr, value) {
  return arr.filter(function (ele) { return ele != value; });
}

from [1]

function removeAllOptionsFromSelect(select) {
  var i, L = select.options.length - 1;
  for (i = L; i >= 0; i--) {
    select.remove(i);
  }
}

// Formats value "Something_Is_HERE" to "Something is here" like sentence
// value: The value to format
// separator: the separator string between words
function formatValueToSentenceLike(value, separator) {
  const allLowerCaseValue = value.split(separator).join(" ").toLowerCase();
  return allLowerCaseValue[0].toUpperCase() + allLowerCaseValue.substr(1);
}
// modifies array of objects by removing if value == keys
function arrayRemoveValueTextPairByValue(arr, key) {
  for (let i = arr.length - 1; i >= 0; i--) {
    if (arr[i].value == key) { arr.splice(i, 1); }
  }
}

Local Storage

// read browser's local storage for last session data
localStorageData = window.localStorage.getItem("my_data");
if (localStorageData) {
    var my_data = JSON.parse(localStorageData);
} else {
    var my_data = [];
}

Export and Import / Download and Upload data to Variable

HTML

<a id="downloadAnchor" style="display:none"></a>
<button id="download_data" onclick="download_data()">Export</button> and Import: <input id="upload_data" type="file" onchange="upload_data(this)">

JS

// download data
function download_data() {
    const dataStr = "data:text/json;charset=utf-8," + encodeURIComponent(JSON.stringify([settings, data]));
    let html_dl_anchor = document.getElementById("downloadAnchor");
    html_dl_anchor.setAttribute("href", dataStr);
    html_dl_anchor.setAttribute("download", "eta.json");
    html_dl_anchor.click();
}

// upload data
function upload_data(input) {
    // from https://javascript.info/file
    let file = input.files[0];
    let reader = new FileReader();
    reader.readAsText(file);
    reader.onload = function () {
        const uploaded_data = JSON.parse(reader.result);
        settings = uploaded_data[0];
        data = uploaded_data[1];
    };
    reader.onerror = function () {
        console.log(reader.error);
    };
}

Hide/remove HTML elements

HTML

< div id="text_intro" >...< /div >

JS

function hide_intro() {
    // from https://stackoverflow.com/questions/1070760/javascript-href-vs-onclick-for-callback-function-on-hyperlink
    const html_text_intro = document.getElementById('text_intro');
    html_text_intro.remove();
}

Internet Explorer Backward Compatibility

Error: Object doesn't support property or method 'includes'

variant 1:

replace:

if(myarray.includes(key)) {

by:

if(myarray.indexOf(key) > -1) {

variant 2: polyfile

if (!String.prototype.includes) {
  String.prototype.includes = function(search, start) {
    'use strict';

    if (search instanceof RegExp) {
      throw TypeError('first argument must not be a RegExp');
    } 
    if (start === undefined) { start = 0; }
    return this.indexOf(search, start) !== -1;
  };
}

Tabulator

// wait for tableBuilt event
table.on("tableBuilt", function () {
    myUpdateTableFnc();
});

eCharts

Jest Unittests

install jest and jsdom for testing browser features like window.localStorage

npm install --save-dev jest jest-environment-jsdom

in package.json set

"scripts": {"test": "jest --coverage","testc": "jest --coverage"},
"jest": { "testEnvironment": "jsdom" }

run jest

npm test
npm run testc

In the file I want to test here `helper.js`

var module = module || {};
module.exports = {
  zeroPad,
...
}

The testcasefile `helper.test.js`

describe("Testing zeroPad", () => {
 const { zeroPad } = require("./helper");
  test("1->01", () => {
    expect(zeroPad(1, 2)).toEqual("01");
  });
});

test.each

To test many cases use

describe("zeroPad()", () => {
  const { zeroPad } = require("./helper");
  const cases = [
    // arg1, arg2, expectedResult
    [1, 2, "01"],
    [6, 3, "006"],
  ];
  test.each(cases)(
    "given '%p', '%p' it shall return %p",
    (arg1, arg2, expectedResult) => {
      expect(zeroPad(arg1, arg2)).toEqual(expectedResult);
    }
  );
});

ignore Test Coverage for missing else etc

place such a comment before the non-relevant line

/* istanbul ignore else */
/* istanbul ignore next */