Using the Repetition
Component
The Repetition
component is used to produce a repeating group of elements based on an array of values. All elements nested inside of the Repetition
element will repeat in each iteration. The content inside of a repetition is managed by a controller. You can set Repetition
’s content
property manually, with a standard array for a simple repetition, or you can expand the component’s capabilities by assigning a RangeController
component to its contentController
property.
You can use Repetition
as a building block to repeat any number of user interface components. (The MontageJS List component, for example, uses Repetition
to support selection management.) Use the bindable iteration.object
template property to point to the current list item.
A Simple Repetition
The following example shows a simple Repetition
(items
) that produces three text items. Each iteration is associated with a corresponding item in a source collection that is populated through Repetition
’s content
property.
<div data-montage-id="content" class="Content">
<ul data-montage-id="items">
<li data-montage-id="item"></li>
</ul>
</div>
{
"owner": {
"properties": {
"element": { "#": "content" }
}
},
"items": {
"prototype": "montage/ui/repetition.reel",
"properties": {
"element": { "#": "items" }
},
"bindings": {
"content": { "<-": "@owner.myListProperty" }
}
},
"item": {
"prototype": "montage/ui/text.reel",
"properties": {
"element": { "#": "item" }
},
"bindings": {
"value": { "<-": "@items:iteration.object.quote" },
"classList.has('highlight')": { "<-": "@items:iteration.object.important" }
}
}
}
.highlight {
font-weight: bold;
}
var Component = require("montage/ui/component").Component;
exports.Content = Component.specialize({
myListProperty: {
value: [{
"quote": "If music be the food of love, play on.",
"important": false
}, {
"quote": "O Romeo, Romeo! wherefore art thou Romeo?",
"important": true
}, {
"quote": "All that glitters is not gold.",
"important": false
}, {
"quote": "I am amazed and know not what to say.",
"important": false
}]
}
});
The output looks like this:
View in Mfiddle.
Using a RangeController
with Repetition
In this example:
Repetition
’scontentController
property is set to be aRangeController
for managing some content.- The
Text
component is a child ofRepetition
and derives itsvalue
property from itsiteration.object
template property. - Clicking the Change Content button replaces the managed content with new random content.
<div data-montage-id="component">
<button data-montage-id="button"></button>
<ul data-montage-id="repetition">
<li data-montage-id="value"></li>
</ul>
</div>
{
"owner": {
"properties": {
"element": {"#": "component"}
}
},
"repetition": {
"prototype": "montage/ui/repetition.reel",
"properties": {
"element": {"#": "repetition"},
"contentController": {"@": "rangeController"}
}
},
"rangeController": {
"prototype": "montage/core/range-controller"
},
"value": {
"prototype": "montage/ui/text.reel",
"properties": {
"element": {"#": "value"}
},
"bindings": {
"value": {"<-": "@repetition:iteration.object.quote"}
}
},
"changeButton": {
"prototype": "digit/ui/button.reel",
"properties": {
"element": {"#": "button"},
"label": "Change Content"
},
"listeners": [
{
"type": "action",
"listener": {"@": "owner"}
}
]
}
}
var Component = require("montage/ui/component").Component;
exports.Owner = Component.specialize({
constructor: {
value: function Owner() {
this.content = [{
"quote": "If music be the food of love, play on.",
"important": false
}, {
"quote": "O Romeo, Romeo! wherefore art thou Romeo?",
"important": true
}, {
"quote": "All that glitters is not gold.",
"important": false
}, {
"quote": "I am amazed and know not what to say.",
"important": false
}];
}
},
templateDidLoad: {
value: function () {
this.templateObjects.rangeController.content = this.content;
}
},
handleChangeButtonAction: {
value: function (evt) {
var randomContentIndex = Math.floor(Math.random() * this.content.length);
this.templateObjects.rangeController.add(this.content[randomContentIndex]);
}
}
});
The output looks like this:
View in Mfiddle.
Sorting and Filtering the Items in Repetition
To sort and filter items in a repetition you can use FRB expressions on the RangeController
.
- Adding or removing items from the source collection automatically updates the returned items.
- When the value of the
filterPath
isfalse
, the item will not be included in the repetition. - Using
sortPath
returns the items in sequential order, sorted by the value of theproperty
when the path is a single property.
You can also use complex expressions inside of sort and filter operations. For example, when Repetition
’s content has an index property that is a sequence of integers, you can filter out the odd numbers like this: !(index%2)
.
<div data-montage-id="component">
<button data-montage-id="filterButton"></button>
<button data-montage-id="sortButton"></button>
<ul data-montage-id="repetition">
<li data-montage-id="quote"></li>
</ul>
</div>
{
"owner": {
"properties": {
"element": {"#": "component"}
}
},
"repetition": {
"prototype": "montage/ui/repetition.reel",
"properties": {
"element": {"#": "repetition"},
"contentController": {"@": "rangeController"}
}
},
"rangeController": {
"prototype": "montage/core/range-controller"
},
"quote": {
"prototype": "montage/ui/text.reel",
"properties": {
"element": {"#": "quote"}
},
"bindings": {
"value": {"<-": "@repetition:iteration.object.quote"}
}
},
"filterButton": {
"prototype": "digit/ui/button.reel",
"properties": {
"element": {"#": "filterButton"},
"label": "Filter"
},
"listeners": [
{
"type": "action",
"listener": {"@": "owner"}
}
]
},
"sortButton": {
"prototype": "digit/ui/button.reel",
"properties": {
"element": {"#": "sortButton"},
"label": "Sort"
},
"listeners": [
{
"type": "action",
"listener": {"@": "owner"}
}
]
}
}
var Component = require("montage/ui/component").Component;
exports.Owner = Component.specialize({
constructor: {
value: function Owner() {
this.content = [{
"quote": "If music be the food of love, play on.",
"important": false
}, {
"quote": "O Romeo, Romeo! wherefore art thou Romeo?",
"important": true
}, {
"quote": "All that glitters is not gold.",
"important": false
}, {
"quote": "I am amazed and know not what to say.",
"important": false
}];
}
},
templateDidLoad: {
value: function () {
this.templateObjects.rangeController.content = this.content;
}
},
handleFilterButtonAction: {
value: function () {
var rangeController = this.templateObjects.rangeController;
// toggle filterPath to either filter by "important" key or not filter
rangeController.filterPath = rangeController.filterPath ? "" : "important";
}
},
handleSortButtonAction: {
value: function () {
var rangeController = this.templateObjects.rangeController;
// toggle sortPath to either filter by "quote" key or not filter
rangeController.sortPath = rangeController.sortPath ? "" : "quote" ;
}
}
});
The output looks like this:
View in Mfiddle.
RangeController
uses FRB to filter and sort under the hood. You can do the same to filter and sort in a binding, e.g.:
{
"bindings": {
"filteredEvens": {"<-": "numbers.filter{!(%2)}"},
"sorted": {"<-": "numbers.sorted{}"}
}
}
Allowing Users to Select Repetition
Items
To allow users to select an item in a repetition:
- Set the
Repetition
component’sisSelectionEnabled
property totrue
. - The
selected
CSS rule is automatically applied to the selected item.
Note that users could select multiple items.
<div data-montage-id="component">
<div data-montage-id="repetition">
<p data-montage-id="value"></p>
</div>
<select data-montage-id="select"></select>
<p data-montage-id="log"></p>
</div>
{
"owner": {
"properties": {
"element": {"#": "component"}
}
},
"repetition": {
"prototype": "montage/ui/repetition.reel",
"properties": {
"element": {"#": "repetition"},
"isSelectionEnabled": true,
"contentController": {"@": "rangeController"}
}
},
"rangeController": {
"prototype": "montage/core/range-controller",
"properties": {
"selection": []
}
},
"value": {
"prototype": "montage/ui/text.reel",
"properties": {
"element": {"#": "value"}
},
"bindings": {
"value": {"<-": "@repetition:iteration.object.quote"}
}
},
"select": {
"prototype": "digit/ui/select.reel",
"properties": {
"element": {"#": "select"},
"contentController": {"@": "rangeController"},
"labelPropertyName": "quote"
}
},
"log": {
"prototype": "montage/ui/text.reel",
"properties": {
"element": {"#": "log"}
},
"bindings": {
"value": {"<-": "@owner.message"}
}
}
}
.selected {
color:red;
}
var Component = require("montage/ui/component").Component;
exports.Owner = Component.specialize({
constructor: {
value: function Owner() {
this.content = [{
"quote": "If music be the food of love, play on.",
"important": false
}, {
"quote": "O Romeo, Romeo! wherefore art thou Romeo?",
"important": true
}, {
"quote": "All that glitters is not gold.",
"important": false
}, {
"quote": "I am amazed and know not what to say.",
"important": false
}];
}
},
templateDidLoad: {
value: function () {
this.templateObjects.rangeController.content = this.content;
//Observe the selection for changes
this.templateObjects.rangeController.addRangeAtPathChangeListener(
"selection", this, "handleSelectionChange");
}
},
handleSelectionChange: {
value: function (plus, minus) {
this.message = "Selection changed from: "
+ (minus[0] ? minus[0].quote : "nothing")
+ " -> "
+ (plus[0] ? plus[0].quote : "nothing");
}
}
});
The output looks like this:
View in Mfiddle.