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>
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';
I'm trying to nesting in Dart PolymerElements in another PolymerElement like this.
#CustomTag('test-box')
class Box extends PolymerElement{
#observable List<Child> childs = new List<Child>();
Box.created() : super.created() { }
}
#CustomTag('test-child')
class Child extends PolymerElement{
Child.created() : super.created() { }
}
and then in testbox.html
<link rel="import" href="testchild.html">
<polymer-element name="test-box">
<template>
<div>
<ol name="child-list">
<template repeat="{{child in childs}}">
{{child}}
</template>
</ol>
</div>
</template>
<script type="application/dart" src="testbox.dart"></script>
</polymer-element>
Is that possible with Dart/Polymer? All my tries are failed.
I want to handle html nodes like classes.
Thanks in advance
You can use a model object to pass data to a child element via published property.
Check this example: https://github.com/sethladd/dart-polymer-dart-examples/tree/master/web/observable_objects_inside_list_changes
You can add the children nodes to the box, for example:
#CustomTag('test-box')
class Box extends PolymerElement{
#observable List<Child> childs = new List<Child>();
Box.created() : super.created() {
}
void _addChildren(List<Child> children) {
children.forEach((Child c) {
this.children.add(c);
}
}
#override void attached() { super.attached(); _addChildren(childs); }
}
Then you can monitor changes on childs using observable API to reflect the changes on the array.
Beware that Child object should be created with new Element.tag("test-child").
But IMHO the best solution is the one offered by #Leksat using a more pure MVC approach.
I had pretty much the same problem and solved it using some kind of proxy element.
ProxyElement Dart code:
library ProxyElement;
import 'package:polymer/polymer.dart';
#CustomTag('proxy-element')
class ProxyElement extends PolymerElement {
#published PolymerElement target;
ProxyElement.created() : super.created();
attached() {
shadowRoot.querySelector('#proxy').append(target);
}
}
And its HTML code:
<link rel="import" href="../packages/polymer/polymer.html">
<polymer-element name="proxy-element">
<template>
<style>
:host {
display: inline;
}
</style>
<template if="{{target == null}}">
No target element defined.
</template>
<template if="{{target != null}}">
<div id="proxy"></div>
</template>
</template>
<script type="application/dart" src="proxy_element.dart"></script>
</polymer-element>
Usage:
...
<template repeat="{{child in children}}">
<proxy-element target="{{child}}"></proxy-element>
</template>
...
In the following example, when the Update Class button is clicked, {{testClass.someProperty}} is updated with the new class' someProperty, but updating testClass.someProperty does not update {{testClass.someProperty}} in the template.
I thought making the TestClass #observable would make its properties observable and when updating the property, the property in the template would change.
What would be the correct way to bind TestElement.testClass.someProperty to {{testClass.someProperty}}, and as an extension, any class' properties to a Polymer Element template?
test_class.dart
library test;
import "package:polymer/polymer.dart";
#observable
class TestClass extends ObservableBase {
String someProperty;
TestClass(this.someProperty);
}
test_element.dart:
library test_element;
import "package:polymer/polymer.dart";
import "test_class.dart";
#CustomTag("test-element")
class TestElement extends PolymerElement with ObservableMixin {
#observable
TestClass testClass = new TestClass("original");
void updateClass() {
testClass = new TestClass("xyz");
}
void updateProperty() {
testClass.someProperty = "foobar";
}
}
test_element.html
<polymer-element name="test-element">
<template>
{{testClass.someProperty}}
<button on-click="updateProperty">Update Property</button>
<button on-click="updateClass">Update Class</button>
</template>
<script type="application/dart" src="test_element.dart"></script>
</polymer-element>
test.dart
library test;
int main() {
}
test.html:
<!DOCTYPE html>
<html>
<head>
<link rel="import" href="test_element.html" />
<script src="packages/polymer/boot.js"></script>
</head>
<body>
<test-element></test-element>
<script type="application/dart" src="test.dart"></script>
</body>
</html>
I got it to work by moving #observable from the class to the property. I thought annotating the class with #observable was supposed to be the same as annotating all the properties as observable, but that didn't work:
test.dart
library test;
import "package:polymer/polymer.dart";
class TestClass extends ObservableBase {
#observable
String someProperty;
TestClass(this.someProperty);
}
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.
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);
}