Angular Dart - execute a function right after ng-repeat has finished - dart

Searching stackoverflow for this question always give me the solution for Angular.js.
How would you do this in Anuglar dart?
That is I want to have a function run after an ng-repeat directive in angular dart has finished rendering its elements.
EDIT:
It turns out that knowing when ng-repeat has finished enumerating doesn't means the appropriate dom element has being attached to the document.
I guess my intended question was how to determine when all
elements enumerated by ng-repeat is attached to the dom tree and can be
queried by selector
EDIT2
<div ng-repeat='fruit in fruits'>
<li class="fruit">{{fruit}}</li>
</div>
querySelectorAll('.fruit'); // should return all <li> element created by ng-repeat
// the trick is knowing when and where to call querySelectorAll
Thanks

I tried {{foo($last)}} and it didn't work for me either.
This example uses MutationObserver to get notified about added elements.
I tried it and it worked.
<!DOCTYPE html>
<html>
<head>
<script src="packages/shadow_dom/shadow_dom.debug.js"></script>
</head>
<body ng-cloak>
<ul somefruits>
<li ng-repeat='fruit in ctrl.fruits track by $index'
class="fruit" ng-class="{'ng-last': $last}">{{fruit}}</li>
</ul>
<script type="application/dart" src="index.dart"></script>
<script type="text/javascript" src="packages/browser/dart.js"></script>
</body>
</html>
library angular_default_header.main;
import 'dart:html';
import 'package:angular/angular.dart';
#NgController(selector: '[somefruits]', publishAs: 'ctrl')
class Fruits {
Fruits(){
print('fruits created');
}
List fruits = ['Apple', 'Banana', 'Orange', 'Kiwi'];
}
class MyAppModule extends Module {
MyAppModule() {
type(Fruits);
}
}
void mutationCallback(List<MutationRecord> mutations, MutationObserver observer) {
mutations.forEach((mr) {
mr.addedNodes.forEach((Node n) {
if(n is Element) {
if(n.classes.contains('ng-last')) {
print('last added');
observer.disconnect();
n.style.border = '1px solid red';
}
}
});
});
}
main() {
print('main');
var ul = document.querySelector('ul');
new MutationObserver(mutationCallback).observe(ul, childList: true);
ngBootstrap(module: new MyAppModule());
}

See angular.NgRepeatDirective
`$last` [bool] true if the repeated element is last in the iterator.
However unless your list is fairly large, should not most of the elements be drawn at roughly the same time? Perhaps you could watch the collection for changes.

have a look at this GitHub issue (https://github.com/angular/angular.dart/issues/843) for more information.
It smells like a bug to me, but I'm not sure.
As a workaround you could schedule a future outside of angular. There's more info about this in the above mentioned issue.

Related

Auto convert html string to element

Say I have a polymer-element <polymer-element> <div id="foo"> {{html}} </div> </polymer-element>, where html is supposed to be a HTML formated string, like <p>blah</p>, what I want is, when html changes, the polymer-element also changes, and use html as its innerHtml, i.e. auto convert the string to an element and insert it as foo's child.
Can polymer/polymer_expression do this for me, or I have to do a querySelector(), then set html as innerHtml manually?
My solution is a custom element that extends a div and uses the DocumentFragment class to parse HTML strings into the DOM via data binding.
From my Github gist
<!-- Polymer Dart element that allows HTML to be inserted into the DOM via data binding -->
<link rel="import" href="packages/polymer/polymer.html">
<polymer-element name="html-display" extends="div">
<script type="application/dart">
import 'dart:html';
import 'package:polymer/polymer.dart';
#CustomTag('html-display')
class HtmlDisplay extends DivElement with Polymer, Observable {
#published String htmlContent;
// we need this stuff because we're extending <div> instead of PolymerElement
factory HtmlDisplay() => new Element.tag('div', 'html-display');
HtmlDisplay.created() : super.created() {
polymerCreated();
}
#override void attached() {
super.attached();
}
// respond to any change in the "htmlContent" attribute
void htmlContentChanged(oldValue) {
if (htmlContent == null) {
htmlContent = "";
}
// creating a DocumentFragment allows for HTML parsing
this.nodes..clear()..add(new DocumentFragment.html("$htmlContent"));
}
}
</script>
</polymer-element>
<!--
Once you've imported the Polymer element's HTML file, you can use it from another Polymer element like so:
<link rel="import" href="html_display.html">
<div is="html-display" htmlContent="{{htmlString}}"></div>
*htmlString* can be something like "I <em>love</em> Polymer Dart!"
-->
I use solution as described in https://stackoverflow.com/a/20869025/789338.
The key class is DocumentFragment.
The official way to do it is described in the doc: https://www.polymer-project.org/docs/polymer/databinding-advanced.html#boundhtml
The example on the doc:
<polymer-element name="my-element">
<template>
<div id="message_area"></div>
</template>
<script>
Polymer({
message: 'hi there',
ready: function() {
this.injectBoundHTML('<b>{{message}}</b>', this.$.message_area);
}
});
</script>
</polymer-element>

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.

Polymer Dart $[] selector in an autobinding model

Since polymer-body has been removed, we need to use an auto-binded template to use polymer binding features outside of a PolymerElement:
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Sample app</title>
<script src="packages/web_components/platform.js"></script>
<script src="packages/web_components/dart_support.js"></script>
<link rel="import" href="packages/polymer/polymer.html">
<script src="packages/browser/dart.js"></script>
</head>
<body>
<template is="auto-binding-dart">
<div>Say something: <input value="{{value}}"></div>
<div>You said: {{value}}</div>
<button id="mybutton" on-tap="{{buttonTap}}">Tap me!</button>
</template>
<script type="application/dart">
import 'dart:html';
import 'package:polymer/polymer.dart';
import 'package:template_binding/template_binding.dart';
main() {
initPolymer().run(() {
Polymer.onReady.then((_) {
var template = document.querySelector('template');
templateBind(template).model = new MyModel();
});
});
}
class MyModel extends Observable {
//$['mybutton'] wont works there
#observable String value = 'something';
buttonTap() => print('tap!');
}
</script>
</body>
</html>
Unfortunately, the whole model now extends Observable, every binding seems to work, but the PolymerElement array selector $['foo'] cant be used anymore...
Is there any easy way to implement this $['id'] selector into an Observable model?
I would suggest to use a normal Polymer element instead of auto-binding-dart.
Then you don't have to care about differences and you need no 'main'.
I always start a Polymer project with an <app-element> Polymer element that acts as main() and container for the entire app.
I also would suggest to not use inline code.
As far as I know it has some limitations especially debugging is not supported (might be fixed already, I don't know because I never use it).
To make $ work you need a small and simple workaround;
import 'dart:html';
import 'package:polymer/polymer.dart';
import 'package:template_binding/template_binding.dart';
Map<String, dynamic> $; // we define our own '$'
main() {
initPolymer().run(() {
Polymer.onReady.then((_) {
var template = document.querySelector('template') as Polymer;
$ = template.$; // we assign template.$ to our own '$' so we can omit the 'template' part
templateBind(template).model = new MyModel();
});
});
}
class MyModel extends Observable {
//$['mybutton'] wont work there - it can't because this is outside of a method
#observable String value = 'something';
buttonTap() {
print($['mybutton'].id); // here it works
print('tap!');
}
}

Google Chart is shown NgDirective, but not NgComponent

I'm having issues with showing Google Graphs in an AngularDart component, but not in an AngularDart directive. For the code below the graph is nicely displayed for the directive, where the div node has had two divs added as children to contain the graph and SVG. But for the compoenent, the graph is not shown, but in the DOM the same divs appear as children. Any ideas of how to debug this or solutions for it?
Running the code below prints out to console both Succesfully printed plot for Component graph and Succesfully printed plot for Directive graph.
The simplest code to showcase it is:
main.dart:
import 'dart:html' as dom;
import 'package:js/js.dart' as js;
import 'package:angular/angular.dart';
void printPlot(dom.Element element, String title) {
var gviz = js.context.google.visualization;
var gTableData = new js.Proxy(gviz.DataTable);
gTableData.addColumn('number', 'Size');
gTableData.addColumn('number', 'Counts');
gTableData.addRow(js.array([1, 2]));
gTableData.addRow(js.array([3, 4]));
var options = js.map({
'title': title
});
// Create and draw the visualization.
var chart = new js.Proxy(gviz.ScatterChart, element);
chart.draw(gTableData, options);
print("Succesfully printed plot for $title");
}
#NgComponent(selector: '[my-component]')
class MyComponent {
final dom.Element element;
MyComponent(this.element) {
js.context.google.load('visualization', '1', js.map({
'packages': ['corechart'],
'callback': (() => printPlot(element, 'Component graph'))
}));
}
}
#NgDirective(selector: '[my-directive]')
class MyDirective {
final dom.Element element;
MyDirective(this.element) {
js.context.google.load('visualization', '1', js.map({
'packages': ['corechart'],
'callback': (() => printPlot(element, 'Directive graph'))
}));
}
}
#NgController(selector: '[controller]',
publishAs: 'ctrl')
class Controller {
Controller() {
}
}
class DashboardModule extends Module {
DashboardModule() {
type(Controller);
type(MyComponent);
type(MyDirective);
}
}
void main() {
ngBootstrap(module: new DashboardModule());
}
index.html
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title></title>
</head>
<body controller>
<div my-component></div>
<div my-directive></div>
<script type="text/javascript" src="https://www.google.com/jsapi"></script>
<script type="application/dart" src="main.dart"></script>
<script type="text/javascript" src="packages/browser/dart.js"></script>
</body>
</html>
This is very probably due to the shadowDOM introduced by the NgComponent.
When the JavaScript library doesn't support shadowDOM (which it probably doesn't) it just won't find your DOM elements and therefore can't act on them.
The AngularDart team is working on someone similar to NgComponent but without shadowDOM (which is currently not possible). But this is in experimental state only (as far as I know) and no release date available.

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.

Resources