【问题标题】:Dynamically composing a UI using Knockout.js使用 Knockout.js 动态组合 UI
【发布时间】:2013-12-28 12:06:20
【问题描述】:

我正在一个项目中使用很棒的 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 的好的文档。这可能吗,还是我应该尝试另一种方法?

【问题讨论】:

  • 我不明白你在做什么。
  • 我认为这里的一些信息是相关的:stackoverflow.com/questions/8676988/…。基本上,您可以使用 ko.applyBindings 对带有单独视图模型的单独 DOM 元素使用,或者创建一个包含您的子视图模型的“应用”级视图模型并使用 template 绑定它们。
  • 我正在我目前正在开发的应用程序中这样做 - 可以具有任意“子视图模型”的视图模型,以及允许我说“使用该子视图模型渲染此模板”的绑定。我想把它打包成 Github 上的一个库,但还没有搞定。如果您有兴趣,请告诉我 - 我可以快速整理出一些东西。
  • @janfoeh -- 我很想看看你的方法。根据 Ryan 的反馈,我勉强找到了一个可行的解决方案,但我对它不是很满意。

标签: javascript jquery knockout.js


【解决方案1】:

在写下所有这些之后,我突然意识到这可能超出了您的问题的范围。如果确实如此,我很抱歉;我希望您仍然可以从中获得一些价值。

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

有了它,我可以

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

以下是其工作原理的简要概述。

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

  1. 名为Main 的根视图模型
  2. MessageList 负责显示消息列表
  3. 第三个叫MessageReply,负责回复功能。

我们所有的视图模型构造函数都整齐地命名为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 库。

视图模型通过借用构造函数、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):返回名为“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);
  };
};

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

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

所以上面当我们将MessageList 添加到Main

messageListVm = mainVm.addChildVm('MessageList');

Main

  • 创建了MessageList 的新实例
  • 将实例添加到它自己的子节点中
  • 并打电话给孩子们init

然后子通过获取对当前用户的引用来设置自己,该引用由父 Main viewmodel 维护。

我们的最后一个视图模型: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
  }
};

“childVm”绑定

Source code

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

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

这只是 Knockouts 自己的 'with:' 绑定的便捷包装。它将视图模型名称作为其值访问器,在当前绑定上下文中查找该名称的子视图模型,并使用“with:”绑定将该子视图模型设置为新上下文。

“waitForVm”绑定

Source code

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

waitForVm 等到指定的视图模型可用后再绑定其子元素。它不会修改绑定上下文。

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

“模态”绑定

Source code

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

想象一下这个模态库

  1. 初始化时,在&lt;/body&gt;之前创建一个DOM容器
  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>

modal

  • 使用父视图模型MessageList,在我们当前的绑定上下文$parent中找到
  • 通过getVm() 询问它的子视图模型实例MessageReply
  • 添加一个点击绑定到&lt;p&gt;,当它被激活时
    • MessageReply 上调用setup(),将$data 交给它——用户点击的当前消息
    • 准备模态并
    • 将绑定到 MessageReply 视图模型的模板“message-reply-template”呈现到模态 DOM 容器中

【讨论】:

  • 如此详细的答案。在过去的一个小时左右,我从中学到了很多东西。谢谢!!
猜你喜欢
  • 1970-01-01
  • 2023-03-08
  • 1970-01-01
  • 1970-01-01
  • 2014-02-18
  • 1970-01-01
  • 1970-01-01
  • 2013-10-15
  • 2017-01-30
相关资源
最近更新 更多