Example: Adding Node Content - Burger Builder

This example demonstrates how to insert content when working with NodeList instances.

Click buttons to build your burger.

  • Burger bun top
  • Burger bun bottom

Setting up the NodeList

First we need some HTML to work with.

<ul class="demo">
    <li class="bun-top"><img src="assets/images/bun_top.png"/></li>
    <li class="bun-bottom"><img src="assets/images/bun_bottom.png"/></li>
</ul>

Next we'll add some buttons to be clicked.

<ul class="buttons-list">
    <li><button class='yui3-button patty'> Patty &#183; Before Last Bun</button></li>
    <li><button class='yui3-button lettuce'> Lettuce &#183; Before Last Bun</button></li>
    <li><button class='yui3-button cheese' disabled="disabled"> Cheese &#183; Before First Patty</button></li>
    <li><button class='yui3-button tomato'> Tomato &#183; After First Bun</button></li>
    <li><button class='yui3-button onions'> Onions &#183; After First Bun</button></li>
    <li><button class='yui3-button pickles'> Pickles &#183; After First Bun</button></li>
    <li><button class='yui3-button ketchup'> Ketchup &#183; After First Bun</button></li>
    <li><button class='yui3-button done'> Done</button></li>
    <li><button class='yui3-button another'> Another Please</button></li>
</ul>

Adding Content

After defining some vars, we'll add a buttonClicks handler to run when an event is fired. It will add content to the demo node.

Note that the this in the handler is the object that was clicked.

var demo = Y.one('.demo'),
    btnList = Y.all('.buttons-list .yui3-button'),
    ...;

    // This inserts burger parts and manages the display of buttons
    var buttonClicks = function (e) {
        var obj;
        if (this.hasClass('patty')) {
            // Create a node with an image of the burger part
            obj = Y.Node.create('<li class="patty"><img src="' + imgPath + 'burg_patty.png"/></li>');

            // Insert it before the bottom bun
            demo.insert(obj, Y.one('.bun-bottom'));

            // ...
        } else if // other buttons follow...

The handler inserts different objects into the demo container object in different places based on these methods:

  • prepend - as firstChild
  • append - as lastChild
  • insert - before a specified node or childNode index

Attaching Events

We assign our handler to all of the yui3-button objects through event subscription to the matching selector.

Y.on('click', buttonClicks, '.example .buttons-list .yui3-button');

Transitions

When an event handler simply inserts an object, it appears instantly, and other DOM objects are rendered in their new locations, which can be a visually jarring experience.

In this example, we've added a transitionObject function to smooth things out. Inserted burger elements have an initial CSS height of 0. After each insertion, we call the transitionObject function, passing the inserted object. It begins growing to a height equal to the image it contains (the images are different heights). This gradually pushes open a space for itself, while it moves in from offscreen left, and fades in.

var transitionObject = function (obj) {
    obj.transition({
        duration: 0.8,

        // height grows from initial 0, to height of contained image
        height: obj.one('img').getStyle('height'),
        marginLeft: '0px', // transition into place from offscreen left.
        opacity: {
            delay: 0.2,
            duration: 0.5,
            value: 1
        }
    });
}

Here's more code in the buttonClicks handler where we call transitionObject, and do some special handling for the cheese button state.

// This inserts burger parts and manages the display of buttons
var buttonClicks = function (e) {
    var obj;
    if (this.hasClass('patty')) {
        // Create a node with an image of the burger part
        obj = Y.Node.create('<li class="patty"><img src="' + imgPath + 'burg_patty.png"/></li>');

        // Insert it before the bottom bun
        demo.insert(obj, Y.one('.bun-bottom'));

        // Smooth out insert with transition
        transitionObject(obj);

        // Cheese button becomes available
        // only when there's a patty to insert before
        Y.one('.buttons-list .cheese')._node.disabled = false;
    } else if // other buttons follow...

Complete Example Source

In the complete source you'll see we also added handling for:

  • Removing elements from the burger
  • Clicking the Done button to vertically compress the burger
  • Requesting another

<style>
.example .demo, .example .buttons-list{
    width: 302px;
    display: inline-block;
    zoom: 1;
    *display: inline;
    margin: 0;
    padding: 0;
    vertical-align: top;
    text-align: center;        
}
.example .buttons-list{
    list-style: none;
    text-align: left;
    margin-left: 100px;
    width: auto;
    height: 21em;    
}
.example .buttons-list .yui3-button {
    margin-bottom: 0.5em;
}
.example .buttons-list .ketchup{
    margin-bottom: 1em;
}
.example .buttons-list .another{
    display: none;
}
.example .demo li {
    position: relative;
    list-style: none;
    height: 0;
    width: 302px;
    opacity: 0;
    margin: 0 0 0 -800px;
    cursor: no-drop;
    font-size: 1px;
}
.example .demo li img {
    position: absolute;
    top: 0;
    left: 0;
}
.example .demo .bun-top, .example .demo .bun-bottom{
    opacity: 1;
    height: 106px;
    margin: 0;
}
</style>
    <ul class="demo">
        <li class="bun-top"><img src="../assets/node/images/burg_bun_top.png" width="271" height="106" alt="Burger bun top"/></li>
        <li class="bun-bottom"><img src="../assets/node/images/burg_bun_bottom.png" width="291" height="115" alt="Burger bun bottom"/></li>
    </ul>
    <ul class="buttons-list">
        <li><button class='yui3-button patty'> Patty &#183; Before Last Bun</button></li>
        <li><button class='yui3-button lettuce'> Lettuce &#183; Before Last Bun</button></li>
        <li><button class='yui3-button cheese' disabled="disabled"> Cheese &#183; Before First Patty</button></li>
        <li><button class='yui3-button tomato'> Tomato &#183; After First Bun</button></li>
        <li><button class='yui3-button onions'> Onions &#183; After First Bun</button></li>
        <li><button class='yui3-button pickles'> Pickles &#183; After First Bun</button></li>
        <li><button class='yui3-button ketchup'> Ketchup &#183; After First Bun</button></li>
        <li><button class='yui3-button done'> Done</button></li>
        <li><button class='yui3-button another'> Another Please</button></li>
    </ul>
<script>
YUI().use('node', 'cssbutton', 'transition', 'UA', function (Y) {
    var demo = Y.one('.demo'),
        btnList = Y.all('.buttons-list .yui3-button'),
        i = 0,
        objList,
        myZIndex,
        imgPath = '../assets/node/images/';

    if (Y.UA.ie && Y.UA.ie < 7) {

        // Add a prefix to the image file name in the imgPath.
        // This is needed to work around IE6 non-support of alpha pngs
        imgPath = imgPath + '8bit_';
        Y.one('.example .demo .bun-top img').setAttribute('src', imgPath + 'burg_bun_top.png');
        Y.one('.example .demo .bun-bottom img').setAttribute('src', imgPath + 'burg_bun_bottom.png');
    }

    // This smoothes out the visual experience of adding
    // an element to the burger
    var transitionObject = function (obj) {
        obj.transition({
            duration: 0.8,

            // height grows from initial 0, to height of contained image
            height: obj.one('img').getStyle('height'),
            marginLeft: '0px', // transition into place from offscreen left.
            opacity: {
                delay: 0.2,
                duration: 0.5,
                value: 1
            }
        });
    }

    // This removes an element of the burger
    // the height transitions to 0, it moves left, and fades out
    var removeObject = function(e) {
        e.currentTarget.transition({
            duration: 0.8,
            height: 0,
            marginLeft: '-400px',
            opacity: {
                delay: 0.2,
                duration: 0.5,
                value: 0
            }
        },
            // after the transition finishes...
            function(){
                e.currentTarget.remove(); // remove the clicked item from the DOM

                // If there's no patty in the burger
                if (Y.one('.example .demo').getHTML().indexOf('patty') === -1) {
                    // Disable the cheese button
                    // Cheese is inserted before first patty.
                    // The cheese button will be enabled when there's a patty
                    Y.one('.buttons-list .cheese')._node.disabled = true;
                }
        });
    }

    // This inserts burger parts and manages the display of buttons
    var buttonClicks = function (e) {
        var obj;
        if (this.hasClass('patty')) {
            // Create a node with an image of the burger part
            obj = Y.Node.create('<li class="patty"><img src="' + imgPath + 'burg_patty.png" width="268" height="75" alt="Burger patty"/></li>');

            // Insert it before the bottom bun
            demo.insert(obj, Y.one('.bun-bottom'));

            // Smooth out insert with transition
            transitionObject(obj);
            
            // Cheese button becomes available 
            // only when there's a patty to insert before
            Y.one('.buttons-list .cheese')._node.disabled = false; 
        } else if (this.hasClass('lettuce')) {
            obj = Y.Node.create('<li class="lettuce"><img src="' + imgPath + 'burg_lettuce.png" width="302" height="87" alt="Lettuce"/></li>');
            demo.insert(obj, Y.one('.bun-bottom'));
            transitionObject(obj);        
        } else if (this.hasClass('cheese')) {
            obj = Y.Node.create('<li class="cheese"><img src="' + imgPath + 'burg_cheese.png" width="274" height="89" alt="Cheese"/></li>');
            demo.insert(obj, Y.one('.patty'));
            transitionObject(obj);
        } else if (this.hasClass('ketchup')) {
            obj = Y.Node.create('<li class="ketchup"><img src="' + imgPath + 'burg_ketchup.png" width="208" height="66" alt="Ketchup"/></li>');
            Y.one('.bun-top').insert(obj, 'after');
            transitionObject(obj);
        } else if (this.hasClass('pickles')) {
            obj = Y.Node.create('<li class="pickles"><img src="' + imgPath + 'burg_pickles.png" width="236" height="61" alt="Pickles"/></li>');
            Y.one('.bun-top').insert(obj, 'after');
            transitionObject(obj);
        } else if (this.hasClass('onions')) {
            obj = Y.Node.create('<li class="onions"><img src="' + imgPath + 'burg_onions.png" width="248" height="77" alt="Onions"/></li>');
            Y.one('.bun-top').insert(obj, 'after');
            transitionObject(obj);
        } else if (this.hasClass('tomato')) {
            obj = Y.Node.create('<li class="tomato"><img src="' + imgPath + 'burg_tomato.png" width="225" height="68" alt="Tomato slice"/></li>');
            Y.one('.bun-top').insert(obj, 'after');
            transitionObject(obj);
        } else if (this.hasClass('done')) {
            objList = Y.all('.demo li');
            myZIndex = objList.size();  // for resetting z-index of burger parts
            
            // Hide all the buttons when done
            btnList.setStyle('display', 'none');
            
            // Show the "Another Please" button
            Y.one('.buttons-list .another').setStyle('display', 'block');

            // The normal z-index of <li>s in a <ul> results in the
            // bottom of the bun picture being on top
            // The z-index of the burger elements must be reversed.
            for (i = 0; i < objList.size(); i += 1) {
                objList.item(i).setStyle('zIndex', myZIndex);
                myZIndex -= 1;
                objList.item(i).setStyle('position', 'relative');
                // transition the height of the elements proportionally smaller
                // so you could get your mouth around it
                objList.item(i).transition({
                    duration: 0.5,
                    height: parseInt(objList.item(i).one('img').getStyle('height'), 10) * 0.15 + 'px'
                });
            }    
        } else if (this.hasClass('another')) {
            // Empty out the content of the burger image
            demo.setContent('');

            // Insert just the buns
            demo.append('<li class="bun-top"><img src="' + imgPath + 'burg_bun_top.png" width="271" height="106" alt="Burger bun top"/></li>');
            demo.append('<li class="bun-bottom"><img src="' + imgPath + 'burg_bun_bottom.png" width="291" height="115" alt="Burger bun bottom"/></li>');

            // Disable the cheese button
            // Cheese is inserted before first patty.
            // The cheese button will be enabled when there's a patty
            Y.one('.buttons-list .cheese')._node.disabled = true;

            // Display all the buttons except the "Another Please"
            btnList.setStyle('display', 'block');
            Y.one('.buttons-list .another').setStyle('display', 'none');
        }
    }
    Y.on('click', buttonClicks, '.example .buttons-list .yui3-button');
    Y.one('.example .demo').delegate('click', removeObject, 'li');
});
</script>