23

我将构建具有许多不同视图的复杂应用程序。想象一下例如 eshop 解决方案。可以有很多不同的观点:

  • 包含一些静态信息的联系页面
  • 新客户登记表
  • 查看您的订单
  • 商品一览
  • 有关产品的详细信息
  • ...

现在我有点困惑,如何使用 Web UI 构建如此复杂的应用程序。我希望将视图的 HTML 模板分隔在多个文件中,并有一些逻辑来确定应该呈现哪一个。

假设我想要一个包含页眉和页脚等基本内容的主模板,那么我有很多内容模板,这些模板应该被注入到主模板内的正确位置。

到目前为止,我一直只看到使用 Dart Web UI 的小型单模板示例,所以我不知道如何实现这一点。

4

3 回答 3

29

我已经整理了一个小例子来说明我目前是如何做到的(希望我们很快就会看到一个更大的最佳实践示例应用程序):

有关此示例的完整源代码,请参阅要点:如何在 Dart 中构建具有多个视图的 Web UI 应用程序

主要应用

  • app.html - 包含主应用程序布局,实例化页眉页脚组件并为视图创建一个容器。
  • app.dart - 处理导航事件并替换视图容器内的视图(见下文)
  • 应用程序.css

网页组件

页眉和页脚

  • header.html - 标题的 Web 组件
  • footer.html - 页脚的 Web 组件

意见

  • contact.html - 联系人视图的 Web 组件
  • contact.dart - 包含 ContactsView 类的 Dart 文件
  • products.html - Products 视图的 Web 组件
  • products.dart - 包含 ProductsView 类的 Dart 文件

在视图之间切换

实例化 Web 组件的标准方法是<x-foo></x-foo>HTML中使用。由于我们有不同的视图,我们必须在Dart代码中实例化 Web 组件。为此,我们必须手动调用 Web 组件生命周期方法。这不是直截了当的,将来可能会改进(参见第 93 期,其中也包含一些示例)。

以下是切换视图的方法(来源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();
  }
}

这里是来源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>

注意:我必须通过导入视图组件,<link rel="components" href="contact.html">即使我没有在 HTML 文件中直接引用它。

于 2013-04-24T12:23:04.300 回答
16

您可以使用结合模板的路由库来极大地自动化该过程。

urls.dart您将定义应用程序将处理的路由。app.dart将设置路由监听器。最后,app.html将持有一个页面容器,该容器将自动切换页面组件(通过使用模板实例化)。

设置此结构后,可以通过常规锚标记处理页面导航,而不是调用自定义函数来更改页面。

要添加新页面,您必须执行以下操作:

  1. 在中添加新路线urls.dart
  2. pages/在文件夹中新建一个 WebComponent
  3. 为页面添加新的条件模板app.html

下面你可以看到一个处理主页和联系页面的应用程序示例:

网址.dart:

library urls;

import 'package:route/url_pattern.dart';

final homeUrl = new UrlPattern(r'/');
final contactUrl = new UrlPattern(r'/contact');

应用程序飞镖:

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();
}

应用程序.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>

编辑:我删除了 app.dart 必须定义自己的页面的步骤。相反,模板会检查 URL 路径是否与 urls.dart 中定义的 UrlPattern 匹配。这应该更简化一些事情。

于 2013-04-25T04:12:01.567 回答
2

我创建了一个 Polymer 元素<bind-view>,它根据当前路线创建和添加视图元素。该元素与route_hierarchical包一起使用。
有关更多详细信息,请参阅GitHub 上的BWU 聚合物路由

路由配置看起来像

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)
          })
        })
      })
    });
  }
}

包含元素,一个占位符,其中为当前路由配置的视图被添加<app-element><bind-view>视图可以嵌套。任何视图本身都可以包含一个<bind-view>元素。这允许在没有太多样板的情况下创建分层视图组合。

<!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>

app_element.dart文件包含路由器初始化代码

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()]));
  }
}

该包还包含一些辅助 mixin,用于向 Polymer 元素添加依赖注入 (DI)功能,例如DiContext此处使用的 mixin。构造函数注入不能与 Polymer 一起使用,但事件是一个很好的替代品。

DiConsumermixin 允许使用这个简单的代码从 DI 请求一个实例

@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'];
  }
}
于 2014-08-10T11:58:03.707 回答