OLD | NEW |
(Empty) | |
| 1 --- |
| 2 layout: default |
| 3 title: "Define a Custom Element" |
| 4 description: "Create a custom HTML element using Polymer." |
| 5 has-permalinks: true |
| 6 tutorial: |
| 7 id: polymer-intro |
| 8 next: fetchdata |
| 9 next-title: "Fetch Data Dynamically" |
| 10 prev: shared-pkgs |
| 11 prev-title: "Install Shared Packages" |
| 12 --- |
| 13 |
| 14 {% capture whats_the_point %} |
| 15 |
| 16 * Polymer.dart is the next evolution of Web UI. |
| 17 * Everything in Polymer.dart is an element. |
| 18 * Custom elements provide semantically meaningful encapsulation. |
| 19 * Use Polymer.dart to build custom elements. |
| 20 * Bind Dart data to HTML elements. |
| 21 * Declaratively bind event handlers to elements. |
| 22 |
| 23 {% endcapture %} |
| 24 |
| 25 {% capture sample_links %} |
| 26 |
| 27 <p> |
| 28 Get the source code for the samples featured in this target:</p> |
| 29 |
| 30 <ul> |
| 31 <li> |
| 32 <a href="https://github.com/dart-lang/dart-tutorials-samples/tree/master/web
/polymer-intro/stopwatch" |
| 33 target="_blank">stopwatch</a> |
| 34 </li> |
| 35 </ul> |
| 36 |
| 37 {% endcapture %} |
| 38 |
| 39 {% capture content %} |
| 40 |
| 41 <div class="tute-target-title"> |
| 42 <h1>{{page.title}}</h1> |
| 43 <h3>Create a custom HTML element using Polymer</h3> |
| 44 </div> |
| 45 |
| 46 A custom element is an HTML element you can define yourself, |
| 47 encapsulating appearance and/or behavior |
| 48 within semantically meaningful HTML. |
| 49 |
| 50 <aside class="alert"> |
| 51 Custom elements are one feature of |
| 52 <a href="http://www.polymer-project.org/" |
| 53 target="_blank">Polymer</a>, |
| 54 a new type of library for the web based on Web Components. |
| 55 <a href="http://www.dartlang.org/polymer-dart/" |
| 56 target="_blank">Polymer.dart</a> |
| 57 is the Dart implementation of Polymer. |
| 58 (Note: Polymer supersedes Web UI.) |
| 59 </aside> |
| 60 |
| 61 * [An example](#an-example) |
| 62 * [Installing Polymer.dart](#getting-polymer-dart) |
| 63 * [Including Polymer.dart in your application](#bootstrap) |
| 64 * [Instantiating a custom element](#instantiating) |
| 65 * [Defining a custom element](#define-element) |
| 66 * [Providing a template for the custom element](#providing-a-template) |
| 67 * [Providing a script for the custom element](#providing-a-script) |
| 68 * [Overiding life-cycle methods](#life-cycle-methods) |
| 69 * [Using data binding](#data-binding) |
| 70 * [Setting up event handlers declaratively](#event-handlers) |
| 71 * [Querying the Shadow DOM](#in-the-shadows) |
| 72 * [Styling a custom element](#scoped-css) |
| 73 * [Other resources](#other-resources) |
| 74 |
| 75 ##An example |
| 76 |
| 77 In the example running below, |
| 78 the LemonChiffon area outlined in black |
| 79 is a custom element implemented using Polymer. |
| 80 |
| 81 <strong>Try it!</strong> |
| 82 Start and stop the stopwatch. |
| 83 Reset the stopwatch to 00:00 using the **Reset** button. |
| 84 |
| 85 <iframe class="running-app-frame" |
| 86 style="height:220px;width:230px;" |
| 87 src="examples/stopwatch/out/web/index.html"> |
| 88 </iframe> |
| 89 |
| 90 To place this custom element on an HTML page, |
| 91 import the file with the custom element definition |
| 92 and use the name of the element as an HTML tag: |
| 93 |
| 94 {% prettify html %} |
| 95 <link rel="import" href="tute_stopwatch.html"> |
| 96 ... |
| 97 <tute-stopwatch></tute-stopwatch> |
| 98 {% endprettify %} |
| 99 |
| 100 The counting text, the three buttons along with their actions, |
| 101 and the style are all contained within the custom element. |
| 102 The definition of the custom element encapsulates and |
| 103 hides the implementation details, |
| 104 which as the user of the element, you care nothing about. |
| 105 |
| 106 When you use developer tools to inspect the element, |
| 107 you see just the custom element's begin and end tags. |
| 108 |
| 109  |
| 110 |
| 111 With custom elements, you can easily create new kinds of elements |
| 112 that have semantically meaningful tags and that are easy to share, |
| 113 reuse, and read. |
| 114 |
| 115 ###Overview of the example files |
| 116 |
| 117 Three main source files implement the Stopwatch example: |
| 118 |
| 119 <dl> |
| 120 <dt> |
| 121 <a href="https://github.com/dart-lang/dart-tutorials-samples/tree/master/web
/polymer-intro/stopwatch/web/index.html" target="_blank">index.html</a> |
| 122 </dt> |
| 123 <dd> |
| 124 The primary HTML file for the app. |
| 125 Includes the Polymer bootstrap script and instantiates the custom element. |
| 126 </dd> |
| 127 <dt> |
| 128 <a href="https://github.com/dart-lang/dart-tutorials-samples/tree/master/web
/polymer-intro/stopwatch/web/tute_stopwatch.html" target="_blank">tute_stopwatch
.html</a> |
| 129 </dt> |
| 130 <dd> |
| 131 The HTML code that defines the custom element. |
| 132 </dd> |
| 133 <dt> |
| 134 <a href="https://github.com/dart-lang/dart-tutorials-samples/tree/master/web
/polymer-intro/stopwatch/web/tute_stopwatch.dart" target="_blank">tute_stopwatch
.dart</a> |
| 135 </dt> |
| 136 <dd> |
| 137 The Dart class that implements the custom element. |
| 138 </dd> |
| 139 </dl> |
| 140 |
| 141 The following diagram shows the structure of the example |
| 142 app and its use of custom elements. |
| 143 |
| 144  |
| 145 |
| 146 ##Installing Polymer.dart {#getting-polymer-dart} |
| 147 |
| 148 To use the features provided by Polymer.dart, |
| 149 you need to install the Polymer package. |
| 150 If you are unfamiliar with installing packages, |
| 151 refer to |
| 152 <a href="/docs/tutorials/shared-pkgs/">Install Shared Packages</a>, |
| 153 which describes the process in detail. |
| 154 |
| 155 In brief, to install the Polymer package: |
| 156 |
| 157 * In the application's `pubspec.yaml` file, |
| 158 add the package to the list of dependencies |
| 159 by adding the package name, `polymer`, to the list. |
| 160 YAML is whitespace-sensitive, |
| 161 so take care to indent the package name as shown: |
| 162 |
| 163  |
| 164 |
| 165 * Run `pub install`, |
| 166 which recursively installs the polymer.dart package |
| 167 and all the packages that it depends on. |
| 168 If you are using Dart Editor, |
| 169 when you save pubspec.yaml |
| 170 the editor automatically runs `pub install` for you. |
| 171 If you are using command line tools, |
| 172 you can run it with the command `pub install`. |
| 173 |
| 174 ##Including Polymer.dart in your application {#bootstrap} |
| 175 |
| 176 To use Polymer.dart features such as custom elements, |
| 177 you need to include Polymer in both |
| 178 the HTML side and the Dart side of your app. |
| 179 |
| 180 * In the primary HTML file for your app, |
| 181 include `packages/polymer/boot.js` |
| 182 in the <head> section. |
| 183 When using this script, |
| 184 you do not need to define a top-level `main()` function, |
| 185 although you can. |
| 186 **Note:** Use this script instead of `packages/browser/dart.js`. |
| 187 |
| 188  |
| 189 |
| 190 * In your Dart code, import the Polymer library: |
| 191 |
| 192  |
| 193 |
| 194 ##Instantiating a custom element {#instantiating} |
| 195 |
| 196 To create an instance of a custom element, |
| 197 use the name of the custom element just as you would any normal HTML tag. |
| 198 In this example, the tag name is `tute-stopwatch`. |
| 199 |
| 200  |
| 201 |
| 202 Using best practices, |
| 203 the custom element definition is in a separate file. |
| 204 Use `link [rel="import"]` to import the HTML definition file as shown. |
| 205 |
| 206 ##Defining a custom element {#define-element} |
| 207 |
| 208 The definition for the <tute-stopwatch> element is |
| 209 in |
| 210 <a href="https://github.com/dart-lang/dart-tutorials-samples/tree/master/web/pol
ymer-intro/stopwatch/web/tute_stopwatch.html" target="_blank">tute_stopwatch.htm
l</a>. |
| 211 A custom element definition should be in its own |
| 212 source file so that it can be included by other files. |
| 213 An HTML file that contains the definition for a custom element |
| 214 does not need <html>, <head>, or <body> tags. |
| 215 |
| 216 To define a custom element, |
| 217 use the <polymer-element> tag and provide a name. |
| 218 |
| 219 {% prettify html %} |
| 220 <polymer-element name="tute-stopwatch"> |
| 221 ... |
| 222 </polymer-element> |
| 223 {% endprettify %} |
| 224 |
| 225 A custom element name must have at least one hyphen (`-`). |
| 226 We advise using an identifiable prefix to |
| 227 avoid naming conflicts with elements shared by others |
| 228 and to help identify the project from which the element originates. |
| 229 For example, for tutorial custom elements, we use the prefix `tute`. |
| 230 |
| 231 Within the <polymer-element> tag, |
| 232 you can provide a template (appearance) and a script (behavior). |
| 233 UI widgets, like our Stopwatch example, |
| 234 typically have both a template and a script, |
| 235 but neither is required. |
| 236 A custom element with a script and no template is purely functional. |
| 237 A custom element with a template and no script is purely visual. |
| 238 |
| 239 {% prettify html %} |
| 240 <polymer-element name="tute-stopwatch"> |
| 241 <template> |
| 242 ... |
| 243 </template> |
| 244 <script type="application/dart" src="tute_stopwatch.dart"> |
| 245 </script> |
| 246 </polymer-element> |
| 247 {% endprettify %} |
| 248 |
| 249 <dl> |
| 250 <dt> <template> </dt> |
| 251 <dd> |
| 252 Describes the custom element's structure—its user interface. |
| 253 The template comprises any valid HTML code within the <template> tag. |
| 254 When the custom element is instantiated, |
| 255 the instance is created from the template. |
| 256 The template can include CSS styles within a <style> tag. |
| 257 </dd> |
| 258 |
| 259 <dt> <script> </dt> |
| 260 <dd markdown="1"> Specifies a Dart script. |
| 261 For custom elements, the Dart script is a Dart class |
| 262 that implements the behavior of the element. |
| 263 The class typically overrides some |
| 264 life-cycle methods and provides event handlers |
| 265 that join the UI with its programmatic behavior. |
| 266 In this example, the script is in |
| 267 <a href="https://github.com/dart-lang/dart-tutorials-samples/tree/master/web
/polymer-intro/stopwatch/web/tute_stopwatch.dart" target="_blank">tute_stopwatch
.dart</a>. |
| 268 </dd> |
| 269 </dl> |
| 270 |
| 271 ##Providing a template for the custom element {#providing-a-template} |
| 272 |
| 273 Here's the template code for the tute-stopwatch element: |
| 274 |
| 275  |
| 276 |
| 277 The tute-stopwatch template uses a <style> tag, which is optional. |
| 278 These styles are scoped; they affect only |
| 279 the appearance of the custom element and the elements it contains. |
| 280 More about scoped CSS in [Styling a custom element](#scoped-css). |
| 281 |
| 282 The rest of the code within the <template> tag |
| 283 is normal HTML, with two exceptions: |
| 284 |
| 285 |---|---| |
| 286 | `{``{``counter}}` | Uses a Polymer syntax to [bind Dart data](#data-binding) t
o the HTML page. The double curly braces are commonly known as a "double mustach
e". | |
| 287 | `on-click` | Uses Polymer [declarative event mapping](#event-handlers), which
allows you to set up event handlers for a UI element. `on-click` sets up an even
t handler for mouse clicks. Polymer has mappings for other event types, such as
`on-change` for input change events. | |
| 288 {: .table} |
| 289 |
| 290 Let's take a look at the structure of the Dart code |
| 291 before we get into the details of data binding, event handlers, |
| 292 and scoped CSS. |
| 293 |
| 294 ##Providing a script for the custom element {#providing-a-script} |
| 295 |
| 296 On the Dart side, a class implements the behavior of the custom element. |
| 297 You associate the Dart class with the custom element using the `@CustomTag` |
| 298 annotation and the name of the custom element. |
| 299 |
| 300  |
| 301 |
| 302 This diagram gives an overview of the TuteStopwatch class: |
| 303 |
| 304  |
| 305 |
| 306 Any Dart class that backs a Polymer element must subclass PolymerElement. |
| 307 If you want to use data binding, the class must also use ObservableMixin. |
| 308 |
| 309 {% comment %} |
| 310 [xx: more about PolymerElement] |
| 311 [xx: more about ObservableMixin] |
| 312 {% endcomment %} |
| 313 |
| 314 The class can respond to life-cycle milestones |
| 315 by overriding [life-cycle methods](#life-cycle-methods). |
| 316 For example, the TuteStopwatch class overrides the `inserted()` |
| 317 method—which is called when the element is inserted |
| 318 into the DOM—to initialize the app. |
| 319 |
| 320 The `start()` method is an event handler for the **Start** button. |
| 321 The event handler is declaratively connected to the button. |
| 322 Refer to [Setting up event handlers declaratively](#event-handlers) to see how. |
| 323 |
| 324 ##Overriding life-cycle methods {#life-cycle-methods} |
| 325 |
| 326 A custom element has four life-cycle methods |
| 327 that it can override: |
| 328 |
| 329 |---|---| |
| 330 | `created()` | Called when an instance of a custom element is created. | |
| 331 | `inserted()` | Called when an instance of a custom element is inserted into th
e DOM. | |
| 332 | `removed()` | Called when an instance of a custom element is removed from the
DOM. | |
| 333 | `attributeChanged()` | Called when an attribute, such as `class`, of an instan
ce of the custom element is added, changed, or removed. | |
| 334 {: .table} |
| 335 |
| 336 You can override any of these life-cycle methods. |
| 337 The overriding method |
| 338 *must* call the super class method first. |
| 339 |
| 340 The Stopwatch app overrides the `inserted()` method because it |
| 341 needs a reference to each of the three buttons |
| 342 so that it can enable and disable them. |
| 343 When a tute-stopwatch custom element is inserted into the DOM |
| 344 the buttons have been created, so the references to them |
| 345 will be available when the inserted() method is called. |
| 346 You'll notice that it gets the buttons by |
| 347 [querying the custom element's shadow root](#in-the-shadows). |
| 348 |
| 349 {% prettify dart %} |
| 350 void inserted() { |
| 351 super.inserted(); |
| 352 startButton = getShadowRoot('tute-stopwatch').query('#startButton'); |
| 353 stopButton = getShadowRoot('tute-stopwatch').query('#stopButton'); |
| 354 resetButton = getShadowRoot('tute-stopwatch').query('#resetButton'); |
| 355 |
| 356 stopButton.disabled = true; |
| 357 resetButton.disabled = true; |
| 358 } |
| 359 {% endprettify %} |
| 360 |
| 361 ##Using data binding {#data-binding} |
| 362 |
| 363 In the HTML definition of a custom element, |
| 364 use double curly brackets to embed Dart data into the webpage. |
| 365 In your Dart code, use the `@observable` annotation |
| 366 to mark the embedded data. |
| 367 Here, the data is a string called `counter`. |
| 368 |
| 369  |
| 370 |
| 371 The tute-stopwatch element uses a periodic |
| 372 <a href="https://api.dartlang.org/dart_async/Timer.html" target="_blank">Timer</
a> |
| 373 to fire an event every second. |
| 374 When the Timer fires, it calls the `updateTimer()` method, |
| 375 which modifies the `counter` string. |
| 376 Polymer takes care of updating the HTML page with the new string. |
| 377 |
| 378 This type of binding is called _one-way data binding_ |
| 379 because the data can change only on the Dart side. |
| 380 Polymer also supports two-way data binding. |
| 381 In two-way data binding, when data changes on the HTML side—for example |
| 382 with an input element—the value in the Dart code changes to match. |
| 383 |
| 384 You can use expressions within the double curly brackets. |
| 385 <a href="http://pub.dartlang.org/packages/polymer_expressions" |
| 386 target="_blank">Polymer expressions</a> |
| 387 provide the default syntax. Examples of allowable expressions include: |
| 388 |
| 389 |---|---| |
| 390 | `{``{``myObject.aProperty}}` | Property access. | |
| 391 | `{``{``!empty}}` | Operators, like the logical not operator. | |
| 392 | `{``{``myList[3]}}` | List indexing. | |
| 393 | `{``{``myFilter()}}` | Data filtering. | |
| 394 {: .table} |
| 395 |
| 396 ##Setting up event handlers declaratively {#event-handlers} |
| 397 |
| 398 This example has three buttons, each |
| 399 with an event handler that is written in Dart, |
| 400 but attached to the button declaratively from HTML. |
| 401 |
| 402  |
| 403 |
| 404 In HTML, use the `on-click` attribute |
| 405 to attach a mouse click handler to an HTML element. |
| 406 The value of the attribute must be the name of a method |
| 407 in the class that implements the custom element. |
| 408 When the user clicks the button, the specified method is called |
| 409 with three parameters: |
| 410 |
| 411 * An |
| 412 <a href="https://api.dartlang.org/dart_html/Event.html" target="_blank">Event</a
> |
| 413 that contains information about the event, |
| 414 such as its type and when it occurred. |
| 415 |
| 416 * The _detail_ object can provide additional, event-specific information. |
| 417 |
| 418 * The <a href="https://api.dartlang.org/dart_html/Node.html" target="_blank">Nod
e</a> |
| 419 that fired the event—the **Start** button in this case. |
| 420 |
| 421 You can attach event handlers for other kinds of events. |
| 422 For example, you can use `on-change` to handle events for input text elements |
| 423 when the text changes. |
| 424 |
| 425 Refer to |
| 426 [Declarative event mapping](http://www.polymer-project.org/polymer.html#declarat
ive-event-mapping) |
| 427 for further details. |
| 428 |
| 429 ##Querying the shadow root {#in-the-shadows} |
| 430 |
| 431 The Shadow DOM is key to encapsulation. |
| 432 The DOM subtree for a custom element is |
| 433 hidden from outside of the custom element. |
| 434 |
| 435  |
| 436 |
| 437 You can programmatically get items from the Shadow DOM |
| 438 by querying a custom element's _shadow root_—a |
| 439 special node from which an instance of a custom element is rendered. |
| 440 |
| 441 {% prettify dart %} |
| 442 startButton = getShadowRoot('tute-stopwatch').query('#startbutton'); |
| 443 stopButton = getShadowRoot('tute-stopwatch').query('#stopbutton'); |
| 444 resetButton = getShadowRoot('tute-stopwatch').query('#resetbutton'); |
| 445 {% endprettify %} |
| 446 |
| 447 Call `getShadowRoot()` with the name of the custom element. |
| 448 The `getShadowRoot()` method returns the shadow root element |
| 449 for this instance of the specified element. |
| 450 Use `query()` with a CSS selector to get the element(s) of interest. |
| 451 |
| 452 Note that this code uses `query()` to get each button by ID. |
| 453 By querying the shadow root object rather than the DOM, |
| 454 you are guaranteed to get the objects from within the custom element, |
| 455 not from anywhere else on the page. |
| 456 |
| 457 ##Styling a custom element {#scoped-css} |
| 458 |
| 459 You can optionally include CSS styles for your custom element |
| 460 that apply only to the contents of the custom element. |
| 461 |
| 462  |
| 463 |
| 464 The `@host` rule allows you to target and style an element internally, |
| 465 from within its definition. |
| 466 The `:scope` pseudo-class refers to the custom element itself. |
| 467 The only selectors that work in `@host` are those targeting the host element its
elf. |
| 468 So you don't need to worry about naming conflicts on the page. |
| 469 Any CSS selectors within the template need to be unique only within the template
. |
| 470 |
| 471 For further details about styling custom elements, |
| 472 refer to |
| 473 [A Guide to Styling Elements](http://www.polymer-project.org/articles/styling-el
ements.html) |
| 474 |
| 475 ##Other resources |
| 476 |
| 477 Use these other resources to learn more about Polymer: |
| 478 |
| 479 * The tutorial |
| 480 <a href="https://github.com/dart-lang/dart-tutorials-samples/tree/master/web" |
| 481 target="_blank">github repo</a> |
| 482 contains many examples that use Polymer, |
| 483 including samples that have been converted from Web UI. |
| 484 |
| 485 * The |
| 486 <a href="http://www.dartlang.org/polymer-dart/" |
| 487 target="_blank">Polymer.dart</a> homepage provides information |
| 488 specific to the Dart port of the Polymer project. |
| 489 |
| 490 * The Polymer project website |
| 491 <a href="http://www.polymer-project.org/" |
| 492 target="_blank">polymer-project.org</a> |
| 493 contains information about the Polymer project as a whole. |
| 494 |
| 495 {% endcapture %} |
| 496 |
| 497 {% include tutorial.html %} |
OLD | NEW |