I am going to build complex application having many different views. Imagine for example eshop solution. There can be a lot of different views:
Contact page with some static information
Registration form for new customers
View of your order
List of the products
Details about a product
...
Now I am little bit confused, how to build such a complex application using Web UI. I would like to have the HTML templates for the views separated in multiple files and having some logic to determine, which one should be rendered.
Assume I want to have a single master template containing basic things like header and footer, then I have a lot of content templates, these should get injected into the right place inside the master template.
Until now, I have always seen only small single-template examples of using Dart Web UI, so I have no idea, how to achieve this.
I've put together a little example of how I currently do it (hope we will soon see a larger best practice example application for this):
For the complete source code of this example see gist: How to build a Web UI application with multiple views in Dart
Main Application
app.html - Contains the main application layout, instantiates the header and footer component and creates a container for the views.
app.dart - Handles navigation events and replaces the view inside the view container (see below)
app.css
Web Components
Header and Footer
header.html - Web Component for header
footer.html - Web Component for footer
Views
contact.html - Web Component for the Contacts View
contact.dart - Dart file containing ContactsView class
products.html - Web Component for the Products View
products.dart - Dart file containing ProductsView class
Switching Between Views
The standard way to instantiate Web Components is by using <x-foo></x-foo> in HTML.
As we have different views, we will have to instantiate the Web Components inside our Dart code. Doing this we have to manually call the Web Components lifecycle methods. This is not straight forward and might be improved in the future (see Issue 93 which also contains some exmples).
Here is how you can switch views (source of app.dart):
import 'dart:html';
import 'package:web_ui/web_ui.dart';
import 'contact.dart';
import 'products.dart';
void main() {
// Add view navigation event handlers
query('#show-contact-button').onClick.listen(showContactView);
query('#show-products-button').onClick.listen(showProductView);
}
// Used to call lifecycle methods on the current view
ComponentItem lifecycleCaller;
/// Switches to contacts view
void showContactView(Event e) {
removeCurrentView();
ContactView contactView = new ContactView()
..host = new Element.html('<contact-view></contact-view>');
lifecycleCaller = new ComponentItem(contactView)..create();
query('#view-container').children.add(contactView.host);
lifecycleCaller.insert();
}
/// Switches to products view
void showProductView(Event e) {
removeCurrentView();
ProductsView productsView = new ProductsView()
..host = new Element.html('<products-view></products-view>');
lifecycleCaller = new ComponentItem(productsView);
lifecycleCaller.create();
query('#view-container').children.add(productsView.host);
lifecycleCaller.insert();
}
void removeCurrentView() {
query('#view-container').children.clear();
if (lifecycleCaller != null) {
// Call the lifecycle method in case the component needs to do some clean up
lifecycleCaller.remove();
}
}
And here is the source for app.html:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>A Complex Web UI Application</title>
<link rel="stylesheet" href="app.css">
<!-- import the header and footer components -->
<link rel="components" href="header.html">
<link rel="components" href="footer.html">
<!-- import the view components -->
<link rel="components" href="contact.html">
<link rel="components" href="products.html">
</head>
<body>
<header-component></header-component>
<div id="view-container"></div>
<button id="show-contact-button">show contact view</button>
<button id="show-products-button">show products view</button>
<footer-component></footer-component>
<script type="application/dart" src="app.dart"></script>
<script src="packages/browser/dart.js"></script>
</body>
</html>
Note: I had to import the view components via <link rel="components" href="contact.html"> even though I do not directly reference it in the HTML file.
You can use the route library combined with templates to greatly automate the process.
In urls.dart you will define the routes that the app will handle. app.dart will setup the route listener. Lastly, app.html will hold a page container that will automatically switch the page component (through the use of template instantiation).
With this structure set up, page navigation can be handled through regular anchor tags instead of calling custom functions to change the page.
In order to add a new page you will have to do the following:
Add a new route in urls.dart
Create a new WebComponent in the pages/ folder
Add a new conditional template for the page in app.html
Below you can see an example of an app that handles a home page and a contact page:
urls.dart:
library urls;
import 'package:route/url_pattern.dart';
final homeUrl = new UrlPattern(r'/');
final contactUrl = new UrlPattern(r'/contact');
app.dart:
import 'dart:html';
import 'package:web_ui/web_ui.dart';
import 'package:route/client.dart';
import 'urls.dart' as urls;
import 'package:web_ui/watcher.dart' as watchers;
// Setup the routes to listen to
void main() {
var router = new Router()
..addHandler(urls.homeUrl, showPage)
..addHandler(urls.contactUrl, showPage)
..listen();
}
// Change the page that we are on
void showPage(String path) {
watchers.dispatch();
}
app.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Sample app</title>
<link rel="stylesheet" href="app.css">
<!-- import the pages -->
<link rel="components" href="pages/xhomepage.html">
<link rel="components" href="pages/xcontactpage.html">
</head>
<body>
<!-- You could put a header here if you want !-->
<!-- Templates take care of automatically switching the page !-->
<div class="pages">
<template instantiate="if urls.homeUrl.matches(window.location.pathname)">
<x-home-page></x-home-page>
</template>
<template instantiate="if urls.contactUrl.matches(window.location.pathname)">
<x-contact-page></x-contact-page>
</template>
</div>
<!-- You could put a footer here if you want !-->
<script type="application/dart" src="app.dart"></script>
<script src="packages/browser/dart.js"></script>
</body>
</html>
Edit: I've removed the step where app.dart has to define its own pages. Instead, templates check to see if the URL path matches the UrlPattern defined in urls.dart. This should simplify things a bit more.
I created a Polymer element <bind-view> that creates and adds a view element depending on the current route. The element works with the route_hierarchical package.
See BWU Polymer Routing on GitHub for more details.
A route configuration looks like
library bwu_polymer_routing_example.route_initializer;
import 'package:route_hierarchical/client.dart' as rt;
import 'package:bwu_polymer_routing/module.dart';
class RouteInitializer implements Function {
void call(rt.Router router, RouteViewFactory views) {
views.configure({
'usersList': routeCfg(
path: '/users',
view: 'user-list',
defaultRoute: true,
dontLeaveOnParamChanges: true,
enter: (route) => router.go('usersList', {})),
'user': routeCfg(
path: '/user/:userId',
view: 'user-element',
dontLeaveOnParamChanges: true,
mount: {
'articleList': routeCfg(
path: '/articles',
view: 'article-list',
defaultRoute: true,
dontLeaveOnParamChanges: true,
mount: {
'article': routeCfg(
path: '/article/:articleId',
view: 'article-element',
bindParameters: ['articleId', 'userId'],
dontLeaveOnParamChanges: true,
mount: {
'view': routeCfg(
path: '/view',
defaultRoute: true,
dontLeaveOnParamChanges: true),
'edit': routeCfg(
path: '/edit',
dontLeaveOnParamChanges: true)
})
})
})
});
}
}
the <app-element> contains the <bind-view> element, a placeholder where the view configured for the current route gets added.
Views can be nested. Any view can itself contain a <bind-view> element. This allows to create hierarchical view composition without much boilerplate.
<!DOCTYPE html>
<link rel='import' href='../../../../packages/polymer/polymer.html'>
<link rel='import' href='../../../../packages/bwu_polymer_routing/bind_view.html'>
<link rel='import' href='user_list.html'>
<link rel='import' href='user_element.html'>
<link rel='import' href='article_list.html'>
<link rel='import' href='article_element.html'>
<polymer-element name='app-element'>
<template>
<bind-view id='app-element'></bind-view>
</template>
<script type='application/dart' src='app_element.dart'></script>
</polymer-element>
The app_element.dart file contains the router initialization code
class AppModule extends Module {
AppModule() : super() {
install(new RoutingModule(usePushState: true));
bindByKey(ROUTE_INITIALIZER_FN_KEY, toValue: new RouteInitializer());
}
}
#CustomTag('app-element')
class AppElement extends PolymerElement with DiContext {
AppElement.created() : super.created();
#override
void attached() {
super.attached();
initDiContext(this, new ModuleInjector([new AppModule()]));
}
}
The package also contains some helper mixins to add dependency injection (DI) functionality to Polymer elements like the DiContext mixin used here.
Constructor injection can't be used with Polymer but events are a good substitute.
The DiConsumer mixin allows to request an instance from DI with this simple code
#CustomTag('article-list')
class ArticleList extends PolymerElement with DiConsumer {
#observable String userId;
#override
void attached() {
super.attached();
// The two lines below show how to request instances from DI
// but they are redundant here because
// route parameters are assigned to attributes of the view automatically
// when the view is created or when the values change
var di = inject(this, [RouteProvider /* add more types here as needed */]);
userId = (di[RouteProvider] as RouteProvider).parameters['userId'];
}
}
Related
I am experimenting with the JavaFX WebView control and I want to use the MathJax Javascript library to render mathematical content.
As a test I have created a basic JavaFX FXML project, added a WebView to the FXML and updated the controller code like so:
public class SampleController implements Initializable {
#FXML
private WebView webView;
#Override
public void initialize(URL url, ResourceBundle rb) {
webView.getEngine().load(
"file:///Users/benjamin/Desktop/Page.html");
}
}
The html file looks like this:
<!DOCTYPE html>
<html>
<head>
<title></title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<script type="text/x-mathjax-config">
MathJax.Hub.Config({tex2jax: {inlineMath: [['$','$'], ['\\(','\\)']]}});
</script>
<script type="text/javascript"
src="/Users/benjamin/Downloads/mathjax/MathJax.js?config=TeX-AMS-MML_HTMLorMML">
</script>
</head>
<body>
When $a \ne 0$, there are two solutions to \(ax^2 + bx + c = 0\) and they are
$$x = {-b \pm \sqrt{b^2-4ac} \over 2a}.$$
</body>
</html>
This works as expected and produces the following result:
Note that for the test, both the html and JavaScript file paths are hard coded to locations on my hard drive so the next step is to package the html as a resource that is bundled with the application so that it is not looking for local files.
I have updated the controller code to look up the page like this (the html has not been changed).
#Override
public void initialize(URL url, ResourceBundle rb) {
webView.getEngine().load(
this.getClass().getResource("Page.html").toExternalForm());
}
but this produces the following result:
As you can see, the mathematical content is no longer rendered.
If I change the html <script> tag to reference the JavaScript from a CDN, then everything works as in the original example but I would like to be able to reference the local JavaScript file (and eventually a version that is bundled with the application).
Is what I'm trying to achieve possible?
Add the MathJax.js file to the same package/folder of Page.html then reference to it as
<script type="text/javascript"
src="MathJax.js?config=TeX-AMS-MML_HTMLorMML">
</script>
Short Version: The custom web component example in the first link isn't working for me. Why not? I'm running this in Dartium.
Long Version:
I copied and pasted this Dart Web UI example from this tutorial into the Dart Editor and tried to run it. Nothing showed up on the page. I've used dart before but not with web components, so I noticed that one difference between this code and other code I've written is that there was no <script src="packages/browser/dart.js"></script>, so I added that at the bottom. Now I got the error:
Internal error: Dart_Invoke: did not find top-level function 'main'.
I put a print statement in the main() function, and the text was printed before the error, which is strange. I guessed and tried adding main() {} inside the <script> tag that was in the custom component. That is, the script tag looked like:
<script type="application/dart">
import 'package:web_ui/web_ui.dart';
main() {print("in main in component");}
class CounterComponent extends WebComponent {
int count = 0;
void increment() { count++; }
}
</script>
Now that error goes away, and both print statements are printed, but nothing happens.
Here is the original tutorial code for your convenience:
<!DOCTYPE html>
<!--
Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
for details. All rights reserved. Use of this source code is governed by a
BSD-style license that can be found in the LICENSE file.
-->
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<link rel="stylesheet"
href="//cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/2.3.1/css/bootstrap.css">
</head>
<body>
<element name="click-counter" constructor="CounterComponent" extends="div">
<template>
<button on-click="increment()">Click me</button>
<span>(click count: {{count}})</span>
</template>
<script type="application/dart">
import 'package:web_ui/web_ui.dart';
class CounterComponent extends WebComponent {
int count = 0;
void increment() { count++; }
}
</script>
</element>
<div class="well">
<div is="click-counter"></div>
</div>
<script type="application/dart">
main(){}
</script>
</body>
</html>
Web UI applications need to be "built" (usually by a script called build.dart in the project root, see this article for more details).
If I go to the Dart Editor, create a new test project using the wizard and selecting the Web Project (using the web_ui library) option, this creates the boilerplate including the build script.
Open up the html file and paste in the code from the github tutorial, replacing what is there already. When you save the file, the build.dart will be invoked, outputting the build version to /web/out/
Hit the run button, and the Dart Editor will open the app in Dartium (it knows to add /out/ to the URL).
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.
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.
}
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.