shama / on-load

On load/unload events for DOM elements using a MutationObserver
113 stars 19 forks source link

trouble with complex yoyo components #1

Closed SilentCicero closed 8 years ago

SilentCicero commented 8 years ago
const yo = require("yo-yo")
const onLoad = require("on-load")

const Component = function (_yield) {
   var open = true
   var el = render(_yield)

   onLoad(el, function(){
     console.log("loaded!");
   });

   function render (_yield) {
       return yo`
       <div> 
         <button onclick=${toggle}>
            Component 1
         </button>

         ${open && "Open!" || "Closed!"}

         <hr />

         ${_yield}
       </div>
     `
   }

   function toggle () {
      open = !open
      yo.update(el, render(_yield))
   }

  return el
}

var comp = Component(Component(Component()))

document.body.appendChild(comp)

try on RequireBin: http://requirebin.com/?gist=8371be058c7c0c087ebe

I'd like onLoad to fire when each component is loaded. However, it only fires when the first component is loaded. The cb does not fire for any other component.

Thoughts?

I was able to get around this by creating my own MutationObserver, giving each component a unique ID, which then allowed me to select component ID's with my Mutation Observers listeners.

But my observer allows for me to select element by ID.

This is the code for that MutationObserver: http://ryanmorr.com/using-mutation-observers-to-watch-for-element-availability/

SilentCicero commented 8 years ago

This MutationObserver design picks up the changes better, although it is a different design using querySelector and ID's.

var listeners = [];
var doc = window.document,
MutationObserver = window.MutationObserver || window.WebKitMutationObserver,
observer;

function ready(selector, fn){
    // Store the selector and callback to be monitored
    listeners.push({
        selector: selector,
        fn: fn
    });
    if(!observer){
        observer = new MutationObserver(function(mutations){
          for(var i = 0, len = listeners.length, listener, elements; i < len; i++){
              listener = listeners[i];

              elements = document.querySelectorAll(listener.selector);
              for(var j = 0, jLen = elements.length, element; j < jLen; j++){
                  element = elements[j];

                  if(!element.ready){
                      element.ready = true;
                      listener.fn.call(element, element);
                  }
              }
          }
        });
        observer.observe(doc.documentElement, {
            childList: true,
            subtree: true
        });
    }
}
SilentCicero commented 8 years ago

This is my querySelector design:

var watch = []

if (window && window.MutationObserver) {
  var observer = new MutationObserver(function (mutations) {
    for(var i = 0; i < watch.length; i++) {
      if(document.querySelector(watch[i][0]) && !watch[i][3]) {
        watch[i][3] = 1;
        watch[i][1]();
      }else if(!document.querySelector(watch[i][0]) && watch[i][3]) {
        watch[i][2]();
        watch.splice(i, 1);
      }
    }
  })
  observer.observe(document.body, {childList: true, subtree: true})
}

function onload (el, l, u) {
  l = l || function () {}
  u = u || function () {}
  watch.push([el, l, u, 0])
}

So the idea here, when there is a mutation, check all listeners for the element. If the element exists and it is not marked as "ready" (i.e. watch[i][3]) then fire the onLoad listener. If the element is marked as "ready" and the element can be found in the querySelection, mark as unloaded.

I believe this to be the most reliable for checking if an element exists with querySelection.

Another approach would be to walk through the children like so, this way we really cover all our bases:

var watch = []

function walkChildren (node, visit) {
  visit(node)
  for (var i = 0; i < node.childNodes.length; i++) {
    walkChildren(node.childNodes[i], visit)
  }
}

if (window && window.MutationObserver) {
  var observer = new MutationObserver(function (mutations) {
    for (var i = 0; i < mutations.length; i++) {
      var mutation = mutations[i]
      var x, y
      for (x = 0; x < mutation.addedNodes.length; x++) {
        for (y = 0; y < watch.length; y++) {
          walkChildren (mutation.addedNodes[x], function(node){
            if (watch[y][0] === node) {
              watch[y][1]()
            }
          });
        }
      }
      for (x = 0; x < mutation.removedNodes.length; x++) {
        for (y = 0; y < watch.length; y++) {
          walkChildren (mutation.removedNodes[x], function(node){
            if (watch[y][0] === node) {
              watch[y][2]()
              watch.splice(y, 1)
            }
          });
        }
      }
    }
  })
  observer.observe(document.body, {childList: true, subtree: true})
}

function onload (el, l, u) {
  l = l || function () {}
  u = u || function () {}
  watch.push([el, l, u])
}
shama commented 8 years ago

With >= 2.1.0 this should now be fixed. Thanks!