我正在尝试将一个twitter引导模式打开到一个窗口,该窗口中有一个可编辑的文本区域,然后在保存时,它会保存相应的数据。我目前的代码:
HTML:
<table class="display table table-striped">
<tbody data-bind="foreach: entries">
<tr>
<td>
Placeholder
</td>
<!-- ko foreach: entry_data -->
<td>
<div class="input-group">
<input type="text" class="form-control col-sm-2" data-bind="value: entry_hours">
<span class="input-group-addon"><a class="comment" data-bind="click: function() { $root.modal.comment($data); $root.showModal(); }, css: { 'has-comment': comment.length > 0, 'needs-comment': comment.length == 0 }, attr: { title: comment }"><span class="glyphicon glyphicon-comment"></span></a></span>
</div>
</td>
<!-- /ko -->
</tr>
</tbody>
</table>
<!-- Modal template -->
<script id="commentsModal" class="modal-dialog" type="text/html">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-bind="click:close" aria-hidden="true">×</button>
<h4 data-bind="html:header" class="modal-title"></h4>
</div>
<div class="modal-body">
<textarea class="form-control" rows="3" data-bind="value: $root.modal.comment.comment"></textarea>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-bind="click:close,html:closeLabel">Close</button>
<button type="button" class="btn btn-primary" data-bind="click:action,html:primaryLabel" id="save-changes">Save changes</button>
</div>
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
</script>
<!-- Create a modal via custom binding -->
<div data-bind="bootstrapModal:modal" class="modal fade" id="commentsModal" tabindex="-1" role="dialog" data-keyboard="false" data-backdrop="static"></div>
JS:
/* Custom binding for making modals */
ko.bindingHandlers.bootstrapModal = {
init: function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
var props = valueAccessor(),
vm = bindingContext.createChildContext(viewModel);
ko.utils.extend(vm, props);
vm.close = function() {
vm.show(false);
vm.onClose();
};
vm.action = function() {
vm.onAction();
}
ko.utils.toggleDomNodeCssClass(element, "modal fade", true);
ko.renderTemplate("commentsModal", vm, null, element);
var showHide = ko.computed(function() {
$(element).modal(vm.show() ? 'show' : 'hide');
});
return {
controlsDescendantBindings: true
};
}
}
var entriesdata = [{"entry_id":"51794","project_id":"2571","user_id":"89","entry_data":[{"entry_data_id":"359192","entry_id":"51794","entry_hours":"0.00","entry_date":"2013-12-22","comment":""},{"entry_data_id":"359193","entry_id":"51794","entry_hours":"8.00","entry_date":"2013-12-23","comment":"Test comment"},{"entry_data_id":"359194","entry_id":"51794","entry_hours":"8.00","entry_date":"2013-12-24","comment":"Test comment"},{"entry_data_id":"359195","entry_id":"51794","entry_hours":"0.00","entry_date":"2013-12-25","comment":""},{"entry_data_id":"359196","entry_id":"51794","entry_hours":"8.00","entry_date":"2013-12-26","comment":"Test comment"},{"entry_data_id":"359197","entry_id":"51794","entry_hours":"8.00","entry_date":"2013-12-27","comment":"Test comment"},{"entry_data_id":"359198","entry_id":"51794","entry_hours":"0.00","entry_date":"2013-12-28","comment":""}]}];
var projectsdata = [{"project_txt":"Test Project","project_id":12345}];
var TimeEntriesModel = function(entries, projects) {
var self = this;
self.projects = ko.observableArray(projects);
self.entries = ko.observableArray(ko.utils.arrayMap(entries, function(entry) {
return {
entry_id : entry.entry_id,
project_id : entry.project_id,
user_id : entry.user_id,
entry_data : ko.observableArray(entry.entry_data)
}
}));
self.save = function () {
ko.utils.stringifyJson(self.entries);
}
self.modal = {
header: "Add/Edit Comment",
comment: ko.observableArray([{comment: "test"}]),
closeLabel: "Cancel",
primaryLabel: "Save",
show: ko.observable(false), /* Set to true to show initially */
onClose: function() {
self.onModalClose();
},
onAction: function() {
self.onModalAction();
}
}
console.log(ko.isObservable(self.modal.comment));
self.showModal = function() {
self.modal.show(true);
}
self.onModalClose = function() {
// alert("CLOSE!");
}
self.onModalAction = function() {
// alert("ACTION!");
self.modal.show(false);
}
}
ko.applyBindings(new TimeEntriesModel(entriesdata, projectsdata));
小提琴:http://jsfiddle.net/sL3HK/
正如您在小提琴中看到的那样,模态打开时带有文本框,但我无法弄清楚如何将“注释”文本放入模态中或在按下“保存”按钮时更新注释
有什么想法吗?
另外,我对Knockout很新,所以如果那里看起来不太合适,请随时纠正我。
更新:
我一直在摆弄代码,并且已经能够将“评论”纳入模态,但到目前为止我还没能成功更新它。我最终遇到的另一个问题是,我只希望在点击“保存”时更新评论,而不是模糊的正常更新。我真的认为我会以错误的方式解决这个问题,但我不确定正确的方式是什么。非常感谢任何帮助。
答案 0 :(得分:16)
以下是JsFiddle,您可以在其中编辑每个条目的评论。以下是我如何获得这一点。
首先,我喜欢将我的观点划分为部分。对于每种类型的partial,我创建一个ViewModel。并且“上层”ViewModel用作所有部分ViewModel的容器。在这里你需要一个我用这种方式定义的EntryDataViewModel:
var EntryDataViewModel = function (rawEntryData) {
var self = this;
self.entry_data_id = rawEntryData.entry_data_id;
self.entry_id = rawEntryData.entry_id;
self.entry_hours = rawEntryData.entry_hours;
self.entry_date = rawEntryData.entry_date;
self.comment = ko.observable(rawEntryData.comment);
}
基本上,这个构造函数会将您的原始数据转换为您可以在视图中操作的内容。根据您的想法,您可以观察或不观察。 comment
用于某些绑定,预计会发生变化。我们希望页面动态地对其变化作出反应,所以让它可以观察到
由于这一变化,我们将改变创建“上层”ViewModel(此处为TimeEntriesModel
)的方式,特别是:
self.entries = ko.observableArray(ko.utils.arrayMap(entries, function (entry) {
return {
entry_id: entry.entry_id, //same as before
project_id: entry.project_id, // same as before
user_id: entry.user_id, // same as before
entry_data: ko.observableArray(entry.entry_data.map(function (entry_data) {
return new EntryDataViewModel(entry_data); // here we use the new constructor
}))
}
}));
现在我们的ViewModel已准备好进行更新。 所以让我们改变模态。
同样,在模态中,comment
将会发生变化,我们想要检索其值(更新我们的EntryData)。所以这是一个可观察的
现在我们必须通知我们正在修改哪个EntryData的模式(我认为这是你的代码缺少的主要部分)。我们可以通过保留用于打开模态的EntryData的引用来完成此操作:
self.modal = {
...
comment:ko.observable(""),
entryData : undefined,
...
}
最后要做的是在打开模态时更新所有这些变量:
self.showModal = function (entryDataViewModel) {
// modal.comment is already updated in your bindings, but logic can be moved here.
self.modal.entryData = entryDataViewModel; // keep track of who opened the modal
self.modal.show(true);
}
当你保存时:
self.onModalAction = function () {
self.modal.entryData.comment(self.modal.comment()); //save the modal's comment into the entryData.
self.modal.show(false);
}
我不想更改所有绑定和代码,因此会有很多小的更改,我认为您必须使用代码来查看它们如何影响页面的行为,它是如何工作的。 我的解决方案当然不完美。您的HTML标记中仍然存在一些必须移动到JS的逻辑,我不确定您是否真的需要所有自定义绑定的东西。而且,我对这种模式并不满意。模态内容应属于EntryDataViewModel
,因为编辑注释会对一个EntryData起作用,但正如我所说,我不想更改所有代码。如果您的解决方案有问题,请告诉我:)。
当我说“将逻辑从HTML移动到JS”时,这就是我的意思。以下绑定看起来很复杂,属于HTML标记。
<a class="comment" data-bind="click: function() { $root.modal.comment(comment()); $root.showModal($data); }, css: { 'has-comment': comment().length > 0, 'needs-comment': comment().length == 0 }, attr: { title: comment() }">
您可以做的一些事情:将$root.modal.comment(comment())
移至showModal
,然后您的点击约束变为click : $root.showModal
。即使是“需求 - 评论”绑定也有逻辑,您可以向包含此逻辑的needsComment
添加方法EntryDataViewModel
。
请记住,HTML标记不应包含任何逻辑,它应该只调用JS函数。如果一个函数作用于视图的一部分(例如,一个EntryData),那么这个函数属于局部视图模型(这就是为什么我抱怨模态,它只作用于一个EntryData但是这里位于TimesEntriesModel
)。如果函数操作一组元素(例如,如果创建“添加”按钮),则此函数属于容器ViewModel。
这是一个非常长且具体的答案。为此道歉。您应该能够在网络上的模型视图ViewModel(MVVM)上找到大量资源,这将有助于您的旅程:)
答案 1 :(得分:2)
为了它的价值,我编写了knockout-modal
项目,以便在使用Knockout时更容易使用模态。
欢迎任何有关它的反馈,无论如何我希望看看有帮助。