【问题标题】:How to build a complex Web UI application with multiple views?如何构建具有多个视图的复杂 Web UI 应用程序?
【发布时间】:2013-04-17 18:58:15
【问题描述】:

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

  • 包含一些静态信息的联系页面
  • 新客户注册表
  • 查看您的订单
  • 产品列表
  • 产品详情
  • ...

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

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

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

【问题讨论】:

    标签: dart dart-webui


    【解决方案1】:

    我创建了一个 Polymer 元素 <bind-view>,它根据当前路线创建并添加了一个视图元素。该元素适用于 route_hierarchical 包。
    有关详细信息,请参阅 GitHub 上的 BWU Polymer Routing

    路由配置看起来像

    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,用于将 dependency injection (DI) 功能添加到 Polymer 元素,例如此处使用的 DiContext mixin。 构造函数注入不能与 Polymer 一起使用,但事件是一个很好的替代品。

    DiConsumer mixin 允许使用这个简单的代码从 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'];
      }
    }
    

    【讨论】:

      【解决方案2】:

      您可以将route 库与模板结合使用,大大自动化该过程。

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

      有了这个结构,页面导航可以通过常规的锚标签来处理,而不是调用自定义函数来改变页面。

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

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

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

      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>
      

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

      【讨论】:

      • 您的解决方案似乎不再有效。 web_ui 包现在被称为聚合物,而观察者的东西似乎不再存在
      【解决方案3】:

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

      有关此示例的完整源代码,请参阅gist: How to build a Web UI application with multiple views in Dart

      主要应用

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

      网页组件

      页眉和页脚

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

      观看次数

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

      在视图之间切换

      instantiate Web Components 的标准方法是在 HTML 中使用 &lt;x-foo&gt;&lt;/x-foo&gt;。 由于我们有不同的视图,我们必须在 Dart 代码中实例化 Web 组件。为此,我们必须手动调用 Web 组件生命周期方法。这不是直截了当的,将来可能会改进(请参阅Issue 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>
      

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

      【讨论】:

      • 就我个人而言,我不会使用 web_ui 完全实例化,而是使用带有连接到顶级函数的 if 属性的模板:
      • @Jasper 要将数据传递给产品视图,您可以在 products.dart 内创建一个构造函数 ProductsView(List&lt;Product&gt; products) 并使用此构造函数实例化 ProductsView
      • 或者如果您使用我提到的模板解决方案,您可以为您的产品添加一个可选属性:&lt;x-products prodList="{{selectedProds}}"&gt;&lt;/x-products&gt;
      • 获取源代码,添加 yaml 并运行它:单击“显示联系人视图”时出现错误。知道为什么:源代码中的iterable.dart中的“中断异常:错误状态:无元素”:E get single { Iterator it = iterator; if (!it.moveNext()) throw new StateError("No elements"); E 结果 = it.current; if (it.moveNext()) throw new StateError("More than one element");返回结果; }
      猜你喜欢
      相关资源
      最近更新 更多
      热门标签