Nerd For Tech
Published in

Nerd For Tech

Introducing Odom: Creating a Reactive Signup Page

Designed by Mishieck Mwale.

Introduction

When developing a signup page, there are a couple of things to consider. On the design sign side, the user must easily figure out what information to enter in each field. If the information must be in a particular format, it is best to give the user a hint on what they are expected to do. On the form handling side, validation ensures that the form is not sent to the server with invalid information. The user can be notified of the validity of the information either while entering or upon submission. When reporting errors, the more specific the error, the earlier the user is going to figure out what went wrong.

Achieving all this involves a lot of work. We can make the implementation process easier by using Odom. Odom is a JavaScript framework for building user interfaces. This is the second part of the series Introducing Odom. In the first episode, The Open UI Framework, the basic features of Odom were looked at. In this episode, we are going to see Odom in action by creating a Single Page Application (SPA). The application will contain a signup section. The signup section will contain a form for signing up. The picture below shows a snapshot of the application (on wide screens).

Snapshot of the application. Captured by Mishieck Mwale.

We are going to focus on the following topics:

The repo for the app is published on GitHub. The live demo is published on Netlify. You can clone or download the repo if you want. However, in this article, I am going to include all the code and give instructions on how to build the project.

File Structure

We are going to use the following file structure for the application:

.
└── odom-signup/
├── src/
│ ├── assets/
│ │ └── index.css
│ ├── components/
│ │ ├── alert-message.js
│ │ ├── app.js
│ │ ├── confirmation-message.js
│ │ ├── form.js
│ │ ├── input-group.js
│ │ └── signup.js
│ └── index.js
└── index.html

Create the file system as shown above using empty files. We will populate all the files later.

Creating Components

For the sake of simplicity, we are going to use a CDN to create the app. Destructuring will be used to get methods from the window object window.odom. This will make it easy for you to convert the project if you would rather work with package managers like NPM. The following code demonstrates how you can replace the destructuring assignments with import statements.

We will refer to all the exports in the modules in /odom-signup/src/components as components because they are self-contained entities. However, some of the modules will be exporting functions that return DOM nodes instead of an instance of the component offered by Odom. The reasons for doing so were given in the first episode.

I will split some of the modules into multiple code snippets to make the code more readable.

App

The root component of our application will be App. This is the component we are going to render to the DOM. We will create the component in the module at/odom-signup/src/components/app.js. Paste the following code into the file:

In the module, the component Signup is imported. The method createComponent is obtained from odom. The markup contains two elements. The root element is a div and the inner element has an attribute odom-node set to Signup, which refers to the component we have imported. Replace the comment (/* styles */) in the template literal for the variable styles with the following CSS code:

The selector :scope selects the root element of the component. In :scope , we declare CSS variables for the colour theme and spacers (used for margin, padding and more). The rest of the CSS sets the width, minimum height, font family and the text colour of the component.

Let us look at what each one of the functions in the module does.

  • App: The constructor for the component. In Odom, a constructor is a function that returns an instance of a component, DOM node, markup or text. When invoked, App will return an instance of Component, which is also the return value of createComponent. Capitalising the name of a constructor is not mandatory. We will use this as a convention. We will also begin the names of the instance of the components with lowercase letters. You can not use the new keyword to invoke Odom constructors as you would JavaScript native constructors.
  • createComponent: Creates the component. The parameter is an object with three properties. The markup will be used to create the DOM element for the component. The styles will be used to style the component. The object utils has one property, nodes, which is used to provide node assets. So, the value Signup, for the attribute odom-node in the markup refers to utils.nodes.Signup.

Signup

Signup is the only section of App. It will handle the form submission and reporting of failure or success of the submission. Put the following code in the module at /odom-signup/src/components/signup.js:

In the module, we import the components Form and ConfirmationMessage. We also get the methods createComponent and replaceNode from odom. The value form of the attribute odom-src in the markup refers to an instance of Form. The value form is not capitalised because it refers to an instance of a component and not the component itself. The media query in styles sets a padding on top of the component on windows with widths that are at least 576px. Notice that we used the variable --spacer-xl which was declared in App. All components in Odom inherit styles from their ancestors.

Let us look at what each function in the module does:

  • Signup: The constructor for the node. Inside the function, we create a component, use it and return a DOM node signup.scope. We return a node instead of a component because the component interface is not need anymore.
  • onvalid: Sends the form if all inputs are valid. If the form has been successfully sent, it shows a confirmation message. The method replaceNode is used to replace the form with a confirmationMessage. If sending of the form fails, form.onSignupFail is invoked to inform the user that they have not been signed up because of an error.
  • createComponent: Used to create the component. The property components of the parameter provides the component assets referenced in the markup.
  • sendForm: Sends the form to the server. The function simulates the process of sending to the server. It returns early with a promise that resolves to a mock-up of the response object. Removing the return statement leaves with code you can use to send the form to an actual server.

Notice that we passed onvalid into Form as a prop. In the next section, we will see how onvalid is used in Form.

Form

Form initiates form submission. It also reports errors from input validation and form submission. Put the following code into the module at /odom-signup/src/components/form.js:

In the module, we are importing the components AlertMessage and InputGroup. Replace the comment in the value for markup with the following HTML code:

The values for the attribute odom-src in section elements refer to the instances of components.

Replace the comment in the value for styles with the following CSS code:

The selector > *:not(footer) is equivalent to :scope > *:not(footer). The media query sets a border and a fixed width on the component on windows that are at least 576px wide.

Replace the empty object for props with the following code:

The object props contains the props for all the instances of InputGroup. The following are the purposes for each one of the properties for the props of each instance:

  • type: The type of the input element.
  • name: The name of the input element set via the attribute name.
  • label: The text content for the label of each input group.
  • pattern: The pattern used to validate the input values.
  • instruction: An instruction on the format of information expected from the user.
  • errorMessage: The message displayed if the input is invalid.
  • successMessage: The message displayed if the input is valid.

Let us look at the purposes of the functions used in the module:

  • Form: The constructor for the component.
  • onsubmit: A listener for the event submit. It checks if all inputs are valid. If inputs are valid, it initiates the sending process by calling onvalid, the prop from Signup. It also disables the submit button so that the user does not click on it while the sending is still in progress. It removes the attribute name from the element for confirming password so that the value of the element is not sent with the form to the server. If any of the inputs is invalid, it shows an error message by invoking onerror.
  • onerror: Displays an error message by calling alertMessage.show.
  • onSignupFail: It displays an error message if there is a signup failure. It also enables the submit button to enable the user to try again.
  • password.addObserver: Used to add an observer of the input value changes in the component InputGroup. Since password is an instance of InputGroup, the method is used to observe the password changes. Notice that the pattern (in props) for confirmPassword is the same as that of password. We want the user to confirm their password by re-entering it. The observer is used to update the pattern for confirmPassword so that it matches the password. It also sets the validity of confirmPassword to false to prevent the user from submitting an invalid confirmation password if they edit the password after entering the confirmation password.
  • createComponent: Used to create the component. The property props specifies the props of the component. Props specified this way can be referenced in markup and end up on the interface of the component. In this case, the method onSignupFail is used by Signup via the interface. The property eventListeners of the parameter maps the CSS selector :scope to an array containing an event object. The selector is used to select elements we want to apply event listeners to, which in this case is the root element of the component. The array can contain as many event objects as we want. The property type specifies the type of event we want to listen to. The property listener specifies the listener for the event.

AlertMessage

The component AlertMessage handles the display of error messages in Form. Put the following code into the module at /odom-signup/src/components/alert-message.js:

The markup contains a root element and its only child. The class hide is set in order to hide the alert message by default. The selector :scope.hide in styles selects the root element when it is has the class hide. The functions have the following purposes:

  • AlertMessage: The constructor for the component.
  • show: Show the alert message.
  • createComponent: Creates the component. The property props in the parameter is used to add show to the component interface.

InputGroup

InputGroup gets user input and performs validation. Put the code below in the module at /odom-signup/src/components/input-group.js:

The markup contains a section as the root element. The label indicates what the user is supposed to enter. The value @props.inputID refers to the value set in props, a property of the parameter to createComponent (more on this later). The attribute odom-text of the span inside the label indicates that the element will be replaced by text. Notice that there are no attributes on the input element. We will set the attributes later. This is because the input element has several attributes and setting them directly in the markup would make the markup hard to read.

Replace the comment in the value for styles with the following CSS code:

Let us look at what each function in the module does:

  • InputGroup: The constructor of the component.
  • updateValue: Used to update the value of the input in the component. The value is updated every time the user changes the value in the input field.
  • updateValidity: Updates the validity of the input. The update is performed every time the user changes the input.
  • updateMessage: Updates the validation message. If the input is invalid, the message will be coloured green. Otherwise, the message will be coloured red.
  • hasValidValue: Checks whether or not the input is valid. It also triggers the border colour update by invoking updateInputBorder.
  • updateInputBorder: Updates the border of the input field. If the input is invalid, the border will coloured red. Otherwise, it will be coloured grey, the default colour.
  • props.addObserver: Adds an observer for the input value. Every time the value changes, the observer is invoked with the new value.
  • props.setValidity: Sets the validity of the input value. This is used on the interface of the component.
  • createComponent: Creates the component. The functions takes options as its parameter.

The property props of the parameter options is used to set properties on the interface and provide values that can be referenced in markup. In the markup, the value @props.inputID refers to props.inputID. The property attributes is used to set attributes on the input element. The CSS selector input selects the input filed. The object set to input contains key-value pairs of the attribute names and values of the input element.

The property utils.data contains data that can be referenced in markup. The property utils.data.dynamic contains the data that may change during the execution of the app program. The data can be bound to the DOM, so that the changes made to the DOM are reflected in the component data and vise versa. So, the value ::@data.value refers to utils.data.dynamic.value.data. The two colons indicate that there will be a double bind between the DOM updates and the component data. The array utils.data.dynamic.value.updaters specifies the functions that will be responding to the updates. The property utils.data.dynamic.valid follows the same pattern, but without the DOM binding.

The property utils.texts specifies the text asset referenced in the markup by the attribute odom-text.

All the dynamic data specified in data end up in inputGroup.dynamicData. To update a value like data.dynamic.valid.data, you set the value directly on inputGroup.dynamicData. For example, setValidity sets the value directly using the asignment inputGroup.dynamicData.valid = valid. Doing so invokes updateValidity. The function updateValidity gets the new value as a parameter and returns a Boolean value after updating the message. Notice that the method updateValue sets inputGroup.dynamicData.valid to its parameter value, which is a string. The function updateValidity intercepts the value, checks if it is valid and returns a boolean. If the value is not a string, it is assumed to be a Boolean, thus it is return without modification. The returned value is what inputGroup.dynamicData.valid will be set to.

ConfirmationMessage

Confirmation of successful signup will be done by ConfirmationMessage. Put the following code in the module at /odom-signup/src/components/confirmation-message.js:

Replace the comment in the value for styles with the following code:

The media query sets a border on the component on windows with widths that are at least 576px. The function ConfirmationMessage is the constructor. It returns a promise that resolves to a DOM node.

Rendering

In this section, we are going to render the component App to the DOM. Put the following code in the module at /odom-signup/src/index.js.

In the module, App is imported. In the IIFE, an instance (app) of the component is created. The method app.render renders the component to the DOM. The parameter of the method is a CSS selector for the element that is to be replaced by App in the DOM.

In the file at /odom-signup/index.html, put the following code:

The link element in the head element adds styles to the page. In the file at /odom-signup/assets/index.css, put the following CSS code:

The styles are used to reset some of the default styles. The value for the box-sizing is reset to border-box for all elements. The margin and padding of the body element are reset to 0.

The element div#app is what is going to be replaced by the component App. The first script element includes Odom to the page via a CDN. The second script element adds the main JavaScript file we created earlier to the page.

Save the file. Make sure that all the files created are saved. Open the HTML file (/odom-signup/index.html) in the browser. You should be able to see the page with the form on it. If anything does not seem right, you can check if you did not follow any step properly, or you can use the files in the repo.

Enter valid values in all fields and submit the form. You should be able to see the confirmation message. Refresh the page and enter an invalid value in any fields and submit the form. You should be able to see an error message just below the heading of the form. Refresh the page and submit the form without entering any value in any field. You should be able to see an error message displayed as before.

Optimisation

Styles

If you inspect the page elements using the development tools in your browser, you should see 8 style elements in the head element. The style elements are for the components App, Signup, Form, Alert and InputGroup (4). All 4 instances of InputGroup use the same styles. This makes 3 of the style elements redundant. We can get rid of the redundancy by telling Odom that the 4 instances of InputGroup come from the same component. We can do so by setting an ID on InputGroup. In the module for InputGroup (/odom-signup/src/components/input-group.js) set a property options.id to odom-"input-group" or any String value you like. The code below illustrates how to do so.

Save the file, refresh the page and inspect the elements again. You should see only 5 style elements this time. Odom was able to prevent the redundancy by not processing and adding styles for all instances after the first one. All instances used the same styles.

DOM Node Nesting

In Odom, avoiding nesting of DOM nodes whenever possible is very important for optimisation. All child components of a given component are build at once. This means that the amount of time it takes to build all child components is approximately equal to the amount of time it takes to build the component that takes longest to build.

Let’s assume that it takes 100 milliseconds to build AlertMessage, and 200 milliseconds to build InputGroup. It would take approximately 200 milliseconds to build all the child components of Form. If we decided to put all the InputGroup elements into a single element like a section element, the InputGroup instances would be built first. The AlertMessage instance would be built after that. This means that the amount of time it would take to build all the components would be approximately equal to 300 milliseconds (100 + 200). We have already performed this kind of optimisation by making all components are direct descendants of the root element of Form.

Conclusion

Whether it’s adding OAuth, splitting the signup process into multiple steps or anything else, I’d like to see how you can customise the application. If you find time to do so, please share your project and experience in the comment section. Thank you for reading.

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store