在 Knockout 组件中异步加载模板
Async loading a template in a Knockout component
我对 Knockout 很有经验,但这是我第一次使用组件,所以我真的希望我遗漏了一些明显的东西!我将尝试稍微简化我的用例来解释我的问题。
我有一个 HTML 和名为 Index.js 的 JS 文件。 Index.html 具有组件的数据绑定,Index.js 具有 ko.components.register
调用。
Index.html
<div data-bind="component: { name: CurrentComponent }"></div>
Index.js
var vm = require("SectionViewModel");
var CurrentComponent = ko.observable("section");
ko.components.register("section", {
viewModel: vm.SectionViewModel,
template: "<h3>Loading...</h3>"
});
ko.applyBindings();
然后我有另一个 HTML 和 JS 文件 - Section.html 和 SectionViewModel.js。正如您在上面看到的,SectionViewModel 是我指定为组件的视图模型。
Section.html
<div>
<span data-bind="text: Section().Name"></span>
</div>
SectionViewModel.js
var SectionViewModel = (function() {
function SectionViewModel() {
this.Section = ko.observable();
$.get("http://apiurl").done(function (data) {
this.Section(new SectionModel(data.Model)); // my data used by the view model
ko.components.get("dashboard", function() {
component.template[0] = data.View; // my html from the api
});
});
}
return SectionViewModel;
});
exports.SectionViewModel = SectionViewModel;
作为 SectionViewModel 构造函数的一部分,我调用 API 以获取填充视图模型所需的所有数据。此 API 还调用 returns 我需要在模板中使用的 HTML(基本上是从 Section.html 中读取的)。
显然,在我调用 applyBindings 之前不会调用此构造函数,因此当我进入 API 调用的成功处理程序时,组件上的模板已设置为我的默认文本。
我需要知道的是,我可以更新这个模板吗?如上所示,我在我的成功处理程序中尝试了以下操作:
ko.components.get("section", function(component) {
component.template[0] = dataFromApi.Html;
});
这确实用我的 API 返回的 html 替换了我的默认文本(如在调试工具中所见),但此更新未反映在浏览器中。
所以,基本上在这之后,我真正想问的是,有没有办法在绑定后更新组件模板的内容?
我知道您可能会想到解决上述问题的一个选项是需要模板,但我确实简化了上面的内容并且在它的完整实现中,我无法做到这一点,因此为什么 HTML 由 API.
返回
非常感谢任何帮助!我目前确实有一个可行的解决方案,但我真的不喜欢我必须构建 JS 代码才能使其工作的方式,因此上述解决方案将是理想的。
谢谢。
您可以在组件中使用 template binding。
模板绑定的正常使用是这样的:
<div data-bind="template: { name: tmplName, data: tmplData }"></div>
您可以同时创建 tmplData
和 tmplName
observable,这样您就可以更新绑定数据,并更改模板。 tmplName
是其内容将用作模板的元素的 ID。如果您使用此语法,您需要一个具有所需 id
的元素,因此,在您的成功处理程序中,您可以使用类似 jQuery 的东西来创建一个具有适当 id
的新元素,然后更新 tmplname
,以便更新模板内容。
*这行不通:
另一种选择是以不同的方式使用模板绑定:
<div data-bind="template: { nodes: tmplNodes, data: tmplData }"></div>
在这种情况下,您可以直接向模板提供节点。 IE。使 tmplNodes 可观察,用 <h3>Loading...</h3>
元素初始化。然后更改它以保存从服务器接收到的节点。
因为 nodes
不支持 observables:
nodes — directly pass an array of DOM nodes to use as a template. This should be a non-observable array and note that the elements will be removed from their current parent if they have one. This option is ignored if you have also passed a nonempty value for name.
因此您需要使用第一个选项:创建一个新元素,将其添加到具有已知 id 的文档 DOM 中,并使用该 id 作为模板名称。演示:
// Simulate service that return HTML
var dynTemplNumber = 0;
var getHtml = function() {
var deferred = $.Deferred();
var html =
'<div class="c"> \
<h3>Dynamic template ' + dynTemplNumber++ + '</h3> \
Name: <span data-bind="text: name"/> \
</div>';
setTimeout(deferred.resolve, 2000, html);
return deferred.promise();
};
var Vm = function() {
self = this;
self.tmplIdx = 0;
self.tmplName = ko.observable('tmplA');
self.tmplData = ko.observable({ name: 'Helmut', surname: 'Kaufmann'});
self.tmplNames = ko.observableArray(['tmplA','tmplB']);
self.loading = ko.observable(false);
self.createNewTemplate = function() {
// simulate AJAX call to service
self.loading(true);
getHtml().then(function(html) {
var tmplName = 'tmpl' + tmplIdx++;
var $new = $('<div>');
$new.attr('id',tmplName);
$new.html(html);
$('#tmplContainer').append($new);
self.tmplNames.push(tmplName);
self.loading(false);
self.tmplName(tmplName);
});
};
return self;
};
ko.applyBindings(Vm(), byName);
div.container { border: solid 1px black; margin: 20px 0;}
div {padding: 5px; }
.a { background-color: #FEE;}
.b { background-color: #EFE;}
.c { background-color: #EEF;}
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="byName" class="container">
Select template by name:
<select data-bind="{options: tmplNames, value: tmplName}"></select>
<input type="button" value="Add template"
data-bind="click: createNewTemplate"/>
<span data-bind="visible: loading">Loading new template...</span>
<div data-bind="template: {name: tmplName, data: tmplData}"></div>
</div>
<div id="tmplContainer" style="display:none">
<div id="tmplA">
<div class="a">
<h3>Template A</h3>
<span data-bind="text: name"></span> <span data-bind="text: surname"></span>
</div>
</div>
<div id="tmplB">
<div class="b">
<h3>Template B</h3>
Name: <span data-bind="text: name"/>
</div>
</div>
</div>
component.template[0] = $(data)[0]
我知道这是旧的,但我发现它试图做同样的事情,在我的案例中,approcah 帮助我想到了这个,模板似乎是一个元素,而不仅仅是原始的 html
我对 Knockout 很有经验,但这是我第一次使用组件,所以我真的希望我遗漏了一些明显的东西!我将尝试稍微简化我的用例来解释我的问题。
我有一个 HTML 和名为 Index.js 的 JS 文件。 Index.html 具有组件的数据绑定,Index.js 具有 ko.components.register
调用。
Index.html
<div data-bind="component: { name: CurrentComponent }"></div>
Index.js
var vm = require("SectionViewModel");
var CurrentComponent = ko.observable("section");
ko.components.register("section", {
viewModel: vm.SectionViewModel,
template: "<h3>Loading...</h3>"
});
ko.applyBindings();
然后我有另一个 HTML 和 JS 文件 - Section.html 和 SectionViewModel.js。正如您在上面看到的,SectionViewModel 是我指定为组件的视图模型。
Section.html
<div>
<span data-bind="text: Section().Name"></span>
</div>
SectionViewModel.js
var SectionViewModel = (function() {
function SectionViewModel() {
this.Section = ko.observable();
$.get("http://apiurl").done(function (data) {
this.Section(new SectionModel(data.Model)); // my data used by the view model
ko.components.get("dashboard", function() {
component.template[0] = data.View; // my html from the api
});
});
}
return SectionViewModel;
});
exports.SectionViewModel = SectionViewModel;
作为 SectionViewModel 构造函数的一部分,我调用 API 以获取填充视图模型所需的所有数据。此 API 还调用 returns 我需要在模板中使用的 HTML(基本上是从 Section.html 中读取的)。
显然,在我调用 applyBindings 之前不会调用此构造函数,因此当我进入 API 调用的成功处理程序时,组件上的模板已设置为我的默认文本。
我需要知道的是,我可以更新这个模板吗?如上所示,我在我的成功处理程序中尝试了以下操作:
ko.components.get("section", function(component) {
component.template[0] = dataFromApi.Html;
});
这确实用我的 API 返回的 html 替换了我的默认文本(如在调试工具中所见),但此更新未反映在浏览器中。
所以,基本上在这之后,我真正想问的是,有没有办法在绑定后更新组件模板的内容?
我知道您可能会想到解决上述问题的一个选项是需要模板,但我确实简化了上面的内容并且在它的完整实现中,我无法做到这一点,因此为什么 HTML 由 API.
返回非常感谢任何帮助!我目前确实有一个可行的解决方案,但我真的不喜欢我必须构建 JS 代码才能使其工作的方式,因此上述解决方案将是理想的。
谢谢。
您可以在组件中使用 template binding。
模板绑定的正常使用是这样的:
<div data-bind="template: { name: tmplName, data: tmplData }"></div>
您可以同时创建 tmplData
和 tmplName
observable,这样您就可以更新绑定数据,并更改模板。 tmplName
是其内容将用作模板的元素的 ID。如果您使用此语法,您需要一个具有所需 id
的元素,因此,在您的成功处理程序中,您可以使用类似 jQuery 的东西来创建一个具有适当 id
的新元素,然后更新 tmplname
,以便更新模板内容。
*这行不通: 另一种选择是以不同的方式使用模板绑定:
<div data-bind="template: { nodes: tmplNodes, data: tmplData }"></div>
在这种情况下,您可以直接向模板提供节点。 IE。使 tmplNodes 可观察,用 <h3>Loading...</h3>
元素初始化。然后更改它以保存从服务器接收到的节点。
因为 nodes
不支持 observables:
nodes — directly pass an array of DOM nodes to use as a template. This should be a non-observable array and note that the elements will be removed from their current parent if they have one. This option is ignored if you have also passed a nonempty value for name.
因此您需要使用第一个选项:创建一个新元素,将其添加到具有已知 id 的文档 DOM 中,并使用该 id 作为模板名称。演示:
// Simulate service that return HTML
var dynTemplNumber = 0;
var getHtml = function() {
var deferred = $.Deferred();
var html =
'<div class="c"> \
<h3>Dynamic template ' + dynTemplNumber++ + '</h3> \
Name: <span data-bind="text: name"/> \
</div>';
setTimeout(deferred.resolve, 2000, html);
return deferred.promise();
};
var Vm = function() {
self = this;
self.tmplIdx = 0;
self.tmplName = ko.observable('tmplA');
self.tmplData = ko.observable({ name: 'Helmut', surname: 'Kaufmann'});
self.tmplNames = ko.observableArray(['tmplA','tmplB']);
self.loading = ko.observable(false);
self.createNewTemplate = function() {
// simulate AJAX call to service
self.loading(true);
getHtml().then(function(html) {
var tmplName = 'tmpl' + tmplIdx++;
var $new = $('<div>');
$new.attr('id',tmplName);
$new.html(html);
$('#tmplContainer').append($new);
self.tmplNames.push(tmplName);
self.loading(false);
self.tmplName(tmplName);
});
};
return self;
};
ko.applyBindings(Vm(), byName);
div.container { border: solid 1px black; margin: 20px 0;}
div {padding: 5px; }
.a { background-color: #FEE;}
.b { background-color: #EFE;}
.c { background-color: #EEF;}
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="byName" class="container">
Select template by name:
<select data-bind="{options: tmplNames, value: tmplName}"></select>
<input type="button" value="Add template"
data-bind="click: createNewTemplate"/>
<span data-bind="visible: loading">Loading new template...</span>
<div data-bind="template: {name: tmplName, data: tmplData}"></div>
</div>
<div id="tmplContainer" style="display:none">
<div id="tmplA">
<div class="a">
<h3>Template A</h3>
<span data-bind="text: name"></span> <span data-bind="text: surname"></span>
</div>
</div>
<div id="tmplB">
<div class="b">
<h3>Template B</h3>
Name: <span data-bind="text: name"/>
</div>
</div>
</div>
component.template[0] = $(data)[0]
我知道这是旧的,但我发现它试图做同样的事情,在我的案例中,approcah 帮助我想到了这个,模板似乎是一个元素,而不仅仅是原始的 html