JavaScript

Overview

Purpose

Cards and Buttons can have a JavaScript function.

  • Card: computing Card Totals (locally or remotely), or fetching a numeric value such as the Num. of items in stock.
  • Button: for HTTP requests, e.g. posting a form (see the last example)

Function Argument (Payload)

  • Card: describes the Card content.
  • Button: describes its parent Card content.

When does it execute?

  • Card: on File load, and on payload-relevant changes.
  • Button: when clicked in Previewer.

Return Value

  • Card: has to be a number (or numeric string) and becomes the Total.
  • Button: is ignored.

Technical details

For security, functions run inside Web Workers. Therefore, there are limitations, like no direct DOM manipulation.

The code timeouts after 8 seconds. Therefore, if you open a File that has a JavaScript code error, you'll see a spinner until it times out. Similarly, if the execution on a Button fails, and the Button has more actions, they'll execute after the timeout.

Example

This example has Keys (they're explained in the next section, but take a look at them here first).

Key Editor showing set keys

CardCode. The JavaScript editor pre-populates a snippet that sums numeric Entries that have Keys. In this example:

function (card) {
  const side_a = card.side_a.value
  const side_b = card.side_b.value
  return side_a + side_b
}

After hitting Apply, the Total will be computed by that function. And it will recompute when the Entries change.

Key Editor

Keys are for referencing specific Cards or Entries in the payload, and/or in custom CSS Stylesheet. The keys don't have to be unique, if there are repeated, the last one wins.

ViewKey Editor

The name suggestions are based on the Card Title or Entry Label.

Key Editor showing suggestions

With the Keys set.

Key Editor with accepted keys

Key Grammar

Case-sensitive and must begin with a letter. Then, they can have underscores and digits.

name   last_name   LastName2  
_name   1name   last.name

Payload

The payload, the argument to be passed to the function, describes the Card (the one with the JavaScript function) and its children. Each child is a description of either an Entry, or a Nested Card.

If you want Entries or Totals from other Cards, connect them to the Card with the function. By the way, those connected targets can be Hidden in Previewer.

As hidable children show up in the payload, you can use them for API keys, tokens, etc.

Like in Formulas, Nested Cards get computed first. Therefore, their Totals are ready to use in the parent payload. In other words, asynchronous code is supported.

Tip There's a Download Payload Mock button that shows the exact argument the function will be called with.

Card (Root or Nested)

  • key String
  • value Card Total as a Number
  • stringValue Card Total as a String
  • children Array, in order, of the Entries and Nested Cards
  • isNumeric Boolean, true if the Card has a numeric Total. As Totals can only be numeric, this is like a hasTotal. But as Entries also have an isNumeric property, this consistent naming is handy for cases like the Max example below

Entry

  • key String
  • value Number if it's numeric; 1 or 0 if it's a Checkbox; String otherwise
  • stringValue
  • struck Boolean for Strikethrough
  • label String
  • isNumeric Boolean

Examples

Sum All

function (card) {
  return card.children
    .reduce((sum_so_far = 0, child) => sum_so_far + child.value)
}

That function is like Formula PresetsSum All, but the actual preset does more checks, see the next example.

Max

Equivalent to Formula PresetsMaximum.

Ensures computing on numeric children that aren't struck. And handles when there are none by returning an empty string.

Although Nested Cards have no struck property, it's safe to filter it.

function (card) {
  const values = card.children
    .filter(child => child.isNumeric && !child.struck)
    .map(child => child.value)

  return values.length
    ? Math.max(...values)
    : ''
}

Count children costing 50 or more

function (card) {
  return card.children
    .filter(child => child.value >= 50)
    .length
}

Sum children costing over 50

function (card) {
  return card.children
    .filter(child => child.value > 50)
    .reduce((sum_so_far = 0, child) => sum_so_far + child.value)
}

Stepped Discounts

The price gets a 15% discount if buying 10 or more, or 25% on 50+.

Two cards setup two compute a discounted price

The Auxiliary Card code:

function (card) {
  const num_of_items = card.num_of_items.value
  const base_price   = card.base_price.value
  const discounts    = card.volume_discounts.children

  let discount_so_far = 0
  for (const discount of discounts)
    if (num_of_items >= Number(discount.label))
      discount_so_far = discount.value

  return base_price * (1 - discount_so_far)
}

Examples with HTTP Requests

This repository has the server-side handlers for these examples.

HTTP Get

Card with an Auxiliary Entry for passing a parameter to a remote API for setting its Card Total with the response
function (card) {
  const product_id = card.product_id.value
  return fetch(`http://localhost:7000/in-stock-count/${product_id}`)
    .then(response => response.json())
    .then(data => data.count)
    .catch(console.error)
}
I

HTTP Post

Card with an Auxiliary Entry for passing a parameter to a remote API for use as API key. And two other Entries the sides of a triangle for computing its hypotenuse in a server side API.
function (card) {
  return fetch('http://localhost:7000/hypotenuse', {
    body: JSON.stringify(card),
    method: 'POST',
    headers: new Headers({ 'Content-Type': 'application/json' })
  })
    .then(response => response.json())
    .then(data => data.hypotenuse)
    .catch(console.error)
}

HTTP Post Button

A Card with a Button with a JavaScript action
function (card) {
  return fetch('http://localhost:7000/form-submit', {
    body: JSON.stringify(card),
    method: 'POST',
    headers: new Headers({ 'Content-Type': 'application/json' })
  })
    .then(() => '')
    .catch(console.error)
}
Next CSS Editor  ️⇾