I am using knockoutjs with masonry and have created a custom knockout binding handler to apply masonry to a html element.
The container I want to apply masonry to has it's content injected dynamically using knockout's foreach
binding.
The issue I am having is getting masonry to apply itself after the masonry container has been updated dynamically.
In the snippet example if you click the masonryize button then it will destroy the masonry container and reapply mansonry, how do I get this behaviour into my binding handler?
ko.bindingHandlers.masonry = {
update: function(element, valueAccessor) {
var options = valueAccessor();
$(element).masonry(options);
}
}
var vm = {
term: ko.observable(),
page: ko.observable(1),
per_page: ko.observable(3),
items: ko.observableArray(),
masonryize: function() {
$('.grid').masonry('destroy');
$('.grid').masonry({
itemSelector: '.item',
columnWidth: 200
});
},
getStuff: function() {
$.ajax({
url: 'https://api.github.com/search/repositories',
method: 'GET',
data: {
q: this.term,
page: this.page(),
per_page: this.per_page()
}
}).then(function(r) {
this.items(r.items)
}.bind(this))
}
}
ko.applyBindings(vm);
.grid {
width: 400px;
}
.item {
margin-bottom: 2em;
border: 1px solid black;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="http://masonry.desandro.com/masonry.pkgd.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<div>
<label>term:</label>
<input type="text" data-bind="value: term" />
</div>
<div>
<label>page:</label>
<input type="text" data-bind="value: page" />
</div>
<div>
<label>no of items:</label>
<input type="text" data-bind="value: per_page" />
</div>
<div>
<button data-bind="click: getStuff">get stuff</button>
<button data-bind="click: masonryize">masonryize</button>
</div>
<div class="grid" data-bind="masonry: {itemSelector: '.item', columnWidth: 200}, foreach: items">
<div class="item">
<p data-bind="text:name"></p>
<p data-bind="text:language"></p>
</div>
</div>
It depends on what you mean by saying after masonsy container was updated. Do you mean resize or maybe loading new items? If you know when the container needs to update you can do it like this:
And apply your binding like so:
So what is happening here:
allBindingsAccessor().masonryUpdater
by reading it. This way whenever it updates the computed we create will reevaluate and reapply masonry.vm.masonryUpdater.valueHasMutated();
This kind of looks like voodoo, plus some may say we are exploiting side effects. Well maybe only a little. On the plus side we are making things quite simple for ourselves. Everything that needs to be done once is executed outside computed, everything that needs to be a dependecy or update inside computed. It is also easier to debug and save data for later use.
It is described in some detail here http://www.knockmeout.net/2012/06/knockoutjs-performance-gotcha-3-all-bindings.html