Iteration¶
The last concept we will look at in the context of templating and controllers is iteration, which we will use to recreate the modules list from the first week. First we will create a route and components for the modules:
$ yarn ember generate route modules
$ yarn ember generate component modules/modules-list
$ yarn ember generate component modules/modules-navbar
You can see, that this created a folder app/components/modules
and initialized the new components inside it. Place the following content in the app/templates/modules.hbs
:
{{page-title 'Modules'}}
<main class="container">
<div class="row">
<Modules::ModulesNavbar />
<Modules::ModulesList />
</div>
</main>
The two colons indicate that the component is stored in a subfolder. You can call components in subfolders following this syntax <Subfolder::ComponentName />
. Next, insert the following code into app/components/modules/modules-navbar.hbs
:
<div class="modules-menu col-lg-3 col-md-2 bg-light">
<h1>My Modules</h1>
<nav class="navbar navbar-light" aria-label="Modules">
<ul role="menu" aria-label="Modules">
<li><a href="" tabindex="0" role="menuitem" class="active">Current Semester</a></li>
<li><a href="" tabindex="-1" role="menuitem">Last Semester</a></li>
<li role="separator"></li>
<li><a href="" tabindex="-1" role="menuitem">Enroll in Module</a></li>
</ul>
</nav>
</div>
Finally, insert the following code into app/components/modules/modules-list.hbs
:
<section class='col-lg-9 col-md-10 bg-light'>
<table>
<thead>
<tr>
<th>Code</th>
<th>Name</th>
<th>Sections</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<tr>
<td><a href=''>INF.06528.01</a></td>
<td><a href=''>Clientseitige Webanwendungen</a></td>
<td>
<nav aria-label='Sections of Clientseitige Webanwendungen'>
<ul role='menu' class='horizontal' aria-label='Sections'>
<li>
<a href='' role='menuitem' aria-label="Dates" title="Dates" class="mdi mdi-calender-clock" tabindex='0'></a>
</li>
<li>
<a href=''
role='menuitem'
aria-label="Documents"
title="Documents"
class="mdi mdi-file-document-box-multiple-outline"
tabindex='0'></a>
</li>
<li>
<a href='' role='menuitem' aria-label="Exercises" title="Exerciseses" class="mdi mdi-test-tube" tabindex='0'></a>
</li>
<li>
<a href=''
role='menuitem'
aria-label="TeilnehmerInnen"
title="TeilnehmerInnen"
class="mdi mdi-account-multiple"
tabindex="0"></a>
</li>
</ul>
</nav>
</td>
<td>
<nav aria-label='Actions for Clientseitige Webanwendungen'>
<ul role='menu' class='horizontal' aria-label='Actions'>
<li>
<a href=''
role='menuitem'
aria-label="Delete this module"
title="Delete this module"
class="mdi mdi-delete warning"
tabindex='0'></a>
</li>
</ul>
</nav>
</td>
</tr>
<tr>
<td><a href=''>INF.05370.01</a></td>
<td><a href=''>Informatik in den Geistes- und Kulturwissenschaften</a></td>
<td>
<nav aria-label='Sections of Informatik in den Geistes- und Kulturwissenschaften'>
<ul role='menu' class='horizontal' aria-label='Sections'>
<li>
<a href='' role='menuitem' aria-label="Dates" title="Dates" class="mdi mdi-calender-clock" tabindex='0'></a>
</li>
<li>
<a href=''
role='menuitem'
aria-label="Documents"
title="Documents"
class="mdi mdi-file-document-box-multiple-outline"
tabindex='0'></a>
</li>
<li>
<a href='' role='menuitem' aria-label="Exercises" title="Exerciseses" class="mdi mdi-test-tube" tabindex='0'></a>
</li>
<li>
<a href=''
role='menuitem'
aria-label="TeilnehmerInnen"
title="TeilnehmerInnen"
class="mdi mdi-account-multiple"
tabindex='0'></a>
</li>
</ul>
</nav>
</td>
<td>
<nav aria-label='Actions for Informatik in den Geistes- und Kulturwissenschaften'>
<ul role='menu' class='horizontal' aria-label='Actions'>
<li>
<a href=''
role='menuitem'
aria-label="Delete this module"
title="Delete this module"
class="mdi mdi-delete warning"
tabindex='0'></a>
</li>
</ul>
</nav>
</td>
</tr>
<tr>
<td><a href=''>INF.05370.01</a></td>
<td><a href=''>Informatik in den Geistes- und Kulturwissenschaften</a></td>
<td>
<nav aria-label='Sections of Informatik in den Geistes- und Kulturwissenschaften'>
<ul role='menu' class='horizontal' aria-label='Sections'>
<li>
<a href='' role='menuitem' aria-label="Dates" title="Dates" class="mdi mdi-calender-clock" tabindex='0'></a>
</li>
<li>
<a href=''
role='menuitem'
aria-label="Documents"
title="Documents"
class="mdi mdi-file-document-box-multiple-outline"
tabindex='0'></a>
</li>
<li>
<a href='' role='menuitem' aria-label="Exercises" title="Exerciseses" class="mdi mdi-test-tube" tabindex='0'></a>
</li>
<li>
<a href=''
role='menuitem'
aria-label="TeilnehmerInnen"
title="TeilnehmerInnen"
class="mdi mdi-account-multiple"
tabindex='0'></a>
</li>
</ul>
</nav>
</td>
<td>
<nav aria-label='Actions for Informatik in den Geistes- und Kulturwissenschaften'>
<ul role='menu' class='horizontal' aria-label='Actions'>
<li>
<a href=''
role='menuitem'
aria-label="Delete this module"
title="Delete this module"
class="mdi mdi-delete warning"
tabindex='0'></a>
</li>
</ul>
</nav>
</td>
</tr>
</tbody>
</table>
</section>
In order to generate this list of modules dynamically we will need a model. The model is defined in the route, so we modify the app/routes/modules.js
route:
import Route from '@ember/routing/route';
export default class ModulesRoute extends Route {
model() {
return [
{
code: 'INF.06528.01',
name: 'Cient-seitige Web-Anwendungen',
},
{
code: 'INF.05370.01',
name: 'Informatik in den Geistes- und Kulturwissenschaften',
},
{
code: 'INF.06450.01',
name: 'eHumanities Grundlagen',
}
]
}
}
And while we’re at it, update app/styles/app.scss
by adding styles for the table and the menu:
/*
* Modules Styles
*/
ul[role='menu'].horizontal {
list-style-type: none;
flex-direction: row;
display: flex;
li {
margin-left: 5px;
}
&[aria-label='Sections'] li{
display: flex;
}
}
.modules-menu {
max-height: 100%;
nav ul[role='menu'] {
list-style-type: none;
li[role='separator'] {
padding-bottom: $breadcrumb-margin-bottom;
}
a {
text-decoration: none;
color: $dark;
}
}
}
th {
text-align: center;
}
td {
padding-right: 15px;
a {
text-decoration: none;
color: $dark;
}
}
Template Control: {{each}}
¶
Now that we have a model, we can need to pass it to our component in order to make use of it in the template. To pass the model, update app/templates/modules.hbs
to this:
{{page-title 'Modules'}}
<main class='container'>
<div class='row'>
<Modules::ModulesNavbar />
<Modules::ModulesList @modules={{this.model}}/>
</div>
</main>
The name of the property is chosen arbitrarily, so you can name it however you please, however, since we want to deal with the modules, this name was chosen. Just remember that you need to referentiate to the name you chose. Next, update the app/components/modules/modules-list.hbs
, adding the {{#each}}
and {{/each}}
tags around the first row of the table:
<tbody>
{{#each @modules as |module|}}
<tr>
<td><a href="">INF.06528.01</a></td>
<td><a href="">Client-seitige Web-Anwendungen</a></td>
<td>
<nav aria-label="Sections of Client-seitige Web-Anwendungen">
<ul role="menu" class="horizontal" aria-label="Sections">
<li><a href="" role="menuitem" aria-label="Dates" title="Dates" class="mdi mdi-calendar-clock" tabindex="0"></a></li>
<li><a href="" role="menuitem" aria-label="Documents" title="Documents" class="mdi mdi-file-document-box-multiple-outline" tabindex="-1"></a></li>
<li><a href="" role="menuitem" aria-label="Exercises" title="Exercises" class="mdi mdi-test-tube" tabindex="-1"></a></li>
<li><a href="" role="menuitem" aria-label="Teilnehmerinnen" title="Teilnehmer_innen" class="mdi mdi-account-multiple" tabindex="-1"></a></li>
</ul>
</nav>
</td>
<td>
<nav aria-label="Actions for Client-seitige Web-Anwendungen">
<ul role="menu" class="horizontal" aria-label="Actions">
<li><a href="" role="menuitem" aria-label="Leave" title="Leave" class="mdi mdi-delete warning" tabindex="0"></a></li>
</ul>
</nav>
</td>
</tr>
{{/each}}
You access the parent element’s attribute via @attributeName
, as you can see in the each
loop, we access the model that is passed to the component as @modules
. To see that the iteration is working correctly, you can try adding a few list entries to the model
property in the app/routes/modules.js
. However, all we are doing at the moment is outputting the static data repeatedly. Thus update the app/components/modules/modules-list.hbs
to output the code
and name
properties dynamically:
<td><a href="">{{module.code}}</a></td>
<td><a href="">{{module.name}}</a></td>
When that is working, add entries to the model
property for all of the modules listed in the static <table>
and then remove those <tr>
, so that you only have the one <tr>
that is inside the {{each}}
loop. Don’t forget to change the ARIA labels to {{module.name}}
as well.
Template Control: {{each-in}}
¶
With the basic iteration working, we will now look at making the sections and actions data-driven as well, starting with the actions. Update the first entry in the model
list so that it looks like this:
{
code: 'INF.06528.01',
name: 'Cient-seitige Web-Anwendungen',
sections:{
'dates': {
title: 'Dates',
icon: 'mdi mdi-calendar-clock'
},
'documents': {
title: 'Documents',
icon: 'mdi mdi-file-document-box-multiple-outline'
},
'exercises': {
title: 'Exercises',
icon: 'mdi mdi-test-tube'
},
'students': {
title: 'TeilnehmerInnen',
icon: 'mdi mdi-account-multiple'
}
},
actions: {
'delete': {
title: 'Delete this module',
icon: 'mdi mdi-delete warning'
}
}
},
Unlike with the individual modules, for the available actions we use a dictionary instead of a list. This is because we want to use the keys to later identify the action that the user can undertake.
Now update the”section” menu in the app/components/modules/modules-list.hbs
:
<ul role='menu' class='horizontal' aria-label='Sections'>
{{#each-in module.sections as |action display|}}
<li>
<a href='' role='menuitem' aria-label={{display.title}} title={{display.title}} class={{display.icon}} tabindex='0'></a>
</li>
{{/each-in}}
</ul>
and the “actions” menu to:
<ul role='menu' class='horizontal' aria-label='Actions'>
{{#each-in module.actions as |action display|}}
<li><a href='' role='menuitem' aria-label={{display.title}} title={{display.title}} class={{display.icon}} tabindex='0'></a></li>
{{/each-in}}
</ul>
To iterate over a dictionary we use the {{each-in}}
helper. As you can see, it essentially works the same as the {{each}}
, except that it has two variables it sets on each iteration. The first, called action
here, is the key of the entry in the dictionary, while the second (display
) is the value of the entry. For the moment we just use the display
value to set the title and icon of each action.
You will see that the icon is rendered, but in black, not in the red we wanted to indicate the “warning”. Add the following to the app/styles/app.scss
:
/*
* Icon styles
*/
.mdi.warning:link, .mdi.warning:visited {
color: $danger;
}
.mdi.warning:hover, .mdi.warning:focus {
color: lighten($danger, 10%);
}
Here we see the use of another function provided by Bootstrap. While we can use a colour from the palette that is defined in node_modules/bootstrap/scss/bootstrap.scss
by calling the variable prefixed by $
, lighten
makes the colour lighter based upon the second parameter.
Now that the styling works again, copy the actions
in the model
property to all your entries. Then change one of them to have a different icon. For example, if you want to show an “exit” icon, rather than the delete icon, replace mdi-delete
with mdi-exit-run
.