I am trying to understand how observable getters work when they use other class instance properties:
When I bind the implicit getter/setter pair 'name' it updates in the input and in the div and everything is synced nicely.
However the explicit getter 'fullname' is not updating in the HTML. Is there a way to make that work (basically the 'fullname' in the element binding should update as well)?? Maybe I am missing a setter, but then again setter does not make sense here...
Very simple example to demonstrate:
test-element.html
<link rel="import" href="../../packages/polymer/polymer.html">
<polymer-element name="test-element">
<template>
<input value="{{ds.name}}">
<div>{{ds.name}}</div>
<div>{{ds.fullname}}</div>
</template>
<script type="application/dart" src="test1.dart"></script>
</polymer-element>
test-element.dart
import 'package:polymer/polymer.dart';
import 'package:popoli/sysmaster-settings.dart';
#CustomTag('test-element')
class TestElement extends PolymerElement {
#observable VerySimpleTest ds;
TestElement.created() : super.created() {
ds = new VerySimpleTest()..name = 'Peter';
}
}
ds.dart
class VerySimpleTest extends Observable {
#observable String name = '';
#observable String get fullname => 'Test: $name';
VerySimpleTest() : super();
}
You need to notify Polymer that a value has changed the getter depends on.
String set name(String val) {
name = notifyPropertyChange(#fullname, name, val);
}
or this should work too
#ComputedProperty('Test: $name') String get fullname => 'Test: $name';
See http://japhr.blogspot.co.at/2014/08/the-polymerdart-computedproperty.html for more details.
Some minor adaptations on Günter’s proposal make it work for me:
class VerySimpleTest extends Observable {
String _name = '';
// #observable // apparently, not even required
String get name => _name;
// #observable // apparently, not even required
String get fullname => 'Test: $name';
set name(String val) {
String oldVal = _name;
_name = notifyPropertyChange(#name, oldVal, val);
_name = notifyPropertyChange(#fullname, oldVal, val);
}
}
Related
I am migrating an application to Polymer 1 from 0.5 and using Dart. I was doing okay but now I am facing some data binding issues:
I have created a polymer element that programatically inserts on its Local DOM another polymer element giving the information of a contact. The second element will get the information from the constructor and show it on the interface via data binding.
Parent dart:
#PolymerRegister('test-app')
class TestApp extends PolymerElement {
TestApp.created() : super.created();
attached() {
super.attached();
async(() {
insertTile();
});
}
void insertTile() {
String contactJSON = '{"contactId":"1", "firstName":"Lex", "surname":"Luthor"}';
Contact contact = new Contact(JSON.decode(contactJSON));
ContactTile tile = new ContactTile(contact, this);
Polymer.dom(this.root).append(tile);
}
}
Parent html:
<dom-module id="test-app">
<style>
:host {
display: block;
}
</style>
<template>
</template>
</dom-module>
Children dart:
#PolymerRegister('contact-tile')
class ContactTile extends PolymerElement {
factory ContactTile(Contact contact, Element parent) {
ContactTile tile = new Element.tag('contact-tile');
tile.contact = contact;
return tile;
}
ContactTile.created() : super.created();
#property Contact contact;
}
Children html:
<dom-module id="contact-tile">
<style>
:host {
display: block;
}
</style>
<template>
<div>{{ contact.contactId }}</div>
<div>{{ contact.firstName }}</div>
<div>{{ contact.surname }}</div>
</template>
</dom-module>
Contact class:
class Contact {
String contactId;
String firstName;
String surname;
Contact(Map map) {
contactId = map['contactId'];
firstName = map['firstName'];
surname = map['surname'];
}
}
For some reason the data binding does not show any of the attributes of the contact on the web interface after the contact is updated on the constructor. Is anybody able to help me with this? I have done this many times on Polymer 0.5 but on Polymer 1 this does not work.
Thank you very much.
==============================
SOLUTION
The problem was related to the notification not being raised on the constructor of ContactTile. I could fix this by modifying the contact using the "set" function which updates the property and notifies. There are two ways to do this:
a. #Property(notify: true)
#PolymerRegister('contact-tile')
class ContactTile extends PolymerElement {
factory ContactTile(Contact contact, Element parent) {
ContactTile tile = new Element.tag('contact-tile');
tile.contact = contact;
return tile;
}
ContactTile.created() : super.created();
#Property(notify: true)
Contact contact;
}
b. polymerElement.set("name", value);
#PolymerRegister('contact-tile')
class ContactTile extends PolymerElement {
factory ContactTile(Contact contact, Element parent) {
ContactTile tile = new Element.tag('contact-tile');
// tile.contact = contact;
tile.set("contact", contact);
return tile;
}
ContactTile.created() : super.created();
#property
Contact contact;
}
Also for accessing to the object attributes, I had to make the class to extend JsProxy and add #property to every attribute (or #reflectable for methods).
import 'package:polymer/polymer.dart';
class Contact extends JsProxy {
#property
String contactId;
#property
String firstName;
#property
String surname;
Contact(Map map) {
contactId = map['contactId'];
firstName = map['firstName'];
surname = map['surname'];
}
}
Since the Contact class isn't a full custom element, should it inherit from JsProxy and have annotations of the properties. This will make the properties accessible in html. Shown below.
class Contact extends JsProxy {
#property String contactId;
#property String firstName;
#property String surname;
Contact(Map map) {
contactId = map['contactId'];
firstName = map['firstName'];
surname = map['surname'];
}
}
I have a custom class called MyClass. I have a List<MyClass> testList in my custom element and want to use databinding for displaying the List's content.
MyClass:
class MyClass {
String name;
MyClass(String name) {
this.name = name;
}
}
custom_element.dart:
...
attached() {
super.attached();
var lst = new List<MyClass>();
lst.add(new MyClass('test'));
set('testList', lst);
}
...
custom_element.html:
...
<template is="dom-repeat" items="{{testList}}">
<span>{{item}}</span>
<span>{{item.name}}</span>
</template>
...
However, the output is:
<span>[object DartObject]<span>
<span><span>
<span>[object DartObject]<span>
<span><span>
Why doesn't it show the object's name? How can I access the property name? Neither item.name nor item['name'] work... It used to work on Polymer 0.5 and the corresponding Polymer-Dart version. :(
This is a bit confusing as there is no documentation out yet for the new polymer version, but all you (should) need to do is make your class extend the JsProxy class. If you are on the behaviors branch you will also need to annotate the class with #jsProxyReflectable. For instance:
// #jsProxyReflectable // only if you are on the `behaviors` branch
class MyClass extends JsProxy {
#reflectable // from >= 1.0.0-rc.2
String name;
MyClass(String name) {
this.name = name;
}
}
I am trying to understand how observable getters work when they use other class instance properties:
When I bind the implicit getter/setter pair 'name' it updates in the input and in the div and everything is synced nicely.
However the explicit getter 'fullname' is not updating in the HTML. Is there a way to make that work (basically the 'fullname' in the element binding should update as well)?? Maybe I am missing a setter, but then again setter does not make sense here...
Very simple example to demonstrate:
test-element.html
<link rel="import" href="../../packages/polymer/polymer.html">
<polymer-element name="test-element">
<template>
<input value="{{ds.name}}">
<div>{{ds.name}}</div>
<div>{{ds.fullname}}</div>
</template>
<script type="application/dart" src="test1.dart"></script>
</polymer-element>
test-element.dart
import 'package:polymer/polymer.dart';
import 'package:popoli/sysmaster-settings.dart';
#CustomTag('test-element')
class TestElement extends PolymerElement {
#observable VerySimpleTest ds;
TestElement.created() : super.created() {
ds = new VerySimpleTest()..name = 'Peter';
}
}
ds.dart
class VerySimpleTest extends Observable {
#observable String name = '';
#observable String get fullname => 'Test: $name';
VerySimpleTest() : super();
}
You need to notify Polymer that a value has changed the getter depends on.
String set name(String val) {
name = notifyPropertyChange(#fullname, name, val);
}
or this should work too
#ComputedProperty('Test: $name') String get fullname => 'Test: $name';
See http://japhr.blogspot.co.at/2014/08/the-polymerdart-computedproperty.html for more details.
Some minor adaptations on Günter’s proposal make it work for me:
class VerySimpleTest extends Observable {
String _name = '';
// #observable // apparently, not even required
String get name => _name;
// #observable // apparently, not even required
String get fullname => 'Test: $name';
set name(String val) {
String oldVal = _name;
_name = notifyPropertyChange(#name, oldVal, val);
_name = notifyPropertyChange(#fullname, oldVal, val);
}
}
I'm setting up custom Polymer elements that need to display data from the model (Property objects that need to be passed around).
I can successfully display the values of the properties, but when I update a value inside a Property object, the value is not updated inside the element.
What am I missing? I based myself on the following examples:
https://github.com/sethladd/dart-polymer-dart-examples/tree/master/web/bind_component_field_to_model_field
https://github.com/sethladd/dart-polymer-dart-examples/tree/master/web/getter_setter_as_attribute
wb-control-text.html
<polymer-element name="wb-control-text">
<template>
<div>
<label for="{{id}}">{{label}}</label>
<input type="text" id="{{id}}" value="{{value}}" />
<button on-click="{{updateValue}}">Update me</button>
</div>
</template>
<script type="application/dart" src="wb-control-text.dart"></script>
</polymer-element>
wb-control-text.dart
import "package:polymer/polymer.dart";
import "package:rqn_workbench/wb-property.dart";
#CustomTag("wb-control-text")
class WorkbenchControlTextElement extends PolymerElement {
final TextProperty _idProperty = new TextProperty("id", value: "1");
final TextProperty _valueProperty = new TextProperty("value", value:"Bla");
final TextProperty _labelProperty = new TextProperty("label", value:"Label!");
WorkbenchControlTextElement.created() : super.created();
#published String get id => _idProperty.value;
void set id(String id) { _idProperty.value = id; }
#published String get value => _valueProperty.value;
void set value(String value) { _valueProperty.value = value; }
#published String get label => _labelProperty.value;
void set label(String label) { _labelProperty.value = label; }
void updateValue() {
value += "Bla";
print(value); // Will print the appended value
}
}
wb-property.dart
library workbench_property;
import "package:polymer/polymer.dart";
#observable
class Property {
String key;
}
#observable
class TextProperty extends Property {
String value;
TextProperty ( String key, {String value} ) {
this.key = key;
this.value = value;
}
}
The solution was to use #reflectable on the getters and setters, so it could be picked up by the PathObserver.
until lately I could use bindProperty like shown below or in this question, but that has changed with 0.8.0 and I don't know how to change my code to get the old behaviour (doSomething() gets called):
<polymer-element name="my-login" attributes="model">
<template>
<template if="{{"model.isLoggedIn}}">
...
</template>
</template>
<script type= ... ></script>
</polymer-element>
.
#CustomTag("my-login")
class MyLogin extends PolymerElement with ObservableMixin {
LoginModel model;
#override
inserted() {
void doSomething() {
...
}
logoutChangeSubscription = bindProperty(model, #isLoggedIn, () => doSomething());
}
}
class Model extends Object with ObservableMixin {
#observable bool isLoggedIn = false;
}
With Polymer.dart 0.8 or greater, you can also use this convenience form:
isLoggedInChanged(oldValue) {
doSomething();
}
Notice how you can create a method inside your PolymerElement that uses a name of yourFieldName*Changed
There's also onPropertyChange as defined here: http://api.dartlang.org/docs/bleeding_edge/observe.html#onPropertyChange
From the docs:
class MyModel extends ObservableBase {
StreamSubscription _sub;
MyOtherModel _otherModel;
MyModel() {
...
_sub = onPropertyChange(_otherModel, const Symbol('value'),
() => notifyProperty(this, const Symbol('prop'));
}
String get prop => _otherModel.value;
set prop(String value) { _otherModel.value = value; }
}
Polymer.dart >= 1.0.0
#Property(observer: 'doSomething') bool isLoggedIn;
#reflectable
void doSomething(bool newValue, bool oldValue) => ...
or
#Observe('isLoggedIn')
void doSomething(event, detail) => ...
Polymer.dart < 1.0.0
Ok, found it
new PathObserver(model, "isLoggedIn").changes.listen((e) => doSomething());
It seems that the syntax has changed a little bit.
The syntax of the solution proposed by Günter now seems to be:
new PathObserver(model, "isLoggedIn").open((e) => doSomething());