【发布时间】:2018-05-06 01:55:08
【问题描述】:
我在 Knockout.js 中编写了一个持续时间选择器控件(sn-p 下面,also on jsFiddle):
$(function() {
ko.bindingHandlers.clickOutside = {
init: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
var callback = ko.utils.unwrapObservable(valueAccessor());
var clickHandler = function (e) {
if (!($.contains(element, e.target) || element === e.target)) {
callback();
}
};
$('html').on('click', clickHandler);
ko.utils.domNodeDisposal.addDisposeCallback(element, function () {
$('html').off('click', clickHandler);
});
}
};
ko.components.register('durationInput', {
viewModel: function (params) {
var self = this;
if (!ko.isObservable(params.value)) {
throw "value param should be an observable!";
}
this.value = params.value;
var match = /^([0-9]{1,2}):([0-5]?[0-9]):([0-5]?[0-9])$/.exec(this.value());
this.hours = ko.observable(match != null ? match[1] : "00");
this.minutes = ko.observable(match != null ? match[2] : "00");
this.seconds = ko.observable(match != null ? match[3] : "00");
this.label = params.label;
this.id = params.id;
this.popupVisible = ko.observable(false);
this.inputClick = function () {
self.popupVisible(!self.popupVisible());
};
this.clickOutside = function () {
self.popupVisible(false);
};
this.evalValue = function () {
var hrs = self.hours();
while (hrs.length < 2)
hrs = "0" + hrs;
var mins = self.minutes();
while (mins.length < 2)
mins = "0" + mins;
var secs = self.seconds();
while (secs.length < 2)
secs = "0" + secs;
self.value(hrs + ':' + mins + ':' + secs);
};
this.hours.subscribe(this.evalValue);
this.minutes.subscribe(this.evalValue);
this.seconds.subscribe(this.evalValue);
},
template: '<div class="form-group" data-bind="clickOutside: clickOutside">\
<label data-bind="text: label, attr: { for: id }" />\
<div class="input-group">\
<input class="form-control duration-picker-input" type="text" data-bind="value: value, click: inputClick" readonly>\
<div class="panel panel-default duration-picker-popup" data-bind="visible: popupVisible">\
<div class="panel-body">\
<div class="inline-block">\
<div class="form-group">\
<label data-bind="attr: { for: id + \'-hours\' }">Hours</label>\
<input data-bind="textInput: hours, attr: { id: id + \'-hours\' }" class="form-control" type="number" min="0" max="99" />\
</div>\
</div>\
<div class="inline-block">\
<div class="form-group">\
<label data-bind="attr: { for: id + \'-minutes\' }">Minutes</label>\
<input data-bind="textInput: minutes, attr: { id: id + \'-minutes\' }" class="form-control" type="number" min="0" max="59" />\
</div>\
</div>\
<div class="inline-block">\
<div class="form-group">\
<label data-bind="attr: { for: id + \'-seconds\' }">Seconds</label>\
<input data-bind="textInput: seconds, attr: { id: id + \'-seconds\' }" class="form-control" type="number" min="0" max="59" />\
</div>\
</div>\
</div>\
</div>\
</input>\
<span class="input-group-addon" data-bind="click: inputClick"><span class="hover-action glyphicon glyphicon-chevron-down"></span></span>\
</div>\
</div>'
});
var viewmodel = function() {
this.time = ko.observable("12:34:56");
};
ko.applyBindings(new viewmodel());
});
.hover-action {
color: #bbb;
cursor: pointer;
}
.hover-action:hover {
color: #333;
cursor: pointer;
}
.inline-block {
display: inline-block;
}
.duration-picker-input {
position: relative;
}
.duration-picker-popup {
z-index: 100;
position: absolute;
top: 100%;
right: 0;
}
<link rel="stylesheet" type="text/css" href="https://maxcdn.bootstrapcdn.com/bootswatch/3.3.7/paper/bootstrap.min.css">
<div style="width: 400px">
<!--ko component: {
name: "durationInput",
params: {
id: "time",
value: time,
label: "Total time"
}
} -->
<!-- /ko -->
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
问题在于,它不会对通过参数传递的可观察值的变化做出反应。但是,我不知道如何在不陷入无限循环的情况下实现它。目前,我有:
(initialization)
|
Current value is being read from observable passed via params
|
Hours, minutes and seconds values are generated basing on current value
然后:
(Hours, minutes or seconds change)
|
New value is generated as hours:minutes:seconds
|
New value is set to observable passed via params
问题是,如果我对传递的 observable 的变化做出反应并重新生成小时、分钟和秒,我会导致他们的订阅者被通知,所以值会被重新生成,所以它会改变,所以小时,分钟并且秒数将被重新生成,因此他们的订阅者将再次收到通知......等等。
如何实现从单个可观察对象到其他三个可观察对象的所谓双向桥接?类似于 WPF 中的多重绑定。
【问题讨论】:
-
做一个 sn-p 的好东西!我冒昧地移动了 sn-p。如果您不喜欢这些更改,只需在编辑历史记录中点击回滚即可。 :-)
-
我无法重现陷入无限循环。你还有这个问题吗?
-
@Skeevs 你不能因为我没有在原始值更改上实现刷新小时/分钟/秒。我不知道该怎么做。
-
如果我通过另一个输入更改原始值,一切都会按预期工作。 jsfiddle.net/bkhxjqed 和@Skeevs 一样,我无法重现您描述的问题。
-
@user3297291 是吗?使用我的编辑器将分钟更改为 40,然后使用另一个输入将时间更改为“12:34:56”。虽然编辑框会显示正确更改的时间,但分钟框仍会显示 40。
标签: javascript jquery knockout.js data-binding