Howdy, Stranger!

It looks like you're new here. If you want to get involved, click one of these buttons!

Why element appended to DOM is not avaliable immediately after $(content).append(child)?

I'm using Twine 2.0.11 and SugarCube (v1.0.34) bundled with it.
I'm playing with some JavaScript and found something I don't quite understand.
Why element appended to DOM is not avaliable immediately after $(content).append(child)?

(1)
prerender["addSomethingToDom"] = function (content) {

	if (tags().contains("dynamic")) {
                               
        var child = "<div class='game' id='one'></div>";
        var grandchild = "<div class='game2' style='width:20px;height:20px;background:white' id='two'></div>";
        $(content).append(child);
        
        var target;
                
        target = document.getElementById('one');
        //target not defined...
        $(target).append(grandchild);
        
        //but if we wait 10 ms it works
        waitForElement('#one',function(){
            target = document.getElementById('one');
            $(target).append(grandchild);
        });        
    }
}

(Not shown: waitForElement function body, but I think it's irrelevant)

In jQuery I can write, for example:

(2)
$('#main-div').mouseover(function() {

        var child = "<div class='game' id='one'>Dynamic!</div>";
        var grandchild = "<div class='game2' style='width:20px;height:20px;background:red' id='two'></div>";        
        var grandgrandchild = "<div class='game3' style='width:40px;height:20px;background:green' id='three'></div>"; 
        $('#main-div').append(child);        
        var target;        
 
        target = document.getElementById('one');
        $(target).append(grandchild);
        target = document.getElementById('two');
        $(target).append(grandgrandchild);
});

And it works, append after append.

As for my SugarCube example, I've found a workaround by adding a timeout, although I'm not fond of this solution.
I am interested in understading what's a difference between (1) and (2), so I can implement something more elegant.
Anyone care to elucidate me?

PS.
I know I can chain appends i.e. $(content).append(child).append(grandchild); that would solve this particular case.
But my original code is a little different and doing this is not an option.

Comments

  • edited March 17
    After further investigation I think it's just javascript rendering hiccup. It's common enough behavior that others also encountered it. For example:

    http://www.vairix.com/blog/jquery-append-into-heavy-loaded-dom-trees

    So nothing specific to Twine/Sugarcube. My solution at the moment is:
    requestAnimationFrame(function() {            
                target = document.getElementById('one');
                $(target).append(grandchild);
            });
    

    Edit: Can someone please comment on this so I can mark as answered? :smile:
  • edited March 17
    First. Mixing jQuery and the native DOM API helter-skelter is kind of bizarre. I'd suggest picking one and sticking with it.

    Lycoris wrote: »
    Why element appended to DOM is not avaliable immediately after $(content).append(child)?
    Because you are not appending it to the DOM. The content parameter of prerender task callbacks is a reference to the render buffer, which is not part of the DOM. The contents of the render buffer are not appended to the DOM until shortly before postdisplay tasks are invoked.

    Instead of the following:
    var target;
    
    target = document.getElementById('one');
    

    You could simply use jQuery:
    var $target = $(content).find('#one');
    $target.append(grandchild);
    

    Or continue mixing the native DOM API with jQuery
    var target = content.querySelector('#one');
    $(target).append(grandchild);
    


    Lycoris wrote: »
    After further investigation I think it's just javascript rendering hiccup. It's common enough behavior that others also encountered it.
    I think exactly one person within the linked SO questions actually knew what they were talking about—which is that the DOM is not something you can expect immediate answers from. Regardless, neither that link, nor the linked SO questions, have anything to do with your issue.
  • You are using the prerender event which occurs before the current passage's generated HTML output is added to the page's DOM.

    The content variable contains the generated HTML output that will be added to the page's DOM at a later point in SugarCube passage render cycle. You are adding your #one element to content so that it will also be rendered at the same time as the current passage's other content.

    You are searching the current page's DOM (eg. document.getElementById('one'); ) for your #one element before it has been actually added to current page, which is why you are not finding it.

    Your delayed search works because by the time it is triggered SugarCube's passage render cycle has completed and the page's DOM has been updated to include the new content.
  • First. Mixing jQuery and the native DOM API helter-skelter is kind of bizarre. I'd suggest picking one and sticking with it.

    Thank you for pointing that out.

    Regarding my question - I'm immenesly greatful to both of you for detailed explanation. I hope I can repay by providing many great games for the community.
Sign In or Register to comment.