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.
Related
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>
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!');
}
}
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.
I started with the generating click-counter example. I made the click-counter into a library which I then imported into my main file. The click-counter component can be added manually by putting the appropriate HTML into the web page before running the program. However, I've been unable to find a way to dynamically add the click-counter web component to a div. My attempts have either ended in "Aw, snap!" errors or simply with nothing happening.
The click-counter (xclickcounter.dart):
library clickcounter;
import 'package:web_ui/web_ui.dart';
class CounterComponent extends WebComponent {
int count = 0;
void increment() {
count++;
}
}
The main HTML:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Sample app</title>
<link rel="stylesheet" href="test1.css">
<!-- import the click-counter -->
<link rel="components" href="xclickcounter.html">
</head>
<body>
<h1>Test1</h1>
<p>Hello world from Dart!</p>
<div id="sample_container_id">
<div is="x-click-counter" id="click_counter" count="{{startingCount}}"></div>
</div>
<script type="application/dart" src="test1.dart"></script>
<script src="packages/browser/dart.js"></script>
</body>
</html>
main file:
import 'dart:html';
import 'package:web_ui/web_ui.dart';
import 'xclickcounter.dart';
// initial value for click-counter
int startingCount = 5;
void main() {
// no error for adding an empty button
var button = new ButtonElement();
query('#sample_container_id').append(button);
// doesn't work (gives "Aw, snap!" in Dartium)
query('#sample_container_id').append(new CounterComponent());
// Nothing happens with this code. Nothing appears.
// But I promise this same thing was giving Aw, Snap
// for a very similar program
final newComponentHtml = '<div is="x-click-counter"></div>';
query('#sample_container_id').appendHtml(newComponentHtml);
}
I tried added an empty constructor to click-counter but it still crashes.
I had the same issue.
See the example (not mine) at https://github.com/dart-lang/web-ui/blob/master/test/data/input/component_created_in_code_test.html and let me know if it works for you.
TL;DR:
void main() {
var element = query('#sample_container_id');
appendComponentToElement(element, new CounterComponent() );
}
void appendComponentToElement(Element element, WebComponent component) {
component.host = new DivElement();
var lifecycleCaller = new ComponentItem(component)..create();
element.append(component.host);
lifecycleCaller.insert();
}
There's more info at my web-ui#dartlang.org post: https://groups.google.com/a/dartlang.org/d/topic/web-ui/hACXh69UqG4/discussion
Hope that helps.
How do I dynamically load a snippet of HTML and insert it into my web page? I am using Dart.
Glad you asked! Using Dart for this task isn't much different than JavaScript, except you get typing, code completion, and a slick editing experience.
First, create the snippet.html:
<p>This is the snippet</p>
Next, create the application. Notice the use of XMLHttpRequest to request the snippet. Also, use new Element.html(string) to create a block of HTML from a string.
import 'dart:html';
void main() {
var div = querySelector('#insert-here');
HttpRequest.getString("snippet.html").then((resp) {
div.append(new Element.html(resp));
});
}
Finally, here's the host HTML page:
<!DOCTYPE html>
<html>
<head>
<title>dynamicdiv</title>
</head>
<body>
<h1>dynamicdiv</h1>
<div id="insert-here"></div>
<script type="application/dart" src="dynamicdiv.dart"></script>
<script src="packages/browser/dart.js"></script>
</body>
</html>
main.dart:
import 'dart:html';
DivElement div = querySelector('div');
main() async {
String template = await HttpRequest.getString("template.html");
div.setInnerHtml(template, treeSanitizer: NodeTreeSanitizer.trusted);
}
template.html:
<h1>Hello world.</h1>
Check my bird... <em>it flies</em> !
<img src="https://www.dartlang.org/logos/dart-bird.svg">
For the full example, that runs out of the box, see:
https://gist.github.com/kasperpeulen/536b021ac1cf397d4e6d
Note that you need 1.12 to get NodeTreeSanitizer.trusted working.
You can try this example.
https://jsfiddle.net/kofwe39d/ (JS compiled from Dart source code.)
web/main.dart
import 'dart:async';
import 'dart:html';
import 'package:virtual_dom/components/component.dart';
import 'package:virtual_dom/features/state.dart';
import 'package:virtual_dom/helpers/h.dart';
import 'package:virtual_dom/helpers/mount.dart';
import 'package:virtual_dom/helpers/styles.dart';
import 'package:virtual_dom/helpers/vhtml.dart';
void main() {
final app = document.getElementById('app')!;
mount(app, _App());
}
class _App extends Component {
#override
Object render() {
final timer = State.get('timer', () => 3);
final setTimer = State.set<int>('timer');
if (timer > 0) {
Timer(Duration(seconds: 1), () {
setTimer(timer - 1);
});
}
final html = timer > 0
? ''
: '''
Hello, <strong>World!</strong>
''';
final style = styles({'padding': '6px'});
return h('div', {
'style': style
}, [
if (timer > 0) '$timer sec',
h('p', 'Your html:'),
vHtml('div', html),
]);
}
}
web/index.html
<!DOCTYPE html>
<html style="height: 100%;">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Example application</title>
<link rel="stylesheet" href="normalize.css">
<link rel="stylesheet" href="styles.css">
<script defer src="main.dart.js"></script>
</head>
<body style="height: 100%; font-family: Verdana,sans-serif; font-size:15px; line-height:1.5">
<div id="app" style="height: 100%;"></div>
</body>
</html>