JS survival kit for Shiny devs

David Granjon

Types

Types

JS defines several primitive types:

  • Number.
  • String.
  • Boolean.
  • Null.
  • Undefined.

typeof is a type operator:

typeof 1; // Number
typeof 'pouic'; // String

JS does not distinguish between integers and others. In R for instance, numeric contains integers and double.

Operators

List of arithmetic operators in JS:

  • +
  • -
  • *
  • /
  • % (modulo).
  • ++ (incrementation).
  • -- (decrementation).

+ adds two numbers, while it concatenates two strings:

1 + 1; // 2
"toto" + "titi"; // "tototiti"

Exercise

  • What would return typeof plop?
  • What is the output of typeof '1'?

Variables

Variables

In JS, a variable is defined by:

  • A type.
  • A name.
  • A value.

Most of the time, camelCase is the preferred choice.

A valid variable name:

  • Doesn’t use a reserved JS name like typeof!
  • Doesn’t start with a number (123soleil)!
  • Doesn’t include any space (total price)!

Const

const:

  • Cannot be modified.
  • Cannot share the same name.
  • Must be assigned a value.
const n = 1;
n = 2; // error
const n = 3; // error
const a;
a = 1; // errors

let

Define a variable and edit it later:

let myNumber = 1; // initialize
myNumber--; // decrement
console.log(myNumber); // print 0

var

What is the difference with let?

var i = 1;
{
  var i = 2; // this will modify i globally, not locally
}
console.log(`i is ${i}`); // i is 2.

With let:

let j = 1;
{
  let j = 2; // j is only declared locally and not globally!
}
console.log(`j is ${j}`); // j is 1

Other operators

Arithmetic operators still apply.

Assignement operators:

  • =
  • +=: a += b is equivalent to a = a + b.

Exercise

  • What is the output of this (Hint: think about type and value)?
undefined === null
undefined == null
  • What is the result of this?
let test = "";
test === null;
test === undefined

Conditions

Basics

Comparison operators:

  • ===: check value AND type.
  • ==: check value. Don’t do this.
  • !==: not equal value and type.
  • >, >=, <, <=.
  • AND or &&.
  • OR or ||.

Prefer === over == since 5 == '5' is true.

Tests

if and else:

if (condition) {
  //
}
if (condition) {
  //
} else {
  //
}

Ternary operator:

condition ? instruction if true : instruction if false

If loads of conditions, switch:

switch (variable) {
  case val1: // instruction 1
  break; // don't forget the break!
  case val2:  // instruction 2
  break;
  default: // when none of val1 and val2 are satisfied
}

Data structures

Objects basics

JS is an object-oriented programming language. An object is defined by:

  • A type.
  • Some properties (key-value pairs). _ Some methods (to manipulate properties).
const me = {
  name : 'Divad',
  age : 29,
  music : ''
}

this refers to the object itself.

Why can we change me while it is declared with const? We can’t reassign me but we can alter its content. The memory location remains the same.

Object manipulations

Read object properties with .:

object.<property> = ...;

We can define new properties/methods or modify them:

me.geek = true;
me.age = 33; // time flies.
Object.defineProperty(me, 'printAge', {
  value: function() {
    console.log(`I am ${this.name}`);
  },
  writable: false
})

Human readable format:

JSON.stringify(me)

Arrays: basics

An array is a structure to store information with different type. They’re similar to list in R:

const table = [1, 'plop'];
table = [2]; // error
console.log(table);

Array may be nested:

const nested = [1, ['a', [1, 2, 3]], 'plop'];
console.log(nested);
console.log(nested[0]);
// To get deeply nested element
// we may chain index
nested[1][1] // Access [1, 2, 3]

Be careful! In JS, indexing starts from 0, not 1 like in R.

Arrays: methods

JS provides methods specific to arrays, below a sample:

method description
length Returns the number of elements
push Add element at the end.

Copy by value/reference

Copy by value

Content is duplicated for simple type (boolean, string, number):

let var1 = 25;
let var2 = var1;
flowchart LR
  A(var1) --> C(25)
  B(var2) --> D(25)

What will happen to var1 if we do var2 = 3?

Copy by reference

For more complex types:

let fruits = ["banana"];
let superFruits = fruits;

What will happen to fruits if we do superFruits[0] = 'peach'?

flowchart LR
  fruits --> container(banana)
  superFruits --> container(banana)

How to prevent this? Use spread operator:

let superFruits = [...fruits];

Iteration

For loops

ES6 version:

// ES6 syntax
const nested = [1, ['a', [1, 2, 3]], 'plop'];
for (let el of nested) {
  console.log(el);
}

Classic version (more verbose and error prone):

// or with the classic approach
for (let i = 0; i < nested.length; i++) {
  console.log(nested[i]);
}

With an array, we can use forEach:

const letters = ['a', 'b', 'c', 'd'];
letters.forEach((letter) => {
  console.log(letter);
});

While

Q: how many times i will be printed?

const h = 3;
let i = 0;
while (i <= h) {
  console.log(i);
  i++; // increment to avoid infinite loop
}

Iterate on object

for key in object:

for (let key in object) { 
  if (object.hasOwnProperty(key)) {
    //
  }
}

Otherwise, we can leverage Object.entries:

for (const [key, value] of Object.entries(object)) {
  console.log(`${key}: ${value}`);
}

Similar methods exist like Object.keys or Object.values.

Functions

Basics

Wrap a succession of instructions to accomplish a given task.

const a = 1;
const fun = (parm1, parm2) => {
  console.log(a);
  let p = 3;
  // The Math object contains the max method
  return Math.max(parm1, parm2);
}
let res = fun(1, 2);
console.log(res); // prints a and 2
console.log(p);

The old way:

function funName(parm) {
  // do things
}

Events

DOM manipulation: get

One can play with JSFiddle.

A button in the DOM:

<button id="mybutton">Go!</button>

Get the element:

const btn = document.getElementById('mybutton');

Other selector methods exist like getElementByClassName, querySelector, querySelectorAll, …

DOM manipulation: modify

You may add a new CSS rule to test:

.class1 {
  background: green;
  color: white;
}

Add a new class:

btn.classList = 'class1 class2';
btn.classList.add('class3'); // keep othe classes.
btn.classList.remove('class2');
console.log(btn.classList);

Change text:

btn.innerHTML = "New text";

Exercice: edit DOM element

Consider this <button id="mybutton">Go!</button> button. Within JSFiddle, add some JS code to change the button content to My super button.

DOM manipulation: create

Combine createElement and append:

const divTxt = "Awesome text!"
const myDiv = document.createElement("div");
myDiv.classList = "class1";
myDiv.innerHTML = `<span>${txt}</span>`;
document.append(myDiv);

JS events

Add an event listener:

btn.addEventListener('click', function() {
  alert('Thanks!');
});

Exercise: events

Consider this <button id="mybutton">Go!</button> button. Add it an event listener that would change the button text to You clicked me! once clicked.

jQuery

jQuery is a famous JS library providing a user-friendly interface to manipulate the DOM.

Wrap your code in (ensure the DOM is ready):

$(document).ready(function(){
  // your code
});

jQuery is less verbose than JS:

// vanilla JS
let inner = document.getElementsByClassName('text').innerHTML;
// jQuery
let inner = $('.text').html();
$('button').on('click', function() {

});

Exercise.

Consider the following button

<button>Clicks: 0</button>

Write a script that shows how much time we clicked on the button. Fill in the blanks:

$(function() {

  // Init the click number
  let n = ...;

    // event listener for button element
    $(...).click(function() {
    // Increment the number of clicks
    n += ...;
    
    // Update content
    $(this).html(...);
  });

});

Correction

$(function() {

  // Init the click number
  let n = 0;

    // event listener for button element
    $("button").click(function() {
      // Increment count
      n += 1;
      // (2) add the button value to the inner text
      $(this).html(`Clicks: ${n}`);
  });

});

Modularisation

Modularisation

How to reuse a function in different scripts?

Old way:

// utils scripts
const findMax = (parm1, parm2) => {
  return Math.max(parm1, parm2);
}

module.exports = {
  findMax : findMax
}

Then, within the main.js script:

const { findMax } = require('<PATH_TO_utils.js>');
findMax(1, 2); // prints 2

ES6 way:

export { findMax, ... }; // in utils.js
import { findMax, ...} from './utils.js'; // in test.js

Code management

As ES6 is not fully supported by all web browsers, we use a transpiler like Babel to convert it to standard JS.

JS bundlers for modules: webpack, esbuild, …

Get started with esbuild

Pre-requisite: have node installed (for npm).

  1. Create a new folder.
  2. Run npm install --save-exact --save-dev esbuild.
  3. Create a utils.js and add it:
export const findMax = (parm1, parm2) => {
  return Math.max(parm1, parm2);
}
  1. Create main.js and add it:
import { findMax } from './utils.js';
console.log(findMax(1, 2));
  1. Edit package.json to add a build instruction:
{
  "scripts": {
    "build": "esbuild main.js --bundle --minify --sourcemap=external --outfile=out.min.js"
  },
  "devDependencies": { 
    "esbuild": "0.19.6"
  }
}
  1. Run npm run build and node out.js to test the new script. Notice that you can replace build by any predefined instruction.