Breaking Down MSON Template Queries

Written by redgeoff | Published 2021/03/18
Tech Story Tags: json | javascript | programming | mongodb | low-code | lowcode | database | backend

TLDRvia the TL;DR App

Template Queries are dynamic templates constructed with MongoDB-style operators. They allow you to customize MSON components with less code and are very extensible.

OK, but what the heck is MSON?

MSON is a low-code way to create apps from JSON. The ultimate goal of MSON is to allow anyone to develop software visually. You can also use pieces of MSON to turbo charge your existing apps.
A simple form component used to collect a name and email address could look like:
{
  name: 'MyForm',
  component: 'Form',
  fields: [
    { name: 'name', component: 'TextField', label: 'Name' },
    { name: 'email', component: 'EmailField', label: 'Email' }
  ]
}
You can read more about MSON's language principles here.

What are MongoDB operators?

MongoDB uses an extensive query language that is written in JSON. Because this language is so powerful, a number of projects, that work independently from MongoDB, have adopted support for the query language:
  1. In-memory libraries for JS, like mingo and siftjs.
  2. Operators in sequelize ORM.
  3. Other databases, like CouchDB
This query language also contains a construct for pipeline aggregations, which can be used to execute operations like
$add
,
$multiply
,
$filter
,
$map
, etc... For example:
{
  $add: [ 1, 2 ]
}

A basic MSON Template Query

Now that you have that background, let's take a look at an example where we extract the year from a user-provided date. Upon selecting a date with the date picker, you'll see that the
Year
field is populated:
Let's step through this example. First, we construct a form with
date
and
year
fields:
{
  component: "Form",
  fields: [
    {
      name: "date",
      component: "DateField",
      label: "Date"
    },
    {
      name: "year",
      component: "IntegerField",
      label: "Year"
    }
  ]
}
Second, we listen for changes to the
date
value, extract the year and then set the value of the
year
field:
{
  component: "Form",
  fields: ...,
  listeners: [
    {
      event: "fields.date.value",
      actions: [
        {
          component: "Set",
          name: "fields.year.value",
          value: {
            $year: {
              $toDate: "{{fields.date.value}}"
            }
          }
        }
      ]
    }
  ]
}
The
$toDate
operator is used to convert the date value, which is in milliseconds, to a date object. The
$year
operator is used to extract the year portion from the date. The
Set
action is native to MSON and is used to set the value of the
year
field.
Pretty cool? Let's go a bit deeper...

How can we make this reusable?

You can create custom actions, which can then be reused. Let's assume that we want to create an
app.GetYear
action that retrieves the year from a DateField value:
compiler.registerComponent("app.GetYear", {
  // Extend Set, a core MSON component, that sets a
  // property on another component
  component: "Set",

  // Define the inputs to the custom action
  schema: {
    component: "Form",
    fields: [
      {
        name: "date",
        component: "DateField",
        required: true
      }
    ]
  },

  // Omitting the "name" here allows the value
  // to be passed to the next action via
  // "{{arguments}}"
  //
  // name: "",

  // Use the template query to extract the year
  // from the date
  value: {
    $year: {
      $toDate: "{{date}}"
    }
  }
});
We can then use this custom action in a chain of actions:
const form = compiler.newComponent({
  component: "Form",
  fields: ...,
  listeners: [
    {
      event: "fields.date.value",
      actions: [
        // Extract the year and pass it to the next action
        {
          component: "app.GetYear",
          date: "{{fields.date.value}}"
        },

        // Use the extracted year to set the year field
        {
          component: "Set",
          name: "fields.year.value",

          // {{arguments}} is the output of app.GetYear
          value: "{{arguments}}"
        }
      ]
    }
  ]
});
Here is the completed example:

Another example: a conditional survey question

Let's assume that we have a survey question and we want the user to be able to enter a free response when they select
Other
:
We define the form and hide the
covidOther
text field by default:
{
  component: "Form",
  fields: [
    {
      name: "covid",
      component: "SelectField",
      label: "My government's response to COVID-19 was...",
      fullWidth: true,
      options: [
        { label: "Good", value: "good" },
        { label: "Bad", value: "bad" },
        { label: "Other", value: "other" }
      ]
    },
    {
      name: "covidOther",
      component: "TextField",
      label: "Please explain",
      multiline: true,
      hidden: true
    }
  ]
}
If the user selects
Other
, we toggle the
hidden
boolean property of the
covidOther
field. The
$ne
operator is shorthand for not equal.
listeners: [
  {
    event: "fields.covid.value",
    actions: [
      {
        component: "Set",
        name: "fields.covidOther.hidden",
        value: {
          $ne: ["{{fields.covid.value}}", "other"]
        }
      }
    ]
  }
]

Why doesn't MSON support custom JS in templates?

By now, you can probably see the power of Template Queries, but you may be wondering why MSON doesn't support custom JavaScript. To support custom JS in template parameters, it would require breaking two of MSON's core design principles:
  1. Compilation by instantiation - Components are _compiled_ into JS objects by simply instantiating a JS object and setting the props dynamically. This method of _compilation_ allows us to avoid a transpilation step and makes it much easier to dynamically modify components.
  2. Serialization & deserialization without
    eval()
    - Components can be serialized using
    JSON.stringfy()
    and stored practically anywhere. Moreover, components can be deserialized by dynamically compiling the result of
    JSON.parse()
    . As a result, raw JS (in a string), including JS template literals, are not supported as deserializing such JS would require the use of
    eval()
    or
    new Function()
    , which would expose a XSS vulnerability and add significant performance issues.
You can read more about this reasoning here.

Wrap It Up!

Template Queries add a ton of capability to MSON by providing another powerful way of customizing your MSON components. And, they can be particularly useful in minimizing the code it takes to conditionally chain a series of actions.

About the Author

Geoff Cox is the creator of MSON, a new declarative programming language that will allow anyone to develop software visually. He loves taking on ambitious, yet wife-maddening projects like creating a database and distributed data syncing system.
You can read more of his posts at redgeoff.com or reach him @CoxGeoffrey or at Github.

Written by redgeoff | A coder with a passion for JS, React, GraphQL, Docker and Serverless
Published by HackerNoon on 2021/03/18