I have a problem with the performance of binding data to a table.
I started to use core-menu with paper-items.
paper-items are supposed to display a nice animation when clicked.
It works fine, but if I try to bind some data in the selected index changed handler, the animation frame rate is awful.
Is there anything I can do to improve the frame rate on paper-item?
Here is an example (binds 250 cells in a table).
clickcounter.html
<link rel="import" href="packages/polymer/polymer.html">
<link rel="import" href="packages/core_elements/core_menu.html">
<link rel="import" href="packages/core_elements/core_icons.html">
<link rel="import" href="packages/paper_elements/paper_item.html">
<polymer-element name="click-counter">
<template>
<style>
core-menu {
color: #01579b;
margin: 10px 0 0 0;
}
core-menu > paper-item {
transition: all 300ms ease-in-out;
}
core-menu > paper-item.core-selected {
background: #e1f5fe;
}
</style>
<div>
<core-menu selected="{{selIndex}}">
<paper-item icon="settings" label="Item 0">
<a ></a>
</paper-item>
<paper-item icon="settings" label="Item 1">
<a ></a>
</paper-item>
</core-menu>
</div>
<table>
<thead>
<tr>
<th>C1</th>
<th>C2</th>
<th>C3</th>
<th>C4</th>
<th>C5</th>
</tr>
</thead>
<tbody>
<tr template repeat="{{row in dataRows}}" >
<td>{{row.s1}}</td>
<td>{{row.s2)}}</td>
<td>{{row.s3}}</td>
<td>{{row.s4}}</td>
<td>{{row.s5}}</td>
</tr>
</tbody>
</table>
</template>
<script type="application/dart" src="clickcounter.dart"></script>
</polymer-element>
clickcounter.dart
import 'package:polymer/polymer.dart';
/**
* A Polymer click counter element.
*/
class TableItem {
String s1;
String s2;
String s3;
String s4;
String s5;
TableItem(this.s1, this.s2, this.s3, this.s4, this.s5);
}
#CustomTag('click-counter')
class ClickCounter extends PolymerElement {
#published var selIndex = 0;
#observable List<TableItem> dataRows = [];
ClickCounter.created() : super.created() {
makeData(selIndex);
}
selIndexChanged(var oldVal, var newVal) {
makeData(newVal);
}
void makeData(int newVal) {
List newList = new List();
for (int r = 0; r< 50; r++) {
int i = newVal * r;
String s = "${i}";
newList.add(new TableItem("${i}-1", "${i}-2", "${i}-3", "${i}-4", "${i}-5"));
}
dataRows = toObservable(newList);
}
}
Answering my question after Gunter's tips.
1 reassiging entire observable list
(as presented in question) - definitely the slowest
2 reassiging items in an observable list
improvement, but still unacceptable.
#observable List dataRows = toObservable([]);
void makeData(int newVal) {
List newList = new List();
for (int r = 0; r< 250; r++) {
int i = newVal * r;
String s = "${i}";
newList.add(new TableItem("${i}-1", "${i}-2", "${i}-3", "${i}-4", "${i}-5"));
}
dataRows.clear();
dataRows.addAll(newList);
}
3 reassiging items in an observable list with one time binding
much better, almost acceptable
Code changes as above, with one time binding:
<tr template repeat="{{row in dataRows}}" >
<td>[[row.s1]]</td>
<td>[[row.s2]]</td>
<td>[[row.s3]]</td>
<td>[[row.s4]]</td>
<td>[[row.s5]]</td>
</tr>
4 No list reassingment, observable elements
Definitely the fastest, works fine even with 1000 rows :)
class TableItemObs extends Object with Observable {
#observable String s1;
#observable String s2;
#observable String s3;
#observable String s4;
#observable String s5;
TableItemObs(this.s1, this.s2, this.s3, this.s4, this.s5);
}
selIndexChanged(var oldVal, var newVal) {
changeData(newVal);
}
void changeData(int newVal) {
for (int r = 0; r< 250; r++) {
int i = newVal * r;
dataRows[r].s1 = "${i}-1";
dataRows[r].s2 = "${i}-2";
dataRows[r].s3 = "${i}-3";
dataRows[r].s4 = "${i}-4";
dataRows[r].s5 = "${i}-5";
}
}
keep
<td>{{row.s1}}</td>
Update:
I tested another option:
3a reassiging items in an observable list with one time binding, with batches
A variation of option 3, but adding after clearing the list new items are added in batches.
Each function adds a part of the items.
Faster than option 3, but still below option 4.
selIndexChanged(var oldVal, var newVal) {
new Future(() => makeData1(newVal))
.then((_) => makeData2(newVal))
.then((_) => makeData3(newVal))
.then((_) => makeData4(newVal))
.then((_) => makeData5(newVal));
}
Related
I want to scroll to the last element of iron-list after each update of its model but calling IronList.scrollToIndex() shows only part of the list. More clearly, I have polymer element:
#PolymerRegister('main-app')
class MainApp extends PolymerElement {
#property
List<String> items = [];
MainApp.created() : super.created();
#reflectable
void onAdd([_, __]){
var fst = items.length;
var lst = new List.generate(30, (x) => (fst+x).toString());
addAll("items", lst);
($['list'] as IronList).scrollToIndex(items.length-1);
}
}
html:
<dom-module id="main-app">
<template>
<style>
#list {
height: 100px;
width: 100px;
}
</style>
<iron-list items="{{items}}" id="list">
<template>
<div>{{item}}</div>
</template>
</iron-list>
<paper-button raised on-tap="onAdd">add</paper-button>
</template>
</dom-module>
When I click on the button for the first time it gives me:
What's wrong here? How to update the view properly?
(package versions: polymer 1.0.0-rc.6, polymer_elements 1.0.0-rc.3, polymer_interop 1.0.0-rc.5)
I am trying to move an element using drag and drop. I want to be able to drag and element to a different location, and when I drop it, the element moves to the dropped location. Super basic, and nothing fancy. This is what I have so far:
html:
<input type='button' id='drag' class='draggable' value='drag me' draggable='true'>
Dart code:
Element drag = querySelector('.draggable');
drag.onDragEnd.listen((MouseEvent e) {
drag.style.left = '${e.client.x}px';
drag.style.top = '${e.client.y}px';
});
This doesn't quite do what I want it to do. The element is slightly off from where I drop it. I see examples in javascript with appendChild, clone(), parentNode, but none of the examples that I have seen can be reproduced in Dart. What is the best way to accomplish this? I don't want to use the DND package, since I am really trying to personally understand the concepts better.
index.html
<!doctype html>
<html>
<head>
<style>
#dropzone {
position: absolute;
top: 50px;
left: 50px;
width: 300px;
height: 150px;
border: solid 1px lightgreen;
}
#dropzone.droptarget {
background-color: lime;
}
</style>
</head>
<body>
<input type='button' id='drag' class='draggable' value='drag me'
draggable='true'>
<div id="dropzone"></div>
<script type="application/dart" src="index.dart"></script>
<script src="packages/browser/dart.js"></script>
</body>
</html>
index.dart
library _template.web;
import 'dart:html' as dom;
import 'dart:convert' show JSON;
main() async {
dom.Element drag = dom.querySelector('.draggable');
drag.onDragStart.listen((event) {
final startPos = (event.target as dom.Element).getBoundingClientRect();
final data = JSON.encode({
'id': (event.target as dom.Element).id,
'x': event.client.x - startPos.left,
'y': event.client.y - startPos.top
});
event.dataTransfer.setData('text', data);
});
dom.Element dropTarget = dom.querySelector('#dropzone');
dropTarget.onDragOver.listen((event) {
event.preventDefault();
dropTarget.classes.add('droptarget');
});
dropTarget.onDragLeave.listen((event) {
event.preventDefault();
dropTarget.classes.remove('droptarget');
});
dropTarget.onDrop.listen((event) {
event.preventDefault();
final data = JSON.decode(event.dataTransfer.getData('text'));
final drag = dom.document.getElementById(data['id']);
event.target.append(drag);
drag.style
..position = 'absolute'
..left = '${event.offset.x - data['x']}px'
..top = '${event.offset.y - data['y']}px';
dropTarget.classes.remove('droptarget');
});
}
The answer above is correct, and I didn't want to edit it for that reason. However, I wanted to also offer another answer that I derived from the above. It is a lot more basic compared to the above, and so easier to follow the basic concepts for beginners. As mentioned below, I don't think you can move elements unless they are within a droppable area.
index.html:
<!DOCTYPE html>
<html>
<head>
<style>
#dropzone {
position: absolute;
top: 100px;
left: 50px;
width: 300px;
height: 150px;
border: solid 1px;
color: lightgreen;
}</style>
</head>
<body>
<div id="dropzone">
<input type='button' id='drag' class='draggable' value='drag me'
draggable='true'>
</div>
<script type="application/dart" src="main.dart"></script>
</body>
</html>
main.dart:
import 'dart:html';
main() {
Element drag = querySelector('.draggable');
Element drop = querySelector('#dropzone');
drag.onDragStart.listen((MouseEvent e) {
var startPos = (e.target as Element).getBoundingClientRect();
String xPos = "${e.client.x - startPos.left}";
String yPos = "${e.client.y - startPos.top}";
e.dataTransfer.setData('x', xPos);
e.dataTransfer.setData('y', yPos);
});
drop.onDragOver.listen((MouseEvent e) {
e.preventDefault();
});
drop.onDrop.listen((MouseEvent e) {
e.stopPropagation();
String xPos = e.dataTransfer.getData('x');
String yPos = e.dataTransfer.getData('y');
int x = num.parse(xPos);
int y = num.parse(yPos);
drag.style.position = 'absolute';
drag.style
..left = '${e.offset.x - x}px'
..top = '${e.offset.y - y}px';
});
}
I had the same question and since the answers above did not meet my needs in:
Element drag-gable by itself(No drop zone)
Reusable
For a wrapper based solution, this package could be the answer:https://pub.dartlang.org/packages/dnd
Custom element based approach(Currently cursor styling is not working):
main(){
document.registerElement('draggable-element',
DraggableElement);
querySelector('body').append(new DraggableElement()..text='draggable');
}
class DraggableElement extends HtmlElement with Draggability{
DraggableElement.created():super.created(){
learn_custom_draggability();
}
factory DraggableElement(){
return new Element.tag('draggable-element');
}
}
class Draggability{
bool __custom_mouseDown = false;
//The Coordinates of the mouse click
//relative to the left top of the
//element.
Point<int> __custom_relative_mouse_position;
void learn_custom_draggability(){
if(this is! HtmlElement ){
throw ("Draggability mixin "
"is not compatible with"
' non-HtmlElement.');
}
var self = (this as HtmlElement);
self.onMouseDown.listen(mouseDownEventHandler);
self.onMouseUp.listen(mouseUpEventHandler);
//styling
self.style.position = 'absolute';
window.onMouseMove
.listen(mouseMoveEventHandler);
}
void mouseMoveEventHandler(MouseEvent e){
if(!__custom_mouseDown) return;
int xoffset = __custom_relative_mouse_position.x,
yoffset = __custom_relative_mouse_position.y;
var self = (this as HtmlElement);
int x = e.client.x-xoffset,
y = e.client.y-yoffset;
print(x);
if(y == 0) return;
self.style
..top = y.toString() +'px'
..left = x.toString()+'px';
}
void mouseDownEventHandler(MouseEvent e){
print('mouse down');
__custom_mouseDown = true;
var self = (this as HtmlElement);
self.style.cursor = 'grabbing';
__custom_relative_mouse_position =
e.offset;
}
void mouseUpEventHandler(MouseEvent e){
print('mouse up');
__custom_mouseDown = false;
var self = (this as HtmlElement);
self.style.cursor = 'default';
}
}
Edit:
Yay, Thank you Günter Zöchbauer for informing me about reflectable. It's so small and compiles fast.
A little off the topic but posting since mixins and the below pattern goes hand in hand.
import 'package:reflectable/reflectable.dart';
class Reflector extends Reflectable{
const Reflector():
super(
instanceInvokeCapability,
declarationsCapability
);
}
const reflector = const Reflector();
#reflector
class CustomBase extends HtmlElement{
CustomBase.created():super.created(){
learn();
}
learn(){
InstanceMirror selfMirror = reflector.reflect(this);
var methods = selfMirror.type.instanceMembers;
RegExp pattern = new RegExp('^learn_custom_.*bility\$');
for(var k in methods.keys){
if(pattern.firstMatch(k.toString()) != null){
selfMirror.invoke(k,[]);
}
}
}
}
Include: "reflectable: ^0.5.0" under dependencies and "- reflectable: entry_points: web/index.dart" etc under transformers
in the pubspec.yaml and extend a custom class like the above instead of a HtmlElement and selfMirror.invoke magically calls your initializer as long as their names match the given pattern. Useful when your classes have a quite few abilities.
I am trying to use a button to add a new row in an html table when the button is clicked, and another button to remove the row/s with its checkbox checked. I am experience two problems:
Whenever the add-button is clicked I get Removing disallowed attribute SELECT on-change="{{ onChangeTypeFired }}"
If I select two checkboxes and clicked the delete row button it throws an indexIndexSizeError: Index or size was negative, or greater than the allowed value. The index provided (2) is greater than the number of rows in the table (2).
The files are shown below
.html
<!DOCTYPE html>
<link rel='import' href='../../../../packages/polymer/polymer.html' >
<link rel='import' href='../../../../packages/paper_elements/paper_checkbox.html' >
<link rel='import' href='../../../../packages/paper_elements/paper_button.html' >
<link rel='import' href='../../../../packages/core_elements/core_collapse.html' >
<link rel='import' href='../../../../packages/core_elements/communication_icons.html' >
<polymer-element name='phone-form'>
<template>
<div
class='parent-container'>
<paper-button id='add-btn' on-click='{{toggle}}'>
<core-icon id='toggle-btn-icon' icon=''></core-icon>
Phone
<core-icon id='validation-icon' class='margin-l5px' icon=''></core-icon>
</paper-button>
<table id='table' border='1' width='350px'>
<tbody><tr>
<td><input name='chk' type='checkbox'></td>
<td>
<select id='phoneType' class='width95percent'
selectedIndex='{{typeSelected}}'
on-change='{{onChangeTypeFired}}'>
<option template repeat='{{key in types.keys}}'
value='{{types[key]}}'>{{types[key]}}
</option>
</select>
</td>
</tr>
</tbody></table>
</div>
<div>
<paper-button raised id='add-row-btn' class='margin-8px'
on-click='{{addRow}}'>
Add Row
<core-icon id='add-row-btn-icon' icon='check-all'></core-icon>
</paper-button>
<paper-button raised id='delete-row-btn' class='margin-8px'
on-click='{{deleteRow}}'>
Delete Selected Row(s)
<core-icon id='delete-row-btn-icon' icon='check'></core-icon>
</paper-button>
</div>
</div>
</template>
<script type='application/dart' src='phone_form.dart'></script>
</polymer-element>
.dart
import 'package:polymer/polymer.dart';
import 'dart:html' as dom;
import 'package:paper_elements/paper_button.dart' show PaperButton;
import 'package:core_elements/core_collapse.dart';
//import 'package:epimss_shared/epimss_shared.dart';
//import 'package:epimss_shared/epimss_shared_client.dart' hide DataEvent;
#CustomTag('phone-form')
class PhoneForm extends PolymerElement {
#observable String errorMsg;
String topic;
PaperButton addBtn;
int typeSelected = 0;
CoreCollapse coreCollapse;
#observable
Map<String, String> types = <String, String> {
'': '',
'Car': 'Car',
'Direct': 'Direct',
'Fax': 'Fax',
'Home': 'Home',
'Mobile': 'Mobile',
'Video': 'Video',
'Work': 'Work'
};
PhoneForm.created() : super.created();
void toggleCoreCollapse() {
coreCollapse.toggle();
}
void onSelectTypeFired()
{
}
void onChangeTypeFired( dom.Event e, var detail, dom.SelectElement target)
{
print(target.value);
}
void addRow()
{
var table = $['table'];
var rowCount = table.rows.length;
var row = table.insertRow(rowCount);
var colCount = table.rows[0].cells.length;
for(var i = 0; i<colCount; i++ )
{
var newcell = row.insertCell(i);
newcell.innerHtml = table.rows[0].cells[i].innerHtml;
switch(newcell.childNodes[0].runtimeType.toString())
{
case 'text':
newcell.childNodes[0].value = '';
break;
case 'checkbox':
newcell.childNodes[0].checked = false;
break;
case 'select':
newcell.childNodes[0].selectedIndex = 0;
break;
}
}
}
void deleteRow()
{
var rowsToDelete = [];
try{
var table = $['table'];
var rowCount = table.rows.length;
for(var i = 0; i < rowCount; i++)
{
var row = table.rows[i];
var chkbox = row.cells[0].childNodes[0];
if(chkbox != null && chkbox.checked)
{
if(rowCount <= 1)
{
print('Cannot delete all the rows.');
break;
}
else
{
rowsToDelete.add(i);
}
}
}
rowsToDelete.forEach( (row)
{
table.deleteRow(row);
});
}
catch(e)
{ print(e); }
}
#override
void attached() {
super.attached();
topic = this.dataset['topic'];
coreCollapse = $['core-collapse'];
addBtn = $['add-btn'];
}
}
Thanks for your help.
It is probably caused by this line
newcell.innerHtml = table.rows[0].cells[i].innerHtml;
You need to specify which elements and tags are allowed to be added to the DOM from a string.
see https://stackoverflow.com/a/27334820/217408 for more details.
That doesn't apply when you generate the elements imperatively like new DivElement().
In your example it would probably easier and better to do something like
newcell.children.clear();
newcell.children.addAll(table.rows[0].cells[i].children.map((c) =>
c.clone(true)));
Caution: I'm not sure about how the code should exactly look like but I think you get the idea. If you can't work it out add a comment and I'll have a closer look.
When I have a reference to an element that was produced by a <template>, in Polymer.js such elements have an attribute templateInstance that provides a references to its template like it's used here:
https://github.com/PolymerLabs/polymer-selector/blob/master/polymer-selector.html#L286
Polymer >= 1.0.0
#reflectable
void someClickHandler(dom.Event event, [_]) {
// for native events (like on-click)
var model = new DomRepeatModel.fromEvent(event);
// or for custom events (like on-tap, works also for native events)
var model = new DomRepeatModel.fromEvent(convertToJs(event));
var value = model.jsElement['items'];
// or
var value = model.jsElement[$['mylist'].attributes['as']];
// if you used the `as="somename"`
// in your <core-list> or <template is="dom-repeat">
}
There is an open issue related to custom events: https://github.com/dart-lang/polymer-dart/issues/624
Polymer <= 0.16.0
EDIT
The example below needs only this 3 lines, the other code is just for demonstration purposes
import 'package:template_binding/template_binding.dart' as tb;
tb.TemplateInstance ti = tb.nodeBind(e.target).templateInstance;
var value = ti.model.value as Inner;
EDIT END
This functionality was added recently (see https://code.google.com/p/dart/issues/detail?id=17462)
I created an example to test how it works:
index.html
<!DOCTYPE html>
<html>
<head>
<title>nested-repeat</title>
<!-- <script src="packages/web_components/platform.js"></script>
not necessary anymore with Polymer >= 0.14.0 -->
<script src="packages/web_components/dart_support.js"></script>
<link rel="import" href="nested_templates.html">
</head>
<body>
<nested-templates></nested-templates>
<script type="application/dart">export 'package:polymer/init.dart';</script>
</body>
</html>
nested_templates.html
<link rel="import" href="packages/polymer/polymer.html">
<polymer-element name="nested-templates">
<template>
<style>
:host { display: block; height: 100%; }
ul { margin: 0; padding: 0; }
li { font-size: 0.85rem; padding-left: 0.75rem; }
li:hover { background: lightgrey; cursor: pointer; }
li.selected { color: red; }
</style>
<div>
<template repeat="{{o in outer}}">
<strong>{{o.name}}</strong>
<ul>
<template repeat="{{i in o.inner}}">
<li id="{{i.name}}" on-click="{{innerClickHandler}}" template-value='{{i}}'>{{i.name}}</li>
</template>
</ul>
</template>
</div>
</template>
<script type="application/dart" src="nested_templates.dart"></script>
</polymer-element>
nested_templates.dart
import 'dart:html' as dom;
import 'package:polymer/polymer.dart';
import 'package:template_binding/template_binding.dart' as tb;
#CustomTag('nested-templates')
class NestedTemplates extends PolymerElement {
NestedTemplates.created() : super.created();
#observable List<Outer> outer = toObservable([new Outer('o1', toObservable(
[new Inner('a'), new Inner('b')])), new Outer('o2', toObservable([new Inner(
'c'), new Inner('d')]))], deep: true);
void innerClickHandler(dom.Event e) {
shadowRoot.querySelectorAll('li.selected').forEach((e) => (e as
dom.HtmlElement).classes.remove('selected'));
(e.target as dom.HtmlElement).classes.add('selected');
tb.TemplateInstance ti = tb.nodeBind(e.target).templateInstance; // get access to the TemplateInstance of the element
// TemplateInstance provides access to the model and the actual value
var value = ti.model.value as Inner;
print('name: ${value.name}'); // works
print('equals: ${value == (e.target as dom.HtmlElement).attributes['template-value']}'); // prints "false"
print(
'${(e.target as dom.HtmlElement).attributes['template-value']}'); // prints "Instance of 'Inner'"
// shows that the attribute only has the result of 'toString()' but not the actual value of type 'Inner'
print(
'${(e.target as dom.HtmlElement).attributes['template-value'].runtimeType}'); // prints "String"
}
}
class Inner extends Observable {
#observable String name;
Inner(this.name);
}
class Outer extends Observable {
#observable String name;
List<Inner> inner;
Outer(this.name, this.inner);
}
I have found multiple ways to do this in various browsers and languages but nothing I can find has worked for Dart-polymer in Chrome.
Otherwise everything is very simple and standard:
<template>
<style>
textarea {
width: 825px;
}
</style>
<div>
<textarea id="ta" rows="10" on-mouseover="{{on_mouse_over}}">
{{message}}
</textarea>
</div>
</template>
Thanks!
I guess this is what you want:
#CustomTag('my-text')
class MyText extends PolymerElement {
#observable String message = "";
MyText.created() : super.created();
void messageChanged(old) {
var ta = $['ta'];
ta.scrollTop = ta.scrollHeight;
}
void attached() {
super.attached();
new Timer.periodic(new Duration(seconds: 1), (_) => message += 'some text ${new DateTime.now()}\n');
}
}
I added id="ta" to the <textarea> to make $['ta'] work