Get the code with: git clone git://github.com/dzenanr/polymer_links.git
. We start our discussion with the first spiral in this project: polymer_linkspolymer_links_s01
.
In this spiral, we just show a web component that contains a list of links of type String
, as shown in the following screenshot:
The startup HTML page polymer_links.html
imports the web component with:
<link rel="import" href="links.html">
The component with name web-links
is instantiated in the <body>
tag of the page:
<h1>Web Links</h1> <web-links></web-links>
The links.html
file contains the UI definition of the component:
<polymer-element name="web-links"> <template> (1) <ul> <template repeat="{{webLink in webLinks}}"> (2) <li> <a href="{{webLink}}"> (3) {{webLink}} </a> </li> </template> </ul> </template> <script type="application/dart" src="links.dart"></script> (4) </polymer-element>
The outer template in line (1)
is required. The template in line (2)
uses a repeat
statement to iterate over the list webLinks
: repeat="{{webLink in webLinks}}
. The variable webLink
takes on the value of each list item in succession, and is shown through {{webLink }}
on the next line. The list is constructed in the links.dart
script referenced in line (4)
:
import 'package:polymer/polymer.dart'; @CustomTag('web-links') class WebLinks extends PolymerElement { List webLinks = ['http://ondart.me/', 'https://www.dartlang.org/polymer-dart/']; WebLinks.created() : super.created(); }
Now, we base the web component on a model with one simple concept: a web link. This has two attributes: a name
and a url
. The class Link
is defined in liblinks.dart
:
library links; class Link { String name; Uri url; Link(this.name, String link) { url = Uri.parse(link); } }
It is a best practice to put our model in its own library links
in a separate lib
folder. The Polymer framework is based on the statement: everything is a component. This implies that it is recommended to create a component for the HTML page that starts the application and this is what we do here. We now introduce a web component <my-app></my-app>
in polymer_links.html
that encapsulates the user interface and is also imported through a <link>
tag: <link rel="import" href="my_app.html">
. We show all our links as a <web-links>
component in my_app.html
, so component <web-links>
is embedded in <my-app>
:
<polymer-element name="my-app"> <link rel="import" href="web_links.html"> <template> <web-links weblinks="{{links}}"></web-links> (1) </template> <script type="application/dart" src="my_app.dart"></script> </polymer-element>
This component in its turn is defined in web_links.html
and web_links.dart
.
In the my_app.dart
file, we find the code for the <my_app>
component that constructs a links collection named links
:
import 'package:polymer_links/links.dart'; import 'package:polymer/polymer.dart'; @CustomTag('my-app') class MyApp extends PolymerElement { var links = new List<Link>(); MyApp.created() : super.created() { var link1 = new Link('On Dart', 'http://ondart.me/'), var link2 = new Link('Polymer.dart' 'https://www.dartlang.org/polymer-dart/'), var link3 = new Link('Books To Read', 'http://www.goodreads.com/'), links..add(link1)..add(link2)..add(link3); (2) } }
Our component <web-links>
is now instantiated through the template in line (1)
in the preceding code; it needs a links
variable, which is made in the MyApp
constructor in line (2)
.
The web component in web_links.html
also uses the repeating template introduced in spiral s01, but now shows the names of the links:
<template repeat="{{weblink in weblinks}}"> <li> <a href="{{weblink.url}}"> {{weblink.name}} </a> </li> </template>
The web_links.dart
file now also imports our model from the links
library and annotates the weblinks
variable with @published
in line (3)
to show its contents:
import 'package:polymer_links/links.dart'; import 'package:polymer/polymer.dart'; @CustomTag('web-links') class WebLinks extends PolymerElement { @published List<Link> weblinks; (3) WebLinks.created() : super.created(); }
When you run the preceding code, it looks like the following screenshot:
Apart from some added style in spiral s03, the <web-links>
component is identical to that in spiral s02.
In this spiral, we also provide the possibility to add a web link by the user:
A new link has to be shown; in order to accomplish this, we have to mark the list with toObservable
in my_app.dart
file:
var links = toObservable(new List<Link>());
The definition of the web links component in the web_links.html
file now contains additional UI markup in its <template>
tag to enable adding links:
<div> <label for="name">Name</label> <input id="name" type="text"/> <label for="url">web Link</label> <input id="url" type="text"/><br/> <button on-click="{{add}}" class="button">Add</button> (1) <label id="message"></label> </div> <ul> <!-- repeating template --> </ul></template> <script type="application/dart" src="web_links.dart"></script>
The add
behavior from line (1)
is found in the script web_links.dart
:
add(Event e, var detail, Node target) { (2) InputElement name = shadowRoot.querySelector("#name"); (3) InputElement url = shadowRoot.querySelector ("#url"); LabelElement message = shadowRoot.querySelector ("#message"); var error = false; message.text = ''; if (name.value.trim() == '') { message.text = 'name is mandatory; ${message.text}'; error = true; } if (url.value.trim() == '') { message.text = 'web link is mandatory; ${message.text}'; error = true; } if (!error) { var weblink = new Link(name.value, url.value); weblinks.add(weblink); (4) } }
Notice how in line (1)
the add
event handler is called in {{ }}
, and in line (2)
we can see that it has three arguments: the third one is a direct reference to the target element on which the event happened. In lines (3)
and the following, we see how to get a reference to the inner markup of a web component. The familiar querySelector
method call is now preceded by shadowRoot
:
shadowRoot.querySelector("#name");
In spiral s05, we add the functionality to store our web links in local storage by adding the code needed to load and save data to the model. The save functionality is implemented in the web_links.dart
script of the <web-links>
component. In the preceding code after line (4)
, we now add:
if (!error) { // previous code save(); } and this save-method: save() { window.localStorage['polymer_links'] = JSON.encode(Model.one.toJson()); }
We want to save our data in JSON format. To this end, our model class Link
needs to know how to transform itself in a Map (with a toJson
method) or to construct itself from a Map (using the Link.fromJson
constructor).
Map<String, Object> toJson() { var linkMap = new Map<String, Object>(); linkMap['name'] = name; linkMap['url'] = url.toString(); return linkMap; } Link.fromJson(Map<String, Object> linkMap) { name = linkMap['name']; url = Uri.parse(linkMap['url']); }
Instead of always using List<Link>
, let's introduce a Model
class that envelops such a List
using a singleton design pattern in line (1)
(see liblinks.dart
):
class Model {
var links = new List<Link>();
// singleton design pattern: // http://en.wikipedia.org/wiki/Singleton_pattern
static Model model;
Model.private();
static Model get one { (1)
if (model == null) {
model = new Model.private();
}
return model;
}
init() {
var link1 = new Link('On Dart', 'http://ondart.me/'),
var link2 = new Link('Web UI', 'http://www.dartlang.org/articles/web-ui/'),
var link3 = new Link('Books To Read', 'http://www.goodreads.com/'),
Model.one.links
..add(link1);
..add(link2);
..add(link3);
}
List<Map<String, Object>> toJson() {
var linkList = new List<Map<String, Object>>();
for (Link link in links) {
linkList.add(link.toJson()); (2)
}
return linkList;
}
fromJson(List<Map<String, Object>> linkList) {
if (!links.isEmpty) {
throw new Exception('links are not empty'),
}
for (Map<String, Object> linkMap in linkList) {
Link link = new Link.fromJson(linkMap); (3)
links.add(link);
}
}
}
The Model class also has toJson
and fromJson
methods, applying the corresponding methods for Link
class while iterating over its internal list of Link
objects, see lines (2)
and (3)
in the preceding code. The constructor MyApp()
method in my_app.dart
first creates a list in line (4)
, and then calls the load()
method in line (5)
to read the data from local storage:
MyApp.created() : super.created() { toObservable(Model.one.links); (4) load(); (5) } load() { String json = window.localStorage['polymer_links']; if (json == null) { Model.one.init(); (6) } else { Model.one.fromJson(JSON.decode(json)); (7) } }
If nothing was stored yet, the Model
object is initialized via the init()
method in line (6)
, else it is parsed from local storage in line (7)
. In line (4)
, something special happens: We apply the singleton pattern to make sure that we only have one object of Model
class ever. The getter one
in line (1)
in the Model
class only constructs an object when model
is null, and this object is always returned. Because there is only one model
object, we can safely refer to the unique links object of the model
to feed links within the web component's Dart code.
Here the possibility is added to remove links, as shown in the following screenshot:
A second button is placed inside the <template>
tag of the <web-links>
component:
<button on-click="{{delete}}" class="button">Remove</button>
The delete
method is implemented in the script of the web_links.dart
component:
delete(Event e, var detail, Node target) { InputElement name = shadowRoot.querySelector("#name"); InputElement url = shadowRoot.querySelector ("#url"); LabelElement message = shadowRoot.querySelector("#message"); message.text = ''; Link link = links.find(name.value); if (link == null) { message.text = 'web link with this name does not exist'; } else { url.value = link.url.toString(); if (links.remove(link)) save(); (8) } }
It calls in line (8)
a remove
method in the updated Links
class in liblinks.dart
. The Model
class now encapsulates:
var links = new Links();
Instead of:
var links = new List<Link>();
The Links
class now contains:
var _list = new List<Link>();
The remove
method goes like this:
bool remove(Link link) { return _list.remove(link); }
It also has a getter for the private List
_list
:
List<Link> get internalList => _list;
This is now called in MyApp.created()
as follows:
toObservable(Model.one.links.internalList);
3.15.189.199