使用Knockout.js动态组合UI
我正在使用项目中令人敬畏的Knockout.js库,并且正在寻找一种在运行时构建UI部分的方法。
例如,我有一些由子模板组成的模板(简化,如下)。 我想将视图模型传递给它们并渲染它们,然后能够从条件表单中追加(并删除)内容。
我开始沿着ko.renderTemplate
的路径走,但我似乎无法找到关于如何创建新div并将结果附加到现有div的任何好文档。 这是可能的,还是我应该尝试另一种方法?
写下这一切后,我突然意识到这可能会超出你的问题的范围。 如果确实如此,我道歉; 我希望你仍然可以从中获得一些价值。
这里的东西来自一个我已经工作了几个月的真实应用程序。 这是一个快速而肮脏的提取,可能包含错误或拼写错误,我删除了特定于应用程序的代码或简化它以使其更容易遵循。
有了它,我可以
- 任意嵌套视图模型
- 动态添加视图模型
- 渲染Knockout模板绑定到这些嵌套的视图模型,并灵活地使用结果
这是一个如何工作的快速概述。
假装一秒钟,您将构建一个显示消息列表的应用程序。 用户可以单击消息以打开模式对话框并进行回复。 我们有三个viewmodel:
- 一个名为
Main
的根视图模型 -
MessageList
,负责显示消息列表 - 第三个叫
MessageReply
,负责回复function。
我们所有的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); });
我们的标记看起来像这样:
-
那里有两个自定义绑定, childVm
和modal
。 前者只是查找子视图模型并将其设置为绑定上下文,而modal
绑定负责在正确的上下文中呈现模板并将结果交给单独的JS库。
Viewmodels通过同时借用构造函数, Parent
, Child
或两者来获得嵌套的能力。 这是他们的来源 。
父母
如果viewmodel应该能够拥有子视图模型,它会借用Parent
构造函数:
app.viewmodels.Main = function Main() { app.viewmodels.Parent.apply(this); this.currentUser = //.. imagine the current user being loaded here from somewhere };
作为父视图模型, Main
获得了三件事:
-
.addChildVm(string)
:通过传递名称来添加子视图模型。 它会自动在app.viewmodel
命名空间中app.viewmodel
。 -
.getVm(name)
:返回名为’name’的子视图模型 -
._childVms
:包含所有子项的可观察列表
孩子
除根Main
之外的每个viewmodel至少是一个子视图模型。 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’绑定
源代码
这只是一个围绕Knockouts拥有’with:’绑定的便利包装器。 它将viewmodel名称作为其值访问器,在当前绑定上下文中查找该名称的子视图模型,并使用’with:’绑定将该子项设置为新上下文。
‘waitForVm’绑定
源代码
这不在上面的示例中使用,但如果要在运行时动态添加viewmodel,而不是在ko.applyBindings
之前,则非常有用。 这样,您可以延迟初始化应用程序的各个部分,直到用户真正想要与它们进行交互。
waitForVm
在绑定其子元素之前一直等到指定的viewmodel可用。 它不会修改绑定上下文。
...
‘模态’绑定
源代码
这需要一个Knockout模板,将它与viewmodel结合,渲染它并将结果传递给处理模式对话框的外部JS库。
想象一下这个模态库
- 初始化时,在
之前创建一个DOM容器
- 当被要求显示模态时,取这个容器并显示它覆盖在页面的其余部分,灯箱样式
让我们再看一下行动中的模态绑定:
modal
会
- 使用父视图模型
MessageList
,在$parent
当前绑定上下文中找到 - 通过
getVm()
询问它的子视图模型实例MessageReply
- 添加点击绑定到
- 在
MessageReply
上调用setup()
,将它们的$data
– 用户点击的当前消息传递给它 - 准备模态和
- 将绑定到
MessageReply
视图模型的模板“message-reply-template”呈现到modals DOM容器中
- 在