使用Knockout.js动态组合UI [英] Dynamically composing a UI using Knockout.js
问题描述
我正在使用项目中令人敬畏的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 $的路径徘徊c $ c>但我似乎无法找到关于如何创建新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:
- 一个名为
Main
$的根视图模型b $ b - 一个
MessageList
负责显示消息列表 - 第三个名为
MessageReply
负责回复功能。
- a root viewmodel called
Main
- a
MessageList
that takes care of displaying the list of messages - 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:
-
.addChildVm(string)
:通过传递添加子视图模型其名称。它会自动在app.viewmodel
命名空间中查找。 -
.getVm(name)
:返回名为'name'的子视图模型 -
._ childVms
:包含所有子项的可观察列表
.addChildVm(string)
: add a child viewmodel by passing its name. It's automatically looked up in theapp.viewmodel
namespace..getVm(name)
: returns the child viewmodel named 'name'._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.
想象一下,这个模态库
- 初始化时,在
< / body>之前创建一个DOM容器
- 当被要求显示模态时,拿走这个容器并显示它覆盖在页面的其余部分,灯箱样式
让我们再看一下行动中的模态绑定:
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 instanceMessageReply
- add a click binding to the
<p>
, which when activated- calls
setup()
onMessageReply
, 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屋!
- calls
- 在