auto apply masonry in knockout binding handler aft

2019-04-13 08:08发布

问题:

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>

回答1:

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:

ko.bindingHandlers.masonry = {
    init: function(element, valueAccessor, allBindingsAccessor) {
        ko.computed(function () {
             var options = ko.unwrap(valueAccessor());

             ko.unwrap(allBindingsAccessor().masonryUpdater);

             $(element).masonry('destroy');
             $(element).masonry(options);
        }, null, disposeWhenNodeIsRemoved: element);
    }
}

// ...

vm.masonryUpdater = ko.observable();

// When you need to update just run

vm.masonryUpdater.valueHasMutated();

And apply your binding like so:

<div class="grid" data-bind="masonry: {itemSelector: '.item', columnWidth: 200}, masonryUpdater: masonryUpdater, foreach: items">
  <div class="item">
    <p data-bind="text:name"></p>
    <p data-bind="text:language"></p>
  </div>
</div>

So what is happening here:

  1. We create a subscription to an observable allBindingsAccessor().masonryUpdater by reading it. This way whenever it updates the computed we create will reevaluate and reapply masonry.
  2. To tell knockout that our observable has changed we call vm.masonryUpdater.valueHasMutated();
  3. The computed will be safely disposed after our grid element is removed

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