使用Knockout.js动态组合UI

Joe*_*Joe 3 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>
Run Code Online (Sandbox Code Playgroud)

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

jan*_*oeh 5

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

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

有了它,我可以

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

这是一个如何工作的快速概述.

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

  1. 一个名为的根视图模型 Main
  2. a MessageList负责显示消息列表
  3. 第三个被称为MessageReply负责回复功能的人.

我们所有的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);
});
Run Code Online (Sandbox Code Playgroud)

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

<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>
Run Code Online (Sandbox Code Playgroud)

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

Viewmodels通过同时借用构造函数a Parent,a Child或两者来获得嵌套的能力.这是他们的来源.

父母

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

app.viewmodels.Main = function Main() {
  app.viewmodels.Parent.apply(this);

  this.currentUser = //.. imagine the current user being loaded here from somewhere
};
Run Code Online (Sandbox Code Playgroud)

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

  1. .addChildVm(string):通过传递其名称来添加子视图模型.它会自动在app.viewmodel命名空间中查找.
  2. .getVm(name):返回名为"name"的子视图模型
  3. ._childVms:包含所有子项的可观察列表

孩子

除根之外的每个视图模型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);
  };
};
Run Code Online (Sandbox Code Playgroud)

作为儿童观看模式,MessageList收益:

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

所以,当我们上面添加MessageListMain

messageListVm = mainVm.addChildVm('MessageList');
Run Code Online (Sandbox Code Playgroud)

, 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
  }
};
Run Code Online (Sandbox Code Playgroud)

'childVm'绑定

源代码

<body>
  <!-- context here: the Main viewmodel -->

  <div data-bind="childVm: 'MessageList'">
    <!-- context here: the MessageList viewmodel -->
  </div>
Run Code Online (Sandbox Code Playgroud)

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

'waitForVm'绑定

源代码

这不在上面的示例中使用,但如果您想在运行时动态添加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>
Run Code Online (Sandbox Code Playgroud)

'模态'绑定

源代码

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

想象一下这个模态库

  1. 初始化时,之前创建一个DOM容器 </body>
  2. 当被要求显示模态时,取出这个容器并显示它覆盖在页面的其余部分,灯箱样式

让我们再看一下行动中的模态绑定:

      <!-- 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>
Run Code Online (Sandbox Code Playgroud)

modal

  • 使用父视图模型MessageList,在我们当前的绑定上下文中找到$parent
  • 通过getVm()它的子viewmodel实例询问它MessageReply
  • 添加点击绑定<p>,激活时
    • 呼吁setup()MessageReply,它交给我们$data-当前消息的用户点击
    • 准备模态和
    • 将绑定到MessageReplyviewmodel 的模板'message-reply-template'呈现到modals DOM容器中