Real HTML Components
Create components in plain HTML. No special language, No JavaScript. No rendering code. No complexity.
Add your component to any HTML page using regular HTML tags, like my-component.
Create components as separate .js files. Add components to apps using simple script tags. No complex dependencies. No complex build systems. Simple.
Separate Interface & Data
Separate data from interface components. No longer have data trapped in UI widgets.
Data is kept in a single, shared data area. A single copy shared by all app components. Interface components present & interact with application data, not own it.
As data changes, all interface components across the app automatically respond to changes.
Regular JavaScript & REST Logic
Write logic in regular JavaScript & REST calls.
Logic changes shared data. All UI components automatically change in response. No more writing code to tie interface updates to logic & service calls. Execute logic, merge results into the shared data, done.
Conveniently define regular JavaScript functions & REST calls inside component .js files. Incorporate libraries into components using regular script or import tags in your components.
Anatomy of a Component
Let's look at a Tic-Tac-Toe game. The game board is a reusable HTML component element named game-board that we can add to any page simply by adding the game-board tag to the HTML page (just like we've added it here!).
Go ahead and click a box to play!
(or click here to see a more complete version comparable to other tutorials)
Leap components are HTML template elements composed of 1) Styles, 2) HTML, 3) Start-Up JavaScript & 4) JavaScript/REST Logic. Components can be defined directly in an HTML page, or can be defined in separate a component .js file. Here is our Tic-Tac-Toe board component file board.js
leap.component(` <template id='game-board'> <style> button { float: left; width: 3rem; height: 3rem; margin: 0em; border: 1px solid black; text-align: center; font-size: 2rem; background-color:white; } </style> <div> <button data-field='game[0][0]'>{{game[0][0]}}</button> <button data-field='game[0][1]'>{{game[0][1]}}</button> <button data-field='game[0][2]'>{{game[0][2]}}</button> <button data-field='game[1][0]' style='clear:both;'>{{game[1][0]}}</button> <button data-field='game[1][1]'>{{game[1][1]}}</button> <button data-field='game[1][2]'>{{game[1][2]}}</button> <button data-field='game[2][0]' style='clear:both;'>{{game[2][0]}}<button> <button data-field='game[2][1]'>{{game[2][1]}}</button> <button data-field='game[2][2]'>{{game[2][2]}}</button> </div> <script> leap.set( 'players', [ 'X', 'O' ] ); leap.set( 'plays', 0 ); leap.addDataListener( 'game', 'change', checkForWin ); leap.addComponentEventListener( 'button', 'click', setPlayer ); </script> </template>`); function setPlayer(e) { if( ( typeof e == 'undefined' || e.target.value == '' ) && leap.get('winner') == '' ) { let players = leap.get('players'); let numPlays = leap.get('plays'); leap.setElementData( e.target, players[numPlays % players.length] ); leap.set( 'plays', ++numPlays); } } function checkForWin() { let game = leap.get('game'); let winner; for( let i=0; i < 3; i++ ) { winner = ( game[i][0] == game[i][1] && game[i][1] == game[i][2] ) ? game[i][0] : ( ( game[0][i] == game[1][i] && game[1][i] == game[2][i] ) ? game[0][i] : null ); if( winner ) break; } winner = winner ? winner : ( game[0][0] == game[1][1] && game[1][1] == game[2][2] ) ? game[0][0] : ( ( game[0][2] == game[1][1] && game[1][1] == game[2][0] ) ? game[0][2] : '' ); leap.set('winner', winner ); if(winner) alert( winner + ' WINS' ); }
1) Styles
For the game board component, we've define a style to make the game buttons square with boarders. The style element of the copmonent's template. Each component's style is independent of the page using the component & will not interfere with the styles of the page.
2) HTML
The HTML for a component is added directly inside the template. The Tic-Tac-Toe board uses button elements the user can click on. Each button is associated with a box in a 2-dimensional game data object which holds the state of the board.
<button data-field='game[0][0]'>{{game[0][0]}}</button>
The current value of each box of the game array is displayed in each button's text by using the {{game[0][1]}} reference to the data. As player click a box, the game data is automatically changed & the displayed text is automatically updated.
By simply setting a name for a data value in the data-field attribute, Leap automatically creates the shared data field for you.
3) Start-Up JavaScript
For each component that is added to a page, the script defined inside the template is run. This allows the component to perform any necessary initialization.
Our Tic-Tac-Toe component first defines two data elements: players which holds the list of players for the game & plays which will hold the number of plays that have been made:
leap.set( 'players', [ 'X', 'O' ] ); leap.set( 'plays', 0 );
Next, listeners are added to take action when things change. Leap follows Model Oriented Design (MOD) principles where all application data is stored in data models (not in UI widgets or components). UI widgets & components automatically use & reflect the current state of the data models. In fact, if you right click on this page to Inspect it, then in the Console enter:
leap.append( 'players', 'A' )
you will notice that the Tic-Tac-Toe board now has three players!
In MOD, actions are most often taken in response to changes in the data models, not directly to changes in the UI widgets or components. This means that ALL changes from anywhere in the app (widgets, functions, REST service responses, etc.) can be handled at a single point. As such, event listeners & handlers are attached to data models.
In our Tic-Tac-Toe board, we've added an event listener for all changes that take place in the game data model (which will call our function to check for a winner).
leap.addDataListener( 'game', 'change', checkForWin );
Note that we can still attach event listeners & handlers to UI widgets & component elements, just like we've done for our button elements where we capture a click event on the button to call our function to set the player for a box:
leap.addComponentEventListener( 'button', 'click', setPlayer );
4) JavaScript & REST Logic
The end of the component definition includes any JavaScript functions that provide logic to the component. This logic can be local (provided directly inside the functions) or remote (provided through REST/AJAX calls).
As the logic is executed, results are populated directly into the data models, which in turn automatically causes the UI widgets & components to be updated with the latest. We see this in our Tic-Tac-Toe setPlayer function where we increment the value of the plays data model and in the checkForWin function where the winner data model is set if a player has won.
leap.set( 'plays', ++numPlays); leap.set('winner', winner ? winner : ( game[0][0] == game[1][1] && game[1][1] == game[2][2] ) ? game[0][0] : ( ( game[0][2] == game[1][1] && game[1][1] == game[2][0] ) ? game[0][2] : '' ) );
Note that we could have just as easily called a remove REST call that checked for a winner, passing the game data model, then populating the winner data model with the returned result.
Application HTML Page
The entire application page using the component only needs to 1) Include the Leap capability, 2) Add the game board component script file, 3) Start Leap & 4) Put the game-board on the page:
<html> <head> <script src='http://leapjs.org/latest/leap.min.js'></script> <script src='board.js'></script> <script> leap.start(); </script> </head> <body> <game-board></game-board> </body> </html>