TextArea Resizing in Dart during DOM Initialization - dart

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.

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>

Polymer Dart core-style ref not resolved

When I pass parameters to a Polymer element the core-style ref does not get resolved.
Here is the child code:
<link rel="import" href="packages/polymer/polymer.html">
<link rel="import" href="packages/core_elements/core_style.html">
<core-style id="s1" unresolved> div { background: yellow; } </core-style>
<core-style id="s2" unresolved> div { background: pink; } </core-style>
<polymer-element name='test-cell' attributes='s t' noscript>
<template>
<core-style ref="{{s}}"></core-style>
<div>{{t}}</div>
</template>
</polymer-element>
As you can see, there are two core styles.
Here is the parent code. It takes a List and instantiates 'test-cell' with text and a style reference.
<polymer-element name='test-rows'>
<template>
<template repeat='{{ v in x }}'>
<test-cell s={{v.s}} t={{v.t}}></test-cell>
</template>
</template>
</polymer-element>
In this simple example the Dart code is inline. Here it is:
<script type='application/dart'>
import 'package:polymer/polymer.dart';
//======================================
class Info {
String s, t;
Info(this.s, this.t) {}
}
//======================================
#CustomTag('test-rows')
class TestRows extends PolymerElement {
#observable List<Info> x = toObservable([]);
//-----------------------------------
TestRows.created() : super.created() {
x.add(toObservable(new Info('s1', 'first' )));
x.add(toObservable(new Info('s2', 'second')));
}
}
</script>
In the generated HTML the text comes through OK but the core-style instances both have
ref="{{s}}"
and the styles are not applied. Can I force resolution of the style ref parameter by some alternative annotation? It is essentially a final/const.
Update
I think the problem is your noscript in your test-cell element.
Binding doesn't work in Dart with Polymer elements without a backing script (noscript) as far as I know.
I think in your case the <test-cell> element needs a field
#observable
var s;
to make this work.
Original
Your code doesn't show if you ever assign a value to s.
I doubt toObservable works on plain Dart objects. This is for lists and maps as far as I know.
The Info class should look like this and you don't need to use toObservable() with it.
class Info extends Object with Observable {
#observable
String s;
#observable
String t;
Info(this.s, this.t) {}
}

Polymer core-selector does not refresh

The Dart code for my Polymer element looks like this:
#CustomTag('my-element')
class MyElement extends PolymerElement {
final List<String> colors = toObservable(['red', 'green', 'blue']);
MyElement.created() : super.created();
}
And the HTML looks like this:
<polymer-element name="my-element">
<template>
<style>
.core-selected {
font-weight: bold;
}
</style>
<core-selector id="selector" selected="1">
<template repeat="{{color in colors}}">
<div value="{{color}}">{{color}}</div>
</template>
</core-selector>
<hr>
<!-- Prints the selected index, but does not update -->
<div>{{$['selector'].selected]}}</div>
</template>
<script type="application/dart" src="my_element.dart"></script>
</polymer-element>
Using <div>{{$['selector'].selected]}}</div> correctly shows the index of the selected color, but picking a different color does not refresh the value of selected. Am I using this correctly? Or is this a bug?
I agree it's a bug, but in the meantime you can work around it like this
<core-selector id="selector" selected="{{selected}}">
...
<div>{{selected}}</div>
with the backing code containing the obvious
#observable int selected = 1;
I do wonder if your version works when used in a pure JS environment? But that's another question.

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

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.

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