I currently have a basic implementation that captures when all images have loaded but I'm unhappy with the code, scalability and somewhat tight coupling between the UI and viewModel.
I have an array of promises that I've declared outside of my viewModel and bindingHandler so that each have access to it. The bindingHandler will push each unresolved promise into the array and the init function will wait until the array has received all resolved promises. Once that happens a function is called that sets a uniform height for all images.
Here's the UI:
<ul data-bind="foreach: movies">
<li>
<div class="image">
<img data-bind="imageLoad: { src: posters.Detailed, alt: title }"/>
</div>
</li>
</ul>
The other side:
function ViewModel() {
var self = this;
self.movies = ko.observableArray([]);
self.init = function(data) {
self.isLoading(true);
self.movies = ko.utils.arrayMap(data, function(item) {
return new robot.ko.models.Movie(item);
});
$.when.apply($, promises).done(function () {
robot.utils.setThumbnailHeight($('.thumbnails li'), function () {
self.isLoading(false);
});
});
};
}
ko.bindingHandlers.imageLoad = {
init: function(element, valueAccessor) {
var options = valueAccessor() || {};
var promise = $.Deferred();
var loadHandler = function() { return promise.resolve; };
promises.push(promise);
ko.applyBindingsToNode(element, {
event: { load: loadHandler },
attr: { src: options.src, alt: options.alt }
});
}
}
In a perfect world my viewModel would just hand-off a callback to my bindingHandler but it would be dynamic, it wouldn't need to know anything about the UI and the bindingHandler would be able to figure out which element it needed to adjust. I've toyed around with the idea of some kind of deferredObservable but in the end I'm just unhappy having this in the viewModel. I've also thought about using a data-
attribute on the parent element to know which element to actually set the height on, but that seems sloppy.
I feel like I'm missing something.
So, I'm asking, what is a better way to accomplish this that would allow loose coupling and scalibility so that I wouldn't have to specifically tell the UI what to do (as much) and when, from my viewModel?
Use a custom binding at the foreach scope that deals with it. For example:
<ul data-bind="foreach: movies, uniformHeight: movies">
<li>
<div class="image">
<img data-bind="imageLoad: { src: posters.Detailed, alt: title }"/>
</div>
</li>
</ul>
Make your assumptions based on the shape of the ViewModel - specifically, assuming the state of the promises reflects the actual state of the loaded images. The bindingHandler could look something like so:
ko.bindingHandlers.uniformHeight = {
init: function(element, valueAccessor) {
//assume valueAccessor contains an array of promises
$.when.apply($, valueAccessor()()).done(function () {
robot.utils.setThumbnailHeight($(element).find('.thumbnails li'), function () {
self.isLoading(false);
});
});
}
};
This might require a little more reorganization around how you're handling the logic in robot.ko.models.Movie
but based on what you've posted, nothing drastic.
Requires more plumbing, but probably will end up being more reliable.
Your outer uniformHeight
binding could register itself on the bindingContext
and wait for child imageLoad
bindings to each raise an event back to notify the image has been loaded; accumulate the heights as they come back and maybe debounce.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With