使用Knockout.js动态组合UI [英] Dynamically composing a UI using Knockout.js

查看:80
本文介绍了使用Knockout.js动态组合UI的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I'm working with the awesome Knockout.js library on a project and am looking for a way to compose sections of my UI at run-time.

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

For example I have have a couple of templates (simplified, below) that are made up of child templates. Id like to pass a view model to these and render them, and then be able to append (and remove) the contents from criteria form.

<!-- 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的任何好文档。这是可能的,还是我应该尝试另一种方法?

I've begin wandering down the path of ko.renderTemplate but I can't seem to find any good documentation on how to create a new div and append the result to an existing div. Is this possible, or is there another approach I should be trying?

推荐答案

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

After writing all this down, it dawns on me that this might exceed the scope of your question quite a bit. If that is indeed the case, I apologize; I hope that you still might get some value out of it.

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

This stuff here comes from a real app I have been working on for several months now. It's a quick and dirty extraction and might contain bugs or typos where I removed app-specific code or simplified it to make it easier to follow.

有了它,我可以


  • 任意嵌套视图模型

  • 动态添加视图模型

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

这里是一个快速概述它是如何工作的。

Here's a quick overview how it works.

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

Pretend for a second you are going to build an app that shows a list of messages. The user can click on a message to open a modal dialog and reply. We have three viewmodels:


  1. 一个名为 Main
  2. $的根视图模型b $ b
  3. 一个 MessageList 负责显示消息列表

  4. 第三个名为 MessageReply 负责回复功能。

  1. a root viewmodel called Main
  2. a MessageList that takes care of displaying the list of messages
  3. a third one called MessageReply that is responsible for the reply functionality.

我们所有的viewmodel构造函数都在<$中整齐地命名空间C $ C> app.viewmodels 。让我们设置它们:

All our viewmodel constructors are neatly namespaced in app.viewmodels. Let's set them up:

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

我们的加价看起来像这样:

Our markup looks something like this:

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

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

There are two custom bindings in there, childVm and modal. The former just looks up a child viewmodel ands sets it as the binding context, whereas the modal binding is responsible for rendering the template in the correct context and handing the result to a separate JS library.

Viewmodels通过借用构造函数来获得嵌套的能力,一个 Parent ,一个孩子或两者同时出现。 以下是他们的来源

Viewmodels gain the ability to nest by borrowing constructor functions, a Parent, a Child or both at the same time. Here is the source for them.

父母

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

If a viewmodel should be able to have child viewmodels, it borrows the Parent constructor:

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

  this.currentUser = //.. imagine the current user being loaded here from somewhere
};

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

As a parent viewmodel, Main has gained three things:


  1. .addChildVm(string):通过传递添加子视图模型其名称。它会自动在 app.viewmodel 命名空间中查找。

  2. .getVm(name):返回名为'name'的子视图模型

  3. ._ childVms :包含所有子项的可观察列表

  1. .addChildVm(string): add a child viewmodel by passing its name. It's automatically looked up in the app.viewmodel namespace.
  2. .getVm(name): returns the child viewmodel named 'name'
  3. ._childVms: an observable list containing all the children

儿童

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

Every viewmodel apart from the root Main is at least a child viewmodel. MessageList is both a child to Main, and a parent to MessageReply. Very appropriately to its name, it houses the messages to be displayed in the list.

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 获得:

As a child viewmodel, MessageList gains:


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

  • the ability to access its parent through this._parentVm
  • an optional init function, which is called automatically by the parent if present

所以当我们将 MessageList 添加到 Main 时,

So above when we added MessageList to Main with

messageListVm = mainVm.addChildVm('MessageList');

Main


  • 创建了 MessageList的新实例

  • 将实例添加到自己的孩子

  • 并称为孩子 init

  • created a new instance of MessageList
  • added the instance to its own children
  • and called the childs init

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

The child then set itself up by getting a reference to the current user, which is mainted by the parent Main viewmodel.

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

Our last viewmodel: the MessageReply

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

MessageReply is just a child viewmodel; like it's parent MessageList did itself, it too copies the current user when initialized. It expects to be handed a Message object from the modal binding, then creates a new Message in reply to it. That reply can be edited and submitted through the form in the modal.

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'绑定

源代码

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

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

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

This is merely a convenience wrapper around Knockouts own 'with:' binding. It takes a viewmodel name as its value accessor, looks up a child viewmodel of that name in the current binding context, and uses the 'with:' binding to set that child as the new context.

'waitForVm'绑定

源代码

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

This isn't used in the example above, but is quite useful if you want to add viewmodels dynamically at runtime, as opposed to before ko.applyBindings. This way, you can delay initializing parts of your application until the user actually wants to interact with them.

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

waitForVm waits until the specified viewmodel is available before binding its child elements. It does not modify the binding context.

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

'模态'绑定

源代码

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

This takes a Knockout template, marries it to a viewmodel, renders it and passes the result to an external JS library that handles the modal dialog.

想象一下,这个模态库


  1. 初始化时,在< / body>之前创建一个DOM容器

  2. 当被要求显示模态时,拿走这个容器并显示它覆盖在页面的其余部分,灯箱样式

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

Let's look at the modal binding in action again:

      <!-- 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> ,激活后

    • MessageReply 上调用 setup(),递交我们的 $ data - 用户点击的当前消息

    • 准备模态和

    • 呈现模板'message-reply -template',绑定到 MessageReply viewmodel,进入模态DOM容器

    • use the parent viewmodel MessageList, found in our current binding context at $parent
    • ask it via getVm() for its child viewmodel instance MessageReply
    • add a click binding to the <p>, which when activated
      • calls setup() on MessageReply, handing it our $data - the current message the user clicked on
      • prepares the modal and
      • renders the template 'message-reply-template', bound to the MessageReply viewmodel, into the modals DOM container

      这篇关于使用Knockout.js动态组合UI的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆