Javascript

Fixing MomentJS interpreting dates as local time

Problem:

You live in a non-UTC timezone. Dates parsed using MomentJS like

moment("2017-01-01")

are interpreted as local time as opposed to UTC (as would be appropiate based on the Z in the ISO8601 timestamp) and therefore trying to format them yields an offset timestamp:

moment("2017-01-01").toDate().toISOString()
// "2016-12-31T23:00:00.000Z" <-- Offset by 1 hour (MEZ - UTC)

This causes you trouble as often the original date is not preserved: In the example above, the correct date would be 2017-01-01 but it is 2016-12-31 instead.

Solution 1: Force moment to parse the date as UTC

moment.utc("2017-01-01").toDate().toISOString()
// "2017-01-01T00:00:00.000Z" <-- Correct

Solution 2: Manually subtract the timezone offset

let m = moment("2017-01-01");
// Subtract the difference between timezone and UTC
m = m.subtract(m.toDate().getTimezoneOffset(), 'minutes');
// m.toDate().toISOString() === "2017-01-01T01:00:00.000Z" - CORRECT

Use this if Solution 1 does not work or you can’t modify the parsing code: This solution works even if the date has already been parsed.

DO NOT use

new Date().getTimezoneOffset() // NEVER USE THIS! MIGHT USE THE WRONG OFFSET!

because it uses the timezone offset of the current date instead of the date parsed by MomentJS. Obviously this is wrong in countries with summertime, since dates may have different offsets depending on the date. Moreover, in rare cases the client’s computer timezone may have changed due to travel etc in the meantime and might therefore might not represent the correct offset to use.

Posted by Uli Köhler in Javascript

Fixing Angular Error: Unexpected value ‘undefined’ declared by the module

Symptom:

For a module of yours, you get an error message like this on load:

Error: Unexpected value 'undefined' declared by the module 'MyModule'
    at syntaxError (compiler.js:1021)
    at compiler.js:10623
    at Array.forEach (<anonymous>)
    at CompileMetadataResolver.push../node_modules/@angular/compiler/fesm5/compiler.js.CompileMetadataResolver.getNgModuleMetadata (compiler.js:10621)
    at JitCompiler.push../node_modules/@angular/compiler/fesm5/compiler.js.JitCompiler._loadModules (compiler.js:23876)
    at JitCompiler.push../node_modules/@angular/compiler/fesm5/compiler.js.JitCompiler._compileModuleAndComponents (compiler.js:23857)
    at JitCompiler.push../node_modules/@angular/compiler/fesm5/compiler.js.JitCompiler.compileModuleAsync (compiler.js:23817)
    at CompilerImpl.push../node_modules/@angular/platform-browser-dynamic/fesm5/platform-browser-dynamic.js.CompilerImpl.compileModuleAsync (platform-browser-dynamic.js:143)
    at core.js:4999
[...]

Solution:

First, try to restart ng serve. In some cases this will outright fix the issue.

The error message is caused by some element of your @NgModule declarations: [ /* ... */ ] to be undefined (… declared by the module in the error message should be taken literally, it’s in the declarations!).

For example, if you have a @NgModule declaration like this:

@NgModule({
  imports: [
    RecallCommonModule,
    CommonModule,
    MyRoutingModule
  ],
  declarations: [,
    MyDetailComponent,
    MySearchComponent
  ],
  providers: []
})

The issue is in the stray comma in the declarations line: When compiled, it results in [undefined, MyDetailComponent, MySearchComponent]. Removing the stray comma fixes the issue.

Another possible cause of this error is if one of the values in declarations is undefined.

For example if you have an import statement like

import { MyComponent } from './my.component';

in your module file, and, for some reason, MyComponent is undefined, this will cause the same error message to appear.

In order to track down which of your components in the declarations array, you could temporarily add a statement like this before your @NgModule :

if(MyComponent === undefined) {
    console.log("MyComponent is undefined!");
}

In case you don’t have any of the issues above, follow the standard procedure:
Comment out every element in your declarations array and see which causes the error message to disappear.

Posted by Uli Köhler in Angular, Javascript

Fixing NodeJS Intl.DateTimeFormat not formatting properly for locales

Symptom:

NodeJS starting from version v9.x supports the ES6 Intl.DateTimeFormat

When you use it with the ‘en-US’ locale, it works properly and prints "August 13, 2018":

const df = new Intl.DateTimeFormat('en-US', {day: 'numeric', month: 'long', year: 'numeric', timeZone: 'UTC'});
console.log(df.format(new Date("2018-08-13T04:00:00.000Z")));

However, using it with a different locale fails:

const df = new Intl.DateTimeFormat('de', {day: 'numeric', month: 'long', year: 'numeric', timeZone: 'UTC'});
console.log(df.format(new Date("2018-08-13T04:00:00.000Z")));

While you would expect this to print "13. August 2018" , it will print "2018 M08 13"

Reason:

By default, NodeJS is only built with small-icu support, thereby only installing the en-US locale in order to reduce the installation filesize.

Solution 1 (preferred):

You can use the intl polyfill module to completely replace NodeJS’s implementation of intl:

Installation:
npm i --save intl
Usage:
// Replace Intl by polyfill
Intl = require("intl")

const df = new Intl.DateTimeFormat('de', {day: 'numeric', month: 'long', year: 'numeric', timeZone: 'UTC'});
console.log(df.format(new Date("2018-08-13T04:00:00.000Z")));

This will print 13. August 2018 as expected.

Solution 2 (alternate):

You can use the full-icu package to continue using the NodeJS ICU implementation (i.e. no polyfill), but just install the ICU data.

While this reduces the total installation filesize, installation is slow and the exact method depends on the NodeJS version and requires more work than just using the intl polyfill.

In order to install, use

npm i --save full-icu

This will take some time to compile the data and then will print instructions like this:

 √ icudt62l.dat (link)
Node will use this ICU datafile if the environment variable NODE_ICU_DATA is set to “node_modules/full-icu”
or with node --icu-data-dir=node_modules/full-icu YOURAPP.js
 For package.json:
{"scripts":{"start":"node --icu-data-dir=node_modules/full-icu YOURAPP.js"}}

By the way, if you have full data, running this in node:
> new Intl.DateTimeFormat('es',{month:'long'}).format(new Date(9E8));
... will show “enero”. If it shows “January” you don't have full data.
News: Please see https://github.com/icu-project/full-icu-npm/issues/6

In order to actually use full-icu, you need to use the --icu-data-dir=node_modules/full-icu argument every time you run node. In order to run node interactively, use

node --icu-data-dir=node_modules/full-icu

If you use scripts in your application (e.g. the start script, i.e. what gets executed if you run npm start), you need to adjust the configuration in package.json:

Instead of

// [...]
"scripts": {
    "start": "node index.js"
}
// [...]

use

// [...]
"scripts": {
    "start": "node --icu-data-dir=node_modules/full-icu index.js"
}
// [...]

Depending on your application, you might need to use a different script name than index.js – common names include server.js and start.js

Posted by Uli Köhler in Allgemein, Javascript, NodeJS

How to fix Angular6 “_getAriaLabel is not a function” with production build

Problem:

You are building an Angular6 application and in development mode everything works fine. However, if you build in production mode:

ng build --prod --aot

you see an error like this in the client:

main.4d1baabffbba5677af03.js:1 ERROR TypeError: i.ɵnov(...)._getAriaLabel is not a function
    at Object.updateRenderer (main.4d1baabffbba5677af03.js:1)
    at Object.updateRenderer (main.4d1baabffbba5677af03.js:1)
[...]

Solution:

The issue appears to be caused by incorrectly updated NodeJS modules. You can fix it by simply deleting your node_modules folder:

rm -rf node_modules

Furthermore, it’s recommended to update @angular/cli as the bug does not seem to be present in newer versions of @angular/cli:

sudo npm i -g @angular/cli

Source & discussion on GitHub

Posted by Uli Köhler in Angular, Javascript

Fixing npm/node-gyp Error: not found: make on Ubuntu

When you run npm install and it tries to install a native package like bcrypt and you see an error message like this:

gyp ERR! build error 
gyp ERR! stack Error: not found: make
gyp ERR! stack     at getNotFoundError (/usr/lib/node_modules/npm/node_modules/which/which.js:13:12)
gyp ERR! stack     at F (/usr/lib/node_modules/npm/node_modules/which/which.js:68:19)
gyp ERR! stack     at E (/usr/lib/node_modules/npm/node_modules/which/which.js:80:29)
gyp ERR! stack     at /usr/lib/node_modules/npm/node_modules/which/which.js:89:16
gyp ERR! stack     at /usr/lib/node_modules/npm/node_modules/isexe/index.js:42:5
gyp ERR! stack     at /usr/lib/node_modules/npm/node_modules/isexe/mode.js:8:5
gyp ERR! stack     at FSReqWrap.oncomplete (fs.js:182:21)

you simple need to install GNU Make. On Ubuntu, the easiest way of doing this is to run

sudo apt install build-essential

This will not only install make but also related tools like gcc and some standard header files and tools.

Posted by Uli Köhler in Linux, NodeJS

Fixing node/npm ImportError: No module named compiler.ast on Ubuntu 18.04

If you run npm install and encounter this error message:

ImportError: No module named compiler.ast

you need to install the python development files using

sudo apt install python-dev

Note: In my case, using apt install python3-dev did not solve the issue.

Posted by Uli Köhler in Linux, NodeJS

How to set cell value to string using js-xlsx

This snippet reads a XLSX file using js-xlsx, sets the C2 cell to abc123 and writes the result to another file:

const XLSX = require('xlsx');

const table = XLSX.readFile('mytable.xlsx');
// Use first sheet
const sheet = table.Sheets[table.SheetNames[0]];

// Option 1: If you have numeric row and column indexes
sheet[XLSX.utils.encode_cell({r: 1 /* 2 */, c: 2 /* C */})] = {t: 's' /* type: string */, v: 'abc123' /* value */};
// Option 2: If you have a cell coordinate like 'C2' or 'D15'
sheet['C2'] = {t: 's' /* type: string */, v: 'abc123' /* value */};

XLSX.writeFile(table, 'result.xlsx');

 

Posted by Uli Köhler in Javascript, NodeJS

How to iterate over XLSX rows using js-xlsx

This snippet allows you to easily iterate over rows in any XLSX files using the js-xlsx library (in this example we don’t iterate over all columns but rather only get the B column as an example):

const table = XLSX.readFile('mytable.xlsx');
const sheet = table.Sheets[table.SheetNames[0]];

var range = XLSX.utils.decode_range(sheet['!ref']);
for (let rowNum = range.s.r; rowNum <= range.e.r; rowNum++) {
    // Example: Get second cell in each row, i.e. Column "B"
    const secondCell = sheet[XLSX.utils.encode_cell({r: rowNum, c: 1})];
    // NOTE: secondCell is undefined if it does not exist (i.e. if its empty)
    console.log(secondCell); // secondCell.v contains the value, i.e. string or number
}

 

Posted by Uli Köhler in Javascript, NodeJS

How to get first XLSX sheet in js-xlsx

If you want to read an XLSX file and just get the first sheet in that file using js-xlsx, you can use this snippet:

const XLSX = require('xlsx');

const table = XLSX.readFile('mytable.xlsx');
// First sheet is extracted here
const sheet = table.Sheets[table.SheetNames[0]];

 

Posted by Uli Köhler in Javascript, NodeJS

How to fix Angular4/5/6 Unexpected token ‘px’

If you encounter an error message like this:

Parser Error: Unexpected token 'px' at column 3 in [70px] in ng:///AppModule/[email protected]:34 ("="let string of strings">

look at the line the error is referring to. It will look similar to this:

<mat-expansion-panel-header [collapsedHeight]="70px">

You have two options of fixing this:

Option 1: Recommended if the value (70px in this case) is always constant.

Remove the brackets from the attribute: [collapsedHeight] to collapsedHeight. The brackets mean that the value shall be interpreted as Javascript and removing them means interpreting the value as attribute. You code should look like this:

<mat-expansion-panel-header collapsedHeight="70px">

Option 2: Force angular to interpret the value (70px in this case) as a string:

Add single quotes before and after the value makes it valid Javascript:

<mat-expansion-panel-header [collapsedHeight]="'70px'">

I recommend to use this option only if you expect the value to be a non-constant javascript expression in the future.

Posted by Uli Köhler in Angular, Javascript

PDFJS: Read PDF from memory Buffer in NodeJS

Note: This post uses async/await and therefore requires NodeJS 8+.

This is how to read a PDF file from a file, e.g. mypdf.pdf:

pdfjs.getDocument('mypdf.pdf');

Full example:

const pdfjs = require('pdfjs-dist');

async function readPDF() {
    const pdf = await pdfjs.getDocument('mypdf.pdf');
    // ...
}

Here’s how you can read the PDF from a memory buffer:

pdfjs.getDocument({data: buffer});

Full example

const fs = require('mz/fs')
const pdfjs = require('pdfjs-dist');

async function readPDF() {
    // Read file into buffer
    const buffer = await fs.readFile('mypdf.pdf')
    // Parse PDF from buffer
    const pdf = await pdfjs.getDocument({data: buffer});
    // ...
}

Using mz/fs is not required, it’s just used as an utility library to be able to use await with files.

 

Posted by Uli Köhler in Javascript, PDF

Convert pt (postscript/PDF unit) to inch or mm in Javascript

Here are some simple utility functions to convert the preprint unit pt (defined as 1/72 inch) into inches or mm.

function convertPtToInch(pt) { return pt / 72; }
function convertInchToMM(inch) { return inch * 25.4; }
function convertPtToMM(pt) {
  return convertInchToMM(convertPtToInch(pt)); }

// Example usage
console.log(convertPtToMM(595)) // Prints 209.90277777777777

Note that while this conversion is exact, there is some tolerance required when comparing these units:
An ISO A4 paper is defined as 210x297 mm – or 595x842 pt.

However, converting 595×842 pt into mm results in 209.902777 mm and 297.038888 mm respectively. Watch out for those tolerances if you try to compare paper sizes. I recommend a tolerance of at least 0.25 mm.

Posted by Uli Köhler in Javascript, PDF

Extract PDF page sizes using PDFJS & NodeJS

Although most PDFs have some pages with only one page size (e.g. DIN A4 or Letter in portrait orientation), PDFs sometimes also have pages that have another size or orientation (which is treated just like another size) that other pages in the same document.

This post provides an easy-to-reuse example on how to use PDFJS in NodeJS (though it will be just as easy to do in the browser) to extract the PDF

It is based on this previous post on how to read all pages from a PDF document using PDFJS, so be sure to check that out first.

First install the required dependencies:

npm install bereich pdfjs-dist

then you can use this source code to read the page sizes of mypdf.pdf:

const pdfjs = require('pdfjs-dist');
const bereich = require('bereich');

class PageSize {
  constructor(width, height) {
    this.width = width;
    this.height = height
  }
}

function getPageSize (page) {
    const [x, y, w, h] = page.pageInfo.view;
    const width = w - x;
    const height = h - y;
    const rotate = page.pageInfo.rotate;
    // Consider rotation
    return (rotate === 90 || rotate === 270)
        ? new PageSize(height, width) : new PageSize(width, height);
}

async function readPDFPageSizes() {
  const pdf = await pdfjs.getDocument('mypdf.pdf');
  const numPages = pdf.numPages;

  const pageNumbers = Array.from(bereich(1, numPages));
  // Start reading all pages 1...numPages
  const promises = pageNumbers.map(pageNo => pdf.getPage(pageNo));
  // Wait until all pages have been read
  const pages = await Promise.all(promises);
  // You can do something with pages here.
  return pages.map(getPageSize);
}

readPDFPageSizes()
    .then(pageSizes => {console.log(pageSizes)})
    .catch(err => {console.error(`Error while reading PDF: ${err}`)})

Running this with a document having a single A4 page will result in

[ PageSize { width: 595, height: 842 } ]

Note that the width & height unit is pt (Points). One pt is defined as 1/72 inches. A DIN A4 page (portrait) is 595x842pt, therefore you see those values here.
See this TechOverflow post for code to convert pt to mm and inches.

Posted by Uli Köhler in Javascript, PDF

PDFJS: Read all pages using async/await in NodeJS

PDFJS has an official example that – among other things, reads all pages from a PDF document.
However, their promise-based method is rather complex to understand and to write. Luckily, there is an easier way using async/await (which is supported starting from NodeJS 8.x).

I’m using the bereich library (bereich is german for range) in order to generate an array of page numbers (1..numPages).
Install the required libraries using

npm install pdfjs-dist bereich

Here’s the source code example:

const pdfjs = require('pdfjs-dist');
const bereich = require('bereich');

async function readPDFPages() {
  const pdf = await pdfjs.getDocument('mypdf.pdf');
  const numPages = pdf.numPages;

  const pageNumbers = Array.from(bereich(1, numPages));
  // Start reading all pages 1...numPages
  const promises = pageNumbers.map(pageNo => pdf.getPage(pageNo));
  // Wait until all pages have been read
  const pages = await Promise.all(promises);
  // You can do something with pages here.
  return pages;
}

readPDFPages().then(pages => {
    console.log(pages)
}).catch(err => {
    console.error(`Error while reading PDF: ${err}`)
})

 

Posted by Uli Köhler in Javascript, PDF

How to read PDF creation & modification date in NodeJS

Problem:

You have a PDF file from which you want to know the creation and modification date: Not the dates stored in the file itself but those from the PDF metadata.

Solution:

This solution assumes you use NodeJS version 8+ which supports async/await.
You can use pdfjs to read these dates. First install it using

npm install pdfjs-dist

Then use this code to extract the dates.

const pdfjs = require('pdfjs-dist');

async function readPDFDates() {
  const pdf = await pdfjs.getDocument('mypdf.pdf');
  const metadata = await pdf.getMetadata();

  const modDate = new Date(metadata.metadata._metadata['xmp:modifydate']);
  const createDate = new Date(metadata.metadata._metadata['xmp:createdate']);
  return [modDate, createDate]
}

readPDFDates().then(([modDate, createDate]) => {
    console.log(`Creation date: ${createDate}`)
    console.log(`Modification date: ${modDate}`)
}).catch(err => {
    console.error(`Error while reading PDF: ${err}`)
})

 

The PDF files I’ve seen use ISO8601-style formatting, but without a timezone specification. The code therefore assumes that the times are in the local timezone.

Note: metadata is e.g. the following object (not all attributes are present for all PDFs):

{ info: 
   { PDFFormatVersion: '1.5',
     IsAcroFormPresent: false,
     IsXFAPresent: false,
     Title: 'Microsoft Word - mypdf',
     Author: 'uli',
     Creator: 'PScript5.dll Version 5.2.2',
     Producer: 'Acrobat Distiller 9.3.0 (Windows)',
     CreationDate: 'D:20100209100924+01\'00\'',
     ModDate: 'D:20100209100924+01\'00\'' },
  metadata: 
   Metadata {
     _metadata: 
      { 'dc:format': 'application/pdf',
        'dc:creator': 'peter',
        'dc:title': 'Microsoft Word - mypdf',
        'xmp:createdate': '2010-02-09T10:09:24+01:00',
        'xmp:creatortool': 'PScript5.dll Version 5.2.2',
        'xmp:modifydate': '2010-02-09T10:09:24+01:00',
        'pdf:producer': 'Acrobat Distiller 9.3.0 (Windows)',
        'xmpmm:documentid': 'uuid:2fd66f45-5f2a-4dd6-8cb0-297ce85ee9e1',
        'xmpmm:instanceid': 'uuid:f6e62218-4b40-47c7-837b-6cb1e6e90995' } },

 

Posted by Uli Köhler in Javascript, PDF

How to add JS Drag-&-Drop file upload without any dependencies

Problem:

For your new web application, you want to add drag&drop file uploads without using any external library.

Solution:

You can use this set of functions, which you can adapt to your application.

/**
 * Initialize drag & drop event handling for a DOM element.
 * The DOM element does not have to be empty in order to do this.
 * @param elem The DOM element where files can be dragged & dropped
 * @param callback The callback(files) function that gets passed a list of files
 * when files are dragged and dropped.
 *
 * Basic usage example:
 *  var elem = document.getElementById('mydiv');
 *  initializeDragAndDrop(elem, function(files) {
 *      for (var i = 0; i < files.length; i++) {
 *          // Do something with files[i]...
 *          handleUploadedFile(files[i]); // Replace by your code
 *      }
 *  });
 */
function initializeDragAndDrop (elem, callback) {
    elem.addEventListener('drop', function (event) {
        _dragndrop_preventDefault(event);
        callback(event.dataTransfer.files);
    }, false);
    elem.addEventListener('dragover', _dragndrop_preventDefault, false);
    elem.addEventListener('dragdrop', _dragndrop_preventDefault, false);
    elem.addEventListener('dragenter', _dragndrop_preventDefault, false);
    elem.addEventListener('dragleave', _dragndrop_preventDefault, false);
}

/**
 * Internal utility function to prevent default
 * handling for a given event.
 */
function _dragndrop_preventDefault (event) {
    event.stopPropagation();
    event.preventDefault();
}

Usage example:

var elem = document.getElementById('mydiv');
initializeDragAndDrop(elem, function(files) {
    for (var i = 0; i < files.length; i++) {
        // Do something with files[i]...
        handleUploadedFile(files[i]); // Replace by your code
    }
});

Note that you need to run initializeDragAndDrop only after the respective DOM element (mydiv in this example) has been loaded. For example, you could call it like this if you use jQuery:

$(document).ready(function() {
    initializeDragAndDrop(/* ... */);
});

Pure Javascript (no jQuery):

// WARNING: This will replace any window.onload function
// that is currently set.
// Also, this will only fire after everything on the page
// has been loaded, which might not be the desired behaviour.
window.onload = function() {
    initializeDragAndDrop(/* ... */);
};

Also see this previous TechOverflow post on how to read the uploaded files into memory, if you need to.

Posted by Uli Köhler in Javascript

Reading an uploaded file into memory using pure Javascript

You have a File object in Javascript (e.g. from a drag & drop upload or a <input type="file">) which you want to read into memory in the browser. You don’t want to use any library to do that but prefer a pure Javascript solution.

Solution:

Use this function:

/**
 * Utility function to read an entire file into memory.
 *
 * The handler function gets passed an array of objects:
 * {
 *     name: filename as string,
 *     size: size in bytes as number,
 *     type: MIME type as string,
 *     content: file content as Uint8Array
 * }
 * @param file The file to read
 * @param handler
 */
function readFileIntoMemory (file, callback) {
    var reader = new FileReader();
    reader.onload = function () {
        callback({
            name: file.name,
            size: file.size,
            type: file.type,
            content: new Uint8Array(this.result)
         });
    };
    reader.readAsArrayBuffer(file);
}

Usage example:

// Usage example
readFileIntoMemory(file, function(fileInfo) {
    console.info("Read file " + fileInfo.name + " of size " + fileInfo.size);
    // You can use fileInfo.content, which is a Uint8Array, here
});
Posted by Uli Köhler in Javascript

Copying strings to the clipboard using pure Javascript

In order to copy a string to the system clipboard in the browser without using any dependency like clipboard.js, use this function:

function copyStringToClipboard (str) {
   // Create new element
   var el = document.createElement('textarea');
   // Set value (string to be copied)
   el.value = str;
   // Set non-editable to avoid focus and move outside of view
   el.setAttribute('readonly', '');
   el.style = {position: 'absolute', left: '-9999px'};
   document.body.appendChild(el);
   // Select text inside element
   el.select();
   // Copy text to clipboard
   document.execCommand('copy');
   // Remove temporary element
   document.body.removeChild(el);
}

Note: If the user selected anything when you run the function, this selection will be cleared. If you need to preserve the selection, see this Hackernoon article for a more elaborate solution..

You can use it like this:

// Usage example:
copyStringToClipboard("abc123");

Browser compatibility

This solutions uses only basic Javascript (no ES6 features required), the DOM API which has existed for decades and document.exec('copy'); which is, according to the Mozilla Developer’s network, compatible with all major browsers, including Internet Explorer starting from IE9.

How it works

We create a new <textarea> element whose value we set to the string to copy (e.g. "abc123" in our usage example above).

In order to avoid confusing the user and, more importantly, screenreaders, we set it to readonly and shift it -9999px to the left, which is guaranteed to be outside the viewport for all practical purposes.

Only after shifting the element outside the viewport, we add it to the DOM so that it won’t be shown even for a fraction of a second.

Now we can select the text inside the element using el.select() and copy it to the clipboard using document.execCommand('copy');.

Finally, we remove the element from the DOM.

Credits to Angelos Charalis on Hackernoon for the original idea of moving the element outside the viewport.

 

Posted by Uli Köhler in Javascript

How to fix NodeJS request Error: Argument error, options.body

Problem:

You’re using request to send a POST request with a body that should be JSON-encoded, but you are encountering an error similar to this:

Error: Argument error, options.body.
    at setContentLength (/home/uli/myproj/node_modules/request/request.js:434:28)
    at Request.init (/home/uli/myproj/node_modules/request/request.js:439:5)
    at new Request (/home/uli/myproj/node_modules/request/request.js:128:8)
    at request (/home/uli/myproj/node_modules/request/index.js:53:10)
    at Function.post (/home/uli/myproj/node_modules/request/index.js:61:12)

and, additionally this stacktrace:

TypeError [ERR_INVALID_ARG_TYPE]: The first argument must be one of type string or Buffer
    at write_ (_http_outgoing.js:647:11)
    at ClientRequest.write (_http_outgoing.js:622:10)
    at Request.write (/home/uli/myproj/node_modules/request/request.js:1501:27)
    at end (/home/uli/myproj/node_modules/request/request.js:546:18)
    at Immediate.<anonymous> (/home/uli/myproj/node_modules/request/request.js:575:7)
    at runCallback (timers.js:763:18)
    at tryOnImmediate (timers.js:734:5)
    at processImmediate (timers.js:716:5)

Solution:

The error basically tells you that request can’t determine the length of the body, as you didn’t tell it how to encode the body and it isn’t a simple string or buffer. The solution is to tell request to use JSON body encoding by adding json: true to the options parameter (first argument to request.post). A valid options parameter looks like this:

const opts = {
    url: 'http://localhost:1234/api/myapi',
    body: {/* your body object */},
    json: true // <-- Add this line
};
Posted by Uli Köhler in Javascript, NodeJS

How to fix KaTeX parse error: KaTeX doesn’t work in quirks mode

Problem:

You’re using KaTeX on a website, but every time you try to render, you encounter the following error message:

Uncaught Error: KaTeX parse error: KaTeX doesn't work in quirks mode.
    at new e (VM697 katex.min.js:1)
    at Object.l [as render] (VM697 katex.min.js:1)
[...]

Solution:

KaTeX requires a valid DOCTYPE declaration on your HTML page, so add this line at the top of your HTML file(s), above <html>

<!DOCTYPE html>

Thanks to @xymostech on GitHub for the original solution in the the KaTeX issue tracker:

Posted by Uli Köhler in Javascript, LaTeX