Query elements inside <content> of a custom element - dart

I have two custom elements. The one of them is nested to another one. Something like this:
<polymer-element name="my-element">
<template>
<div>
Bla, Bla, Bla!
</div>
</template>
<script type="application/dart" src="my_element.dart"></script>
</polymer-element>
<polymer-element name="my-decorator">
<template>
<div>
<div>Decorator</div>
<content>
<!-- my-element will be here-->
</content>
</div>
</template>
<script type="application/dart" src="my_decorator.dart"></script>
</polymer-element>
using both custom elements in index.html:
<my-decorator>
<my-element></my-element>
</my-decorator>
How can I query the instance of the second element from the code behind of the first one?
#CustomTag('my-decorator')
class MyDecorator extends PolymerElement {
bool get applyAuthorStyles => true;
MyDecorator.created() : super.created() {
// TODO:
// get my-element instance here!
// ..
}
}

#override
void attached() {
// attached is an example the code an be placed somewhere else
// but some places are executed before the childs are added
super.attached();
var nodes = (shadowRoot.querySelector('content') as ContentElement).getDistributedNodes();
var myElement = nodes.firstWhere((e) => e is MyDecorator); // not sure about this `e is MyDecorator`
// you have to choose some way to identify the node in the result
// I currently have no example where I can try it myself
}
if the tag has an id attribute like id='c1' then this works too
var nodes = ($['c1'] as ContentElement).getDistributedNodes();

Related

Creating a custom Polymer element

I'm trying to create a custom Polymer element that extends paper-shadow to display a Tweet.
Here is my implementation:
tweet_element.html
<link rel="import" href="packages/paper_elements/paper_shadow.html">
<link rel="import" href="packages/polymer/polymer.html">
<link rel="stylesheet" href="tweet_element.css">
<polymer-element name="tweet-element" extends="paper-shadow">
<template>
<div id="header">
<div id="user-image">
<img src="{{userImageUrl}}">
</div>
<div id="details">
<div id="user">{{user}}</div>
<div id="date-published">{{datePublished}}</div>
</div>
</div>
<div id="content">
<div id="text">{{text}}</div>
<div id="photos">{{photos}}</div>
</div>
</template>
<script type="application/dart" src="twitter.dart"></script>
</polymer-element>
twitter.dart
import 'package:polymer/polymer.dart';
#CustomTag('tweet-element')
class TweetElement extends PolymerElement {
#Observable String userImageUrl;
#Observable String user;
#Observable String datePublished;
#Observable String text;
TweetElement.created() : super.created();
void update(Tweet tweet) {
userImageUrl = tweet.user.profileImage;
user = '${tweet.user.name} (#${tweet.user.screenName})';
datePublished = _parseDate(tweet.date);
text = tweet.text;
}
...
}
And finally the code that creates a TweetElement and tries to add it to the DOM:
import 'dart:html';
import 'package:polymer/polymer.dart';
import 'twitter.dart';
...
var mainContent = querySelector('#main-content');
var element;
for (var tweet in tweets) {
element = new TweetElement.created();
element.update(tweet);
mainContent.children.add(element);
}
And when I run this I get:
Exception: Uncaught Error: created called outside of custom element creation.
So then I tried to change twitter.dart to:
TweetElement.created() : super.created();
TweetElement (Tweet tweet) {
TweetElement.created();
userImageUrl = tweet.user.profileImage;
user = '${tweet.user.name} (#${tweet.user.screenName})';
datePublished = _parseDate(tweet.date);
text = tweet.text;
}
And add the element to the DOM like this:
var mainContent = querySelector('#main-content');
var element;
for (var tweet in tweets) {
element = new TweetElement(tweet);
mainContent.children.add(element);
}
And now I got this error:
Internal error: unresolved implicit call to super constructor 'PolymerElement()' TweetElement (Tweet tweet) {
Ralph.
You can do that in two ways:
Option A
If you want to create a Polymer Element in your Dart code, you just have to create that tag this way:
var myTweetElement = new Element.tag ('tweet-element');
mainContent.childern.add(myTweetElement);
Also, check that you have imported the html library:
import 'dart:html';
Option B: (the coolest one)
You can do it extra convenient (and more object oriented) by adding this factory to your twitter.dart:
factory TweetElement () => new Element.tag('tweet-element');
Then, the creation of the tag in your dart code, is more readable:
var myTweetElement = new TweetElement (); // This uses the factory transparently
mainContent.children.add(myTweetElement);
Additionally, you can set the values of your properties. As those are observable, the bindings will update automagically:
myTweetElement.userImageUrl = "foo"
myTweetElement.user = "bar"
myTweetElement.datePublished = "baz"
myTweetElement.text = "qux"
Or if you like the cascade operator, you can write this instead:
myTweetElement..userImageUrl = "foo"
..user = "bar"
..datePublished = "baz"
..text = "qux";
Hope this helps.

Template data binding

I'm trying to create a custom element with data binding.
Here is my custom element template:
<link rel="import" href="packages/paper_elements/paper_shadow.html">
<link rel="import" href="packages/polymer/polymer.html">
<polymer-element name="tweet-element">
<template>
<link rel="stylesheet" href="tweet_element.css">
<paper-shadow z="1">
<div id="header" horizontal layout>
<div id="user-image">
<img _src="{{profileImage}}">
</div>
<div id="user-details" flex>
<div horizontal layout>
<div id="name">{{name}}</div>
<div id="screen-name">(#{{screenName}})</div>
</div>
<div id="date-published">{{date}}</div>
</div>
</div>
<div id="content">
<div id="text">{{text}}</div>
</div>
</paper-shadow>
</template>
<script type="application/dart" src="twitter.dart"></script>
</polymer-element>
twitter.dart
import 'dart:html';
import 'package:polymer/polymer.dart';
import 'package:polymer_expressions/polymer_expressions.dart';
#CustomTag('tweet-element')
class TweetElement extends PolymerElement {
#Observable String profileImage;
#Observable String name;
#Observable String screenName;
#Observable String date;
#Observable String text;
TweetElement.created() : super.created();
factory TweetElement() => new Element.tag('tweet-element');
}
This is how I'm creating and adding the elements:
main.dart
import 'dart:html';
import 'package:polymer/polymer.dart';
import 'package:polymer_expressions/polymer_expressions.dart';
import 'twitter.dart';
void main() {
...
var mainContent = querySelector('#main-content');
var element;
for (var tweet in tweets) {
element = new TweetElement();
element
..profileImage = tweet.user.profileImage
..name = tweet.user.name
..screenName = tweet.user.screenName
..date = _parseDate(tweet.date)
..text = tweet.text;
mainContent.children.add(element);
}
}
The tweet-element elements and being added to the DOM, but the fields with data binding are blank:
There is no problem with the tweet objects, because I've tried setting the element fields with other Strings and it also didn't work.
If you have a custom main method in a Polymer.dart application, you need to take care of Polymer initialization yourself.
See how to implement a main function in polymer apps for more details.
I managed to solve the problem. I was using the #Observable tag instead of #observable (notice upper/lowercase letters).

How to update the view on property change of objects in ObservableMap?

I have an observable map, which maps arbitrary objects (say another maps) to their id's. When I make changes to these objects, the objects should be updated in the view, too. However, I didn't get it to work. Here is my setup so far:
myexample.html
<polymer-element name="my-example">
<script type="application/dart" src="myexample.dart"></script>
<template>
<style></style>
<div>
<ul>
<template repeat="{{ entry in map.values }}">
<li>{{ entry }}</li>
</template>
</ul>
<button on-click="{{change}}">Change</button>
</div>
</template>
</polymer-element>
myexample.dart
#CustomTag('my-example')
class MyExample extends PolymerElement {
#observable Map<int, String> map = toObservable({'123': {'name': 'XYZ', 'size': 12}});
MyExample.created() : super.created() {
map.changes.listen((_) => notifyPropertyChange(#map, 1, 0));
}
void change() {
var object = map['123']
object['size'] = 100;
map.notifyChange(new MapChangeRecord('123', null, object));
}
}
On clicking the 'Change'-Button, the object with id '123' is updated in the map, but isn't updated in view. Has anyone an idea, how to deliver the changes to the view?
<link rel="import" href="../../packages/polymer/polymer.html">
<polymer-element name="app-element">
<template>
<div>
<ul>
<template repeat="{{ entry in map.values }}">
<li>x{{ entry['name'] }} {{ entry['size']}}</li>
<template repeat="{{ item in entry.values}}">
y{{item}}
</template>
</template>
</ul>
<button on-click="{{change}}">Change</button>
</div>
</template>
<script type="application/dart" src="app_element.dart"></script>
</polymer-element>
import 'package:polymer/polymer.dart';
#CustomTag('app-element')
class AppElement extends PolymerElement {
#observable
Map map = toObservable({'123': {'name': 'XYZ', 'size': 12}});
AppElement.created() : super.created();
void change() {
var object = map['123']['size'] = 100;
}
}

Nested Polymer Element Not Attached When Run As JavaScript

I have built a Polymer.dart app that uses nested Polymer elements. The parent element takes an attribute and passes its value to the nested, child elment as an attribute. This works fine when "Run in Dartium" from within DartEditor, but the nested element fails to load after the app is "Pub Built" and "Run as JavaScript." There are no error messages during the build process, or any pointers of any other sort. I don't know how to debug this and the fact that it runs as expected without any warnings or errors in Dartium doesn't help.
The following is the code for the simplified version of my app that produces the same problem. my_view is the parent element and my_form is the nested element that is attached when my_view is loaded.
main.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Sample</title>
<link rel="import" href="my_view.html">
<script type="application/dart">export 'package:polymer/init.dart';</script>
<script src="packages/browser/dart.js"></script>
</head>
<body>
<h1>The view polymer element should appear below:</h1>
<my-view viewAttribute="My_Value"></my-view>
</body>
</html>
my_view.html
<polymer-element name="my-view" attributes="viewAttribute">
<link rel="import" href="my_form.html">
<template>
<div style="width: 100%;"><h1>The form should appear below:</h1></div>
<div id="form_div" style="width: 100%;"></div>
</template>
<script type="application/dart" src="my_view.dart"></script>
</polymer-element>
my_view.dart
import 'package:polymer/polymer.dart';
import 'dart:html';
#CustomTag('my-view')
class MyView extends PolymerElement {
#published String viewAttribute;
DivElement _formSlot;
MyView.created() : super.created() {
_formSlot = $['form_div'];
}
void viewAttributeChanged() {
_formSlot..append(new Element.tag('form', 'my-form')..setAttribute("formAttribute", viewAttribute));
}
}
my_form.html
<polymer-element name="my-form" extends="form" attributes="formAttribute">
<template>
<div style="width: 100%;">Attribute value: {{formAttribute}}</div>
<div style="width: 100%;">
<label for="nameInput">name:</label>
<input id="nameInput" type="text" value="{{nameValue}}" />
</div>
<div style="width: 100%;">
<div id="button_div">
<input type="submit" on-click="{{submitForm}}" value="send" />
</div>
</div>
</template>
<script type="application/dart" src="my_form.dart"></script>
</polymer-element>
my_form.dart
import 'package:polymer/polymer.dart';
import 'dart:async';
import 'dart:html';
import 'dart:convert';
#CustomTag('my-form')
class MyForm extends FormElement with Polymer, Observable {
#published String formAttribute;
#observable String nameValue;
HttpRequest _request;
MyForm.created() : super.created();
void submitForm(Event e, var detail, Node target) {
e.preventDefault();
_request = new HttpRequest();
_request.onReadyStateChange.listen(_onData);
_request.open('POST', 'http://my.server.com/contact_form');
_request.send(JSON.encode({'name': nameValue, 'attribute': formAttribute}));
}
_onData(_) {
if (_request.readyState == HttpRequest.DONE) {
switch (_request.status) {
case 200:
// Data was posted successfully
break;
case 0:
// Post failed
break;
}
}
}
}
Any help, hints, well wishes, prayers would be greatly appreciated! Thanks!
I guess this is a dart2js bug.
For answering this question Dynamically create polymer element I built an example.
This code (like you showed in your question) generates the correct markup but the form element didn't work properly. I didn't examine this behaviour further because I myself never needed to extend a DOM element.
You could also try not to extend the <FORM> element but create a new Polymer element and just embed the <FORM> element.

How can a Dart Web Component obtain a reference to its children?

For illustration reasons, I've created a class inheriting from WebComponent called FancyOption that changes to a background color specified by text in one child element upon clicking another child element.
import 'package:web_ui/web_ui.dart';
import 'dart:html';
class FancyOptionComponent extends WebComponent {
ButtonElement _button;
TextInputElement _textInput;
FancyOptionComponent() {
// obtain reference to button element
// obtain reference to text element
// failed attempt
//_button = this.query('.fancy-option-button');
// error: Bad state: host element has not been set. (no idea)
// make the background color of this web component the specified color
final changeColorFunc = (e) => this.style.backgroundColor = _textInput.value;
_button.onClick.listen(changeColorFunc);
}
}
FancyOption HTML:
<!DOCTYPE html>
<html>
<body>
<element name="x-fancy-option" constructor="FancyOptionComponent" extends="div">
<template>
<div>
<button class='fancy-option-button'>Click me!</button>
<input class='fancy-option-text' type='text'>
</div>
</template>
<script type="application/dart" src="fancyoption.dart"></script>
</element>
</body>
</html>
I have three of them on a page like this.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Sample app</title>
<link rel="stylesheet" href="myapp.css">
<link rel="components" href="fancyoption.html">
</head>
<body>
<h3>Type a color name into a fancy option textbox, push the button and
see what happens!</h3>
<div is="x-fancy-option" id="fancy-option1"></div>
<div is="x-fancy-option" id="fancy-option2"></div>
<div is="x-fancy-option" id="fancy-option3"></div>
<script type="application/dart" src="myapp.dart"></script>
<script src="packages/browser/dart.js"></script>
</body>
</html>
Just use getShadowRoot() and query against it:
import 'package:web_ui/web_ui.dart';
import 'dart:html';
class FancyOptionComponent extends WebComponent {
ButtonElement _button;
TextInputElement _textInput;
inserted() {
// obtain references
_button = getShadowRoot('x-fancy-option').query('.fancy-option-button');
_textInput = getShadowRoot('x-fancy-option').query('.fancy-option-text');
// make the background color of this web component the specified color
final changeColorFunc = (e) => this.style.backgroundColor = _textInput.value;
_button.onClick.listen(changeColorFunc);
}
}
Where x-fancy-option string is the name of the element.
Note: I changed your constructor to be inserted() method, which is a life cycle method.
I understand that _root is depracated. Answers recommending _root should use getShadowRoot() in place of _root.

Resources