使用Knockout.js动态组合UI

时间:2013-12-10 19:30:12

标签: javascript jquery knockout.js

我正在使用项目中令人敬畏的Knockout.js库,并且正在寻找一种在运行时构建UI部分的方法。

例如,我有一些由子模板组成的模板(简化如下)。我想将视图模型传递给它们并渲染它们,然后能够从条件表单中追加(并删除)内容。

<!-- used with LineGraphModel -->
<script type="text/html" name="linegraph-template">
  <div id="LineGraph">
      <div data-bind="contextTemplate: { name: 'series-template', data: seriesChoices, context: { selected: series } }"></div>
      <div data-bind="contextTemplate: { name: 'xaxis-template', data: xAxisChoices, context: { selected: xaxis } }"></div>
      <div data-bind="contextTemplate: { name: 'daterange-template', data: dateRangeChoices, context: { selected: dateRange } }"></div>
      <div data-bind="template: { name: 'button-template', data: $data }"></div>
  </div>
</script>

<!-- used with PieChartModel -->
<script type="text/html" name="piechart-template">
  <div id="PieGraph">
    <div data-bind="contextTemplate: { name: 'series-template', data: seriesChoices, context: { selected: series } }"></div>
    <div data-bind="contextTemplate: { name: 'daterange-template', data: dateRangeChoices, context: { selected: dateRange } }"></div>
    <div data-bind="template: { name: 'button-template', data: $data }"></div>
  </div>
</script>

我开始徘徊在ko.renderTemplate的路径上,但我似乎无法找到关于如何创建新div并将结果附加到现有div的任何好文档。这是可能的,还是我应该尝试另一种方法?

1 个答案:

答案 0 :(得分:5)

写下这一切后,我突然意识到这可能会超出你问题的范围。如果确实如此,我道歉;我希望你仍然可以从中获得一些价值。

这里的东西来自我已经工作了几个月的真实应用程序。这是一个快速而肮脏的提取,可能包含错误或拼写错误,我删除了特定于应用程序的代码或简化它以使其更容易理解。

有了它,我可以

  • 任意嵌套视图模型
  • 动态添加视图模型
  • 渲染绑定到这些嵌套视图模型的Knockout模板,并灵活使用结果

以下是对其工作原理的快速概述。

假装一秒钟你将构建一个显示消息列表的应用程序。用户可以单击消息以打开模式对话框并进行回复。我们有三个视图模型:

  1. 名为Main
  2. 的根视图模型
  3. 负责显示消息列表的MessageList
  4. 第三个名为MessageReply,负责回复功能。
  5. 我们所有的viewmodel构造函数都在app.viewmodels中整齐地命名空间。让我们设置它们:

    $(document).ready(function() {
      var mainVm,
          messageListVm,
          messageReplyVm;
    
      // we start with Main as the root viewmodel
      mainVm = new app.viewmodels.Main();
    
      // MessageList is a child of Main
      messageListVm = mainVm.addChildVm('MessageList');
    
      // and MessageReply in turn is a child of MessageList
      messageReplyVm = messageListVm.addChildVm('MessageReply');
    
      // the root is the only one that gets bound directly
      ko.applyBindings(mainVm);
    });
    

    我们的标记看起来像这样:

    <body>
      <!-- context here: the Main viewmodel -->
    
      <div data-bind="childVm: 'MessageList'">
        <!-- context here: the MessageList viewmodel -->
    
        <ul data-bind="foreach: messages">
          <!-- context here: the individual message object -->
          <li>
            <p data-bind="text: body, modal: {viewmodelName: 'MessageReply', parentViewmodel: $parent, setupViewmodelWith: $data, templateName: 'message-reply-template'}">
    
            </p>
          </li>
        </ul>
      </div>
    </body>
    
    <script id="message-reply-template" type="text/html">
      <!-- context here: the MessageReply viewmodel -->
      <div>
        <textarea data-bind="value: message().body"></textarea>
        <input type="submit" data-bind="click: submit">
      </div>  
    </script>
    

    其中有两个自定义绑定,childVmmodal。前者只是查找子视图模型并将其设置为绑定上下文,而modal绑定负责在正确的上下文中呈现模板并将结果交给单独的JS库。

    Viewmodels通过借用构造函数,ParentChild或两者同时获得嵌套的能力。 Here is the source for them

    <强>家长

    如果一个视图模型应该能够拥有子视图模型,它会借用Parent构造函数:

    app.viewmodels.Main = function Main() {
      app.viewmodels.Parent.apply(this);
    
      this.currentUser = //.. imagine the current user being loaded here from somewhere
    };
    

    作为父视图模型,Main获得了三件事:

    1. .addChildVm(string):通过传递名称来添加子视图模型。它会自动在app.viewmodel命名空间中查找。
    2. .getVm(name):返回名为&#39; name&#39;
    3. 的子视图模型
    4. ._childVms:包含所有孩子的可观察列表
    5. <强>儿童

      除根Main之外的每个视图模型至少是一个子视图模型。 MessageList既是Main的孩子,也是MessageReply的父母。它的名称非常合适,它包含要在列表中显示的消息。

      app.viewmodels.MessageList = function MessageList() {
        app.viewmodels.Parent.apply(this);
        app.viewmodels.Child.apply(this);
      
        // children need to set this, so we can find them by name through .getVm()
        this._viewmodelName = function() { return "MessageList"; };
      
        this.currentUser = null;
      
        this.messages = ko.observableArray([]);
      
        this.init = function init() {
          that.currentUser = that._parentVm.currentUser;
      
          var messages = GetMessages() // pseudocode - load our messages from somewhere
          this.messages( messages);
        };
      };
      

      作为子视图模型,MessageList获得:

      • 通过this._parentVm
      • 访问其父级的能力
      • 一个可选的init函数,如果存在,则由父项自动调用

      上面我们将MessageList添加到Main

      messageListVm = mainVm.addChildVm('MessageList');
      

      Main

      • 创建了MessageList
      • 的新实例
      • 将实例添加到自己的孩子
      • 并呼叫孩子init

      然后,孩子通过获取当前用户的引用来设置自己,该用户由父Main视图模型维护。

      我们的上一个视图模型:MessageReply

      MessageReply只是一个子视图模型;就像它的父MessageList所做的那样,它也会在初始化时复制当前用户。它期望从模态绑定传递一个Message对象,然后创建一个新的Message来回复它。该回复可以通过模式中的表单进行编辑和提交。

      app.viewmodels.MessageReply = function MessageReply() {
        app.viewmodels.Child.apply(this);
      
        this._viewmodelName = function() { return "MessageReply"; };
      
        var that = this;
      
        this.currentUser = null;
      
        // called automatically by the parent MessageList
        this.init = function init() {
          that.currentUser = that._parentVm.currentUser;
        };
      
        this.messageWeAreReplyingTo = ko.observable();
      
        // our reply
        this.message = ko.observable();
      
        // called by the 'modal' binding
        this.setup = function setup(messageWeAreReplyingTo) {
      
          // the modal binding gives us the message the user clicked on
          this.messageWeAreReplyingTo( messageWeAreReplyingTo );
      
          // imagine that Message is a model object defined somewhere else
          var ourReply = new Message({
            sender: that.currentUser,
            recipient: that.messageWeAreReplyingTo().sender();
          });
      
          this.message( ourReply );
        };
      
        // this is triggered by the form submit button in the overlay
        this.submit = function submit() {
          // send the message to the server
        }
      };
      

      &#39; childVm&#39;结合

      Source code

      <body>
        <!-- context here: the Main viewmodel -->
      
        <div data-bind="childVm: 'MessageList'">
          <!-- context here: the MessageList viewmodel -->
        </div>
      

      这只是一个围绕Knockouts所拥有的便利包装&#39; with:&#39;捆绑。它将viewmodel名称作为其值访问器,在当前绑定上下文中查找该名称的子视图模型,并使用&#39; with:&#39;绑定以将该子项设置为新上下文。

      &#39; waitForVm&#39;结合

      Source code

      在上面的示例中没有使用它,但如果要在运行时动态添加viewmodel,而不是ko.applyBindings之前,则非常有用。这样,您可以延迟初始化应用程序的各个部分,直到用户真正想要与它们进行交互为止。

      waitForVm在绑定其子元素之前等待指定的viewmodel可用。它不会修改绑定上下文。

      <div data-bind="waitForVm: 'MessageList'">
        <!-- bindings in here are not executed until 'MessageList' is loaded -->
        <div data-bind="childVm: 'MessageList'"> ... </div>
      </div>
      

      &#39;模式&#39;结合

      Source code

      这需要一个Knockout模板,将它与viewmodel结合,渲染它并将结果传递给处理模态对话框的外部JS库。

      想象一下这个模态库

      1. 初始化时,在</body>
      2. 之前创建一个DOM容器
      3. 当被要求显示模态时,拿走这个容器并显示它覆盖在页面的其余部分,灯箱式
      4. 让我们再次看一下模态绑定:

              <!-- context here: the individual message object -->
              <li>
                <p data-bind="text: body, modal: {viewmodelName: 'MessageReply', parentViewmodel: $parent, setupViewmodelWith: $data, templateName: 'message-reply-template'}">
        
                </p>
              </li>
        

        modal

        • 使用我们当前绑定上下文MessageList
        • 中的父视图模型$parent
        • 通过getVm()询问其子视图模型实例MessageReply
        • 添加点击绑定到<p>,激活时
          • setup()上调用MessageReply,将其$data - 用户点击的当前消息
          • 准备模态和
          • 将模板&#39; message-reply-template&#39;绑定到MessageReply视图模型,进入模态DOM容器