"Hello Leap"
Leap is a Model Oriented Design (MOD) framework for JavaScript.
This tutorial demonstrates using Leap to create a simple Hello World MOD single page app.
Model Oriented Design (MOD) Development
Model Oriented Design (MOD - pronounced mode) uses task-specific data models to store & manipulate information related to performing a task. Operators on the data (such as UI's or back-end services) manipulate the shared data models, thus maintaining a coordinated, single view of the state of the task. For example, if we have a UI to enter or edit a person's name, the model could contain a person object with a name property.
When UI components receive user input, the model is changed (not just the UI widget) thus reflecting the change throughout the entire application. This means that changes are automatically reflected on all application components related to the data, including both UI components and back-end services, such as REST calls.
Using models to hold task-related data has several compelling advantages:
- Data is no longer 'trapped' in UI components, such as input; fields
- A single copy of data is shared throughout the entire application
- The DOM simply reflects the state of the world, no longer containing the data.
- Actions can be taken on data changes whenever & however a task's data model changes
Leap MOD
Leap implements MOD using standard HTML & JavaScript, providing an automated model data-store. Using standard HTML & JavaScript means:
- No need to learn a custom framework
- Use of existing code as much or as little as you'd like
- No lock-in to a single framework
Getting Started with Leap
To get started, create a new file hello.html and include the leap.min.js library in your HTML file and start leap from a script tag:
<!DOCTYPE html> <html> <head> <script src="http://leapjs.org/latest/leap.min.js"></script> <script> leap.start(); </script> </head> </html>
leap.start() runs only when the page is fully loaded. It accepts an optional function as a parameter which can be used to execute page start-up JavaScript code that is to be run only after the page is fully loaded.
Creating a Model
Creating a model in Leap is simple - just annotate any widget with a data model path. Leap does the rest, automatically creating the model and associating the widget with it. Additionally, Leap automatically watches the widget for changes and updates the model so you don't have to do any additional work to keep things in sync.
<input type="text" data-field="person.name" value="{{person.name}}"/>
The data-field attribute links the input to the person model's name property. Therefore, a user changing the value of the input automatically changes the model.
Open the hello.html file in a browser. You will see the input field on the page. You can observe the linking of the model to the UI by typing a value in the input box, hitting return, then opening up the web browser's console & displaying the value of the person.name model property by entering:
You will see the value you have typed in the input box.
Referencing Model Data
The data model can be referenced throughout an HTML file, in text or in element attributes, using a data model expression that encloses a data model path inside {{ }}. A data model expression is replaced by the current data model value and is kept in sync as the model changes.
The Hello Leap input element's value attribute references the data model using the {{person.name}} expression. Whenever the model changes, the expression is automatically refreshed, displaying the latest model value.
Data model expressions can appear anywhere in the HTML. For example, a div can be added to display the current value of the data model:
<div>Hello {{person.name}}</div>
Whenever the data model is changed, the div will be changed to reflect the new value.
Changing Model Data
Whenever the input value changes, the person model's name property changes accordingly. Likewise, if you set the model's value in the web browser's console, the model will change along with all related references, such as the input and div elements:
Validation
In MOD, data validation is performed on the model not on the widgets. Validation at the model level ensures that ALL actors on the data are validated and ensured to be correct. This includes data modifications from the UI as well as any back-end services providing data in the form of a response. As such, data validators are added to a data model directly, such as:
leap.addValidator('person.name',val=>(val && val.length > 1 ? "" : "name must have at least two characters"));
Errors produced by validators are held alongside the data in the model and are passed to any error handlers that are listening for errors in the model data.
Error Handling
When a data model encounters errors, the errors are sent to error handlers that have been attached to the data model. For example, to catch errors in the person data model, add an error listener:
leap.addErrorListener('person', (model,errors)=>alert(errors.reduce((p,c)=>p+c+" ","")));
A listener can be added to any node in the model. Here the listener has been added to the person node, meaning that the handler will be executed for any errors that occur in the person node or any of its offspring. As such, errors in the person.name property will cause the error handler to be run. To process only errors for the person.name property, the listener would instead be added directly to the person.name path:
leap.addErrorListener('person.name', (model,errors)=>alert(errors.reduce((p,c)=>p+c+" ","")));
Data Listeners
Data listeners, like error listeners, are added to a data model to capture changes in the model. For example to catch changes in the person data model:
leap.addDataListener('person', 'change', (obj,prop,before,after)=>alert(prop+" changed from "+before+" to "+after));
Several events are fired for data model changes, including read, add, change, remove, error, success & final (as well as related before & after events for add, change & remove events).
Code
The Full Code
<!DOCTYPE html> <html> <head> <script src="http://leapjs.org/latest/leap.min.js"></script> <script> leap.start(()=>{ leap.addValidator('person.name', val=>(val && val.length > 1 ? "" : "name must have at least two characters")); leap.addErrorListener('name', (model,errors)=>alert(errors.reduce((p,c)=>p+c+" ",""))); leap.addDataListener('name', 'change', (model,obj,prop,before,after)=>alert(prop+" changed from "+before+" to "+after)); }); </script> </head> <body> <input type="text" data-field="person.name" value="{{person.name}}"/> <button>SAY HI</button> <div>Hello {{person.name}}</div> </body> </html>
Simplified JavaScript Code
For those less familiar with JavaScript, below is a less condensed version of the code:
<!DOCTYPE html> <html> <head> <script src="http://leapjs.org/latest/leap.min.js"></script> <script> function nameValidator(value) { if( !value || value.length <= 1 ) { return "name must have at least two characters"; } return ""; } function errorHandler( model, errors ) { let message = ""; if( errors ) { for( const error of errors ) { message += ( message != "" ? ', ' : '' ) + error; } } alert( message ); } function changeHandler( model, obj, prop, beforeValue, afterValue ) { alert( prop + " changed from " + beforeValue + " to " + afterValue ); } leap.start( function(){ leap.addValidator('person.name', nameValidator ); leap.addErrorListener('person', errorHandler ); leap.addDataListener('person', 'change', changeHandler ); }); </script> </head> <body> <input type="text" data-field="person.name" value="{{person.name}}" /> <button>SAY HI</button> <div>Hello {{person.name}}</div> </body> </html>