I have a Dart web app using Polymer 1.0. The main-app is displaying a list of items. The main-app can add items to that list and then some time later an AJAX call will return and update the state of the added item. The update methods will need to utilize the dart-polymer helper methods in order to notify polymer of the change. However, I don't see how to pass a reference to the dart-polymer helper methods for this update.
I have it working below by having the MyItem element make the AJAX call at construction and then MyItem update the item on return. This works because MyItem only has a single item. However, this is not ideal because MyItem always makes an AJAX call and cannot simply be used to display the state of item if the page is reloaded.
I'm looking for a way to notify Polymer that an item has changed state that is contained somewhere in a list (the index is unknown).
main-app.html:
<dom-module id="main-app">
<paper-button on-tap="addItem>Add</paper-element>
<template is="dom-repeat" items="items" as="item">
<my-item item="{{item}}"></my-item>
</template>
</dom-module>
main-app.dart:
#PolymerRegister('main-app')
class MainApp extends PolymerElement {
final List<Item> items = [];
#reflectable
void addItem(Event e, Object detail) {
insert('items', 0, new Item());
}
}
my-item.html:
<dom-module id="my-item">
<template>
<div>{{item.state}}</div>
</template>
</dom-module>
my-item.dart:
#PolymerRegister('my-item')
class MyItem extends PolymerElement {
#property
Item item;
MyItem.create() : super.create();
ready() {
HttpRequest.postFormData("/items").then((HttpRequest request) {
set('item.state', request.responseText);
});
}
}
item.dart:
class Item extends JsProxy {
#reflectable
String state;
Item(this.state);
}
* Edit *
As Günter pointed out I could look up the item in the list and then remove and add it again. I wrote this function in main-app to be called on update:
void fireItemUpdate(Item item) {
int i = items.indexOf(item);
removeItem('items', item);
insert('items', i, item);
}
However, when I called that polymer did not render the updated item. I had to create a new item:
void fireItemUpdate(Item item) {
int i = items.indexOf(item);
removeItem('items', item);
Item item2 = new Item(item.state);
insert('items', i, item2);
}
Of course that isn't great either because fireItemUpdate cannot be called more than once (because it removes the original run from the list).
Documentation on Polymer Bindings: https://github.com/dart-lang/polymer-dart/wiki/data-binding-syntax
You need to make items a property
#property final List<Item> items = [];
Related
I am writing a polymer app. I extend a polymer element as follows:
#CustomTag("custom-element")
class CustomElement extends PolymerElement {
#published List<String> years;
CustomElement.created() : super.created();
}
the corresponding html:
<link rel="import" href="../../../../packages/polymer/polymer.html">
<polymer-element name="custom-element" attributes="years">
<template>
Years listed are
<template repeat="{{ year in years }}">
<p> {{ year }} </p>
</template>
</template>
<script type="application/dart" src="custom_element.dart"></script>
</polymer-element>
and in a dart script referenced in the entry html file, after I have fetched some data from server I dynamically create such elements:
initPolymer().run((){
querySelect("#custom-elements").children.add(
new Element.tag("custom-element")
..id = "my_custom_element"
);
});
Given code works fine if years in CustomElement is set to some value, now I want to set it in my main script. If the property years was of type String then I would simply altered the last piece to
initPolymer().run((){
querySelect("#custom-elements").children.add(
new Element.tag("custom-element")
..id = "my_custom_element"
..setAttribute("years", "2010")
);
});
But the problem is that I need to set a whole List, not a single value. I can't figure out a way to do so. How would I go about this? Solutions and suggestions are very welcome.
EDIT:
So my main script looks as follow
void main(){
// in reality this list is fetched from the server
List<String> customYears = ['2010', '2011'];
initPolymer().run((){
querySelect("#custom-elements").children.add(
new Element.tag("custom-element")
..id = "my_custom_element"
// HERE I would like to set a public field *years*
// of the CustomElement
// the following does not work
// 1.
// ..setAttribute('years', customYears)
// Exception: Uncaught Error: type 'List' is not a subtype of type 'String' of 'value'.
// 2.
// ..setAttribute('years', toObservale(customYears))
// Exception: Uncaught Error: type 'ObservableList' is not a subtype of type 'String' of 'value'.
// 3.
// ..years = customYears
// ..years = toObservable( custom Years )
// NoSuchMethodError: method not found: 'years='
);
});
}
So the question is, how do I assign a value to a non-string member field of an instance of CustomElement outside the class itself? A straightforward assignment inside the class causes no problems, but this isn't what I seek for. I hope I explained myself more clearly now.
Basically #Ozan answers your actual question but there is an additional issue.
The element is not yet fully initialized when the code assigns the value. This is the reason Dart fails and tells you that HtmlElement doesn't have a setter years. After adding Polymer.onReady.then() Polymer is fully initialized and the assignment works.
Hint: If you cast the created element to its concrete type you don't get a warning in DartEditor.
import 'package:polymer/polymer.dart';
import 'dart:html';
// added import to be able to use the type
import 'custom_element.dart';
void main() {
// in reality this list is fetched from the server
List<String> customYears = ['2010', '2011'];
// Polymer => 0.16.0
initPolymer().then((zone) => zone.run(() {
Polymer.onReady.then((_) { // added - wait for onReady
querySelector("#custom-elements").children.add(
(new Element.tag('custom-element') as CustomElement)
..id = "my_custom_element"
..years = toObservable(customYears));
});
}));
}
it is more convenient to add a constructor to the custom element like
import 'package:polymer/polymer.dart';
import 'dart:html';
#CustomTag('custom-element')
class CustomElement extends PolymerElement {
factory CustomElement() {
final x = new Element.tag('custom-element');
return x;
}
CustomElement.created() : super.created();
#published List<String> years;
}
then you can create the element like
querySelector("#custom-elements").children.add(
new AppElement()
..id = "my_custom_element"
..years = toObservable(customYears));
see also
Instantiating polymer element via dart code
how to implement a main function in polymer apps
Try to simply assign the value to the field:
import 'package:observe/observe.dart';
initPolymer().run((){
querySelect("#custom-elements").children.add(
new Element.tag("custom-element")
..id = "my_custom_element"
..years = toObservable(["2010"])
);
});
Usually custom elements are used in a main polymer element and attributes like years are bound to its scope:
<link rel="import" href="../../../../packages/polymer/polymer.html">
<polymer-element name="main-element">
<template>
<custom-element years="{{years}}"></custom-element>
</template>
<script type="application/dart" src="main_element.dart"></script>
</polymer-element>
#CustomTag("main-element")
class MainElement extends PolymerElement {
#published List<String> years = toObservable(["2010"]);
MainElement.created() : super.created();
}
I have two dart polymer components defined: an input and a list
This looks a little something like this:
#CustomTag('input-button')
class Input extends PolymerElement {
#observable String value = '';
Input.created() : super.created();
void blah(Event e, var detail, Node target) {
someMethodCallToTheOtherObject(value);
}
}
and the other element:
#CustomTag('page-content')
class PageContent extends PolymerElement {
final List<String> values = stuff;
PageContent.created() : super.created();
someMethodCallListerningForEventInOtherObject(String value) {
values.add(value);
}
}
As demonstrated in the code, I'm trying to set up and ActionListerner so that when one method is "fired" in the first button object, it calls a method in the second object with given parameters.
I know this might be a little of the basic side, but I haven't see this concept really well documented anywhere. Any input you could give to point me in the right direction would be appreciated.
Thanks!
You can query for one element and add an event listener
(querySelector(' /deep/ page-content') as PageContent)
.someMethodCallListerningForEventInOtherObject(value);
as PageContent is not necessary but it enables autocompletion.
You also need to import the file that contains the PageContent class to make this work.
or you can use an element like core-signals or a number of other possible variants depending on how your elements are organized (see my comments to your question).
I have a CustomPassword component and want to provide a method isActive that allows you to retrieve if the component is still the active element on this website.
Example Code:
custom_password.html
<polymer-element name="custom-password">
<template>
<div>Other things here</div>
<input id="password-field" type="password" value="{{value}}">
</template>
<script type="application/dart" src="custom_password.dart"></script>
</polymer-element>
custom_password.dart
import 'package:polymer/polymer.dart';
#CustomTag('custom-password')
class CustomPassword extends PolymerElement {
#published
String value;
CustomPassword.created() : super.created() {
}
bool isActive() {
// TODO figure out if the CustomPassword element is still active.
return false;
}
}
With a help from the Polymer Group I was able to come up with a solution:
For browsers with shadow DOM support it works out of the box by comparing
the hashCode of the document.activeElement with the components hashCode.
For browsers without shadow DOM support the password field will be the
active element. The trick here is to wrap the document.activeElement in order to compare it to the wrapped passwordField.
Example:
custom_password.dart
import 'package:polymer/polymer.dart';
import 'dart:html';
import 'dart:js' as js;
#CustomTag('custom-password')
class CustomPassword extends PolymerElement {
#published
String value;
CustomPassword.created() : super.created() {
}
bool isActive() {
var passwordField = $['password-field'];
var activeElement = js.context.callMethod('wrap', [document.activeElement]);
// For Browsers with shadow DOM support the shadowRoot.host matches while
// for Browsers without shadow DOM support the password field match.
if (activeElement.hashCode != hashCode &&
activeElement.hashCode != passwordField.hashCode) {
return false;
} else {
return true;
}
}
}
bool isActive(event) {
return document.activeElement == this;
}
This only works in browsers with native Shadow DOM support.
I haven't tested it but this probably only works when your <custom-password> element is in the light DOM and not inside the <template> of another polymer element like <app-element> if you use one.
The current polyfill seems not to work correctly and you get the input element back instead of the Polymer element.
These issues contains information about how activeElement works with Polymer elements
- https://github.com/Polymer/ShadowDOM/issues/478
- https://code.google.com/p/dart/issues/detail?id=18982
- https://code.google.com/p/dart/issues/detail?id=20165
and also this paragraph
- http://www.w3.org/TR/shadow-dom/#active-element
In order to communicate from child to parent, events seem to be the most elegant way.
What are the options to communicate from parent to child?
More specifically, I want a method called in a child when it becomes visible.
These are the ideas I came up with:
xtag - elegant and works
observing 'hidden' - didn't manage to get this working, hidden is not marked as observable in Element
publishing a trigger variable in child, binding and changing it in parent - ugly
Are there any other options?
I have not tried it yet but maybe MutationObserver does what you want.
Seth Ladd's polymer examples containes two examples:
The first listens to the onMutation event
https://github.com/sethladd/dart-polymer-dart-examples/blob/master/web/onmutation-mutation-observer/my_element.dart
library my_element;
import 'package:polymer/polymer.dart';
import 'dart:html';
import 'dart:async';
#CustomTag('my-element')
class MyElement extends PolymerElement {
MyElement.created() : super.created() {
// NOTE this only fires once,
// see https://groups.google.com/forum/#!topic/polymer-dev/llfRAC_cMIo
// This is useful for waiting for a node to change in response
// to some data change. Since we don't know when the node will
// change, we can use onMutation.
onMutation($['list']).then((List<MutationRecord> records) {
$['out'].text = 'Change detected at ${new DateTime.now()}';
});
new Timer(const Duration(seconds:1), () {
$['list'].children.add(new LIElement()..text='hello from timer');
});
}
}
the second example uses the MutationObserver class
https://github.com/sethladd/dart-polymer-dart-examples/blob/master/web/mutation_observers/my_element.dart
=== edit ===
Have you tried the linked example?
The observe method allows to specify what should be observed:
/**
* Observes the target for the specified changes.
*
* Some requirements for the optional parameters:
*
* * Either childList, attributes or characterData must be true.
* * If attributeOldValue is true then attributes must also be true.
* * If attributeFilter is specified then attributes must be true.
* * If characterDataOldValue is true then characterData must be true.
*/
void observe(Node target,
{bool childList,
bool attributes,
bool characterData,
bool subtree,
bool attributeOldValue,
bool characterDataOldValue,
List<String> attributeFilter}) {
To communicate from parent polymer to child polymer , this solution works good for me.
If we have a child polymer element like this:
library my_element;
import 'package:polymer/polymer.dart';
import 'dart:html';
import 'dart:async';
#CustomTag('my-element')
class MyElement extends PolymerElement {
MyElement.created() : super.created() {
}
myCustomMethod(param){
print("pass-in param = $param");
}
}
To access to your child element from parent:
Query your child polymer element in parent element (yes, access to the HTML element first)
Cast it to the binding Class (ex: MyElement.dart in our case)
(theParentClass.querySelector("my-element") as MyElement).myCustomMethod({"done":true});
I have a custom element like below:
<polymer-element>
<template if="{{primaryLoaded}}">
<template repeat="{{pData in primaryData}}">
<p>{{pData.propertyOne}}</p>
<p>{{someClass.someOperation()}}</p>
<template if="{{secodaryLoaded}}">
<p>{{secondaryData.someProperty}}</p>
<p>{{someClass.someOperation()}}</p>
</template>
</template>
</template>
</polymer-element>
and a corresponding dart file:
class CustomElement extends PolymerElement with ObservableMixin
{
#observable bool primaryLoaded = false;
#observable bool secondaryLoaded = false;
#observable var primaryData;
#observable var secondaryData;
#observable var someClass;
void created()
{
primaryData = toObservable(new List<var>());
secondaryData = toObservable(new List<var>());
}
void inserted()
{
someClass = new SomeClass();
loadPrimaryData().then((pData) {
primaryData = pData;
primaryLoaded = true;
loadSecondaryData().then((sData) {
secondaryData = sData;
secondaryLoaded = true;
});
});
}
}
Everything works fine for the primaryData. Its properties get printed and the call to someOperation() on someClass prints its data correctly.
The problem is in the nested template. Nothing under the <template if="{{secondaryLoaded}}"> gets displayed. Even the call to someClass.someOperation() fails to display anything.
Is there a problem with scope here? It looks like the outer template can reference the properties defined in the dart file without problem but the nested template can't.
I read about setting variables as globals in the template by setting a custom binding delegate here. However, I can't set the bindingDelegate in my custom element as isTemplate returns false.
Is there any other way of setting a global variable in a custom template? Or am I going about this all wrong?
It's a bug. You can star this issue to be notified of changes:
https://code.google.com/p/dart/issues/detail?id=12742
The only thing you can do for now is to turn nested templates into other polymer elements or figure out a way so the template isn't nested.
In your case, instead of using if="primaryLoaded" you could just set primaryData = null and the template shoud not display anything until primaryData is set, assuming primaryData is #observable.