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;
}
}
Related
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');
}
...
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).
Not able to reference dom elements.
Most of the test case works except for the last expectation in ClickSignInButton when I want to make sure I can evaluate error message div when form is submitted without any data.
expect(document.querySelector('qme-header').shadowRoot
.querySelector('#headerErrorDiv'), isNotNull); always fails and headerErrorDiv is null even though its there in the div.
Code:
import 'package:test/test.dart';
import 'package:polymer/polymer.dart';
import 'dart:html';
import 'dart:async';
main() {
initPolymer().then((zone) => zone.run(() {
return Polymer.onReady.then((_) {
group('Header Form Elements Are Available', () {
test(("CheckSignInFormItems"), () {
expect(querySelector('qme-header').shadowRoot
.querySelector('#emailField'), isNotNull);
expect(querySelector('qme-header').shadowRoot
.querySelector('#passwordField'), isNotNull);
expect(querySelector('qme-header').shadowRoot
.querySelector('#signInButton'), isNotNull);
});
test(("CheckRegisterForgotItems"), () {
expect(querySelector('qme-header').shadowRoot
.querySelector('#registerButton'), isNotNull);
expect(querySelector('qme-header').shadowRoot
.querySelector('#forgotButton'), isNotNull);
});
});
group('Header Form Click Sign In Button', () {
test(("ClickSignInButton"), () {
(querySelector('qme-header').shadowRoot
.querySelector('#emailField') as InputElement).value = "";
(querySelector('qme-header').shadowRoot
.querySelector('#passwordField') as InputElement).value =
"";
Timer.run(expectAsync(() {
(querySelector('qme-header').shadowRoot
.querySelector('#signInButton') as ButtonElement).click();
expect(document.querySelector('qme-header').shadowRoot
.querySelector('#headerErrorDiv'), isNotNull);
}));
});
});
});
}));
}
Hi Günter, Thank you for your time, I adjusted my code based on your comments and it should work, however I think I am messing up the way I am using polymer templates.
pubspec.yaml
environment:
sdk: '>=1.0.0 <2.0.0'
dependencies:
bootstrap: ^3.3.4
browser: '>=0.10.0 <0.11.0'
polymer: '>=0.16.0 <0.17.0'
test: '>=0.12.3'
transformers:
- polymer:
inline_stylesheets:
packages/bootstrap/css/bootstrap.css: false
packages/bootstrap/css/bootstrap-theme.css: false
entry_points:
- web/index.html
- test/qme_header_test.html
- test/pub_serve:
$include: test/**_test{.*,}.dart
header.html
<polymer-element name="qme-header">
<div class="container">
<div class="navbar-header"><a class="navbar-brand"
href="#"><span>QMe Application</span></a></div>
<template if="{{usingHeaderForm}}">
<div id="navbar" class="navbar-collapse collapse" >
<form on-submit="{{validateSignInForm}}"
class="navbar-form navbar-right">
<div class="form-group">
<input type="text" id="emailField"
value="{{qmeSignIn.userEmail}}" placeholder="Email"
class="form-control">
</div>
<div class="form-group">
<input type="password" id="passwordField"
value="{{qmeSignIn.userPassword}}"
placeholder="Password" class="form-control">
</div>
<button id="signInButton" type="submit"
class="btn btn-success btn-sm">Sign in</button>
<button id="registerButton" class="btn btn-
info btn-xs" type="button-small">Register</button>
<button id="forgotButton" class="btn btn-info
btn-xs" type="button-small">Forgot Password</button>
</form>
</div>
</template>
<template if="{{!usingHeaderForm}}">
<p>Hello login complete</p>
</template>
header.dart
library qme_header;
import 'package:polymer/polymer.dart';
import 'dart:html';
import 'package:qme/views/qme_error.dart';
#CustomTag('qme-header')
class QmeHeader extends PolymerElement {
#published QMeSignIn qmeSignIn;
#observable bool usingHeaderForm = true;
#observable QmeErrorHolder qmeErrorHolder;
QmeHeader.created() : super.created() {
qmeSignIn = new QMeSignIn();
qmeErrorHolder = QmeErrorHolder.instance;
}
toggleFormDisplay() {
usingHeaderForm = !usingHeaderForm;
}
performLogin() {
toggleFormDisplay();
}
bool validateSignInEmail() {
if (qmeSignIn.userEmail.length == 0) {
qmeErrorHolder.headerErrorMessage = "Valid user email
required";
return false;
}
qmeErrorHolder.headerErrorMessage = '';
return true;
}
bool validateSignInPassword() {
if (qmeSignIn.userPassword.length == 0) {
qmeErrorHolder.headerErrorMessage = "Valid user password
required";
return false;
}
qmeErrorHolder.headerErrorMessage = '';
return true;
}
validateSignInForm(Event event, Object detail, Node sender) {
event.preventDefault();
if (validateSignInEmail() && validateSignInPassword()) {
print("Submit");
performLogin();
qmeErrorHolder.headerError = false;
} else {
qmeErrorHolder.headerError = true;
}
}
}
class QMeSignIn extends Observable {
#observable String userEmail;
#observable String userPassword;
QMeSignIn([this.userEmail = "", this.userPassword = ""]);
}
error.html
<polymer-element name="qme-error">
<template>
<template if="{{qmeErrorHolder.headerError}}">
<div class="container">
<div id="headerErrorDiv" class="alert alert-danger"
role="alert">{{qmeErrorHolder.headerErrorMessage}}</div>
</div>
</template>
</template>
<script type="application/dart" src="qme_error.dart">
</script>
</polymer-element>
error.dart
library qme_error;
import 'package:polymer/polymer.dart';
#CustomTag('qme-error')
class QmeError extends PolymerElement {
#observable QmeErrorHolder qmeErrorHolder;
QmeError.created() : super.created() {
qmeErrorHolder = QmeErrorHolder.instance;
}
}
class QmeErrorHolder extends Observable {
#observable bool headerError;
#observable String headerErrorMessage;
static final QmeErrorHolder _instance = new
QmeErrorHolder._internal();
static QmeErrorHolder get instance => _instance;
QmeErrorHolder._internal();
}
headertest.html
<html>
<head>
<link rel="import"
href="../packages/polymer/polymer.html">
<link rel="import"
href="../packages/qme/views/qme_header.html" >
<link rel="import"
href="../packages/qme/views/qme_error.html" >
<link rel="stylesheet"
href="packages/bootstrap/css/bootstrap.css">
<link rel="stylesheet"
href="packages/bootstrap/css/bootstrap-theme.css">
<script type="application/dart"
src="qme_header_test.dart"></script>
<script data-pub-inline src="packages/test/dart.js">
</script>
</head>
<body>
<qme-header></qme-header>
<qme-error></qme-error>
</body>
</html>
headertest.dart
#whenPolymerReady
void runTests() {
group('Header Form Click Sign In Button', () {
test(("ClickSignInButton"), () {
(querySelector('qme-header::shadow #emailField ') as
InputElement).value ="";
(querySelector(
'qme-header::shadow #passwordField') as
InputElement).value = "";
(querySelector('qme-header::shadow #signInButton') as
ButtonElement)
.click();
expect(
document.querySelector('qme-error::shadow #headerErrorDiv'), isNotNull);
});
});
}
index.html
<html>
<head>
<link rel="stylesheet"
href="packages/bootstrap/css/bootstrap.css">
<link rel="stylesheet"
href="packages/bootstrap/css/bootstrap-theme.css">
<link rel="stylesheet" href="styles/main.css">
<link rel="import"
href="packages/qme/views/qme_header.html">
<link rel="import"
href="packages/qme/views/qme_error.html">
</head>
<body>
<qme-header></qme-header>
<qme-error></qme-error>
<script src="packages/bootstrap/jquery/jquery.js"></script>
<script src="packages/bootstrap/js/bootstrap.js"></script>
<script data-pub-inline src="packages/browser/dart.js">
</script>
<script type="application/dart">export
'package:polymer/init.dart';</script>
</body>
</html>
Running index.html in Dartium works, however header test fails with null for headerErrorDiv. I know I am messing up with Polymer template and having gobal error holder most likely, but it works when index.html runs in Dartium and fails test cases when headertest.html is run.
At first, I think you should add the #whenPolymerReady annotation to main() instead of your Polymer initialization code.
Instead of
expect(document.querySelector('qme-header').shadowRoot
.querySelector('#headerErrorDiv'), isNotNull);
you could use
expect(document.querySelector('qme-header::shadow #headerErrorDiv'), isNotNull);
I don't see a problem in your
cod. Can you please provide a complete example that allows to reproduce your problem (incl HTML and pubspec.yaml)
headerErrorDiv only exists if qmeErrorHolder.headerError is true
I think you should ensure the test files end with _test(.dart|.html) to be recognized by the testrunner pub run test
You need to change the Dart script tag to x-link-dart to make the test work with the testrunner (and back to <script> when you want to run it directly)
AFAIK If your test needs additional files like your component files you need to run pub serve test and pass the port to pub run test (see readme of the test package for more details)
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();
How do I write a numeric for loop in a polymer custom element template? I mean something like
<template repeat="{{for i = 1 to 10}}">
<div>item</td>
</template>
Is it possible in the current version of Dart 1.0?
Currently no, this is not possible in Polymer.dart (or Polymer.js to my knowledge). The repeat binding requires an iterable (See Repeating Templates section of the Polymer_expressions library). Unfortunately due to Issue 12669 it is also not possible to use a list literal to accomplish this either.
Using a filter we can accomplish this:
<!-- myelement.html -->
<polymer-element name="my-element">
<template>
<div>
<template repeat="{{ 5 | myFilter }}">
<p>Write me {{ }}</p>
</template>
</div>
</template>
<script type="application/dart" src="myelement.dart"></script>
</polymer-element>
// myelement.dart
import 'package:polymer/polymer.dart';
import 'package:polymer_expressions/filter.dart';
#CustomTag('my-element')
class MyElement extends PolymerElement {
final Transformer myFilter = new GenerateIterable();
MyElement.created() : super.created();
}
class GenerateIterable extends Transformer<Iterable, int> {
int reverse(Iterable i) => i.length;
Iterable forward(int i) => new Iterable.generate(i, (j) => j + 1);
}
Creating a page which imports myelement.html and using <my-element></my-element> will output:
<div>
<p>Write me 1</p>
<p>Write me 2</p>
<p>Write me 3</p>
<p>Write me 4</p>
<p>Write me 5</p>
</div>