What is the Correct way of listening ObservableList adding and removing? - dart

I have next code:
#CustomTag('my-element')
class MyElement extends PolymerElement {
MyElement.created() : super.created();
#published ObservableList persons = toObservable([]);
handleAdd() {
persons.add({'name': 'jhon', 'lastName': 'Doe'});
}
handleRemove() {
persons.remove({'name': 'jhon', 'lastName': 'Doe'});
}
}
and this is the HTML:
<polymer name="my-element">
<template>
<template repeate="{{p in persons}}">
{{p['name']}} {{p['lastName']}}
</template>
<button on-click="{{handleAdd}}">add person</button>
<button on-click="{{handleRemove}}">remove person</button>
<template>
</polymer>
when debugging, it is added and removed from the internal list of objects. However, it never shows the elements as added in HTML.

This code line has no effect
persons.remove({'name': 'jhon', 'lastName': 'Doe'});
because
print({'name': 'jhon', 'lastName': 'Doe'} == {'name': 'jhon', 'lastName': 'Doe'});
try at DartPad
prints false because for collections Dart compares identity not content.
There are helpers available. See How can I compare Lists for equality in Dart?
What you can do instead is
persons.removeWhere((p) => p['name'] == 'jhon' && p['lastName'] == 'Doe');
try at DartPad
This doesn't work because in Dart you can't access elements of a Map using dot-notation
<template repeate="{{p in persons}}">
{{p.name}} {{p.lastName}}
</template>
If you change this to
<template repeate="{{p in persons}}">
{{p['name']}} {{p['lastName']}}
</template>
it should work as intened.

Related

Using an ObservableList to update a template in Dart

I am trying to bind an ObservableList to a template, and I am a bit confused about how it is working. In the following example, when I click the button, the instance of the list that is displayed by iterating through the elements is updated, but the instance that is used directly is not.
dart:
#CustomTag('my-observing-list')
class ObservingList extends PolymerElement {
ObservingList.created() : super.created();
#observable ObservableList theList = new ObservableList.from([0, 1, 2, 3, 4]);
addElement(var x) {
theList.add(5);
}
}
html:
<polymer-element name="my-observing-list">
<template>
<template repeat="{{l in theList}}">
{{l}}
</template>
{{theList}}
<button on-click="{{addElement}}"></button>
</template>
</polymer-element>
Further adding to my confusion, if I add an observable integer to my element, include it in the template, and update it when the button is clicked, it causes both instances of the list to be updated when the button is clicked.
dart:
class ObservingList extends PolymerElement {
ObservingList.created() : super.created();
#observable ObservableList theList = new ObservableList.from([0, 1, 2, 3, 4]);
#observable var number = 0;
addElement(var x) {
theList.add(5);
number = 1;
}
}
html:
<polymer-element name="my-observing-list">
<template>
<template repeat="{{l in theList}}">
{{l}}
</template>
{{theList}}
{{number}}
<button on-click="{{addElement}}"></button>
</template>
</polymer-element>
Can someone explain what's happening here?
It's a while I used Polymer 0.16 ...
{{theList}}
is basically
{{theList.toString()}}
and the returned string is not an observable. Also the reference to theList doesn't change when you add a value to the observable list. I remember there was a workaround to make Polymer recognize the change but don't remember what it was (transformer or something).
The number is observed directly and when it changes it is recognized by Polymer.

how to display simple information using <core-list-dart>

I saw several posts here but I still can't succeed display anything...
I try to run the example in the source code.
https://github.com/dart-lang/core-elements/blob/master/lib/core_list_dart.html
this is my dart code
#CustomTag('exercise-list')
class ExerciseList extends PolymerElement {
#observable int testBind = 50000;
#observable ObservableList data;
#observable int index;
#observable bool selected;
ExerciseList.created() : super.created();
#override
void onReady() {
data = toObservable([
new Person('Bob', true),
new Person('Tim', false)
]);
}
}
class Person extends Observable {
#observable String name;
#observable bool checked;
Person(this.name, this.checked);
}
<link rel="import" href="../../../packages/polymer/polymer.html">
<link rel="import" href="../../../packages/core_elements/core_icons.html">
<link rel="import" href="../../../packages/core_elements/core_list_dart.html">
<polymer-element name="exercise-list">
<template>
test : {{testBind}}
<core-icon icon="star"></core-icon>
<core-list-dart data="{{data}}">
<template>
<div class="row {{ {selected: selected} }}" style="height: 80px">
List row: {{index}}, User data from model: {{model.name}}
<input type="checkbox" checked="{{model.checked}}">
</div>
</template>
</core-list-dart>
</template>
<script type="application/dart" src="exercise_list.dart"></script>
</polymer-element>
name: app
version: 0.0.1
description: app
environment:
sdk: '>=1.2.0 <2.0.0'
dependencies:
browser: any
guinness: any
paper_elements: '>=0.6.0+2 <0.7.0'
core_elements: '>=0.5.0+2 <0.6.0'
polymer: '>=0.15.3 <0.16.0'
unittest: any
transformers:
- polymer:
entry_points: web/index.html
The value testBing and the core-icon are well displayed. It's weird I can't understand where is my problem... Hope you will find the problem. Cheers !
Update
Here is an example of the bare minimum code to display something with core-list-dart. note the mandatory fields in the model !!!
Update
There are a few issues with your code:
exercise_list.dart
You are overriding onReady which is a getter not a method and can't
be overridden. You want to override ready instead.
When I load the page I get exceptions telling me that Person doesn't
have a selected or index property. I added them to the class to
get rid of the error without investigating why this is necessary.
class Person extends Observable {
#observable String name;
#observable bool checked;
bool selected = false; // <== added
int index; // <== added
Person(this.name, this.checked);
}
exercise_list.html
I removed model. from {{model.name}} and {{model.checked}}
Now the list is displayed.
All these things were reported by the development environment. I use WebStorm but I'm sure the same hints and errors would be shown by DartEditor and also by Dartium when run directly (shown in the developer tools console).
You can't reference fields of your exercise_list element in the template element passed to core-list-dart because the template is removed and applied inside the core-list-dart which changes its scope.
This is why selected and index didn't work here.
Old
I guess the problem is that the core-list-dart needs an explicit height to be displayed but your code doesn't show how it is added to your page. (see also this discussion https://github.com/Polymer/core-list/issues/47#issuecomment-63126241)
The height is probably only applied if you set your element to display: block
<polymer-element name="exercise-list">
<template>
<style>
:host {
display: block;
}
core-list-dart {
height: 500px;
}
</style>
....

TextArea Resizing in Dart during DOM Initialization

I'm trying to make a text area that resizes dynamically in Dart based on the height of its contents.
I have a textarea element defined in a polymer element in Dart like so:
<polymer-element name="page-content">
<template>
{{title}}
<ul>
<template repeat="{{element in elist | enumerate}}">
<li value={{element.index}}><textarea class="{{element.value.type}}" resize="none" on-keypress="{{resize}}" on-change="{{updateDatabase}}">{{element.value.content}}</textarea><div on-click="{{deleteElement}}">X</div></li>
</template>
</ul>
</template>
</polymer-element>
When any text is entered into the textarea, the resize method is called and properly resizes the text-area to adjust its height appropriately.
I am not sure how to call the resize method right when the element is loaded into the DOM. I have tried adding on-load="{{resize}}" to the textarea or even querying all the textareas and adjusting their sizes. Nothing seems to be working. My intutition tells me there should be an easy way to do this.
If it helps, my resize method in dart looks like this:
void resize(Event event, var detail, var target) {
Element element = event.target;
print(element.scrollHeight);
element.style.height = "1px";
element.style.height = "${element.scrollHeight}px";
}
This is an interesting question.
I think the best approach would be to wrap the textarea in some autosize-textarea and there add
<polymer-element name="autosize-textarea">
<template>
<textarea id="inner" class="{{element.value.type}}" resize="none"
on-keypress="{{resize}}">
{{element.value.content}}</textarea>
</template>
</polymer-element>
import 'dart:html';
import 'package:polymer/polymer.dart';
#CustomTag('autosize-textarea')
class AutosizeTextarea extends PolymerElement {
AutosizeTextarea.created() : super.created();
#published
Element element;
void attached() {
super.attached();
resize(null, null, null);
}
void resize(Event event, var detail, var target) {
Element textarea $['inner'];
print(textarea.scrollHeight);
textarea.style.height = "1px";
textarea.style.height = "${textarea.scrollHeight}px";
}
}
and the use it like
<link rel="import" href="autosize_textarea.html">
<polymer-element name="page-content">
<template>
{{title}}
<ul>
<template repeat="{{element in elist | enumerate}}">
<li value={{element.index}}>
<autosize-textarea on-change="{{updateDatabase}}" element="{{element}}></autosize-textarea>
<div on-click="{{deleteElement}}">X</div>
</li>
</template>
</ul>
</template>
</polymer-element>
I'm not sure if I understand your code correctly because you named the item created from template repeat element and also the element you got from event.target. I'm not sure if/how they are related.
Not tested but I think it should work.
The code that I worked out is very similar to what Günter Zöchbauer suggested. It queries the shadow DOM (something I did not think to do) to get all the text areas at once (instead of just one) and update their heights accordingly.
The method could be in attached() or after any place where the content is dynamically fetched from the database in my case.
void updatePageSizes() {
var items = shadowRoot.querySelectorAll("textarea");
var j = items.iterator;
while (j.moveNext()) {
j.current.style.height = "${element.scrollHeight}px";
j.current.style.backgroundColor = "blue";
}
}
Also, I figured out that, to my knowledge, it is not possible to call a function in Dart from each element added in an enumerated list in a polymer element. I guess this is just a limitation of Dart.

How to remove a child component with a delete button in the child itself

I have an email component (email-tag.html) that consist of a label, a select and a delete button element.
The email-tag.html component is hosted in its parent email-view-tag.html. email-view-tag contains an add-email-button that adds the email-tag element to the DOM each time it is clicked.
I need help in removing an added email-tag component when its delete-button is clicked. It is the compnoent that contains the delete-button that should be removed.
The two components are shown below:
email-tag.html
<!DOCTYPE html>
<polymer-element name='email-tag'>
<template>
<style>
.main-flex-container
{
display:flex;
flex-flow:row wrap;
align-content:flex-start;
}
.col
{
display:flex;
flex-flow:column;
align-content:flex-start;
flex-grow:1;
}
</style>
<div id='email' class='main-flex-container'>
<section id='col1' class='col'>
<input id=emailTxt
type='text'
list='_emails'
value='{{webContact.homeEmail}}'>
<datalist id='_emails'>
<template repeat='{{email in emails}}'>
<option value='{{email}}'>{{email}}</option>
</template>
</datalist>
</section>
<section id='col2' class='col'>
<button id='delete-email-btn' type='button' on-click='{{deletePhone}}'>Delete</button>
</section>
</div>
</template>
<script type="application/dart">
import 'package:polymer/polymer.dart' show CustomTag, PolymerElement;
import 'dart:html' show Event, Node;
#CustomTag( 'email-tag' )
class EmailElement extends PolymerElement
{
//#observable
EmailElement.created() : super.created();
List<String> emails = [ '', 'Home', 'Personal', 'Private', 'Work', ];
void deletePhone( Event e, var detail, Node target)
{
//shadowRoot.querySelector('#new-phone' ).remove();
//print( 'Current row deleted' );
}
}
</script>
</polymer-element>
email-view-tag.html
<!DOCTYPE html>
<link rel="import" href="email-tag.html">
<polymer-element name='email-view-tag'>
<template>
<style>
.main-flex-container
{
display:flex;
flex-flow:row wrap;
align-content:flex-start;
}
.col
{
display:flex;
flex-flow:column;
align-content:flex-start;
flex-grow:1;
}
</style>
<div id='email-view' class='main-flex-container'>
<section id='row0' >
<button id='add-email-btn' type='button' on-click='{{addPhone}}'>Add Phone</button>
</section >
<section id='rows' class='col'>
<!-- <epimss-phone-header-tag id='col1' class='col'></epimss-phone-header-tag> -->
</section>
</div>
</template>
<script type="application/dart">
import 'package:polymer/polymer.dart' show CustomTag, PolymerElement;
import 'dart:html' show Event, Node, Element;
#CustomTag( 'email-view-tag' )
class EmailViewElement extends PolymerElement
{
//#observable
EmailViewElement.created() : super.created();
void addPhone( Event e, var detail, Node target )
{
$[ 'rows' ].children.add( new Element.tag( 'email-tag' ) );
}
#override
void attached() {
super.attached();
$[ 'add-email-btn' ].click();
}
}
</script>
</polymer-element>
The application does execute normally and clicking the add button does add the email component. The delete button does not work - it is here I am asking for help.
Thanks
The child component, <email-tag> should not be in the business of deleting itself. Instead, it should delegate that responsibility to the the parent component, email-view-tag, by dispatching a custom event.
Here is the code for dispatching a custom event from deletePhone:
void deletePhone( Event e, var detail, Node target){
dispatchEvent(new CustomEvent('notneeded'));
}
Then, in the parent, <custom-view>, change your code for adding <email-tag>s like so:
void addPhone( Event e, var detail, Node target ) {
$['rows'].children.add( new Element.tag('email-tag'));
$['rows'].on["notneeded"].listen((Event e) {
(e.target as Element).remove();
});
}
Also, I would change the name of deletePhone, since the method no longer deletes the record but merely informs the parent that it is not needed. Call it 'notNeeded' or something similar.
EDIT
#ShailenTuli is right about encapsulation should not be broken.
But also JS Polymer elements access the parent in their layout elements because it's still convenient in some scenarios.
This works now in PolymerDart too.
(this.parentNode as ShadowRoot).host
ORIGINAL
You can fire an event and make the email-view-tag listen to this tag and the event handler can remove the event target from it's childs.
I had a similar question a while ago:
How to access parent model from polymer component
This was actually the question I wanted refer to
How can I access the host of a custom element
but the first one may be of some use too.
PolymerJS FAQ - When is the best time to access an element’s parent node?
attached() currently still named enteredView() in Dart, but will be renamed probably soon.

How do I fire a custom event from Polymer Dart?

I want to fire/send/emit a custom event from inside a Polymer element. For example, I want to convert a normal DOM event like "changed" to a more semantic event like "todoupdated".
This is the HTML that I have:
<polymer-element name="todo-item" extends="li" attributes="item">
<template>
<style>
label.done {
color: gray;
text-decoration: line-through;
}
</style>
<label class="checkbox {{item.doneClass}}">
<input type="checkbox" checked="{{item.done}}">
{{item.text}}
</label>
</template>
<script type="application/dart" src="todo_item.dart"></script>
</polymer-element>
I want the change events on checkbox to bubble out of the custom element as something more... useful. :)
Step 1
Capture the change events on the <input>. Notice the on-change below.
<!-- from inside todo_item.html -->
<input type="checkbox" checked="{{item.done}}" on-change="{{change}}">
Step 2
Handle the change event in the custom element code that contains the checkbox.
import 'package:polymer/polymer.dart';
import 'dart:html';
import 'models.dart';
#CustomTag('todo-item')
class TodoItemElement extends PolymerElement with ObservableMixin {
#observable Item item;
bool get applyAuthorStyles => true;
void change(Event e, var details, Node target) {
// do stuff here
}
}
Notice the change event handler. That method is run any time the checkbox state changes.
Step 3
Dispatch a custom event.
void change(Event e, var details, Node target) {
dispatchEvent(new CustomEvent('todochange'));
}
NOTE: the custom event name must not contain dashes.
Step 4
Listen for the custom event in a parent custom element.
<template repeat="{{item in items}}" >
<li is="todo-item" class="{{item.doneClass}}" item="{{item}}" on-todochange="todoChanged"></li>
</template>
Notice the use of on-todochange.
Enjoy!
Polymer has a helper method that simplifies firing events
// dispatch a custom event
this.fire('polymer-select', detail: {'item': item, 'isSelected': isSelected});
Additional info:
To make the event available to subscriber that want to add a listener programmatically
// getter
async.Stream<dom.CustomEvent> get onPolymerSelect =>
PolymerSelection._onPolymerSelect.forTarget(this);
// private EventStreamProvider
static const dom.EventStreamProvider<dom.CustomEvent> _onPolymerSelect =
const dom.EventStreamProvider<dom.CustomEvent>('polymer-select');
subscribe to the event programmatically instead of declaratively:
($['#ps'] as PolymerSelect) // get the children and cast it to its actual type
.onPolymerSelect.listen((e) => print(e['isSelected'])); // subscribe
I managed this using <core-signals> and the polymer helper method fire. This way you are able to listen to events fired from elements that are not children. source.
todochange.html
<!doctype html>
<polymer-element name="todo-item" extends="li">
<template>
<style>
label.done {
color: gray;
text-decoration: line-through;
}
</style>
<label class="checkbox {{item.doneClass}}">
<input type="checkbox" checked="{{item.done}}">
{{item.text}}
</label>
</template>
<script type="application/dart" src="todo_item.dart"></script>
</polymer-element>
todochange.dart
import 'package:polymer/polymer.dart';
import 'dart:html';
#CustomTag('todo-item')
class TodoItemElement extends PolymerElement {
#observable Item item;
void change(Event e, var details, Node target) {
// the name is the name of your custom event
this.fire( "core-signal", detail: { "name": "todochange" } );
}
}
Then any subscriber just has to do this
subscriber.html
...
<link rel="import" href="packages/core_elements/core_signals.html>
...
<template>
<core-signals on-core-signal-todochange="{{handleToDoChange}}"></core-signals>
...
</template>
subscriber.dart
#CustomTag( "subscriber" )
class Sub extends PolymerElement {
...
void handleToDoChange( Event e, var detail, Node target ) {
print( "Got event from <todo-item>" );
}
...
}

Resources