On top of the Category Links model that was discussed in Chapter 4, Modeling Web Applications with Model Concepts and Dartlero (the model is contained in the lib
folder), we will now build a typical data maintenance screen to present and change link data using the web components for displaying, adding, editing, removing, and saving data, as shown in the following screenshot:
In the final spiral, there are three web components per concept of the model: table
(for a list), add
(to add an element to the list), edit
(to edit an element of the list); they can be found in the webcomponent
folder. In the Spiral s01
section, only the Category
entity is defined as ConceptEntity
together with its categories collection. The testcategories_entities_test.dart
script applies unittest
on this model. In spiral s01, we build a component for the Category
entity: this is the first step toward the upper part of the preceding screenshot. In this spiral, we only show a table with the category data, using a <category-table>
web component, as shown in the following screenshot:
The polymer_category_links.html
page exposes a <polymer-app>
web component defined in the polymer_app.html
file:
<link rel="import" href="view/component/category/category_table.html"> (1) <polymer-element name="polymer-app"> <template> <category-table categories="{{categories}}"> </category-table> (2) </template> <script type="application/dart" src="polymer_app.dart"></script> </polymer-element>
This contains the <category-table>
component in line (2)
that shows the data from the categories
collection, which is made and filled with data in line (4)
in polymer_app.dart
:
import 'package:polymer_category_links/category_links.dart'; (3) import 'package:polymer/polymer.dart'; @CustomTag('polymer-app') class PolymerApp extends PolymerElement { @observable Categories categories; PolymerApp.created() : super.created() { var categoryLinksModel = new CategoryLinksModel(); categoryLinksModel.init(); (4) categories = categoryLinksModel.categories; } }
Line (3)
imports the model. The categories
variable in the <category-table>
component gets its value from the {{ categories }}
polymer expression, where the categories
object in the expression is the property of the <polymer-app>
.
In this way, data from the application is passed to the web component. The <category-table>
component is imported through line (1)
and is defined in the category_table.html
file:
<polymer-element name="category-table"> <template> // <style> markup omitted <table> <caption class="marker"> Categories </caption> <tr> <th>Name</th> <th>Description</th> </tr> <tbody template repeat="{{category in categories.toList()}}"> (5) <tr> <td>{{category.code}}</td> <td>{{category.description}}</td> </tr> </tbody> </table> </template> <script type="application/dart" src="category_table.dart"></script> </polymer-element>
The repeat-template
in the <tbody>
tag in line (5)
iterates through List
to show the categories declared in the category_table.dart
file:
import 'package:polymer_category_links/category_links.dart'; import 'package:polymer/polymer.dart'; @CustomTag('category-table') class CategoryTable extends PolymerElement { @published Categories categories; CategoryTable.created() : super.created(); }
In spiral s02, the application is enriched with the add functionality, as shown in the following screenshot. Now, we can add new categories.
This is achieved through a <category-add>
component that is embedded in the <category-table>
component. It is only shown when showAdd
is true
:
<template if="{{showAdd}}"> <category-add categories="{{categories}}"> </category-add> </template>
In the Categories caption (in the table header), a button is added to toggle the appearance of the add
component:
<button id="show-add" on-click="{{show}}">Show Add</button>
The button is monitored by the showAdd
Boolean variable marked as observable
in the script:
@observable bool showAdd = false; show(Event e, var detail, Node target) { ButtonElement addCategory = shadowRoot.querySelector("#show-add"); if (addCategory.text == 'Show Add') { showAdd = true; addCategory.text = 'Hide Add'; } else { showAdd = false; addCategory.text = 'Show Add'; } }
Now, the categories data can change, so we must mark it as observable
. This is done in polymer_app.dart
through:
categories.internalList = toObservable(categories.internalList);
This internalList
is a property of the ConceptEntities
class in the Dartlero model and is inherited by the Categories
class. It is now shown in category-table
through the following code:
<tbody template repeat="{{category in categories.internalList}}"> <tr> <td>{{category.code}}</td> <td>{{category.description}}</td> </tr> </tbody>
Our add
component defined in the category_add.html
component contains two input text fields and an Add button with an add
event handler that checks whether a name is given and the category is not yet in use. The code property is inherited from ConceptEntity
in Dartlero and used as a category name; the inherited add
method checks that the code is unique. Here is the code from category_add.dart
:
add(Event e, var detail, Node target) { InputElement code = shadowRoot.querySelector("#code"); InputElement description = shadowRoot.querySelector("#description"); Element message = shadowRoot.querySelector("#message"); var error = false; message.text = ''; if (code.value.trim() == '') { message.text = 'category name is mandatory;${message.text}'; error = true; } if (!error) { var category = new Category(); category.code = code.value; category.description = description.value; if (categories.add(category)) { message.text = 'added'; categories.order(); } else { message.text = 'category name already in use'; } } }
Spiral s03 adds an Edit functionality analogous to Add. This is achieved by a second embedded <category-edit>
web component, again shown through a conditional template:
<template if="{{showEdit}}"> <category-edit categories="{{categories}}" category="{{category}}"> </category-edit> </template>
Here, the categories
and category
properties of the category
table component are passed to the <category edit>
component by using the {{categories}}
and {{category }}
expressions. We will also add a new button in the table row:
<td><button on-click="{{edit}}" category-code={{category.code}}>Edit</button></td>
This button has the following event handler:
edit(Event e, var detail, Element target) {
String code = target.attributes['category-code']; (1)
category = categories.find(code);
showEdit = true;
}
Notice how we get the category code as the value of an attribute in line (1)
. The edit
component is defined in componentcategory_edit.html
. It is nearly identical to the add
component, but the Name field is read-only. The following is a snippet of the HTML code:
<input readonly="true" value="{{category.code}}"/> <br/> <input id="{{category.code}}-description" type="text" size="96" value="{{description}}"/> (2)
To be able to change the description, we will have to use a description
variable in line (2)
that is marked as @published
and set to the category description in the attached
method in category_edit.dart
in line (3)
:
@published String description; CategoryEdit.created() : super.created(); attached() { super.attached(); description = category.description; (3) }
The Update button calls the corresponding method in the same script:
update(Event e, var detail, Node target) { category.description = description; categories.order(); (4) var polymerApp = querySelector('#polymer-app'), var categoryTable = polymerApp.shadowRoot.querySelector('#category- table'), (5) categoryTable.showEdit = false; (6) }
The sorting of categories in line (4)
is also needed to show the new description. A previously instantiated web component can also be retrieved by querySelector
. Line (5)
uses this to toggle the appearance of the edit
component in line (6)
.
Spiral s04 adds persistency to the browser local storage; our model Category
class implements the necessary toJson
and fromJson
methods. In the body of the <polymer-app>
component, a Save button is added, which is coupled to a save()
method in polymer_app.dart
:
save(Event e, var detail, Node target) { window.localStorage['polymer_category_links'] = JSON.encode(categories.toJson()); }
The data is then read from the local storage in the constructor of the <polymer-app>
component in line (1)
of the following code (see polymer_app.dart
):
PolymerApp.created() : super.created() { var categoryLinksModel = new CategoryLinksModel(); categories = categoryLinksModel.categories; String json = window.localStorage['polymer_category_links']; (1) if (json == null) { categoryLinksModel.init(); } else { categories.fromJson(JSON.decode(json)); } categories.internalList = toObservable(categories.internalList); }
If the data is not in the local storage, the init()
method is called and the model is populated. Spiral s04 also adds a Remove functionality through a new button in every table row, which will invoke the following method in category_table.dart
:
delete(Event e, var detail, Element target) { String code = target.attributes['category-code']; category = categories.find(code); categories.remove(category); }
Now, how about viewing the links for each category? This is taken care of in spiral s05, first by adding the Link
and Links
classes to our model in libmodellink_entities.dart
. Being good Dartlero citizens, they know to construct themselves the fromJson
method and deconstruct the toJson
method, so they are ready for (local storage) persistence. Test programs are also provided in testmodel
. When the app is run, a Show button is added to every category row. If this is clicked on, a new <link-table>
web component will appear with the links of the selected category shown; this was added to <category-table>
. This can be seen in the Category_links spiral s01 screenshot, where the Web Link column contains real hyperlinks:
<template if="{{showCategoryLinks}}"> <link-table category="{{category}}"></link-table> (2) </template>
Here is the code for the Show button:
<button on-click="{{showLinks}}" category-code={{category.code}}>Show</button>
When clicked on, the showLinks
method from category_table.dart
will be executed:
showLinks(Event e, var detail, Element target) { String code = target.attributes['category-code']; ButtonElement categoryLinks = target; if (!showCategoryLinks && categoryLinks.text == 'Show') { showCategoryLinks = true; category = categories.find(code); categoryLinks.text = 'Hide'; } else if (showCategoryLinks && categoryLinks.text == 'Hide') { showCategoryLinks = false; categoryLinks.text = 'Show'; } }
This code toggles the appearance of the <link-table>
component in line (2)
in the preceding code, which also passes the category
variable; the <link-table>
component gets this value in its attached()
method in link_table.dart
:
class LinkTable extends PolymerElement { @published Category category; @published Links links; @observable bool showAdd = false; attached() { super. attached(); links = category.links; links.internalList = toObservable(links.internalList); }
The links are shown in a repeating template in the link_table.html
file:
<tbody template repeat="{{link in links.internalList}}"> <tr> <td> <a href="{{link.url.toString()}}"> {{link.code}} </a> </td> <td>{{link.description}}</td> </tr> </tbody>
This <link-table>
web component also has a Show Add button to activate a <link-add>
component in a conditional template. This is shown when @observable bool showAdd
becomes true and toggled in the code of the show
method of the LinkTable
class.
The link-add
web component is very similar to category-add
. So, with what we discussed here, you should be able to analyze the code for yourself.
Spiral s06 introduces the edit functionality for links through a new <link-edit>
component and finally, in spiral s07, you can remove links from a category. Now, you have all the knowledge to completely understand the code in these last spirals. Moreover, you are able to apply the model and build a web components app for every (1-n) relation between data, such as Departments
and Employees
, or Orders
and Order Details
. We now look at using web components in a (n-m) or many-to-many relationship.
3.139.83.199