jQuery has "closest" which returns the closest matching ancestor in the tree. Is there a Dart equivalent? I'd like to make the following less fragile:
e.target.parent.parent.nextElementSibling.classes.toggle('hide');
perhaps something like:
e.target.closest('div').nextElementSibling.classes.toggle('hide');
There isn't a builtin function as far as I know. But it's pretty easy to code up something.
The findClosestAncestor() function defined below finds the closet ancestor for an element given the ancestor tag:
<!DOCTYPE html>
<html>
<head>
<title>ancestor</title>
</head>
<body>
<div id='outer'>
<div id='inner'>
<p></p>
</div>
</div>
<script type="application/dart">
import 'dart:html';
Element findClosestAncestor(element, ancestorTagName) {
Element parent = element.parent;
while (parent.tagName.toLowerCase() != ancestorTagName.toLowerCase()) {
parent = parent.parent;
if (parent == null) {
// Throw, or find some other way to handle the tagName not being found.
throw '$ancestorTagName not found';
}
}
return parent;
}
void main() {
ParagraphElement p = query('p');
Element parent = findClosestAncestor(p, 'div');
print(parent.id); // 'inner'
}
</script>
<script src="packages/browser/dart.js"></script>
</body>
</html>
Related
A short background:
This example is a slightly more complicated version of my Angular Dart: Data binding doesn't work when manipulating the controller from the outside question that has been answered correctly. I only added a toggleable "show resolved comments" link to this version. Even though I initialized every variable to non-null values the problem still happens.
Full description of the actual problem:
I have two controllers nested into each other. The outer controller shows/hides the inner controller by using an ng-switch directive.
The outer controller also contains a checkbox. If this checkbox gets checked then the inner controller is made visible (via the above ng-switch directive). This checkbox works as intended.
There's also an "open" link outside the controllers. Its onclick handler calls into the outer controller and is supposed to check the checkbox via the model. The problem is that even though the model gets changed, the view doesn't get updated, unless I explicitly call scope.apply(), which I shouldn't. Even if I remove the comment before scope.apply() in my code then data binding doesn't work within InnerController.
This pattern has worked flawlessly in AngularJS but apparently doesn't in AngularDart.
I insist to this pattern or something similar because I'm in the process of integrating AngularDart into a legacy application that doesn't use data binding so I have to trigger model changes from outside the models.
Thanks in advance!
<html ng-app>
<head>
<title>Angular.dart nested controllers</title>
</head>
<body>
open
<div outer-controller ng-switch="outerCtrl.showInnerController">
<input type="checkbox" ng-model="outerCtrl.showInnerController">
<div inner-controller ng-switch-when="true">
Your name: <input ng-model="innerCtrl.yourName">
<br>
Hello {{innerCtrl.yourName}}!
<div ng-switch="innerCtrl.showResolvedComments" style="text-decoration:underline; color:blue; cursor:pointer">
<div ng-switch-when="true" ng-click="innerCtrl.showResolvedComments = false">Hide resolved comments</div>
<div ng-switch-when="false" ng-click="innerCtrl.showResolvedComments = true">Show resolved comments</div>
</div>
</div>
<div inner-controller ng-switch-when="false">
other controller
</div>
</div>
<script type="application/dart">
import "dart:html";
import 'package:angular/angular.dart';
import 'package:angular/application_factory.dart';
OuterController outerController;
#Controller(selector:'[outer-controller]', publishAs:'outerCtrl')
class OuterController {
bool showInnerController = false;
Scope scope;
OuterController(this.scope) {
outerController = this;
}
void showOuterController() {
showInnerController = true;
//scope.apply();
}
}
#Controller(selector:'[inner-controller]', publishAs:'innerCtrl')
class InnerController {
String yourName = 'defaultName';
bool showResolvedComments = true;
}
class MyAppModule extends Module {
MyAppModule() {
type(InnerController);
type(OuterController);
}
}
main() {
applicationFactory().addModule(new MyAppModule()).run();
querySelector('#open').onClick.listen((Event event) {
outerController.showOuterController();
});
}
</script>
</body>
</html>
After some experimentation, it's look like angular listen specified event to activate ng-model, and it doesn't look every variable change, i think because it's complicated to watch every change in variable without impact performance.
You can change your approach by simulate a user click on the check box
like:
CheckboxInputElement checkBox = querySelector("input");
if (checkBox.checked == false) {
checkBox.click();
}
It's maybe not the cleaner way to do this, but it works
Here the full code with the patch
<html ng-app>
<head>
<title>Angular.dart nested controllers</title>
</head>
<body>
open
<div outer-controller ng-switch="outerCtrl.showInnerController">
<input type="checkbox" ng-model="outerCtrl.showInnerController">
<div inner-controller ng-switch-when="true">
Your name: <input ng-model="innerCtrl.yourName">
<br>
Hello {{innerCtrl.yourName}}!
<div ng-switch="innerCtrl.showResolvedComments" style="text-decoration:underline; color:blue; cursor:pointer">
<div ng-switch-when="true" ng-click="innerCtrl.showResolvedComments = false">Hide resolved comments</div>
<div ng-switch-when="false" ng-click="innerCtrl.showResolvedComments = true">Show resolved comments</div>
</div>
</div>
<div inner-controller ng-switch-when="false">
other controller
</div>
</div>
<script type="application/dart">
import "dart:html";
import 'package:angular/angular.dart';
import 'package:angular/application_factory.dart';
OuterController outerController;
#Controller(selector:'[outer-controller]', publishAs:'outerCtrl')
class OuterController {
bool showInnerController = false;
Scope scope;
OuterController(this.scope) {
outerController = this;
}
void showOuterController() {
showInnerController = true;
print("showOuterController");
//scope.apply();
}
}
#Controller(selector:'[inner-controller]', publishAs:'innerCtrl')
class InnerController {
String yourName = 'defaultName';
bool showResolvedComments = true;
}
class MyAppModule extends Module {
MyAppModule() {
type(InnerController);
type(OuterController);
}
}
main() {
applicationFactory().addModule(new MyAppModule()).run();
querySelector('#open').onClick.listen((Event event) {
outerController.showOuterController();
// Added Code
CheckboxInputElement checkBox = querySelector("input");
if (checkBox.checked == false) {
checkBox.click();
}
// End added code
});
}
</script>
</body>
</html>
I have two custom elements. The one of them is nested to another one. Something like this:
<polymer-element name="my-element">
<template>
<div>
Bla, Bla, Bla!
</div>
</template>
<script type="application/dart" src="my_element.dart"></script>
</polymer-element>
<polymer-element name="my-decorator">
<template>
<div>
<div>Decorator</div>
<content>
<!-- my-element will be here-->
</content>
</div>
</template>
<script type="application/dart" src="my_decorator.dart"></script>
</polymer-element>
using both custom elements in index.html:
<my-decorator>
<my-element></my-element>
</my-decorator>
How can I query the instance of the second element from the code behind of the first one?
#CustomTag('my-decorator')
class MyDecorator extends PolymerElement {
bool get applyAuthorStyles => true;
MyDecorator.created() : super.created() {
// TODO:
// get my-element instance here!
// ..
}
}
#override
void attached() {
// attached is an example the code an be placed somewhere else
// but some places are executed before the childs are added
super.attached();
var nodes = (shadowRoot.querySelector('content') as ContentElement).getDistributedNodes();
var myElement = nodes.firstWhere((e) => e is MyDecorator); // not sure about this `e is MyDecorator`
// you have to choose some way to identify the node in the result
// I currently have no example where I can try it myself
}
if the tag has an id attribute like id='c1' then this works too
var nodes = ($['c1'] as ContentElement).getDistributedNodes();
I have a Dart WebComponent that obtains information periodically from a web service. I'd like to inject the web service into the component and have the component decide when to call the web service (this will allow me to prototype all the UI code using a mock web service with no HTTP call until the service is written).
The issue I'm having is that the web service object I'm sending to the WebComponent seems to be null until the page is rendered. I'm not exactly sure when the service reference is passed to the page, but it seems to happen after the page is constructed since the value is null in the WebComponent constructor, but I can see that the value is instantiated when the page itself is rendered. How can I be notified that the service object is now available to the page so I can call the web service?
Follow up question: Is passing the service reference into the WebComponent like I am below a bad practice? If so, what should I do instead to separate the mock implementation so I can inject the real web service later without changing any code.
Here's my base Dart page:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Dart Prototype</title>
<link rel="stylesheet" href="dart_prototype.css">
<link rel="import" href="location_container.html">
</head>
<body>
<h1>Dart Prototype</h1>
<div id="location_management_container">
<location-container location-service="{{applicationContext.locationService}}" count="{{startingCount}}"></location-container>
</div>
<script type="application/dart">
import 'package:web_ui/web_ui.dart';
import 'package:dart_prototype/dart_prototype_library.dart';
final ApplicationContext applicationContext = new ApplicationContext(
new WebService()
);
int startingCount = 5;
main() {
print('main initializing');
print(applicationContext);
print(applicationContext.locationService);
}
</script>
<script src="packages/browser/dart.js"></script>
</body>
</html>
Here's the code for location-container
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<element name="location-container">
<template>
<div>
<ul id="todo-list">
<li>hi there!</li>
<li>{{count}}</li>
<li>{{locationService}}</li>
</ul>
</div>
</template>
<script type="application/dart">
import 'package:web_ui/web_ui.dart';
import 'package:dart_prototype/dart_prototype_library.dart';
class LocationContainer extends WebComponent {
#observable
WebService locationService;
#observable
int count;
LocationContainer() {
print('in loc container constructor');
print(locationService);
print(count);
}
created() {
print('created!');
print(locationService);
print(count);
}
}
</script>
</element>
</body>
</html>
Here's the code for ApplicationContext and WebService
part of prototype_library;
class ApplicationContext {
final WebService locationService;
ApplicationContext(
this.locationService);
}
class WebService {
final Factory _objectFactory;
WebService(this._objectFactory);
Future call(String url) {
throw new UnimplementedError();
}
}
And here's the result of my print strings statements to the console
Invalid CSS property name: -webkit-touch-callout
main initializing
Instance of 'ApplicationContext'
Instance of 'WebService'
in loc container constructor
null
null
created!
null
null
And here's what my rendered page returns:
Dart Prototype
- hi there!
- 5
- Instance of 'WebService'
And the source of that page...
<!DOCTYPE html>
<!-- This file was auto-generated from web/dart_prototype.html. -->
<html><head><style>template { display: none; }</style>
<meta charset="utf-8">
<title>Dart Prototype</title>
<link rel="stylesheet" href="../dart_prototype.css">
</head>
<body>
<h1>Dart Prototype</h1>
<div id="location_management_container">
<span is="location-container"></span>
</div>
<script type="application/dart" src="dart_prototype.html_bootstrap.dart"></script><script src="../packages/browser/dart.js"></script>
</body></html>
I think my problem may be solved by using the inserted life cycle method instead of created.
Initially when I read the WebComponent life cycle method description, it said:
Invoked whenever a component is added to the DOM.
I'm still not sure if this is the right method to use, but I have the object I'm looking for - so I'm going to continue to experiment. Here's my updated WebComponent
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<element name="location-container">
<template>
<div>
<ul id="todo-list">
<li>hi there!</li>
<li>{{count}}</li>
<li>{{locationService}}</li>
</ul>
</div>
</template>
<script type="application/dart">
import 'package:web_ui/web_ui.dart';
import 'package:dart_prototype/dart_prototype_library.dart';
class LocationContainer extends WebComponent {
#observable
WebService locationService;
#observable
int count;
LocationContainer() {
print('in loc container constructor');
print(locationService);
print(count);
}
created() {
print('created!');
print(locationService);
print(count);
}
inserted() {
print('inserted!');
print(locationService);
print(count);
}
}
</script>
</element>
</body>
</html>
I have a small application I am building that is very similar to the example here.
I am using Dart SDK version 0.5.9.0_r22879
The main difference is that I update the results via an AJAX request, and I only make this request when Enter is pressed in my input control.
In my code, the results list does not render unless I explicitly call watchers.dispatch(), as discussed in the 2nd example here.
Why? It is not clear when I would have to explicitly call watchers.dispatch(), and when it would happen automatically, as in the template example.
My HTML:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>My App</title>
<link rel="stylesheet" href="myapp.css">
</head>
<body>
<h1>My App</h1>
<div id="seach-box-container">
<input type="text" name="search-box" id="search-box" placeholder="Search" bind-value="searchText" />
</div>
<div id="results-container">
<template instantiate="if noMatches"><span>No matches</span></template>
<template instantiate="if !noMatches"><span>{{results.length}} entries:</span></template>
<div id="app-entries">
<ul>
<template iterate='entry in results'>
<li><pre>{{entry.message}}</pre></li>
</template>
</ul>
</div>
</div>
<script type="application/dart" src="myapp.dart"></script>
<script src="packages/browser/dart.js"></script>
</body>
</html>
The important parts of myapp.dart:
import 'dart:html';
import 'dart:json' as JSON;
import 'dart:uri' as uri;
import 'package:web_ui/web_ui.dart';
import 'package:web_ui/watcher.dart' as watchers;
String searchText = '';
List<LogEntry> results = [];
bool get noMatches => results.isEmpty;
void main() {
query("#search-box").onKeyPress.listen((e) => handleKeyPress(e));
}
void handleKeyPress(KeyboardEvent e) {
if (!e.ctrlKey && e.keyCode == KeyCode.ENTER) {
doSearch();
}
}
void doSearch() {
if (searchText != '') {
makeRequest();
}
}
void makeRequest() {
HttpRequest.getString( 'http://url.to/rest-api?q=$searchText' )
.then(processString)
.catchError(handleError)
;
}
processString(String jsonString) {
List<Map> logs = JSON.parse(jsonString);
results.clear();
results.addAll( logs.map((l) => new AppEntry.fromJson(l)) );
watchers.dispatch();
}
handleError(Error error) {
print('Request failed');
print(error);
}
class AppEntry {
final String message;
AppEntry.fromJson(Map json) : message = json['message'];
}
You need to call watchers.dispatch() explicitly whenever you need to change the model in a way which is not triggered by events fired by the templates. This means AJAX calls like in your example, timers, etc.
Events installed by templates take care of calling dispatch() for you, so you don't have to do it in that case.
You can find more about this here.
However, at the moment, watchers.dispatch is treated as 'old way', as one of the goals in Web-UI is to make binding more declarative with observables. So the future-proof solution would be to use #observable annotation on your model. This will ensure that observers are updated every time the model changes, without needing you to explicitly update them.
For illustration reasons, I've created a class inheriting from WebComponent called FancyOption that changes to a background color specified by text in one child element upon clicking another child element.
import 'package:web_ui/web_ui.dart';
import 'dart:html';
class FancyOptionComponent extends WebComponent {
ButtonElement _button;
TextInputElement _textInput;
FancyOptionComponent() {
// obtain reference to button element
// obtain reference to text element
// failed attempt
//_button = this.query('.fancy-option-button');
// error: Bad state: host element has not been set. (no idea)
// make the background color of this web component the specified color
final changeColorFunc = (e) => this.style.backgroundColor = _textInput.value;
_button.onClick.listen(changeColorFunc);
}
}
FancyOption HTML:
<!DOCTYPE html>
<html>
<body>
<element name="x-fancy-option" constructor="FancyOptionComponent" extends="div">
<template>
<div>
<button class='fancy-option-button'>Click me!</button>
<input class='fancy-option-text' type='text'>
</div>
</template>
<script type="application/dart" src="fancyoption.dart"></script>
</element>
</body>
</html>
I have three of them on a page like this.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Sample app</title>
<link rel="stylesheet" href="myapp.css">
<link rel="components" href="fancyoption.html">
</head>
<body>
<h3>Type a color name into a fancy option textbox, push the button and
see what happens!</h3>
<div is="x-fancy-option" id="fancy-option1"></div>
<div is="x-fancy-option" id="fancy-option2"></div>
<div is="x-fancy-option" id="fancy-option3"></div>
<script type="application/dart" src="myapp.dart"></script>
<script src="packages/browser/dart.js"></script>
</body>
</html>
Just use getShadowRoot() and query against it:
import 'package:web_ui/web_ui.dart';
import 'dart:html';
class FancyOptionComponent extends WebComponent {
ButtonElement _button;
TextInputElement _textInput;
inserted() {
// obtain references
_button = getShadowRoot('x-fancy-option').query('.fancy-option-button');
_textInput = getShadowRoot('x-fancy-option').query('.fancy-option-text');
// make the background color of this web component the specified color
final changeColorFunc = (e) => this.style.backgroundColor = _textInput.value;
_button.onClick.listen(changeColorFunc);
}
}
Where x-fancy-option string is the name of the element.
Note: I changed your constructor to be inserted() method, which is a life cycle method.
I understand that _root is depracated. Answers recommending _root should use getShadowRoot() in place of _root.