Using Substitutions to Switch Components in MontageJS
Many web applications need a way to change out a component with another. For example, imagine you wanted to make an app that had multiple pages. Normally, you would have to make each page a separate html file. This means that whenever the user wants to go to a different section of the website, they have to load a new page. With Montage, you can store a section of your app as a component, and then switch between each component without having to load a page. This is done through the prebuilt Substitution component.
Substitutions are useful for swapping out which modules are displayed in a specific space. In this tutorial we’ll cover a simple example by creating radio buttons that will be used to select different text modules to be displayed. The end result will also feature the temperature converter from the first tutorial. With substitutions, you can easily embed an existing component into another app. The end result should look something like this:
Creating the Buttons
Let’s start by creating the radio buttons. In order for the buttons to make selections we’ll need to include a radio button controller in our declaration:
"owner": {
"properties": {
"element": {"#": "main"}
}
},
"radioButtonController": {
"prototype": "montage/core/radio-button-controller"
}
The controller does not need to be added in html because it doesn’t actually exist on the page, only its buttons do.
Next we’ll add the actual radio buttons using the component provided in Montage:
1. Add three buttons to the markup. Set each button's type to radio:
<input type="radio" data-montage-id="button1">
<input type="radio" data-montage-id="button2">
<input type="radio" data-montage-id="button3">
2. Now add each button to the declaration:
"button1": {
"prototype": "digit/ui/radio-button.reel",
"properties": {
"element": {"#": "button1"},
"checked": true,
"enabled": true,
"radioButtonController": {"@": "radioButtonController"},
}
},
"button2": {
"prototype": "digit/ui/radio-button.reel",
"properties": {
"element": {"#": "button2"},
"enabled": true,
"radioButtonController": {"@": "radioButtonController"},
}
},
"button3": {
"prototype": "digit/ui/radio-button.reel",
"properties": {
"element": {"#": "button3"},
"enabled": true,
"radioButtonController": {"@": "radioButtonController"},
}
}
The checked property decides which button is checked (these are radio buttons, so only one should be selected at a time). We set each of these buttons to be enabled so they can be clicked on. Finally we set the radioButtonController property to a reference of our radio button controller that we created earlier.
By assigning radioButtonController in each of these buttons, we allow the controller to know which button is selected. Now, if you run the app, you should see three radio buttons.
Adding Labels
In most apps, you will want to have a label on your buttons so the user knows what they are clicking on. This is done by wrapping each input node in a label node, just like regular HTML:
<label>
<input type="radio" data-montage-id="button1">
Section 1
</label>
<label>
<input type="radio" data-montage-id="button2">
Section 2
</label>
<label>
<input type="radio" data-montage-id="button3">
Section 3
</label>
With these HTML labels, you should have three labeled radio buttons:
Adding the Content
We will start by adding three switchable text components under the buttons. In order to be able to switch the content, we will need to add a substitution component:
<div data-montage-id="substitution"></div>
"substitution": {
"prototype": "montage/ui/substitution.reel",
"properties": {
"element": {"#": "substitution"}
}
}
Next we need to add the content components. For now, we will make the content as text components with simple text values.
1. Add three nodes as children of the subsitution node in the markup:
<div data-montage-id="substitution">
<div data-arg="component1" data-montage-id="content1"></div>
<div data-arg="component2" data-montage-id="content2"></div>
<div data-arg="component3" data-montage-id="content3"></div>
</div>
The data-arg attribute is the id that the substitution will use to recognize its child components. This attribute can be the same or different from data-montage-id, which is used for serialization.
2. Add the content as regular text components in the declaration:
"content1": {
"prototype": "montage/ui/text.reel",
"properties": {
"element": {"#": "content1"},
"value": "This is the first sub-component."
}
},
"content2": {
"prototype": "montage/ui/text.reel",
"properties": {
"element": {"#": "content2"},
"value": "This is the second sub-component."
}
},
"content3": {
"prototype": "montage/ui/text.reel",
"properties": {
"element": {"#": "content3"},
"value": "This is the third sub-component."
}
}
At this point, the substitution will still not show any content because we never assign it a component. We can tell the substitution to show a component by assigning to its switchValue property. This property should be set equal to the data-arg of a component. We will start by manually setting switchValue to point to the first component:
"substitution": {
"prototype": "montage/ui/substitution.reel",
"properties": {
"element": {"#": "substitution"},
"switchValue": "component1"
}
}
Now if you run the project you should see the first text component:
Now the substitution gets its component value from the first component in our declaration. Next we need to make the buttons change the current component.
Linking it Together
Now that we have a substitution and three radio buttons, we need to make them interact with each other. First, we will need a way to identify which component each button points to. We will do this by adding an extra property in our buttons:
"button1": {
"prototype": "digit/ui/radio-button.reel",
"properties": {
"element": {"#": "button1"},
"checked": true,
"enabled": true,
"radioButtonController": {"@": "radioButtonController"},
"type": "component1"
}
},
"button2": {
"prototype": "digit/ui/radio-button.reel",
"properties": {
"element": {"#": "button2"},
"enabled": true,
"radioButtonController": {"@": "radioButtonController"},
"type": "component2"
}
},
"button3": {
"prototype": "digit/ui/radio-button.reel",
"properties": {
"element": {"#": "button3"},
"enabled": true,
"radioButtonController": {"@": "radioButtonController"},
"type": "component3"
}
}
By adding these lines in the serialization, we are giving each of our buttons a custom value that points to a component's data-arg. This way, if we get a reference to one of these buttons, we can get information on which component it is linked to.
Note: the type property is a custom property that we arbitrarily assigned, you can name the property whatever you like, as long as you are consistent.
Now that each button knows the data-arg of its corresponding component, we need to bind the substitution’s switchValue to the currently selected button’s type property. Radio Button Controllers have a selectedRadioButton property that we can use to get a reference to the currently selected button.
"substitution": {
"prototype": "montage/ui/substitution.reel",
"properties": {
"element": {"#": "substitution"}
},
"bindings": {
"switchValue": {"<-": "@radioButtonController.selectedRadioButton.type"}
}
},
Note: We also removed the “switchValue” property from the properties section since the binding will take care of setting the value for us.
The buttons should now switch between the different text reels as you select the radio buttons.
Beyond Text
Substitutions aren’t limited to text reels. In this section we will be changing one of our text reels into a static html page.
First we'll need to create a new Montage component for our HTML page to interact with the rest of our application. You can do this by entering "minit create:component -n static" in the console in the directory where your application is located.
Once the static component has been created, we can alter the html to display a header, paragraph, and image:
<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<title>Simple HTML page</title>
<script type="text/montage-serialization">
{
"owner": {
"properties": {
"element": {"#": "owner"}
}
}
}
</script>
</head>
<body>
<div data-montage-id="owner" class="Test">
<h1>This is a simple static HTML page</h1>
<p>Montage substitutions can be used to swap static HTML pages as well as montage components.</p>
<img src="static.jpg" alt="montage_logo">
</div>
</body>
</html>
You can select your own image here or download the montage logo Here
Now that we've created our static html page as a component in Montage, we just need to link the component to our existing application:
Change This:
"content1": {
"prototype": "montage/ui/text.reel",
"properties": {
"element": {"#": "content1"},
"value": "This is the first sub-component."
}
},
Into This:
"static": {
"prototype": "ui/static.reel",
"properties": {
"element": {"#": "static"}
}
},
The first button on your application should now display the static HTML page!
Adding Full Applications
If you’ve completed the temperature converter tutorial you can change one of your text reels into your converter reel.
There are two ways you could get another component into your project:
Method 1: Copy every component in the temperature converter's UI folder into your current project's UI folder. You will need to rename the temperature converter's main.reel so that it does not conflict with your current project's main.reel. When you do this, be sure to also rename the files inside the folder, the class name at the top of the HTML markup, and the class name in the css file.
Method 2: Since the temperature converter's main reel is very small, you can simply copy over just the converter.reel into your project's UI folder. However, the main component in the temperature converter contains some css that you will need to copy over manually into your project.
Once you have copied the temperature converter's component(s), change the prototype of one of your components in the declaration.
"content1": {
"prototype": "ui/converter.reel",
"properties": {
"element": {"#": "content1"}
}
}
If you copied over both converter.reel and main.reel (method 1), set the prototype to the name of the converter's main component. (E.g. if you renamed the converter's main reel to temperature.reel, use that as the prototype).
Your app should now show the temperature converter when the first radio button is selected:
You can use any component in this way, not just the temperature converter. For example, you could have the temperature converter on the first section, the Reddit Client on the second, and the Montage Popcorn app on the third.