2

I would like to use a particular div's height property for my observable. The div's height may change by non-model related causes: let's say if I add content by Firebug. I would like to have the model keep track of the new value.

I have tried using a computed observable, but this just uses the 1st value when it's instantiated (which is normal from what I read in the documentation).

model.divHeight = ko.computed({
    read: function(){return $("#specialDiv").height());},
    owner: model
});

How can I achieve this?

Note that I'm asking for height, but this is expandable for all dom node properties, like width, font-size, color, etc. Much like how Firebug uses a "Computed" properties tab.

pkExec
  • 1,752
  • 1
  • 20
  • 39

2 Answers2

3

Below is an example as well as a binding handler that uses polling.

An alternative to a non-polling solution to detect height change would be this answer (non-polling plugin can be found here - and used within a binding handler preferably)


Example with polling:

model.divHeight = ko.observable($("#element").height());
window.setInterval(function(){
  if( $("#element").height() != model.divHeight() )
      {
          model.divHeight($("#element").height());
      }
}, 50);

Example as a custom binding handler - using polling:

ko.bindingHandlers.checkElementHeight = {
  init: function(element, valueAccessor) {
    var value = valueAccessor();
    window.setInterval(function(){
      if( $(element).height() != value() )
        {
          value($(element).height());
        }
      }, 50);
    }
 };
Community
  • 1
  • 1
Bogdan Goie
  • 1,257
  • 1
  • 13
  • 21
  • Indeed, that link provided the solution: use a polling approach. Care to edit your answer to provide a full example, so I can mark this as the answer? – pkExec Nov 30 '15 at 11:22
  • @pkExec added example with polling approach – Bogdan Goie Nov 30 '15 at 11:53
  • 1
    Properly,this would be a binding handler. – Roy J Nov 30 '15 at 11:58
  • @BogdanGoie Your answer is a bit confusing. Could you explain that it uses polling, but there is an alternative without polling? Besides, you'd better add a link to the github: https://github.com/meetselva/attrchange of the non-polling plugin. I think it will take you a pair of minutes. That would make it perfect. – JotaBe Nov 30 '15 at 12:19
0

EDIT: this answer made more sense before Bogdan Goie's answer edition. Now it simply points you to the docs of ko custom binding, and an alternative, non polling, attribute change option. Bogdan Goie's answer is perfect. I only keep this for the extra link and alternatives.

The correct implementation of this use case, is a custom binding.

The implementation of a custom binding is this:

ko.bindingHandlers.yourBindingName = {
  init: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
    // This will be called when the binding is first applied to an element
    // Set up any initial state, event handlers, etc. here
  },
    update: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
      // This will be called once when the binding is first applied to an element,
      // and again whenever any observables/computeds that are accessed change
      // Update the DOM element based on the supplied values here.
  }
};

In your case, in the init function you have to use the code that detects the change of the element height. In the update you have to do the contrary: you receive the new observable value, and have to use it to update the element's height.

Instead of the polling solution on the other answer, you can use this solution: Cross-Browser, Event-based, Element Resize Detection The code is this:

(function(){
  var attachEvent = document.attachEvent;
  var isIE = navigator.userAgent.match(/Trident/);
  console.log(isIE);
  var requestFrame = (function(){
    var raf = window.requestAnimationFrame || indow.mozRequestAnimationFrame 
      || window.webkitRequestAnimationFrame 
      || function(fn){ return window.setTimeout(fn, 20); };
    return function(fn){ return raf(fn); };
  })();

  var cancelFrame = (function(){
    var cancel = window.cancelAnimationFrame
      || window.mozCancelAnimationFrame
      || window.webkitCancelAnimationFrame
      || window.clearTimeout;
    return function(id){ return cancel(id); };
  })();

  function resizeListener(e){
    var win = e.target || e.srcElement;
    if (win.__resizeRAF__) cancelFrame(win.__resizeRAF__);
    win.__resizeRAF__ = requestFrame(function(){
      var trigger = win.__resizeTrigger__;
      trigger.__resizeListeners__.forEach(function(fn){
        fn.call(trigger, e);
      });
    });
  }

  function objectLoad(e){
    this.contentDocument.defaultView.__resizeTrigger__ = this.__resizeElement__;
    this.contentDocument.defaultView.addEventListener('resize', resizeListener);
  }

  window.addResizeListener = function(element, fn){
    if (!element.__resizeListeners__) {
      element.__resizeListeners__ = [];
      if (attachEvent) {
        element.__resizeTrigger__ = element;
        element.attachEvent('onresize', resizeListener);
      }
      else {
        if (getComputedStyle(element).position == 'static') 
           element.style.position = 'relative';
        var obj = element.__resizeTrigger__ 
           = document.createElement('object'); 
        obj.setAttribute('style', 'display: block; position: absolute; top: 0; left: 0; height: 100%; width: 100%; overflow: hidden; pointer-events: none; z-index: -1;');
        obj.__resizeElement__ = element;
        obj.onload = objectLoad;
        obj.type = 'text/html';
        if (isIE) element.appendChild(obj);
        obj.data = 'about:blank';
        if (!isIE) element.appendChild(obj);
      }
    }
    element.__resizeListeners__.push(fn);
  };

  window.removeResizeListener = function(element, fn){
    element.__resizeListeners__.splice(element.__resizeListeners__.indexOf(fn), 1);
    if (!element.__resizeListeners__.length) {
      if (attachEvent) element.detachEvent('onresize', resizeListener);
      else {
        element.__resizeTrigger__.contentDocument.defaultView.removeEventListener('resize', resizeListener);
        element.__resizeTrigger__ = !element.removeChild(element.__resizeTrigger__);
      }
    }
  }
})();
JotaBe
  • 38,030
  • 8
  • 98
  • 117