Creating a custom Polymer element - dart

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.

Related

Setting a hidden attribute in dart-polymer 1.x

I am trying to toggle (hide/unhide> a state dropdown menu when the USA is selected in a country dropdown menu
.html
<paper-dropdown-menu
label = "Country *"
selected-item-label = "{{countrySelectedItemLabel}}"
error-message = "Country is required"
id = "countryDdm">
<paper-menu class = "dropdown-content">
<template
is = "dom-repeat"
items = "[[countries]]"
as = "country">
<div>[[country.name]]</div>
<br>
</template>
</paper-menu>
</paper-dropdown-menu>
<div class = "layout horizontal">
<paper-dropdown-menu
hidden = $"[[hideState]]"
label = "State *"
selected-item-label = "{{stateSelectedItemLabel}}"
id = "stateDdm">
<paper-menu class = "dropdown-content">
<template
is = "dom-repeat"
items = "[[states]]"
as = "state">
<div>[[state.name]]</div>
<br>
</template>
</paper-menu>
</paper-dropdown-menu>
The relevant .dart code is shown below
##.dart
#property
bool hideState;
#property
String countrySelectedItemLabel = '';
#Listen( 'countryDdm.tap' )
void toggleStateOnUSASelection( event, [_] ) {
switch ( countrySelectedItemLabel ) {
case 'United States of America':
switch ( hideState ) {
case true:
hideState = false;
break;
case false:
break;
}
break;
}
}
void ready( ) {
set('hideState', true);
}
The application is displayed normally but when I select 'United States of America' the state combo is NOT shown. I would also like to hide the state combo if any country than the USA is selected.
Any help is appreciated. Thanks.
The $ is on the wrong position
hidden$="[[hideState]]"
The actual hidden attribute is set by Polymer. This is to work around issues with attributes like href of the <img> element where binding expressions (strings that aren't an actual URI to an image) would produce an error message before Polymer even had a chance to resolve the binding expression.
There are a few further issues (see the comments in the code)
dart
#HtmlImport('app_element.html')
library so33931432_hide_paper_dropdown.web.app_element;
import 'package:web_components/web_components.dart' show HtmlImport;
import 'package:polymer/polymer.dart';
import 'package:polymer_elements/paper_dropdown_menu.dart';
import 'package:polymer_elements/paper_menu.dart';
/// [PaperDropdownMenu], [PaperMenu]
#PolymerRegister('app-element')
class AppElement extends PolymerElement {
AppElement.created() : super.created();
#property
List<String> countries = ['Austria', 'Belgium', 'Czech Republic', 'United States of America'];
#property
bool hideState; // = true; // you could set a default value, then you don't need ready
// This way usually works better for me than #Listen
#Property(observer: 'toggleStateOnUSASelection')
String countrySelectedItemLabel = '';
// If `#Property(observer: ...) is used the function signature
// has to be changed
#reflectable
void toggleStateOnUSASelection(String label, [_]) {
switch (countrySelectedItemLabel) {
// I wasn't sure what you actually tried to accomplish here
// but this worked for me to reproduce your problem
case 'United States of America':
set('hideState', false);
break;
default:
set('hideState', true);
}
}
void ready() {
set('hideState', true);
}
}
html
<!DOCTYPE html>
<dom-module id='app-element'>
<template>
<paper-dropdown-menu
label="Country *"
selected-item-label="{{countrySelectedItemLabel}}"
error-message="Country is required"
id="countryDdm">
<paper-menu class="dropdown-content">
<template
is="dom-repeat"
items="[[countries]]"
as="country">
<div label="[[country]]">[[country]]</div>
</template>
</paper-menu>
</paper-dropdown-menu>
<div>selectedItem:<span>[[countrySelectedItemLabel]]</span> value: <span>[[countrySelectedItemLabel]]</span></div>
<div>hideState: <span>[[hideState]]</span></div>
<div class="layout horizontal">
<paper-dropdown-menu
hidden$="[[hideState]]"
label="State *"
selected-item-label="{{stateSelectedItemLabel}}"
id="stateDdm">
<paper-menu class="dropdown-content">
<template
is="dom-repeat"
items="[[states]]"
as="state">
<div>[[state.name]]</div>
<br>
</template>
</paper-menu>
</paper-dropdown-menu>
</div> <!-- missing in your question -->
</template>
</dom-module>

Polymer linkPaths does not fire on-change et all

EDITED: NOW WITH COMPLETE CODE
Previous Post related
LinkPaths works, but does not fire all on-change event of the elements where the data changed. See the image:
For example, when data is changed on <paper-input label="Produto" value="{{item.descricao}}"></paper-input> (outer dom-repeat) the data in element <paper-input label="Produto Componente" value="{{item_comp.descricao}}" on-change="mudou"></paper-input> (inner dom-repeat) change too. It´s works nice because of the linkPaths!
But on-change="mudou" does not fire.
Just fire if data entry directly on <paper-input label="Produto Componente" value="{{item_comp.descricao}}" on-change="mudou"></paper-input> (inner dom-repeat)
main_app.html
<dom-module id="main-app">
<style>
:host {
display: block;
}
</style>
<template>
<h3>Lista de Produtos</h3>
<template is="dom-repeat" items="{{produtos}}" as="item">
<div class="layout vertical">
<paper-material elevation="1">
<div class="layout horizontal">
<paper-input label="Produto" value="{{item.descricao}}"></paper-input>
</div>
</paper-material>
<template is="dom-repeat" items="{{item.componentes}}" as="item_comp">
<div class="layout vertical">
<div class="layout horizontal">
<paper-input label="Produto Componente" value="{{item_comp.descricao}}" on-change="mudou"></paper-input>
</div>
</div>
</template>
</div>
</template>
</template>
</dom-module>
main_app.dart
import 'dart:html';
import 'package:polymer_elements/paper_input.dart';
import 'package:polymer_elements/paper_fab.dart';
import 'package:polymer_elements/iron_icons.dart';
import 'package:polymer_elements/paper_material.dart';
import 'package:polymer/polymer.dart';
import 'package:web_components/web_components.dart';
#PolymerRegister('main-app')
class MainApp extends PolymerElement {
#Property(notify: true)
List<Produto> produtos = new List();
MainApp.created() : super.created();
void ready() {
Produto p = new Produto()..descricao = 'componente 1';
add('produtos', p);
Produto p2 = new Produto()..descricao = 'componente 2';
add('produtos', p2);
Produto p3 = new Produto()..descricao = 'composto';
add('produtos', p3);
add('produtos.2.componentes', p);
add('produtos.2.componentes', p2);
linkPaths('produtos.2.componentes.0', 'produtos.#0');
linkPaths('produtos.2.componentes.1', 'produtos.#1');
linkPaths('produtos.0', 'produtos.#2.componentes.#0');
linkPaths('produtos.1', 'produtos.#2.componentes.#1');
}
#reflectable
void adicionarProduto(e, d) {
//...
}
#reflectable
mudou(e, d) {
window.alert('teste');
}
}
class Produto extends JsProxy {
#reflectable
String descricao;
#reflectable
List<Produto> componentes;
Produto() {
componentes = new List();
}
}
Is this normal in Polymer? How I can to force fire on-change?
I found a workaround solution.
I used computed binding method, no more on-change event. This form, I passed the paper-input value reference on method call, and all time that value change, the method is called.
The code changed:
.html
...
<paper-input label="Produto Componente" value="{{item_comp.descricao}}"></paper-input>
<span>{{mudou(item_comp.descricao)}}</span>
...
.dart
...
#reflectable
void mudou(a) {
window.alert('teste');
}
...

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).

Query elements inside <content> of a custom element

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();

Getting error converting dart2js on polymer project

Unsupported operation: Can't use ownerName in reflection because it is not included in a #MirrorsUsed annotation.
ownerName is just an published attribute on the polymer element. I understand there are a few things out there (on web, not on here) like this but none have a solid answer...
I also get this below it:
NoSuchMethodError : method not found: 'Symbol("title")'
Anyone got any ideas. Been wrestling with this for 3 hours and ready to dump polymer. Though It was fun in dartium, if it cannot convert to JS I see no real use in it.
import 'package:polymer/polymer.dart';
import 'package:google_postendpoint_v1_api/postendpoint_v1_api_browser.dart';
import 'package:google_postendpoint_v1_api/postendpoint_v1_api_client.dart';
import 'dart:math';
//import 'package:intl/intl.dart';
/**
* A Polymer post element.
*/
#CustomTag('post-element')
class SoberSky extends PolymerElement {
#published int count = 0;
#published String ownerName = "Casey";
#published int ownerId = 888;
#published int postId = 333;
#published String content = "yo ho ho, and a bottle of rumsky!";
#published String postContent = "This is an example of post content";
#published int getOwnerId = 333;
#published CollectionResponse_Comment listComment;
#published CollectionResponse_Post listPost;
#published String listCommentHTML;
#published Post currentPost;
#published bool yes = false;
Postendpoint endpoint = new Postendpoint();
var rng = new Random();
var commentList;
SoberSky.created() : super.created() {
endpoint.rootUrl = "http://localhost:8888/"; // set the root URL
}
void getListComment([int tempPostId]) {
if (tempPostId == null) {
tempPostId = postId;
}
endpoint.listComment( tempPostId ).then((CollectionResponse_Comment commentCollection){
this.listComment = commentCollection;
}).catchError((e) => handleError(e));
}
void getPost() {
endpoint.getPost( postId ).then((Post loadedMessage) {
currentPost = loadedMessage;
getListComment(currentPost.key);
}).catchError((e) => handleError(e));
}
void submitComment() {
int id = rng.nextInt(1000);
Comment comment = new Comment.fromJson({});
comment.id = id;
comment.content = content;
comment.postId = postId;
comment.ownerName = ownerName;
comment.ownerId = ownerId;
endpoint.insertComment( comment ).then((Comment savedComment) => endpoint.getComment(id)).
then((Comment loadedMessage) {
print(loadedMessage.content);
getListComment(loadedMessage.postId);
}).catchError((e) => handleError(e));
}
void submitPost(){
postId = rng.nextInt(1000);
Post post = new Post.fromJson({});
post.key = postId;
post.ownerName = ownerName;
post.ownerId = ownerId;
post.title = postContent;
endpoint.insertPost( post ).then((Post savedPost) => endpoint.getPost(postId)).
then((Post loadedMessage) {
print(loadedMessage.title);
getPost();
getListComment(loadedMessage.key);
}).catchError((e) => handleError(e));
}
void handleError(Error e) {
print("We had an error: ");
print(e.toString());
}
#override
void attached() {
}
}
The HTML Polymer element
<polymer-element name="click-counter" attributes="count">
<template>
<form action="javascript:void(0);" >
<div class="entry">
<label>Enter Post:</label>
<input id="subject" type="text" value="{{postContent}}" size="45" maxlength="255">
<button on-click="{{submitPost}}" class="btn btn-success" >Submit Post</button> <br>
</div>
</form>
<div class="post">
<h3>{{ currentPost.ownerName }}</h3>
<p>{{ currentPost.title }}</p>
<p>This is the postId: {{ currentPost.key }}</p>
<p class="timestamp">{{ currentPost.uploadTime }}</p>
<template repeat="{{comment in listComment.items}}">
<div class="comment">
<h3>{{ comment.ownerName }}</h3>
<p>{{ comment.content }}</p>
<p>This is the commentId: {{ comment.id }}</p>
<p class="timestamp">{{ comment.formatDate }}</p>
</div>
</template>
<form action="javascript:void(0);">
<label>Enter Comment:</label>
<input id="subject" type="text" value="{{content}}" size="45" maxlength="255">
<button on-click="{{submitComment}}" class="btn btn-success">Submit Comment</button> <br>
</form>
</div>
</template>
<script type="application/dart" src="clickcounter.dart"></script>
</polymer-element>
I have not yet seen your Unsupported operation message. Maybe some recent change. Your NoSuchMethodError is common when a property of a class is only referenced by a polymer expression (HTML) because tree-shaking drops all code that is not referenced and polymer expressions are not evaluated for this yet. The #MirrorsUsed annotation helps bridging this gap.
The problem is in your Post class because it's properties are only referenced in polymer expressions
<div class="post">
<h3>{{ currentPost.ownerName }}</h3>
<p>{{ currentPost.title }}</p>
<p>This is the postId: {{ currentPost.key }}</p>
<p class="timestamp">{{ currentPost.uploadTime }}</p>
To get your view updated when the properties in your currentPost change you should make your Post class like
class Post extends Object with Observable {
#observable String ownerName;
#observable String title;
...
}
If you have an annotation like #reflectable, #published, or #observable you don't need #MirrorsUsed.

Resources