I'm trying to dynamically add a number of divs using Dart. The divs contain a custom web component, and I'm trying to pass in a variable. I want to be able to specify n number of variables, pass them to n number of components, and insert those into an HTML document. What's happening, however, is I'm getting the inserted divs without the variables. I wonder if this is a case of Dart trying to pass something into a an already loaded DOM and therefore doing nothing...? Here's some code:
product_elem.dart:
import 'package:web_ui/web_ui.dart';
import 'dart:html';
class ProductComponent extends WebComponent {
var productId;
}
product_elem.html:
<!DOCTYPE html>
<html>
<body>
<element name="product-elem" constructor="ProductComponent" extends="div">
<template>
<div style="width:335px;margin:10px;">
<h3>
{{productId}}
</h3>
</div>
</template>
<script type="application/dart" src="product_elem.dart"></script>
</element>
</body>
</html>
testcase_component.dart:
import 'dart:html';
import 'package:web_ui/web_ui.dart';
var productId;
void main() {
List myList = new List();
myList.addAll(["Foo", "Bar", "Baz"]);
for (var i = 0; i < myList.length; i++) {
productId = myList[i];
query('#products').innerHtml +=
"<div is='product-elem' id='product_elem' product-id='{{productId}}'></div>";
}
}
testcase_component.html:
<!DOCTYPE html>
<html>
<head>
<link rel="import" href="product_elem.html">
</head>
<body>
<div id="products">
<!-- Insert dynamic divs here -->
</div>
<script type="application/dart" src="testcase_component.dart"></script>
<script src="packages/browser/dart.js"></script>
</body>
</html>
You can't just add WebComponents like that. WebUI doesn't know that anything was added, so you just end up with a normal div.
Here is the current (slightly messy) way to dynamically add WebComponents:
void main() {
List myList = new List();
myList.addAll(["Foo", "Bar", "Baz"]);
for (var i = 0; i < myList.length; i++) {
productId = myList[i];
var product = new ProductComponent(productId);
product.host = new DivElement();
var lifecycleCaller = new ComponentItem(product)..create();
query('#products').append(product.host);
lifecycleCaller.insert();
}
}
This way the proper WebUI lifecycles are called.
Also make sure to add a constructor for ProductElem so that productId can be externally set:
class ProductComponent extends WebComponent {
var productId;
ProductComponent(this.productId);
}
Related
What is the proper way of binding an input field to an int property on an object (e.g. input box changes and updates int property of an object causing another element who is binding to the same property to update)
Example code is below; I may be thinking the wrong way going this route but need some clarification.
<!-- index.html -->
<!DOCTYPE html>
<html>
<head>
<link rel="import" href="components/calc.html">
<script type="application/dart">export 'package:polymer/init.dart';</script>
<script src="packages/browser/dart.js"></script>
</head>
<body>
<my-calc></my-calc>
</body>
</html>
<!-- calc.html -->
<polymer-element name="my-calc">
<template>
<label>Price</label>
<input value='{{ price }}'>
<label>Qty</label>
<input value='{{ qty }}'>
<label>Total</label>
<input value='{{ price * qty }}'>
</template>
<script type="application/dart" src="calc.dart"></script>
</polymer-element>
.
// calc.dart
import 'package:polymer/polymer.dart';
#CustomTag('my-calc')
class CalcElement extends PolymerElement {
#observable int price = 0;
#observable int qty = 0;
CalcElement.created() : super.created();
}
You can define a two-way transformer of polymer expression that will convert String to int :
class StringToInt extends Transformer<String, int> {
String forward(int i) => '$i';
int reverse(String s) => int.parse(s);
}
Then you add an attribute asInteger to your PolymerElement (you can alternativelly add the transformer globally as decribed in this other answer).
// calc.dart
import 'package:polymer/polymer.dart';
#CustomTag('my-calc')
class CalcElement extends PolymerElement {
#observable int price = 0;
#observable int qty = 0;
final asInteger = new StringToInt();
CalcElement.created() : super.created();
}
And finally use this transformer :
<!-- calc.html -->
<polymer-element name="my-calc">
<template>
<label>Price</label>
<input value='{{ price | asInteger }}'>
<label>Qty</label>
<input value='{{ qty | asInteger }}'>
....
</template>
<script type="application/dart" src="calc.dart"></script>
</polymer-element>
You're on the right track here. The only problem is that the value attribute of the input element is a string. One way to do it is like this:
<!-- calc.html -->
<polymer-element name="my-calc">
<template>
<label>Price</label>
<input value='{{ price }}'>
<label>Qty</label>
<input value='{{ qty }}'>
<label>Total</label>
<input value='{{ int.parse(price) * int.parse(qty) }}'>
</template>
<script type="application/dart" src="calc.dart"></script>
</polymer-element>
//calc.dart
import 'package:polymer/polymer.dart';
#CustomTag('my-calc')
class CalcElement extends PolymerElement {
#observable String price = "0";
#observable String qty = "0";
CalcElement.created() : super.created();
}
I think the answer above is the right way, but I am using this instead of the Transformer:
class MyPolymerExpressions extends PolymerExpressions {
MyPolymerExpressions(): super(globals: {
'intToString': (int input) => '$input',
});
#override
prepareBinding(String path, name, node) => Polymer.prepareBinding(path, name, node, super.prepareBinding);
}
and add this line in de calc.dart :
#override PolymerExpressions syntax = new MyPolymerExpressions();
Note: in order to use PolymerExpressions, you need:
import 'package:polymer_expressions/polymer_expressions.dart';
After overriding three lifecycle methods of WebComponent: created(), inserted(), and removed(), I can see that the first two are called consistently but removed() is never called. Is there anything special that needs to be done so that removed() is called? Or is it simply never called?
The removed() method is called when a custom element is removed from the DOM. Here is a small program that demonstrates the use of the created(), inserted(), and removed() lifecycle events.
Create a Dart web application with an index.html file that looks like this:
<!DOCTYPE html>
<html>
<head>
<title>index</title>
<link rel="import" href="my_element.html">
<script src="packages/polymer/boot.js"></script>
</head>
<body>
<div id='container'><my-element></my-element></div>
<script type="application/dart">
import 'dart:html';
void main() {
query('#container').onClick.listen((event) {
event.target.remove();
});
}
</script>
</body>
</html>
This file imports and displays a custom element, <my-element>.
Define the following file that defines <my-element>:
<!DOCTYPE html>
<html>
<body>
<polymer-element name="my-element">
<template>
<p>The name is {{person.name}}</p>
</template>
<script type="application/dart" src="my_element.dart"></script>
</polymer-element>
</body>
</html>
And define the accompanying Dart file that demonstrates the lifecycle methods getting called:
import 'package:polymer/polymer.dart';
class Person extends Object with ObservableMixin {
#observable String name;
Person(this.name);
}
#CustomTag("my-element")
class MyElement extends PolymerElement {
#observable Person person = new Person('Shailen');
void created() {
super.created();
print('created');
}
void inserted() {
print('inserted');
}
void removed() {
print('removed');
}
}
When you run index.html, you will see a paragraph with some text in it. The created() and inserted() lifecycle methods are called, and 'created' and 'inserted' messages print in the console. When you click on the div that contains the custom element, the element is removed, the removed() lifecycle method is called, and 'removed' prints in the console.
Hope this helps.
In Dart's Web UI, it was possible to pass arbitrary data to function in response to events, for example, the following snippet passes the value 2 to the increment(int incBy) method in response to the button's on-click event:
<!-- Web UI -->
<element name="x-click-counter">
<template>
<button on-click="increment(2)"> <!-- passing a value of 2 -->
Click me
</button>
</template>
</element>
<script>
import 'package:web_ui/web_ui.dart';
class CounterComponent extends WebComponent {
void increment(int incBy) { // accept the value of 2
count = count + incBy;
}
}
</script>
In Polymer (and Polymer.dart), the on-click event attribute requires a string version of the function name, rather than an actual function call. This is described on the polymer docs page as:
The value of an event handler attribute is the string name of a method
on the component. Unlike traditional syntax, you cannot put executable
code in the attribute.
Using polymer.dart, this looks like:
<polymer-element name="x-click-counter">
<template>
<button on-click="increment"> <!-- can't pass a value of 2, as you need to pass a string -->
Click Me
</button>
</template>
</polymer-element>
<script>
import 'package:polymer/polymer.dart';
#CustomTag("x-click-counter")
class CounterComponent extends PolymerElement with ObservableMixin {
#observable int count = 0;
void increment(Event event, var detail, var target) { // How do I pass 2 to this function?
count = count ++;
}
}
</script>
Question: How do I pass an arbitrary value to the increment function?
You can use html data- attributes to pass extra data, and then access them through the target parameter.
Re-writing the polymer example to add a data-incby field that takes the value increment the count by looks like this:
<polymer-element name="x-click-counter">
<template>
<button on-click="increment" data-incby="2"> <!-- now passing the value as a data attribute -->
Click Me
</button>
</template>
</polymer-element>
<script>
import 'package:polymer/polymer.dart';
#CustomTag("x-click-counter")
class CounterComponent extends PolymerElement with ObservableMixin {
#observable int count = 0;
void increment(Event event, var detail, var target) {
int incBy = int.parse(target.attributes['data-incby']); // extract the value 2
count = count + incBy;
}
}
</script>
Dart and Polymer.dart have changed since Chris' answer. Here is updated code for Dart v1.0:
<polymer-element name="x-click-counter">
<template>
<button on-click="{{increment}}" data-incby="2"> <!-- now passing the value as a data attribute -->
Click Me
</button>
<span>{{count}}</span>
</template>
</polymer-element>
<script type="application/dart">
import 'package:polymer/polymer.dart';
import 'dart:html';
#CustomTag("x-click-counter")
class CounterComponent extends PolymerElement {
#observable int count = 0;
CounterComponent.created() : super.created();
void increment(Event event, var detail, var target) {
int incBy = int.parse(target.attributes['data-incby']); // extract the value 2
count = count + incBy;
}
}
</script>
My solution for Polymer 0.11.0+5
element.html
<link rel="import" href="../packages/polymer/polymer.html">
<polymer-element name="dp-element">
<template>
<div class="row">
<ul>
<template repeat="{{ item in items }}">
<li on-click="{{load}}" data-incby="{{item}}">{{ item }}</li>
</template>
</ul>
</template>
<script type="application/dart">
import 'package:polymer/polymer.dart';
import 'view.dart';
import 'dart:html';
#CustomTag('dp-element')
class DpElement extends PolymerElement {
#observable List<String> items;
DpElement.created() : super.created(){
}
void load(Event event, var detail, var target) {
String incBy = target.attributes['data-incby'];
print(incBy);
}
}
I have a class field that is a String and I want to use a text input to modify that field. How can I use #observable with polymer.dart to do this?
Here is the class field I would like to sync-up with the UI:
class Person {
#observable String name;
Person(this.name);
}
Import the polymer.dart file and mix in the ObservableMixin into Person. Extend PolymerElement, and also use a #CustomTag annotation.
Here is what the dart file using #observable with a custom element could look like:
import 'package:polymer/polymer.dart';
class Person extends Object with ObservableMixin {
#observable String name;
Person(this.name);
}
#CustomTag("custom-element")
class CustomElement extends PolymerElement {
#observable Person person = new Person('John');
}
In the associated .html file, use {{}} syntax to create the binding with the #observable field:
<!DOCTYPE html>
<html>
<body>
<polymer-element name="custom-element">
<template>
<label> Name: <input value="{{person.name}}"></label>
<p>The name is {{person.name}}</p>
</template>
<script type="application/dart" src="element.dart"></script>
</polymer-element>
</body>
</html>
This element can be used in the following manner (note the link to boot.js):
<!DOCTYPE html>
<html>
<head>
<title>index</title>
<link rel="import" href="element.html">
<script src="packages/polymer/boot.js"></script>
</head>
<body>
<custom-element></custom-element>
<script type="application/dart">
void main() {}
</script>
</body>
</html>
I can make a String or a num type observable by using the #observable declaration in the Dart code:
#observable
var x = '';
and {{ }} syntax in the html:
<div>x = {{x}}</div>
But #observable does not work with Lists and Maps. How do I make those observable?
Use toObservable() with the List or Map as an argument. This creates a
binding between the List or Map object and its representation in the UI.
The following example uses toObservable(). Notice that the List and Map
objects have data added to them every second. With toObservable() creating
the proper binding, the UI for these objects auto-magically updates to show
the added items.
When the List or Map are clear()ed, the the UI once again reflects this.
For instructions on how to build and run a script such as this one, see
http://www.dartlang.org/articles/web-ui/tools.html.
Here is the main.dart file:
import 'dart:async';
import 'package:web_ui/web_ui.dart';
#observable
num x = 0; // #observable works fine with a number.
List list = toObservable(new List());
Map<String, num> map = toObservable(new Map());
void main() {
new Timer.periodic(new Duration(seconds: 1), (_) {
x += 1;
list.add(x);
map[x.toString()] = x;
if (x % 4 == 0) {
list.clear();
map.clear();
}
return x;
});
}
And here is the accompanying dart.html file:
<!DOCTYPE html>
<html>
<body>
<p>x = {{ x }}</p>
<ul>
<template iterate='item in list'>
<li>list item = {{item}}</li>
</template>
</ul>
<ul>
<template iterate='key in map.keys'>
<li>map key = {{key}}, map value = {{map[key]}}</li>
</template>
</ul>
<script type="application/dart" src="main.dart"></script>
</body>
</html>