Custom Element - null reference from query() in constructor - dart

I'm working on my first Dart app, having completed the Game of Darts tutorials. I am trying to create a semantically named top-menu element that will eventually display a list of navigation menu tabs at the top of my page. My Dart app is able to recognize my custom element and calls the associated constructor.
However, I am getting a null reference when trying to query for the UL element within my custom element. I need the UL reference in order to dynamically load my LI elements into the menu.
Question 1:
Should the element be visible in the DOM at the point where the constructor is running?
Question 2:
If it is not yet visible, is there a Dart event I can use to trigger loading of the LI elements after the custom element has been completely loaded into the DOM?
Thanks in advance! For reference, here is the source of my custom element:
topmenu-element.html
<!DOCTYPE html>
<html>
<body>
<element name="top-menu" constructor="TopMenu" extends="div">
<template>
<div>
Top Menu
<ul id="top-menu-list"></ul>
</div>
</template>
<script type="application/dart" src="topmenu-element.dart"></script>
</element>
</body>
</html>
topmenu-element.dart
import 'package:web_ui/web_ui.dart';
import 'dart:html';
class TopMenu extends WebComponent {
List menuItems = ['Session', 'Authentication Services', 'Vehicle Services', 'Subscriber Services', 'Data Services'];
void populateMenu() {
UListElement menuList = query('#top-menu-list');
LIElement newMenuItem = new LIElement();
newMenuItem.text = menuItems[0];
menuList.children.add(newMenuItem);
}
TopMenu() {
// populateMenu();
}
}

I can't speak specifically about the DOM visibility in a constructor with the query method as I'm truthfully not certain. However there are perhaps better methods which you can use, which are called at various stages in the elements lifecycle.
That said, can I ask why you need to use this particular method to add the children. It is probably much easier to do it with the template repeat like so:
<!DOCTYPE html>
<html>
<body>
<element name="top-menu" constructor="TopMenu" extends="div">
<template>
<div>
Top Menu
<ul id="top-menu-list">
<li template repeat="item in menuItems">{{item}}</li>
</ul>
</div>
</template>
<script type="application/dart" src="topmenu-element.dart"></script>
</element>
</body>
</html>
Then there's no need to put any of your menu display code in your constructor.

Related

Creating tab header for tabs web component using polymer-dart

I'm trying to create custom-tabs web-component using polymer-dart. The component itself is a tab container, which can have a custom-tab elements inside of it.
I want to have an html like this:
<custom-tabs selected="three">
<custom-tab name="one">... content skipped ...</custom-tab>
<custom-tab name="two">... content skipped ...</custom-tab>
<custom-tab name="three">... content skipped ...</custom-tab>
</custom-tabs>
In custom-tabs html file I want to have something like this:
<polymer-element name="custom-tabs">
<template>
<div class="tabs">
<content select="custom-tab"></content>
</div>
<nav>
For each of custom-tab I want to create tab header (link) here
</nav>
</template>
</polymer-element>
Is it possible to:
For each custom tab inserted into .tabs create link inside div?
If custom-tab element has a property named 'caption', can I get it using some kind of {{attribute-name}} syntax?
Finally I want to look the component like this:
P.S. I only need help on polymer-dart <template> syntax, I can deal with css myself. Thanks in advance!
<link rel="import" href="../../packages/polymer/polymer.html">
<polymer-element name="custom-tabs">
<template>
<style>
:host {
display: block;
}
</style>
<nav>
<template repeat="{{tab in tabHeaders}}">
<div>{{tab}}</div>
</template>
</nav>
<div class="tabs">
<content id="content" select="custom-tab"></content>
</div>
</template>
<script type="application/dart" src="custom_tabs.dart"></script>
</polymer-element>
import 'package:polymer/polymer.dart';
import 'dart:html' as dom;
#CustomTag('custom-tabs')
class CustomTabs extends PolymerElement {
CustomTabs.created() : super.created() {}
#observable
// toObservable() is to make Polymer update the headers (using template repeat) when the tabs list changes later on
List<String> tabHeaders = toObservable([]);
attached() {
super.attached();
// initialize the header list when the custom-tabs element is attached to the dom
updateTabHeaders();
}
// needs to be called every time the list of custom-tab children changes
void updateTabHeaders() {
tabHeaders.clear();
// the content element needs to have the id 'content'
($['content'] as dom.ContentElement).getDistributedNodes().forEach((e) {
// you can skip elements here for example based on attributes like 'hidden'
tabHeaders.add((e as dom.Element).attributes['name']);
});
}
}

Why doesn't the model get updated?

I'm going through the angular dart tutorials and I tried to make a list of items and when one is clicked fill an input with the clicked item's text. This works OK until I modify the text in the input. After that clicking another item in the list does not update the text in the input field (before it did), and neither gets the item in the list modified.
My starting point the 2 tutorial, but trying to combine it with some input as in the first one.
My html file:
<!DOCTYPE html>
<html ng-app>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Test</title>
</head>
<body>
<div data-list>
<ul>
<li ng-repeat="data in ctrl.dataList" ng-click="ctrl.selectData(data)">{{data.text}}</li>
</ul>
<input type="text" value="{{ctrl.selected.text}}">
</div>
<script type="application/dart" src="spa.dart"></script>
<script src="packages/browser/dart.js"></script>
</body>
</html>
My dart file:
import 'package:angular/angular.dart';
#NgController(
selector: '[data-list]',
publishAs: 'ctrl'
)
class DataController{
DataValue selected;
List<DataValue> dataList = [new DataValue("uno"), new DataValue("dos"), new DataValue("tres")];
void selectData(DataValue selected){
this.selected = selected;
}
}
class DataValue{
#NgTwoWay('text')//Also tried without this. Same result
String text;
DataValue(this.text);
}
class DataAppModule extends Module{
DataAppModule(){
type(DataController);
}
}
void main(){
ngBootstrap(module: new DataAppModule());
}
Any idea what I'm doing wrong?
The desired behavior should be:
the items in the list are displayed
when any of them is click the input field gets updated
when editing the input field's value the currently selected item of the list gets updated with the new text
At any moment clicking an item updates the value of the input file
Use the input element with the ng-model directive to have a two-way binding:
...
<input type="text" ng-model="ctrl.selected.text">
...
The #NgTwoWay annotation is only used in components or controllers, used in class DataValue it is ignored.

Rendering a content tag as part of a template in polymer and dart

I wish to make a generic list using polymer and dart. I am extending the UL element to do so. I want to place template variables within the content of this custom element.
<ul is="data-ul">
<li>{{item['first_name']}}</li>
</ul>
The custom element
<polymer-element name="data-ul" extends="ul">
<template repeat="{{item in items}}">
<content></content>
</template>
<script type="application/dart" src="data-ul.dart"></script>
</polymer-element>
I was expecting the template variable to be interpolated however it simply gets outputted to the DOM as is. How do I output the content tag to be rendered as a template and not just directly outputted?
Unfortunately, there are two issues here.
<content> cannot be used like this. It's a placeholder for rendering light DOM nodes at specific locations in the Shadow DOM. The first <content> that selects nodes, wins [1]. Stamping out a bunch like you're doing, while very intuitive, won't work as expected.
You're mixing the internal world of Polymer with the external world outside the element. What this really means is that bindings (e.g. {{}}) only work in the context of <polymer-element>.
One thing you can do is create a copy of the distributed light DOM children as the items property of your element. In JavaScript this looks like:
<template repeat="{{item in items}}">
<li>{{item['first_name']}}</li>
</template>
<content id="content" select="li"></content>
<script>
Polymer('data-ul', {
ready: function() {
this.items = this.$.content.getDistributedNodes();
}
});
</script>
Note: The only reason I've used <content select="li"> is to insure the element only takes in <li> nodes. If you're not worried about users using other types of elements, just use this.items = [].slice.call(this.children);.
To do that you should override the parseDeclaration method. This method is in charge of parsing/creating the needed html that will be bound. For example, let say that you have next template
<polymer-element name="data-ul" extends="ul" attributes="items">
<template>
<template repeat="{{item in items}}" ref="itemTemplate"></template> <!-- this is the replacement of content tag -->
</template>
<script type="application/dart" src="data-ul.dart"></script>
</polymer-element>
Or if you want to have some default elements:
<polymer-element name="data-ul" extends="ul" attributes="items">
<template>
<template repeat="{{item in items}}">
<!-- Def elements -->
<template bind="{{item}}" ref="itemTemplate"></template> <!-- this is the replacement of content tag -->
<!-- Def elements -->
</template>
</template>
<script type="application/dart" src="data-ul.dart"></script>
</polymer-element>
then you should have next class:
#CustomTag('data-ul')
class DataUl extends LiElement with Polymer, Observable {
DataUl.created() : super.created();
#published List items;
void parseDeclaration(Element elementElement) {
// We need to remove previous template from element.templateContent
// in that way it no continues adding a new content every time that we instantiate
// this component.
var previousTemplate = element.templateContent.querySelector('template#item');
if(previousTemplate != null)
previousTemplate.remove();
var t = this.querySelector('#itemTemplate'); // Gets the template with id itemTemplate from the content html
if(t != null) // if not null
element.templateContent.append(t); // append itemTemplate to element.templateContent
else
element.templateContent.append(new TemplateElement()..id='itemTemplate'); //if no template is added append an empty template to avoid errors
super.parseDeclaration(elementElement); // call super
}
}
And finally use the custom element as follow:
<ul is="data-ul" items="{{[{'first_name': 'jay'}, {'first_name': 'joy'}]}}">
<template id="itemTemplate">
<li>{{item['first_name']}}</li>
</template>
</ul>

How do I structure a "Controller" in Dart's web_ui?

I have the following code
xviewcontainer.html
<!DOCTYPE html>
<html>
<head>
<title>xviewcontainer</title>
<link rel="components" href="xsearch.html">
<link rel="components" href="xcard.html">
</head>
<body>
<element name="x-view-container" constructor="ViewContainerComponent" extends="div">
<template>
<template instantiate="if view == 'SEARCH_VIEW'">
<x-search></x-search>
</template>
<template instantiate="if view == 'CARD_VIEW'">
<x-card></x-card>
</template>
</template>
</element>
<script type="application/dart" src="xviewcontainer.dart"></script>
<!-- for this next line to work, your pubspec.yaml file must have a dependency on 'browser' -->
<script src="packages/browser/dart.js"></script>
</body>
</html>
xviewcontainer.dart
import 'package:web_ui/web_ui.dart';
class ViewContainerComponent extends WebComponent {
String view = 'SEARCH_VIEW';
}
I have the event handling code within some other currently rendered sub-component of x-search. How do I get a reference to the containing x-view-container instance? I wish to change the .view property so that x-view-container will render x-card instead of the currently rendered x-search. I would be specifically interested in how to do so from my event handlers relative position, how to do it in a absolute fashion, as well as how to do so in any other manner.
void openCardView(){
WHAT_DO_I_PUT_HERE.view = 'CARD_VIEW';
}
You can query for the element you have on the DOM with query() method. Simplest example is query('x-view-container'). Or assign a class or an id on it and query against that. Then access the xtag property to get the actual web component instance.
Here's an example:
import 'package:web_ui/watcher.dart' as watchers;
main() {
// I'm assuming that the HTML tag is somewhere on the page.
query('x-view-container').xtag.view = 'CARD_VIEW';
watchers.dispatch(); // You may need to call this, or use #observable stuff.
}

When is the removed lifecycle method called for Dart web components?

Latest edit:
This is an open issue in web-ui: https://github.com/dart-lang/web-ui/issues/245
Previously:
I'm trying to figure out how to get removed() from the web component lifecycle methods to be called. Looking through the generated code for my below example, I see there's a call to autogenerated.dispatch(); after replaceElement() which I hoped would be what calls removed(), but I don't see my print statement output.
Maybe related: I glanced through the spec trying to understand what the output of build.dart is doing for the lifecycle methods. Perhaps the spec is out of date? I still don't see composeChildren() listed in the instantiation section of the spec (which is mentioned in this web-ui issue comment) even though composeChildren() gets called in the autogenerated code from build.dart.
The reason behind this question is my interest in a Dart webapp able to load and unload web components within a single parent html file programmatically (via the instantiation instructions in the spec), instead of having to declare web components in the html. I'm running with web_ui-0.2.11. Thanks!
webcomponent:
<!DOCTYPE html>
<html><body>
<element name="x-lifecycle-test" constructor="LifecycleTest" extends="div">
<template> {{foo}} </template>
<script type="application/dart">
import 'dart:html';
import 'package:web_ui/web_ui.dart';
var foo = "testing lifecycle methods";
class LifecycleTest extends WebComponent{
inserted() => print("inserted");
removed() => print("removed");
}
</script>
</element>
</body></html>
Parent html file:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"><title>Lifecycle</title>
<link rel="components" href="lifecycle_test.html">
</head>
<body>
<div>
<button on-click="replaceElement()">replace element</button>
</div>
<div id='holder'>
<x-lifecycle-test></x-lifecycle-test>
</div>
<script type="application/dart">
import 'dart:html';
void replaceElement() {
query('#holder').replaceWith(new DivElement()
..id = 'holder'
..text = 'replaced');
}
main() {}
</script>
<script type='text/javascript' src="https://dart.googlecode.com/svn/branches/bleeding_edge/dart/client/dart.js"></script>
</body>
</html>
Adding an answer here for completeness: web components are used in Dart using Polymer. When an instance of a custom element is removed from the DOM, the leftView life cycle method triggers. You can read more about this at https://www.dartlang.org/docs/tutorials/polymer-intro/#life-cycle-methods.

Resources