0

I've found this strange scenario where Firefox seems to loose the content of a documentFragment after cloning it (with the deep flag set to true). Is this a Firefox bug, or am I missing an implementation detail?

var n = ( function nScope(){
 'use strict';

 function isDom( x ){
  return x.nodeType > 0;
 }

 function notDom( x ){
  return !isDom( x );
 }

 return function n(){
  // Avoids conditional logic later by forcing a standard output.
  var args   = [].map.call( arguments, function wrapDom( x ){
   return isDom( x ) ? [ x ] : x;
  } );
  var dom    = [];
  var vdom   = [];
  // Render virtual DOM, then parse output.
  var view   = m.apply( void 0, args );
  var cfg    = view.attrs.config;

  if( view.children.forEach ){
   view.children.forEach( function divideChildren( x ){
    ( isDom( x ) ? dom : vdom ).push( x );
   } );
  }

  if( dom.length === 0 ){
   return view; 
  }

  view.attrs.config = function appendDom( el, init, context ){
   // Only perform DOM insertion logic at config time:
   // Saves unnecessary execution during strategy none redraws.
            if( !init ){
                dom.forEach( function appendNode( node, index ){
                    // If a virtual DOM element occurs after the real node in the children list, grab it and find its
                    // index in the list of virtual elements as a reference point for inserting the real node.
                    var insertAt = vdom.indexOf( view.children.slice( view.children.indexOf( node ) ).filter( notDom )[ 0 ] );
                    // When a documentFragment is inserted into the document, the reference becomes empty.
                    // Therefore we need to insert clones of the original reference.
                    // Because this happens on every redraw, this means DOM nodes cannot be modified by prior reference
                    // between redraw cycles :(
                    console.log( 'Original node:', node );

                    var clone   = node.cloneNode( true );

                    console.log( 'Cloned node:', clone );

                    
                    if( insertAt ){
                        el.insertBefore( clone, el.childNodes[ insertAt ] );
                    }
                    else {
                        el.appendChild( clone );
                    }
                } );
            }

   if( cfg ){
    return cfg( el, init, context );
   }
  };

  // Make sure only the virtual elements are parsed by m.render.
  view.children = vdom;

  return view;
 };
}() );

// To stop jsfiddle breaking
m.route.mode = 'hash';

var links = document.createDocumentFragment();
var array = [ 1, 2 ];

array.forEach( function appendLink( index ){
    var a  = document.createElement( 'a' );
    
    a.innerText = 'Page ' + index;
    a.href      = '/route' + index;
    
    m.route( a );
    
    links.appendChild( a );
    links.appendChild( document.createTextNode( ' ' ) );
} );

var modules = array.map( function makeModule( index ){
    return {
        controller : function(){},
        view       : function(){
            return n(
                '.module',
                [
                    n( 'h1', {
                        onclick : function(){
                            alert( 'Redraw incoming...' );
                        }
                    }, 'Page ' + index ),
                    links 
                ]
            );
        }
    };
} );

m.route( document.body, '/route1', {
    '/route1' : modules[ 0 ],
    '/route2' : modules[ 1 ]
} );
<script src="https://rawgit.com/lhorie/mithril.js/master/mithril.js"></script>
Barney
  • 16,181
  • 5
  • 62
  • 76

1 Answers1

0

You're using innerText, which is not supported by Firefox: 'innerText' works in IE, but not in Firefox

You could use textContent or innerHTML instead

array.forEach( function appendLink( index ){
    var a  = document.createElement( 'a' );

    a.textContent = 'Page ' + index; // <-- here
    a.href      = '/route' + index;

    m.route( a );

    links.appendChild( a );
    links.appendChild( document.createTextNode( ' ' ) );
} );
Community
  • 1
  • 1
LeoHorie
  • 1,320
  • 11
  • 11
  • Still mystified as to why the cloned fragment is logged as having no childNodes, but that seems to be orthogonal. Thanks! – Barney Dec 16 '14 at 11:24
  • When you append a fragment to an element, its childNodes are transferred over to the element, so if you console.log(clone) and drill down into its properties from console after the rendering happened, you'll see an empty list. You can console.log(clone.childNodes) to see the NodeListCollection that got transferred over. – LeoHorie Dec 16 '14 at 15:45
  • The real surprise is the fact that the console isn't evaluating its input immediately: `clone.childNodes` is also empty, because it's a dynamic reference evaluated after the fact. `clone.childNodes.length`, passed by value as a primitive, is `4`. Can't understand how console decides to evaluate input asynchronously… – Barney Dec 16 '14 at 16:03