问题
i recently struggled with a problem and although i solved it for me, i'm not sure whether there aren't better solutions out there, so I'd appreciate any comments.
Problem. i wanted to create a 'ScrollIntoView' binding. Since scrolling an element into view, requires the DOM-Element, i wrote a custom binding, which i then wanted to explicitly trigger, whenever i pleased. I started with this code:
ko.bindingHandlers.scrollTo = {
update: function (element, valueAccessor, allBindings) {
var _value = valueAccessor();
var _valueUnwrapped = ko.unwrap(_value);
if (_valueUnwrapped) {
element.scrollIntoView();
}
}
};
The binding:
<div data-bind="scrollTo: goToThis">
And in the ViewModel i had this observable:
_self.goToThis = ko.observable(false).extend({notify: 'always'});
which I then could just trigger by calling:
_self.goTohis(true);
So far, so good. However i quickly ran into Problems. Since whenever i set the goTothis() Observable to true, the true value stuck with it, which caused some of the elements to scroll into view, without the user explicity triggering it. For instance, when i changed the view, essentially hiding all the elements with an if binding, and then switched back, the if binding would re-trigger all the goToThis observables, which had previously been set to true. Ugh!
So i thought up this pattern and extended my custum binding like this:
ko.bindingHandlers.scrollTo = {
update: function (element, valueAccessor, allBindings) {
var _value = valueAccessor();
var _valueUnwrapped = ko.unwrap(_value);
if (_valueUnwrapped) {
element.scrollIntoView();
// resets the trigger value to false. Otherwise there will be more and more ViewModels, where the value is true.
if (ko.isWriteableObservable(_value) && typeof (_valueUnwrapped) === 'boolean') {
_value(false);
}
}
}
};
Essentially resetting the boolean value each time it is triggered.
So i guess my question is this: Has anybody ever written a scrollIntoView binding? If yes, how did you solve it?
And generally, is there a pattern for writing triggers? ie i just want to trigger a binding, but there is no real value change.
best regards j
回答1:
Your scrollTo
handler is fine like this:
ko.bindingHandlers.scrollTo = {
update: function (element, valueAccessor, allBindings) {
var _value = valueAccessor();
var _valueUnwrapped = ko.unwrap(_value);
if (_valueUnwrapped) {
element.scrollIntoView();
}
}
};
But instead of working with a goToThis
observable per view model, better would be to have 1 observable in your root which keeps track of the current scroll item and then pass an expression in your binding like this:
<div data-bind="scrollTo: $root.scrolledItem() == $data">
This way you don't need to reset anything and you don't need an goToThis
observable per view model.
See http://jsfiddle.net/290ew0nr/1/
回答2:
I use scrollintoview
in my knockout app, but never thought to put it into a bindingHandler
, probably because I don't feel scrollintoview
is a relationship between the model and the view.
scrollintoview
to me is more like a follow up action after we present data on view.
My approach is to use a css binding to mark the item I want to scrollintoview
, for instance, I mark the element with css class current-item
, and of course there should be only one item marked with this class in the end.
Then what I need to do is to scroll it into view after ko rebuild all the DOM.
To allow ko to build the view before I ran scrollintoview
, I need to delay the action.
// do my model logic above
// then fire my after-render callback
setTimeout(function() {
$('.current-item').scrollintoview({direction: 'y'});
// I am using a different scrollintoview library: https://github.com/litera/jquery-scrollintoview
}, 0);
BTW, ko provided a better way to do afterRender callback http://knockoutjs.com/documentation/template-binding.html#note_4_using_afterrender_afteradd_and_beforeremove
You could use a template
, and put scrollintoview
in that template
's afterRender
callback.
Just my 2 cents.
回答3:
Another way to tackle this problem is with custom events. You could fire an event from your ViewModel when you need to do the scrolling (supplying any relevant data), and then you define a handler on the page to react to that event. That way you can do all the DOM manipulation in the event handler and keep your ViewModel HTML-agnostic.
来源:https://stackoverflow.com/questions/20740212/knockoutjs-scrollintoviewtrigger