How can I bind to an event handler using <template is="dom-bind">?
index.dart
import 'dart:html';
import 'package:polymer/polymer.dart';
DomBind template;
main() async {
await initPolymer();
template = document.querySelector('#t');
final model = new MyModel();
template['model'] = model;
// Polymer 1.0 event handlers can't have `.` in declarative syntax
// and therefore need to be assigned to the template directly.
template['buttonTap'] = model.buttonTap;
}
class MyModel extends JsProxy {
// reflectable is Polymer 1.0.0-rc.2 (was #eventHandler in rc.1)
#reflectable
String value = 'something';
#reflectable
buttonTap([Event e, args]) {
print(template.$['mybutton'].id);
print('tap! $value');
}
}
index.dart
<!doctype html>
<html>
<head>
<link rel="import" href="packages/polymer/polymer.html">
<script src="packages/web_components/webcomponents-lite.min.js"></script>
<script src="packages/browser/dart.js"></script>
</head>
<body>
<template id="t" is="dom-bind">
<div>Model: <span>{{model}}</span></div>
<div>Say something: <input value="{{model.value::change}}"></div>
<div>You said: <span>{{model.value}}</span></div>
<button id="mybutton" on-tap="buttonTap">Tap me!</button>
</template>
<script type="application/dart" src="index.dart"></script>
</body>
</html>
Related
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)
Newbie problem. I am playing with Polymer.dart, and following the beginner tutorials. The Dart code gets invoked, but is unable to instantiate the PaperItem. How can I do this ? The error I get is :
Cannot read property 'toString' of null (first line of addTodoItem below)
mini.dart
import 'dart:html';
import 'package:polymer/polymer.dart';
import 'package:paper_elements/paper_input.dart';
import 'package:paper_elements/paper_item.dart';
import 'package:core_elements/core_menu.dart';
PaperInput paperInput;
CoreMenu coreMenu;
void main() {
print("enter main");
initPolymer().run(() {
Polymer.onReady.then((_) {
paperInput = querySelector('#todo-input');
coreMenu = querySelector('#todo-list');
paperInput.onChange.listen(addTodoItem);
});
});
}
void addTodoItem(Event e) {
PaperItem newTodo = new PaperItem.created();
newTodo.text = paperInput.value;
paperInput.value='';
coreMenu.children.add(newTodo);
}
mini.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Mini</title>
<script src="packages/web_components/dart_support.js"></script>
<!-- <script src="packages/web_components/platform.js"></script>
not necessary anymore with Polymer >= 0.14.0 -->
<link rel="import" href="packages/core_elements/core_header_panel.html">
<link rel="import" href="packages/core_elements/core_toolbar.html">
<link rel="import" href="packages/core_elements/core_menu.html">
<link rel="import" href="packages/paper_elements/paper_tabs.html">
<link rel="import" href="packages/paper_elements/paper_input.html">
<link rel="import" href="packages/paper_elements/paper_item.html">
<script type="application/dart" src="mini.dart"></script>
<script async src="packages/browser/dart.js"></script>
<link rel="stylesheet" href="mini.css">
</head>
<body unresolved touch-action="auto">
<core-header-panel> <core-toolbar>
<paper-tabs id="tabs" selected="all" valueattr="name" self-end>
<paper-tab name="all">ALL</paper-tab> <paper-tab name="favorites">FAVORITES</paper-tab>
</paper-tabs> </core-toolbar>
<h2>TODO list</h2>
<div>
<paper-input floatingLabel label="Action Item" id="todo-input">
</paper-input>
</div>
<div>
<core-menu id="todo-list"> </core-menu>
</div>
</core-header-panel>
</body>
</html>
This is not the right way to dynamically create instances of Polymer elements
PaperItem newTodo = new PaperItem.created();
the right way is
PaperItem newTodo = new Element.tag('paper-item');
or
var newTodo = new Element.tag('paper-item') as PaperItem;
Just as additional note:
Polymer elements that extend DOM elements (which is not the case for core-elements or paper-elements) need an additional parameter - see Dynamically create polymer element for more details.
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.
I have a Dart WebComponent that obtains information periodically from a web service. I'd like to inject the web service into the component and have the component decide when to call the web service (this will allow me to prototype all the UI code using a mock web service with no HTTP call until the service is written).
The issue I'm having is that the web service object I'm sending to the WebComponent seems to be null until the page is rendered. I'm not exactly sure when the service reference is passed to the page, but it seems to happen after the page is constructed since the value is null in the WebComponent constructor, but I can see that the value is instantiated when the page itself is rendered. How can I be notified that the service object is now available to the page so I can call the web service?
Follow up question: Is passing the service reference into the WebComponent like I am below a bad practice? If so, what should I do instead to separate the mock implementation so I can inject the real web service later without changing any code.
Here's my base Dart page:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Dart Prototype</title>
<link rel="stylesheet" href="dart_prototype.css">
<link rel="import" href="location_container.html">
</head>
<body>
<h1>Dart Prototype</h1>
<div id="location_management_container">
<location-container location-service="{{applicationContext.locationService}}" count="{{startingCount}}"></location-container>
</div>
<script type="application/dart">
import 'package:web_ui/web_ui.dart';
import 'package:dart_prototype/dart_prototype_library.dart';
final ApplicationContext applicationContext = new ApplicationContext(
new WebService()
);
int startingCount = 5;
main() {
print('main initializing');
print(applicationContext);
print(applicationContext.locationService);
}
</script>
<script src="packages/browser/dart.js"></script>
</body>
</html>
Here's the code for location-container
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<element name="location-container">
<template>
<div>
<ul id="todo-list">
<li>hi there!</li>
<li>{{count}}</li>
<li>{{locationService}}</li>
</ul>
</div>
</template>
<script type="application/dart">
import 'package:web_ui/web_ui.dart';
import 'package:dart_prototype/dart_prototype_library.dart';
class LocationContainer extends WebComponent {
#observable
WebService locationService;
#observable
int count;
LocationContainer() {
print('in loc container constructor');
print(locationService);
print(count);
}
created() {
print('created!');
print(locationService);
print(count);
}
}
</script>
</element>
</body>
</html>
Here's the code for ApplicationContext and WebService
part of prototype_library;
class ApplicationContext {
final WebService locationService;
ApplicationContext(
this.locationService);
}
class WebService {
final Factory _objectFactory;
WebService(this._objectFactory);
Future call(String url) {
throw new UnimplementedError();
}
}
And here's the result of my print strings statements to the console
Invalid CSS property name: -webkit-touch-callout
main initializing
Instance of 'ApplicationContext'
Instance of 'WebService'
in loc container constructor
null
null
created!
null
null
And here's what my rendered page returns:
Dart Prototype
- hi there!
- 5
- Instance of 'WebService'
And the source of that page...
<!DOCTYPE html>
<!-- This file was auto-generated from web/dart_prototype.html. -->
<html><head><style>template { display: none; }</style>
<meta charset="utf-8">
<title>Dart Prototype</title>
<link rel="stylesheet" href="../dart_prototype.css">
</head>
<body>
<h1>Dart Prototype</h1>
<div id="location_management_container">
<span is="location-container"></span>
</div>
<script type="application/dart" src="dart_prototype.html_bootstrap.dart"></script><script src="../packages/browser/dart.js"></script>
</body></html>
I think my problem may be solved by using the inserted life cycle method instead of created.
Initially when I read the WebComponent life cycle method description, it said:
Invoked whenever a component is added to the DOM.
I'm still not sure if this is the right method to use, but I have the object I'm looking for - so I'm going to continue to experiment. Here's my updated WebComponent
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<element name="location-container">
<template>
<div>
<ul id="todo-list">
<li>hi there!</li>
<li>{{count}}</li>
<li>{{locationService}}</li>
</ul>
</div>
</template>
<script type="application/dart">
import 'package:web_ui/web_ui.dart';
import 'package:dart_prototype/dart_prototype_library.dart';
class LocationContainer extends WebComponent {
#observable
WebService locationService;
#observable
int count;
LocationContainer() {
print('in loc container constructor');
print(locationService);
print(count);
}
created() {
print('created!');
print(locationService);
print(count);
}
inserted() {
print('inserted!');
print(locationService);
print(count);
}
}
</script>
</element>
</body>
</html>
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.