How to build a Form with Scalejs and PJSON

Building a form with Scalejs and PJSON is simple once you know how it's done. Read this tutorial to learn how to do it.

You should already have an application with a Scalejs/PJSON setup. In this tutorial I will be starting from scalejs seed application which is a very basic setup for Scalejs. You can also clone the results from this tutorial from the form-example branch.

Tutorial Steps

  1. Get Common
  2. Add an Adapter
  3. Add an Input
  4. Add another Input
  5. Add a Validation
  6. Add a Rendered Expression
  7. Add a Select that gets values from JSON
  8. Add a Select that gets from a Store
  9. Add a Select that filters values from Store
  10. Add a List
  11. Post your Form Data
  12. Retrieve your Form Data

1. Get Common

If you do not already have common, you must have it in order to use the form elements.

npm install --save scalejs.metadatafactory-common

This will give you access to all of the common modules. You can import the modules in the same location where others are imported. In our sample app, that location is the modules.js file.

The seed application in this tutorial is already importing common in order to use the template module.

2. Add an Adapter

The adapter module allows for tracking of the other components which will be necessary to gather the data for a POST and use expressions.

First ensure the Adapter is being imported into your application

import 'scalejs.metadatafactory-common/dist/adapter/adapterModule';

Now in your JSON file you will add the adapter:

3. Add an Input

The input component is used to gather user data input. It supports plenty of functionality, as any input functionality (large and small) will be baked into the options of an inputViewModel.

We will start off with a very simple input. Again, ensure the Input is being imported into your application.

import 'scalejs.metadatafactory-common/dist/input/inputModule';

In addition to this, our input module relies on an autocomplete package that is currently not configured correctly. As a work around to this, we must add the following to your webpack.config resolve.alias fields.

'jquery-ui/autocomplete':  path.join(__dirname, 'node_modules/jquery-ui/ui/widgets/autocomplete.js')`

Because we made changes to webpack.config you need to restart npm for the changes to take effect.

Add the following input JSON as a child of the adapter:

Refresh your application to see the new input which will be similar to that shown above.

4. Add another Input

A form rarely has just one question so let's add another field. This time we will use the datepicker type.

Now you will see two inputs on the page. One of them is of type datepicker.

5. Add a Validation

Forms are great but one thing we need is to be able to add validations. For most of your validation needs, you can use one of the ones specified in Knockout-Validation.

In addition to this, we also have expression validations which utilize the context of the PJSON form to allow you to write conditional validations against other values which are already in the form.

6. Add Rendered Expression

All PJSON Components have the capability to use rendered expressions. A rendered expression is an expression (again, based on context) which will determine whether or not the component will be rendered.

Once an input is not rendered, it also will not be available within the data context, so if you still need adapter tracking, you'll need to set trackIfHidden to true.

For these changes I'll add an additional input that will determine whether or not we need to gather the user's birthday.

First the user must specify if they are indeed a human.

If so, they have a birthday so only then do we ask for it.

What happened here is that we added a radio input which defaults to Yes/No and underlying values of true/false.

This is also the first time we have used the ids I have been adding with each input. Every input with an id is tracked by the parent adapter. In order to leverage the values from other inputs, you use the id.

For the rendered expression for our birthday input, we are using isHuman, which evaluated to the value of the isHuman input. Which is true/false or empty. The expression will only evaluate to true if the user selects yes which will control the rendering of the birthday input.

Though isHuman may be a very simple expression, there are plenty of other ways one can write and leverage expressions. For instance, another way this could have been written is isHuman === true.

7. Add a Select that gets from JSON

Now that we have added a few inputs we might want to start doing something more complex, such as getting values for a select from a reference data endpoint.

To make it easy let's just start off by adding a select that will get from a local data source (i.e. from PJSON).

That was almost too easy. Better yet if these values were coming from a server so that we could change the options the user can select from without modifying our UI assets.

8. Add a Select that gets from a Store

In step 7 we added a select that simply gets its options from the JSON. Now we will add an extra step to get these values from a REST call.

In order to do this, I will add a simple endpoint to our mock backend to retrieve the values:

After a server restart and I have verified this data is coming in as desired, now I will add a store to our PJSON and hook the Select source into this.

A store is a PJSON Data Component that takes in a dataSourceEndpoint object and populates a key within the scalejs.noticeboard under a specified storeKey.

The first step when using a new component is to ensure it is being imported with your other modules:

import 'scalejs.metadatafactory-common/dist/store/storeModule';

Underneath the hood, the store relies on an action, more specifically, an ajax action to perform its data-service call.

To that end, we need two more imports to satisfy the prerequisites we need to use store:

import 'scalejs.metadatafactory-common/dist/action/actionModule';
import 'scalejs.metadatafactory-common/dist/action/actions/ajax';

Now that we have imported store, action, and an ajax action, and also assuming your PJSON/Scalejs Setup has a proper dataservice (such as the one included in the tutorial seed), we can use this JSON to populate our select:

We have added the store to the top of the adapter's children and also modified the values option in the select from the previous step.

One neat thing about this approach to populating dropdowns is how the fromArray field works. The fromArray field expects an expression to be passed to it. By default, all components have access to the store object in their expressions. In this case we are using a memberExpression to get the colorsSource from the store.

This unexpected usecase for our expression parsing engine allows even greater manipulation of select sources straight from JSON.

9. Add a Select that filters values from Store

So far everything we have done has been rather straight forward. But sometimes a form requires a little extra functionality. In this new use case we want to leverage the expression parsing capabilities of a select dropdown's fromArray field and get a chance to use lodash in our expressions.

In order to do this we will filter a second select based upon what was selected in the first one. Add a new REST endpoint for this new data source (requires server restart):

When the user selects "blue" they will only be able to select things which are blue.

Now we will make this JSON, adding another store and another select.

This actually does work, once we have selected "blue" we can see the options for the second dropdown are filtered.

To understand further what is going on here, let's deconstruct the expression:

_.filter(store.thingsSource, ['color', color])

_.filter is being passed 2 arguments, store.thingsSource and an array which tells lodash which property to filter on. In this case, by passing ['color', color], it will check each object in the source array's color field and make sure it matches the result of the user-input into the color select inputbox. (color is the id of the color selection field)

As you can see, having the ability to use lodash in our expressions allows us to use advanced functionality without having to create custom code.

10. Add a List

Now that we have covered basic inputs and more, one more form feature we would like to show before we move into Persisting our form data is to have a list entry. That means you want to gather multiple answers from the user that they enter in a list format. You have two options for list entry, list and listAdvanced. We will cover these more indepth in our APIs and other docs, with the main difference being that listAdvanced gives you a tabular view of your list as well as the ability to populate additional headers and footers.

For our simple tutorial we will only use a list. A List takes in an array of items which are other PJSON components, namely, inputs.

Again we will start off by importing the module we desire:

import 'scalejs.metadatafactory-common/dist/list/listModule';

In our JSON we will add 2 components - a textLabel component to have a label without the input, and a list component. Within the List's items array, we will specify an input and for aesthetic purposes we will hide the label.

After refreshing you will see an "Add" button appear which allows you to add items to the list.

11. POST your form data

Now that we have created a beautiful(ly functional) form, we will want to try to POST the data and persist it on our backend.

For this we will want to add two things: an ajax save action and a validations component, so that we cannot save until our form is valid.

We already imported the action and the ajax action when we used a store, so we only need to import the validations component:

import 'scalejs.metadatafactory-common/dist/validations/validationsModule';

Once imported you can add the validations component as a child to the adapter. Also, we will want to add an id to the adapter so that we can leverage the validations component, which relies upon the id of the adapter.

At the end of our form we will add an action with an actionType of ajax and tell it to POST to a REST service (which will be implemented in the next step).

Our full JSON now looks like this:

Now we can fill out our form and press the save button. We are able to verify the data is being sent with the network tab.

12. Retrieve your Form Data

To retrieve the form data, first let us add some endpoints to our backend to support persisting the data. Since we are not overly fancy here there is just a single endpoint to take the data and store it in a variable, and another endpoint to retrieve it.

After a server restart, the POST call from before should persist the data, which you can double-check by hitting the form GET endpoint.

Now we just need to make sure the Form is populated with this data by specifying the GET request for the form within the adapter.

Although this populates the adapter's context with the data from the GET request, in order for the inputs within the form to have their values set we need to import and include another component, the setValue component.

The reason we created a seperate setValue component is to keep the setting of the data seperate from the tracking and population of adapter context.

Like other modules you need to import it:

import 'scalejs.metadatafactory-common/dist/setValue/setValueModule';

And also add it to your adapter's children, as well as a dataSourceEndpoint to tell our adapter where to GET the data from.