twitter-bootstrap 将模态数据绑定到淘汰赛模型
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/20751587/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Bind modal data to knockout model
提问by Samsquanch
I'm trying to have a twitter bootstrap modal open to a window that has a text area in it which is editable, then on save, it saves the appropriate data. My current code:
我正在尝试将 twitter bootstrap 模式打开到一个窗口,该窗口中有一个可编辑的文本区域,然后在保存时,它会保存适当的数据。我目前的代码:
HTML:
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:
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/
小提琴:http: //jsfiddle.net/sL3HK/
As you can see in the fiddle, the modal opens with the text box, but I'm unable to figure out how to get the 'comment' text into the modal or update the comment when the 'save' button is pressed.
正如您在小提琴中看到的那样,模态打开时带有文本框,但我无法弄清楚如何在按下“保存”按钮时将“评论”文本放入模态或更新评论。
Any ideas?
有任何想法吗?
Also, I'm very new to Knockout, so if there's anything in there that doesn't look quite right, please feel free to correct me on it.
另外,我对 Knockout 非常陌生,所以如果其中有任何看起来不太正确的地方,请随时纠正我。
UPDATE:
更新:
I've been fiddling with the code, and have been able to get the "comment" into the modal, but I've not been able to successfully update it up to this point. And another problem I will eventually run into is that I only want the comment to be updated when "Save" is clicked, rather than the normal update on blur. I really think I'm going about this the wrong way, but I'm not sure what the rightway is. Any more help is greatly appreciated.
我一直在摆弄代码,并且能够将“评论”放入模态,但到目前为止我还没有成功更新它。我最终会遇到的另一个问题是,我只希望在单击“保存”时更新评论,而不是模糊时的正常更新。我真的认为我以错误的方式解决这个问题,但我不确定正确的方法是什么。非常感谢任何更多帮助。
回答by Paul D.
Here is a JsFiddlein which you should be able to edit comment for each entry. Here is how I proceeded to obtain this.
这是一个JsFiddle,您应该能够在其中编辑每个条目的评论。这是我如何获得这个。
The ViewModels
视图模型
First, I like to divide my viewsinto partials. For each type of partial, I create a ViewModel. And an "upper level" ViewModel is used as a container for all the partial ViewModels. Here you'll need a EntryDataViewModel which I defined this way :
首先,我喜欢将我的观点分成部分。对于每种类型的部分,我创建了一个 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);
}
Basically, this constructor does the conversion from your raw data to something you will be able to manipulate in your views. Depending on what you want to do, you can make things observable or not. commentis used in some bindings and is expected to change. We want the page to react dynamically to its changes, so let's make it observable.
Because of this change, we will change the way we create the "upper level" ViewModel (here TimeEntriesModel), and in particular :
基本上,这个构造函数将原始数据转换为您可以在视图中操作的数据。根据您想要做什么,您可以使事情变得可观察或不可观察。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
}))
}
}));
Now our ViewModels are ready to be updated. So let's change the modal.
现在我们的 ViewModel 已准备好更新。所以让我们改变 modal。
The Modal
模态
Again, in the modal, the commentwill be subject to change, and we want to retrieve its value (to update our EntryData). So it's an observable.
Now we have to inform the modal of which EntryData we are modifying (and I think this is the main part your code was lacking). We can do this by keeping a reference of the EntryData that was used to open the modal :
同样,在模态中,comment会发生变化,我们想要检索它的值(以更新我们的 EntryData)。所以它是一个可观察的。
现在我们必须通知模式我们正在修改哪个 EntryData(我认为这是您的代码缺少的主要部分)。我们可以通过保留用于打开模态的 EntryData 的引用来做到这一点:
self.modal = {
...
comment:ko.observable(""),
entryData : undefined,
...
}
Last thing to do is to update all these variables when you open the modal :
最后要做的是在打开 modal 时更新所有这些变量:
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);
}
And when you save :
当您保存时:
self.onModalAction = function () {
self.modal.entryData.comment(self.modal.comment()); //save the modal's comment into the entryData.
self.modal.show(false);
}
Conclusion
结论
I did not want to change all your bindings and code, thus there were a lot of little changes and I think you'll have to play with the code to see how they affect the behavior of the page, how it works. My solution is not perfect of course. There remains some logic in your HTML markup that must be moved to the JS and I'm not sure you really need all the custom binding stuff. Moreover, I'm not happy about the modal. The modal stuff should belong to a EntryDataViewModelsince editing the comment acts on one EntryData, but as I said, I did not want to change all your code. Tell me if you have problems with my solution :).
我不想更改您所有的绑定和代码,因此有很多小的更改,我认为您必须使用代码来查看它们如何影响页面的行为,它是如何工作的。当然,我的解决方案并不完美。您的 HTML 标记中仍有一些必须移至 JS 的逻辑,我不确定您是否真的需要所有自定义绑定的东西。此外,我对模态不满意。模态的东西应该属于一个,EntryDataViewModel因为编辑评论对一个 EntryData 起作用,但正如我所说,我不想更改您的所有代码。如果您对我的解决方案有问题,请告诉我:)。
Update (some hints for going further)
更新(一些进一步的提示)
When I said "moving logic from HTML to JS", here is what I meant. The following binding looks to complicated to belong to HTML markup.
当我说“将逻辑从 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() }">
Some things you could do : move $root.modal.comment(comment())to showModal, then your click binding becomes click : $root.showModal. Even the "needs-comment" binding has a logic, you could add a method needsCommentto your EntryDataViewModelthat contains this logic.
Keep in mind that HTML markup should not contain any logic, it should just make calls to JS functions. If a function acts on an partial of the view (for example, an EntryData), then this function belongs to the partial view model (this is why I was complaining about the modal, that acts on only one EntryData but here is located in the TimesEntriesModel). If a function manipulates a set of elements (for example, if you create an "add" button), this function belongs in the container ViewModel.
你可以做一些事情:移动$root.modal.comment(comment())到showModal,然后你的点击绑定就变成了click : $root.showModal。即使“needs-comment”绑定也有逻辑,您可以needsComment向EntryDataViewModel包含此逻辑的方法添加一个方法。
请记住,HTML 标记不应该包含任何逻辑,它应该只是调用 JS 函数。如果一个函数作用于视图的一部分(例如,一个 EntryData),那么这个函数属于部分视图模型(这就是我抱怨模态的原因,它只作用于一个 EntryData 但这里位于TimesEntriesModel)。如果一个函数操作一组元素(例如,如果您创建一个“添加”按钮),则该函数属于容器 ViewModel。
This was a VERY long and specific answer. Apologies for that. You should be able to find a lot of resources on Model View ViewModel (MVVM) on the web, that will help you in your journey :)
这是一个非常长且具体的答案。对此表示歉意。您应该能够在网络上找到大量关于 Model View ViewModel (MVVM) 的资源,这将对您的旅程有所帮助:)
回答by Brian M. Hunt
For what it's worth, I wrote the knockout-modalproject to make modals easier to work with when using Knockout.
就其价值而言,我编写了该knockout-modal项目以在使用 Knockout 时使模态更易于使用。
Would welcome any feedback on it, and in any case I hope it is helpful to look at.
欢迎对此提出任何反馈意见,无论如何我希望它对您有所帮助。

