The following code doesn't work. Maybe I do something wrong.. Please correct my code:
index.html:
<html>
<head>
<title>Page</title>
<link rel="import" href="msg_box.html">
</head>
<body>
<msg-box id="msg" caption="Caption 1"></msg-box>
<button id="btn">click me</button>
<script type="application/dart" src="index.dart"></script>
<script src="packages/browser/dart.js"></script>
</body>
</html>
import 'dart:html';
import 'package:polymer/polymer.dart';
import 'msg_box.dart';
void main() {
initPolymer();
ButtonElement btn = querySelector("#btn");
btn.onMouseEnter.listen((e) {
MsgBoxElement elm = querySelector("#msg");
window.alert(elm.caption); // SHOWS 'Caption 1'
elm.caption = "Caption 2"; // DON'T WORK!
window.alert(elm.caption); // SHOWS 'Caption 2', BUT PAGE SHOWS 'Caption 1'!!!
});`
}
msg_box.html
<polymer-element name="msg-box" attributes="caption">
<template>
<h4>{{caption}}</h4>
</template>
<script type="application/dart" src="msg_box.dart"></script>
</polymer-element>
import 'package:polymer/polymer.dart';
#CustomTag('msg-box')
class MsgBoxElement extends PolymerElement {
// fields
String _caption;
String get caption => _caption;
void set caption(String value) {
_caption = notifyPropertyChange(#caption, _caption, value);
}
MsgBoxElement.created() : super.created() {
}
}
This issue is critical for me. See also https://code.google.com/p/dart/issues/detail?id=14753&sort=-id&colspec=ID%20Type%20Status%20Priority%20Area%20Milestone%20Owner%20Summary
I believe the problem here is that there are pending change notifications not being processed because your code is not running in the dirty-checking zone. There are two things you can do to fix this:
call Observable.dirtyCheck() right after your update to caption; or,
run your code within the dirty-checking zone:
void main() {
var dirtyCheckingZone = initPolymer();
dirtyCheckingZone.run(() {
ButtonElement btn = querySelector("#btn");
btn.onMouseEnter.listen((e) {
MsgBoxElement elm = querySelector("#msg");
elm.caption = "Caption 2";
});
});
}
This zone makes sure that after any callback or listener is executed, we'll call Observable.dirtyCheck for you. This approach is slightly better than calling dirtyCheck explicitly because, when we compile for deployment, we switch from dirty-checking to explicit notifications. The zone returned by initPolymer is changed to reflect this.
A separate note: the MsgBoxElement above can be simplified if you use the #published annotation. This is meant to express that a property is both observable and exposed as an attribute of your element.
import 'package:polymer/polymer.dart';
#CustomTag('msg-box')
class MsgBoxElement extends PolymerElement {
#published String caption;
MsgBoxElement.created() : super.created();
}
Based on your information, it appears that the model is being updated, however the DOM isn't updating, most likely because an observable element isn't set. Try adding the following annotations to your msg_box dart code:
import 'package:polymer/polymer.dart';
#CustomTag('msg-box')
class MsgBoxElement extends PolymerElement {
// fields
#observable String _caption;
#reflectable String get caption => _caption;
#reflectable void set caption(String value) {
_caption = notifyPropertyChange(#caption, _caption, value);
}
MsgBoxElement.created() : super.created() {
}
}
See this Breaking change announcement on the dart mailing lists regarding the #reflectable attribute. Also see this discussion on setting up #observable getters
Related
I want to be able to catch the click event on the paper button and change the "clic me" text.
my-module.html:
<dom-module id="my-module">
<template>
<paper-button>{{btnTxt}}</paper-button>
</template>
<script type="application/dart" src="my-module.dart"></script>
</dom-module>
my-module.dart:
#HtmlImport('my-module.html')
library blink.my_module;
import 'package:polymer/polymer.dart';
import 'package:web_components/web_components.dart' show HtmlImport;
#PolymerRegister('my-module')
class MyModule extends PolymerElement {
#property String btnTxt = "Clic me!";
MyModule.created() : super.created();
}
OK, to do so you have to add this to the following files:
my-module.html:
//Adding the 'on-tap' tag with the dart methode name attribute, can also use 'on-click'
<paper-button on-tap="btnClicked">{{btnTxt}}</paper-button>
my-module.dart:
#PolymerRegister('my-module')
class MyModule extends PolymerElement {
#property String btnTxt = "Clic me!";
MyModule.created() : super.created();
//this method will listen for the linked event in the html
#reflectable
void btnClicked(Event e, [_]) {
set('btnTxt', btnTxt = 'Button Clicked!');
}
}
I read Polymer API developer guide, but unfortunately it has examples only for JavaScript developers. Some examples I try to port into Dart, but in this case I get fail. Please, go to https://www.polymer-project.org/0.5/docs/polymer/polymer.html#global (Section Supporting global variables). Here is a clip from the documentation:
elements.html
<polymer-element name="app-globals">
<script>
(function() {
var values = {};
Polymer({
ready: function() {
this.values = values;
for (var i = 0; i < this.attributes.length; ++i) {
var attr = this.attributes[i];
values[attr.nodeName] = attr.value;
}
}
});
})();
</script>
</polymer-element>
<polymer-element name="my-component">
<template>
<app-globals id="globals"></app-globals>
<div id="firstname">{{$.globals.values.firstName}}</div>
<div id="lastname">{{$.globals.values.lastName}}</div>
</template>
<script>
Polymer({
ready: function() {
console.log('Last name: ' + this.$.globals.values.lastName);
}
});
</script>
</polymer-element>
index.html
<app-globals id="globals" firstname="Addy" lastname="Osmani"></app-globals>
Questions:
How to implement this code in Dart?
Reading the code of different Q&A concerning Dart Polymer usage I come across with #observable annotation, toObserve() function and class CustomElement extends PolymerElement with Observable {..}. I know that they somehow related with data bindings, but I have no idea exactly how.
app_gobals.html
<link rel="import" href="packages/polymer/polymer.html">
<polymer-element name="app-globals">
<template>
<style>
:host {
display: none;
}
</style>
</template>
<script type="application/dart" src="app_globals.dart"></script>
</polymer-element>
app_gobals.dart
import 'package:polymer/polymer.dart';
import 'dart:async' show Timer;
#CustomTag('app-globals')
class AppGlobals extends PolymerElement {
static final ObservableMap _staticValues = toObservable({});
Map get values => _staticValues;
AppGlobals.created() : super.created();
ready() {
attributes.keys.forEach((k) {
values[k] = attributes[k];
});
// just to demonstrate that value changes are reflected
// in the view
new Timer.periodic(new Duration(seconds: 2),
(_) => values['periodic'] = new DateTime.now());
}
}
app_element.html (your my-component)
<link rel="import" href="packages/polymer/polymer.html">
<link rel="import" href="app_globals.html">
<polymer-element name="app-element">
<template>
<style>
:host {
display: block;
}
</style>
<app-globals id="globals"></app-globals>
<div>{{$["globals"].values["firstname"]}}</div>
<div>{{$["globals"].values["lastname"]}}</div>
<div>{{$["globals"].values["periodic"]}}</div>
</template>
<script type="application/dart" src="app_element.dart"></script>
</polymer-element>
app_element.dart
import 'package:polymer/polymer.dart';
#CustomTag('app-element')
class AppElement extends PolymerElement {
AppElement.created() : super.created();
ready() {
print('Last name: ${$["globals"].values["lastName"]}');
}
}
#observable indicates that Polymer should be notified when the value changes so it can update the view.
If you have a collection this is not enough because Polymer only gets notified when the field changes (another collection or null is assigned). toObservable(list|map) ensures that Polymer gets notified when elements in the collection are changed (removed/added/replaced).
PolymerElement includes Observable there fore there is nothing special to do on class level. When you extend a DOM element this looks a bit different see https://stackoverflow.com/a/20383102/217408.
Update
This are a lot of questions. I use static final ObservableMap _staticValues = toObservable({}); to ensure all values are stored in one place no matter how many <app-globals> elements your application contains. Statics are stored in the class not in the instance therefore it doesn't matter how many instances exist. #ComputedProperty(expression) var someField; watches expression for value changes and notifies Polymer to update bindings to someField. #observable is the simpler version but works only for fields. #published is like #observable but in addition allows bindings to the field from outside the element. #PublishedProperty() is the same as #published but this annotation form allows to pass arguments. #PublishedProperty(reflect: true) is like #published but in addition updates the actual DOM attribute to make the bound value available not only for other Polymer elements to bind to but also for CSS or other Frameworks which have no knowledge how to bind to Polymer fields.
I found another solution that works for me:
Just define a class for your globals. Use a factory constructor so that every representation of the class is the same instance. The class must extend Object with Observable:
globals.dart:
library globals;
import 'package:polymer/polymer.dart';
class Globals extends Object with Observable {
static Globals oneAndOnlyInstance;
#observable String text; // a "global" variable
#observable int integer; // another "global" variable
factory Globals() {
if (oneAndOnlyInstance==null) {
oneAndOnlyInstance=new Globals.init();
}
return oneAndOnlyInstance;
}
Globals.init();
}
Now you can use this class inside of all your custom elements. Only need to import globals.dart and create an object of it:
inside: main_app.dart:
import 'dart:html';
import 'package:polymer/polymer.dart';
import 'package:abundan/classes/globals.dart';
/// A Polymer `<main-app>` element.
#CustomTag('main-app')
class MainApp extends PolymerElement {
Globals globals=new Globals();
/// Constructor used to create instance of MainApp.
MainApp.created() : super.created();
attached() {
super.attached();
globals.text="HELLO!";
}
and inside of main_app.html:
{{globals.text}}
This works for me and it seems to be the easiest way to have something like globals with polymer custom elements.
I have a Dart app in which I need to set a number of fields based on what's coming back from a JSON request. I'm extending PolymerElement and using #observable to keep the field in sync. What I want to be able to do is to set a global variable and have that drive the class level variable. I'm sure I'm missing something fundamental, but I can't access the object that's being used by the HTML because it hasn't been instantiated (or has it?).
mobilemenu.dart
library mobilemenu;
import 'dart:html';
import 'order_item.dart' as orderitem;
main() {}
mobilemenu.html
<!DOCTYPE html>
<html>
<head>
<link rel="import" href="order_item.html">
<script src="packages/polymer/boot.js"></script>
</head>
<body>
<div class="mobile-menu-product-name">
<order-item></order-item>
</div>
</body>
</html>
order_item.dart
library orderitem;
import 'package:polymer/polymer.dart';
import 'dart:html';
import 'dart:json' as JSON;
#observable String g_product_name;
#CustomTag('order-item')
class OrderItem extends PolymerElement with ObservableMixin {
#observable String product_name = g_product_name;
}
Map processString(String jsonString) {
// Create a map from json string
Map jsonParse = JSON.parse(jsonString);
print(jsonParse);
return jsonParse;
}
setGlobals(Map jsonMap) {
g_product_name = jsonMap["details"][0]["product_name"];
print(g_product_name);
}
main() {
var baseUrl = "https://example.com/add_item.json";
var params = window.location.search;
var fullUrl = baseUrl + params;
HttpRequest.getString(fullUrl)
.then(processString)
.then(setGlobals);
}
order_item.html
<polymer-element name="order-item">
<template>
<div>
<span>{{product_name}}</span>
</div>
</template>
<script type="application/dart" src="order_item.dart"></script>
</polymer-element>
Edit--Working Example
Your real issue is that
#observable String product_name = g_product_name;
doesn't set product_name to g_product_name, it sets product_name to the value of g_product_name at the time of assignment, which is null. Changing the value of g_product_name does not change the value of product_name.
I got around it by making a container for your global variable and making the property observable. That way you can set OrderItem property to the same object that your global variable is set to and it will bind the template to the property in your global variable.
add_item.json
{"details": [{"product_name": "foobar"}]}
order_item.html
<!DOCTYPE html>
<html>
<head>
<link rel="import" href="order_item.html">
<script src="packages/polymer/boot.js"></script>
</head>
<body>
<div class="mobile-menu-product-name">
<order-item></order-item>
</div>
</body>
</html>
order_item.dart
library orderitem;
import 'package:polymer/polymer.dart';
import 'dart:html';
import 'dart:json' as JSON;
class GlobalVariable extends ObservableBase {
#observable
String g_product_name;
}
GlobalVariable globalVariable = new GlobalVariable();
#CustomTag('order-item')
class OrderItem extends PolymerElement with ObservableMixin {
GlobalVariable _globalVariable = globalVariable;
}
Map processString(String jsonString) {
// Create a map from json string
Map jsonParse = JSON.parse(jsonString);
print(jsonParse);
return jsonParse;
}
setGlobals(Map jsonMap) {
globalVariable.g_product_name = jsonMap["details"][0]["product_name"];
print(globalVariable.g_product_name);
}
main() {
HttpRequest.getString("add_item.json")
.then(processString)
.then(setGlobals);
}
It works for me. I replaced your main in order_item.dart with the following and it updated the order item to foobar.
main() {
setGlobals({"details":
[
{
"product_name": "foobar"
}
]
});
}
Obviously example.org/add_item.json isn't a working url, so I'd check to make sure the url is returning what you expect and the json structure is correct. Otherwise, it should work.
My question is: How to observe changes on simple variable like String or num?
I know that you can easy observe object like this:
observe(t, (e) => print ("Value changed"));
but How to do this on simple variable?
(This answer applies to Polymer.dart.)
The observe package includes a wrapper for single observable values: ObservableBox.
import 'package:observe/observe.dart';
import 'dart:async';
void main() {
ObservableBox myValue = new ObservableBox('hello');
myValue.changes.listen((List<ChangeRecord> records) {
PropertyChangeRecord record = records[0] as PropertyChangeRecord;
print('${record.field} changed, it is now ${myValue.value}');
});
new Timer.periodic(const Duration(seconds: 1), (t) {
myValue.value = new DateTime.now();
});
}
There is no way to observe a top-level or function-scope single string, boolean, int, or double without using ObservableBox.
If the string, boolean, int, or double is a field of a class, you can use ObservableMixin and the #observable annotation.
class Foo extends Object with ObservableMixin {
#observable String bar = '';
}
You can then get notified when an instance of Foo changes:
foo.changes.listen((List<ChangeRecord> records) {
// ...
});
Here is an example of the binding of a string value that is object attribute:
<!DOCTYPE html>
<html>
<head>
<title>index</title>
<script src="packages/polymer/boot.js"></script>
</head>
<body>
<template id="_template" bind>
<input type="text" value="{{name}}">
<p>The name is {{name}}</p>
</template>
<script type="application/dart" src="index.dart"></script>
</body>
</html>
import 'dart:html';
import 'package:polymer/polymer.dart';
class Person extends Object with ObservableMixin {
#observable
String name;
Person(this.name);
}
main() {
query("#_template").model = new Person('Bob');
}
Using polymer in Dart I want to observe a value and bind a button to a function that changes it. The following sample code (test.dart) should clarify what I am trying to do
import 'dart:html';
import 'package:polymer/polymer.dart';
import 'package:observe/observe.dart';
import 'package:mdv/mdv.dart' as mdv;
class App extends Object with ObservableMixin {
int _counter = 0;
int get counter => _counter;
void set counter(int c) {
print("counter set to $c");
int oldValue = _counter;
_counter = notifyPropertyChange(const Symbol('counter'), _counter, c);
}
increment(var event) {
print("increment");
counter = counter + 1;
}
}
main() {
mdv.initialize();
var app = new App();
query("#tmpl1").model = app;
}
Used with this HTML
<!DOCTYPE html>
<html>
<head>
<title>Test</title>
</head>
<body>
<template id="tmpl1" bind>
<button on-click="increment">{{counter}}</button>
</template>
<script type="application/dart" src="test.dart"></script>
<script src="packages/browser/dart.js"></script>
</body>
</html>
I would expect when the button is clicked that increment inside the App class is invoked, but nothing happens. I am sure I am overlooking something simple, as surely this must be possible.
Note to run the example code above you do not need to create a build.dart file as no custom-elements are being used.
I have just read the target10 polymer example and I am not sure if you are looking for the kind of solution I am going to post.
get Target 10:polymer example from https://github.com/dart-lang/dart-tutorials-samples/tree/master/web
Insert <button on-click="increment">{{counter}}</button> anywhere into one of the divs in xslambookform.html
Insert anywhere into xslambookform.dart
``
int _counter=0;
int get counter => _counter;
void set counter(int c) {
print("counter set to $c");
int oldValue = _counter;
_counter = notifyPropertyChange(const Symbol('counter'), _counter, c);
}
void increment(var e, var detail, Element target) {
print("increment");
counter = counter + 1;
}
Run slambook.html and press your button. See in console:
increment
counter set to 1
increment
counter set to 2
It turns out that what I tried to achieve is possible. To get it working you need to include the following in the HTML
<script src="packages/polymer/boot.js"></script>
then the following works
<polymer-element name="x-app">
<template>
<button on-click="increment">{{counter}}</button>
</template>
<script type="application/dart">
import 'package:polymer/polymer.dart';
#CustomTag('x-app')
class XApp extends PolymerElement with ObservableMixin {
#observable int counter = 0;
increment(event, detail, target) {
print("increment");
counter++;
Observable.dirtyCheck();
}
}
</script>
</polymer-element>
<x-app></x-app>
And best of all it works without the need for a build step (i.e. no more build.dart).