#

# Example: Portal Style Example

This is a portal style example using Drag & Drop event bubbling and animation.

View Example in New Window

## Full example source

    //Use loader to grab the modules needed
    YUI().use('dd', 'anim', 'yql', 'cookie', 'json', function(Y) {
        //Make this an Event Target so we can bubble to it
        var Portal = function() {
            Portal.superclass.constructor.apply(this, arguments);
        };
        Portal.NAME = 'portal';
        Y.extend(Portal, Y.Base);
        //This is our new bubble target..
        Y.Portal = new Portal();

        //Setup some private variables..
        var goingUp = false, lastY = 0, trans = {};

        //The list of feeds that we are going to use
        var feeds = {
            'ynews': {
                id: 'ynews',
                title: 'Yahoo! US News',
                url: 'rss.news.yahoo.com/rss/us'
            },
            'yui': {
                id: 'yui',
                title: 'YUI Blog',
                url: 'feeds.yuiblog.com/YahooUserInterfaceBlog'
            },
            'slashdot': {
                id: 'slashdot',
                title: 'Slashdot',
                url: 'rss.slashdot.org/Slashdot/slashdot'
            },
            'ajaxian': {
                id: 'ajaxian',
                title: 'Ajaxian',
                url: 'feeds.feedburner.com/ajaxian'
            },
            'daringfireball': {
                id: 'daringfireball',
                title: 'Daring Fireball',
                url: 'daringfireball.net/index.xml'
            },
            'wiredtech': {
                id: 'wiredtech',
                title: 'Wire: Tech Biz',
                url: 'www.wired.com/rss/techbiz.xml'
            },
            'smashing': {
                id: 'smashing',
                title: 'Smashing Magazine',
                url: 'www.smashingmagazine.com/wp-rss.php'
            }
        };

        //Simple method for stopping event propagation
        //Using this so we can detach it later
        var stopper = function(e) {
            e.stopPropagation();
        };

        //Get the order, placement and minned state of the modules and save them to a cookie
        var _setCookies = function() {
            var dds = Y.DD.DDM._drags;
            var list = {};
            //Walk all the drag elements
            Y.each(dds, function(v, k) {
                var par = v.get('node').get('parentNode');
                //Find all the lists with drag items in them
                if (par.test('ul.list')) {
                    if (!list[par.get('id')]) {
                        list[par.get('id')] = [];
                    }
                }
            });
            //Walk the list
            Y.each(list, function(v, k) {
                //Get all the li's in the list
                var lis = Y.all('#' + k + ' li.item');
                lis.each(function(v2, k2) {
                    //Get the drag instance for the list item
                    var dd = Y.DD.DDM.getDrag('#' + v2.get('id'));
                    //Get the mod node
                    var mod = dd.get('node').one('div.mod');
                    //Is it minimized
                    var min = (mod.hasClass('minned')) ? true : false;
                    //Setup the cookie data
                    list[k][list[k].length] = { id: dd.get('data').id, min: min };
                });
            });
            //JSON encode the cookie data
            var cookie = Y.JSON.stringify(list);
            //Set the sub-cookie
            Y.Cookie.setSub('yui', 'portal', cookie);
        };

        //Helper method for creating the feed DD on the left
        var _createFeedDD = function(node, data) {
            //Create the DD instance
            var dd = new Y.DD.Drag({
                node: node,
                data: data,
                bubbles: Y.Portal
            }).plug(Y.Plugin.DDProxy, {
                moveOnEnd: false,
                borderStyle: 'none'
            });
            //Setup some stopper events
            dd.on('drag:start', _handleStart);
            dd.on('drag:end', stopper);
            dd.on('drag:drophit', stopper);
        };

        //Handle the node:click event
        var _nodeClick = function(e) {
            //Is the target an href?
            if (e.target.test('a')) {
                var a = e.target, anim = null, div = a.get('parentNode').get('parentNode');
                //Did they click on the min button
                if (a.hasClass('min')) {
                    //Get some node references
                    var ul = div.one('ul'),
                        h2 = div.one('h2'),
                    h = h2.get('offsetHeight'),
                    hUL = ul.get('offsetHeight'),
                    inner = div.one('div.inner');

                    //Create an anim instance on this node.
                    anim = new Y.Anim({
                        node: inner
                    });
                    //Is it expanded?
                    if (!div.hasClass('minned')) {
                        //Set the vars for collapsing it
                        anim.setAttrs({
                            to: {
                                height: 0
                            },
                            duration: '.25',
                            easing: Y.Easing.easeOut,
                            iteration: 1
                        });
                        //On the end, toggle the minned class
                        //Then set the cookies for state
                        anim.on('end', function() {
                            div.toggleClass('minned');
                            _setCookies();
                        });
                    } else {
                        //Set the vars for expanding it
                        anim.setAttrs({
                            to: {
                                height: (hUL)
                            },
                            duration: '.25',
                            easing: Y.Easing.easeOut,
                            iteration: 1
                        });
                        //Toggle the minned class
                        //Then set the cookies for state
                        div.toggleClass('minned');
                        _setCookies();
                    }
                    //Run the animation
                    anim.run();

                }
                //Was close clicked?
                if (a.hasClass('close')) {
                    //Get some Node references..
                    var li = div.get('parentNode'),
                        id = li.get('id'),
                        dd = Y.DD.DDM.getDrag('#' + id),
                        data = dd.get('data'),
                        item = Y.one('#' + data.id);

                    //Destroy the DD instance.
                    dd.destroy();
                    //Setup the animation for making it disappear
                    anim = new Y.Anim({
                        node: div,
                        to: {
                            opacity: 0
                        },
                        duration: '.25',
                        easing: Y.Easing.easeOut
                    });
                    anim.on('end', function() {
                        //On end of the first anim, setup another to make it collapse
                        var anim = new Y.Anim({
                            node: div,
                            to: {
                                height: 0
                            },
                            duration: '.25',
                            easing: Y.Easing.easeOut
                        });
                        anim.on('end', function() {
                            //Remove it from the document
                            li.get('parentNode').removeChild(li);
                            item.removeClass('disabled');
                            //Setup a drag instance on the feed list
                            _createFeedDD(item, data);
                            _setCookies();

                        });
                        //Run the animation
                        anim.run();
                    });
                    //Run the animation
                    anim.run();
                }
                //Stop the click
                e.halt();
            }
        };

        //This creates the module, either from a drag event or from the cookie load
        var setupModDD = function(mod, data, dd) {
            var node = mod;
            //Listen for the click so we can react to the buttons
            node.one('h2').on('click', _nodeClick);

            //Remove the event's on the original drag instance
            dd.detachAll('drag:start');
            dd.detachAll('drag:end');
            dd.detachAll('drag:drophit');

            //It's a target
            dd.set('target', true);
            //Setup the handles
            dd.addHandle('h2').addInvalid('a');
            //Remove the mouse listeners on this node
            dd._unprep();
            //Update a new node
            dd.set('node', mod);
            //Reset the mouse handlers
            dd._prep();

            Y.YQL('select * from feed where url="http:/'+'/' + data.url + '"', (function(mod) {
                return function(r) {
                    if (r && r.query && r.query.results) {
                        var inner = mod.one('div.inner'),
                        html = '';

                        //Walk the list and create the news list
                        Y.each(r.query.results, function(items) {
                            Y.each(items, function(v, k) {
                                if (k < 5) {
                                    if (v.title && v.title.content) {
                                        v.title = v.title.content;
                                    }
                                    if (v.link && (Y.Lang.isArray(v.link))) {
                                        v.link = v.link[0];
                                    }
                                    if (v.link && (Y.Lang.isObject(v.link))) {
                                        v.link = v.link.href;
                                    }
                                    html += Y.Lang.sub('<li><a href="{link}" target="_blank">{title}</a>', v);
                                }
                            });
                        });
                        //Set the innerHTML of the module
                        inner.set('innerHTML', '<ul>' + html + '</ul>');
                        if (Y.DD.DDM.activeDrag) {
                            //If we are still dragging, update the proxy element too..
                            var proxy_inner = Y.DD.DDM.activeDrag.get('dragNode').one('div.inner');
                            proxy_inner.set('innerHTML', '<ul>' + html + '</ul>');

                        }
                    }
                }
            })(mod));

        };

        //Helper method to create the markup for the module..
        var createMod = function(feed) {
            var str = '<li class="item">' +
                        '<div class="mod">' +
                            '<h2><strong>' + feed.title + '</strong> <a title="minimize module" class="min" href="#">-</a>' +
                            '<a title="close module" class="close" href="#">X</a></h2>' +
                            '<div class="inner">' +
                            '    <div class="loading">Feed loading, please wait..</div>' +
                            '</div>' +
                        '</div>' +
                    '</li>';
            return Y.Node.create(str);
        };

        //Handle the start Drag event on the left side
        var _handleStart = function(e) {
            //Stop the event
            stopper(e);
            //Some private vars
            var drag = this,
                list3 = Y.one('#list1'),
                mod = createMod(drag.get('data'));

            //Add it to the first list
            list3.appendChild(mod);
            //Set the item on the left column disabled.
            drag.get('node').addClass('disabled');
            //Set the node on the instance
            drag.set('node', mod);
            //Add some styles to the proxy node.
            drag.get('dragNode').setStyles({
                opacity: '.5',
                borderStyle: 'none',
                width: '320px',
                height: '61px'
            });
            //Update the innerHTML of the proxy with the innerHTML of the module
            drag.get('dragNode').set('innerHTML', drag.get('node').get('innerHTML'));
            //set the inner module to hidden
            drag.get('node').one('div.mod').setStyle('visibility', 'hidden');
            //add a class for styling
            drag.get('node').addClass('moving');
            //Setup the DD instance
            setupModDD(mod, drag.get('data'), drag);

            //Remove the listener
            this.detach('drag:start', _handleStart);
        };

        //Walk through the feeds list and create the list on the left
        var feedList = Y.one('#feeds ul');
        Y.each(feeds, function(v, k) {
            var li = Y.Node.create('<li id="' + k + '">' + v.title + '</li>');
            feedList.appendChild(li);
            //Create the DD instance for this item
            _createFeedDD(li, v);
        });

        //This does the calculations for when and where to move a module
        var _moveMod = function(drag, drop) {
            if (drag.get('node').hasClass('item')) {
                var dragNode = drag.get('node'),
                    dropNode = drop.get('node'),
                    append = false,
                    padding = 30,
                    xy = drag.mouseXY,
                    region = drop.region,
                    middle1 = region.top + ((region.bottom - region.top) / 2),
                    middle2 = region.left + ((region.right - region.left) / 2),
                    dir = false,
                    dir1 = false,
                    dir2 = false;

                    //We could do something a little more fancy here, but we won't ;)
                    if ((xy[1] < (region.top + padding))) {
                        dir1 = 'top';
                    }
                    if ((region.bottom - padding) < xy[1]) {
                        dir1 = 'bottom';
                    }
                    if ((region.right - padding) < xy[0]) {
                        dir2 = 'right';
                    }
                    if ((xy[0] < (region.left + padding))) {
                        dir2 = 'left';
                    }
                    dir = dir2;
                    if (dir2 === false) {
                        dir = dir1;
                    }
                    switch (dir) {
                        case 'top':
                            var next = dropNode.get('nextSibling');
                            if (next) {
                                dropNode = next;
                            } else {
                                append = true;
                            }
                            break;
                        case 'bottom':
                            break;
                        case 'right':
                        case 'left':
                            break;
                    }

                if ((dropNode !== null) && dir) {
                    if (dropNode && dropNode.get('parentNode')) {
                        if (!append) {
                            dropNode.get('parentNode').insertBefore(dragNode, dropNode);
                        } else {
                            dropNode.get('parentNode').appendChild(dragNode);
                        }
                    }
                }
                //Resync all the targets because something moved..
                Y.Lang.later(50, Y, function() {
                    Y.DD.DDM.syncActiveShims(true);
                });
            }
        };

        /*
        Handle the drop:enter event
        Now when we get a drop enter event, we check to see if the target is an LI, then we know it's out module.
        Here is where we move the module around in the DOM.
        */
        Y.Portal.on('drop:enter', function(e) {
            if (!e.drag || !e.drop || (e.drop !== e.target)) {
                return false;
            }
            if (e.drop.get('node').get('tagName').toLowerCase() === 'li') {
                if (e.drop.get('node').hasClass('item')) {
                    _moveMod(e.drag, e.drop);
                }
            }
        });

        //Handle the drag:drag event
        //On drag we need to know if they are moved up or down so we can place the module in the proper DOM location.
        Y.Portal.on('drag:drag', function(e) {
            var y = e.target.mouseXY[1];
            if (y < lastY) {
                goingUp = true;
            } else {
                goingUp = false;
            }
            lastY = y;
        });

        /*
        Handle the drop:hit event
        Now that we have a drop on the target, we check to see if the drop was not on a LI.
        This means they dropped on the empty space of the UL.
        */
        Y.Portal.on('drag:drophit', function(e) {
            var drop = e.drop.get('node'),
                drag = e.drag.get('node');

            if (drop.get('tagName').toLowerCase() !== 'li') {
                if (!drop.contains(drag)) {
                    drop.appendChild(drag);
                }
            }
        });

        //Handle the drag:start event
        //Use some CSS here to make our dragging better looking.
        Y.Portal.on('drag:start', function(e) {
            var drag = e.target;
            if (drag.target) {
                drag.target.set('locked', true);
            }
            drag.get('dragNode').set('innerHTML', drag.get('node').get('innerHTML'));
            drag.get('dragNode').setStyle('opacity','.5');
            drag.get('node').one('div.mod').setStyle('visibility', 'hidden');
            drag.get('node').addClass('moving');
        });

        //Handle the drag:end event
        //Replace some of the styles we changed on start drag.
        Y.Portal.on('drag:end', function(e) {
            var drag = e.target;
            if (drag.target) {
                drag.target.set('locked', false);
            }
            drag.get('node').setStyle('visibility', '');
            drag.get('node').one('div.mod').setStyle('visibility', '');
            drag.get('node').removeClass('moving');
            drag.get('dragNode').set('innerHTML', '');
            _setCookies();
        });

        //Handle going over a UL, for empty lists
        Y.Portal.on('drop:over', function(e) {
            var drop = e.drop.get('node'),
                drag = e.drag.get('node');

            if (drop.get('tagName').toLowerCase() !== 'li') {
                if (!drop.contains(drag)) {
                    drop.appendChild(drag);
                    Y.Lang.later(50, Y, function() {
                        Y.DD.DDM.syncActiveShims(true);
                    });
                }
            }
        });

        //Create simple targets for the main lists..
        var uls = Y.all('#play ul.list');
        uls.each(function(v, k) {
            var tar = new Y.DD.Drop({
                node: v,
                padding: '20 0',
                bubbles: Y.Portal
            });
        });

        //Get the cookie data
        var cookie = Y.Cookie.getSub('yui', 'portal');
        if (cookie) {
            //JSON parse the stored data
            var obj = Y.JSON.parse(cookie);

            //Walk the data
            Y.each(obj, function(v, k) {
                //Get the node for the list
                var list = Y.one('#' + k);
                //Walk the items in this list
                Y.each(v, function(v2, k2) {
                    //Get the drag for it
                    var drag = Y.DD.DDM.getDrag('#' + v2.id);
                    //Create the module
                    var mod = createMod(drag.get('data'));
                    if (v2.min) {
                        //If it's minned add some CSS
                        mod.one('div.mod').addClass('minned');
                        mod.one('div.inner').setStyle('height', '0px');
                    }
                    //Add it to the list
                    list.appendChild(mod);
                    //Set the drag listeners
                    drag.get('node').addClass('disabled');
                    drag.set('node', mod);
                    drag.set('dragNode', Y.DD.DDM._proxy);
                    drag.detachAll('drag:start');
                    drag.detachAll('drag:end');
                    drag.detachAll('drag:drophit');
                    drag._unprep();
                    //Setup the new Drag listeners
                    setupModDD(mod, drag.get('data'), drag);
                });
            });
        }
    });