我正在尝试将twitter bootstrap 模式打开到其中具有可编辑文本区域的窗口,然后在保存时保存适当的数据。我当前的代码:
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));
fiddle :http://jsfiddle.net/sL3HK/
正如您在 fiddle 中看到的那样,模式会随文本框一起打开,但是当按“保存”按钮时,我无法弄清楚如何将“注释”文本添加到模式中或更新注释。
有任何想法吗?
另外,我对 knockout 还很陌生,因此如果其中有任何看起来不太正确的内容,请随时对其进行纠正。
更新:
我一直在摆弄代码,并且能够将“注释”添加到模式中,但是到目前为止,我还无法成功对其进行更新。我最终会遇到的另一个问题是,我只希望在单击“保存”时更新注释,而不是在模糊时进行常规更新。我真的以为我的方法是错误的,但是我不确定什么是正确的方法。任何更多的帮助,我们将不胜感激。
Updated fiddle.
最佳答案
这是JsFiddle,您应该可以在其中编辑每个条目的注释。这是我如何获得此信息的方法。
ViewModels
首先,我喜欢将我的观点分为部分。对于每种类型的局部,我创建一个ViewModel。并且“上级” ViewModel用作所有部分ViewModels的容器。在这里,您需要一个我通过这种方式定义的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);
}
基本上,此构造函数执行从原始数据到可以在 View 中进行操作的转换。根据您要执行的操作,可以使事物可见或不可见。
comment
在某些绑定(bind)中使用,并且有望更改。我们希望页面能够动态地对其更改使用react,因此让我们对其进行观察。由于此更改,我们将更改创建“较高级别” 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
}))
}
}));
现在,我们的ViewModels已准备好进行更新。 因此,让我们更改模式。
模态
同样,在模式中,
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);
}
结论
我不想更改您的所有绑定(bind)和代码,因此进行了很多小的更改,我认为您必须使用代码来查看它们如何影响页面的行为以及页面的工作方式。 当然,我的解决方案不是完美的。 HTML标记中仍然有一些逻辑,必须移到JS上,我不确定您是否真的需要所有自定义绑定(bind)内容。而且,我对模式不满意。模态化的东西应该属于
EntryDataViewModel
,因为编辑注释作用于一个EntryData,但是正如我所说,我不想更改所有代码。告诉我您的解决方案是否有问题:)。更新(一些进一步的提示)
当我说“将逻辑从HTML移到JS”时,这就是我的意思。以下绑定(bind)看起来很复杂,属于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
,然后您的单击绑定(bind)将变成click : $root.showModal
。即使“需求注释”绑定(bind)也具有逻辑,您也可以将needsComment
方法添加到包含此逻辑的EntryDataViewModel
中。请记住,HTML标记不应包含任何逻辑,而应仅调用JS函数。如果某个函数作用于部分 View (例如,EntryData),则该函数属于部分 View 模型(这就是为什么我提示模态的原因,该模式仅作用于一个EntryData,但此处位于
TimesEntriesModel
)。如果函数操作一组元素(例如,如果您创建“添加”按钮),则此函数属于容器ViewModel。这是一个非常冗长而具体的答案。对该表示歉意。您应该可以在网上的Model View ViewModel(MVVM)上找到很多资源,这将对您的旅程有所帮助:)
关于javascript - 将模态数据绑定(bind)到模型,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/20751587/