YUI 3.x Home -

Site Search (YDN & YUIBlog):

# YUI Library Examples: Drag & Drop: Portal Style Example

## Drag & Drop: Portal Style Example

Portal style example using Drag & Drop Event Bubbling and Animation.

View example in new window.

### Full HTML Source

    <body><div id="doc3" class="yui-t2">    <div id="hd"><h1>YUI 3.0 Portal Example</h1></div>    <div id="bd">        <div id="yui-main">	        <div class="yui-b">                <div class="yui-g">                    <div id="play">                        <ul class="list" id="list1"></ul>                        <ul class="list" id="list2"></ul>                        <ul class="list" id="list3"></ul>                    </div>	            </div>            </div>	    </div>	    <div class="yui-b">            <div id="feeds">                <p>Drag a news feed into one of the columns.</p>                <ul></ul>            </div>        </div>    </div>    <div id="ft">Built with <a href="http://developer.yahoo.com/yui/3/">YUI 3.0</a>: <a href="http://developer.yahoo.com/yui/3/yui">Core</a>, Loader, Get, <a href="http://developer.yahoo.com/yui/3/cookie">Cookie</a>, <a href="http://developer.yahoo.com/yui/3/animation">Animation</a>, <a href="http://developer.yahoo.com/yui/3/io">IO</a>, <a href="http://developer.yahoo.com/yui/3/json">JSON</a> &amp; <a href="http://developer.yahoo.com/yui/3/dd">DD</a>.</div></div><script src="assets/portal.js"></script></body>

    <body>
    <div id="doc3" class="yui-t2">
        <div id="hd"><h1>YUI 3.0 Portal Example</h1></div>
        <div id="bd">
            <div id="yui-main">
    	        <div class="yui-b">
                    <div class="yui-g">
                        <div id="play">
                            <ul class="list" id="list1"></ul>
                            <ul class="list" id="list2"></ul>
                            <ul class="list" id="list3"></ul>
                        </div>
    	            </div>
                </div>
    	    </div>
    	    <div class="yui-b">
                <div id="feeds">
                    <p>Drag a news feed into one of the columns.</p>
                    <ul></ul>
                </div>
            </div>
        </div>
        <div id="ft">Built with <a href="http://developer.yahoo.com/yui/3/">YUI 3.0</a>: <a href="http://developer.yahoo.com/yui/3/yui">Core</a>, Loader, Get, <a href="http://developer.yahoo.com/yui/3/cookie">Cookie</a>, <a href="http://developer.yahoo.com/yui/3/animation">Animation</a>, <a href="http://developer.yahoo.com/yui/3/io">IO</a>, <a href="http://developer.yahoo.com/yui/3/json">JSON</a> &amp; <a href="http://developer.yahoo.com/yui/3/dd">DD</a>.</div>
    </div>
    <script src="assets/portal.js"></script>
    </body>

<body> <div id="doc3" class="yui-t2"> <div id="hd"><h1>YUI 3.0 Portal Example</h1></div> <div id="bd"> <div id="yui-main"> <div class="yui-b"> <div class="yui-g"> <div id="play"> <ul class="list" id="list1"></ul> <ul class="list" id="list2"></ul> <ul class="list" id="list3"></ul> </div> </div> </div> </div> <div class="yui-b"> <div id="feeds"> <p>Drag a news feed into one of the columns.</p> <ul></ul> </div> </div> </div> <div id="ft">Built with <a href="http://developer.yahoo.com/yui/3/">YUI 3.0</a>: <a href="http://developer.yahoo.com/yui/3/yui">Core</a>, Loader, Get, <a href="http://developer.yahoo.com/yui/3/cookie">Cookie</a>, <a href="http://developer.yahoo.com/yui/3/animation">Animation</a>, <a href="http://developer.yahoo.com/yui/3/io">IO</a>, <a href="http://developer.yahoo.com/yui/3/json">JSON</a> & <a href="http://developer.yahoo.com/yui/3/dd">DD</a>.</div> </div> <script src="assets/portal.js"></script> </body>

### Full Javascript Source

    //Use loader to grab the modules neededYUI(yuiConfig).use('dd', 'anim', 'io', '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'        },        'techcrunch': {            id: 'techcrunch',            title: 'TechCrunch',            url: 'feedproxy.google.com/Techcrunch'        },        'smashing': {            id: 'smashing',            title: 'Smashing Magazine',            url: 'www.smashingmagazine.com/wp-rss.php'        }    };     //Setup the config for IO to use flash	Y.io.transport({		id: 'flash',		yid: Y.id,		src: buildDir + 'io/io.swf?stamp=' + (new Date()).getTime()    });     //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').query('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.query('ul'),                    h2 = div.query('h2'),                h = h2.get('offsetHeight'),                hUL = ul.get('offsetHeight'),                inner = div.query('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.Node.get('#' + 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.query('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();         //The Yahoo! Pipes URL        var url = 'http:/'+'/pipes.yahooapis.com/pipes/pipe.run?_id=6b7b2c6a32f5a12e7259c36967052387&_render=json&url=http:/'+'/' + data.url;        //Start the XDR request        var id = Y.io(url, {            method: 'GET',            xdr: { 			    use:'flash'		    },            //XDR Listeners		    on: { 			    success: function(id, data) {                    //On success get the feed data                    var d = feeds[trans[id]],                    //Node reference                    inner = d.mod.query('div.inner'),                    //Parse the JSON data                    oRSS = Y.JSON.parse(data.responseText),                    html = '';                     //Did we get data?		            if (oRSS && oRSS.count) {                        //Walk the list and create the news list			            Y.each(oRSS.value.items, function(v, k) {                            if (k < 5) {                                html += '<li><a href="' + v.link + '" target="_blank">' + v.title + '</a>';                            }                        });		            }                    //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').query('div.inner');                        proxy_inner.set('innerHTML', '<ul>' + html + '</ul>');                     }                },			    failure: function(id, data) {                    //Something failed..                    alert('Feed failed to load..' + id + ' :: ' + data);                }		    }        });        //Keep track of the transaction        feeds[data.id].trans = id;        feeds[data.id].mod = mod;        trans[id.id] = data.id;    };      //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="#"></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.Node.get('#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').query('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.Node.get('#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').query('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').query('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        });    });      Y.on('io:xdrReady', function() {        //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.Node.get('#' + 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.query('div.mod').addClass('minned');                        mod.query('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);                });            });        }    }); });

    //Use loader to grab the modules needed
    YUI(yuiConfig).use('dd', 'anim', 'io', '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'
            },
            'techcrunch': {
                id: 'techcrunch',
                title: 'TechCrunch',
                url: 'feedproxy.google.com/Techcrunch'
            },
            'smashing': {
                id: 'smashing',
                title: 'Smashing Magazine',
                url: 'www.smashingmagazine.com/wp-rss.php'
            }
        };

        //Setup the config for IO to use flash
    	Y.io.transport({
    		id: 'flash',
    		yid: Y.id,
    		src: buildDir + 'io/io.swf?stamp=' + (new Date()).getTime()
        });

        //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').query('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.query('ul'),
                        h2 = div.query('h2'),
                    h = h2.get('offsetHeight'),
                    hUL = ul.get('offsetHeight'),
                    inner = div.query('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.Node.get('#' + 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.query('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();

            //The Yahoo! Pipes URL
            var url = 'http:/'+'/pipes.yahooapis.com/pipes/pipe.run?_id=6b7b2c6a32f5a12e7259c36967052387&_render=json&url=http:/'+'/' + data.url;
            //Start the XDR request
            var id = Y.io(url, {
                method: 'GET',
                xdr: {
    			    use:'flash'
    		    },
                //XDR Listeners
    		    on: {
    			    success: function(id, data) {
                        //On success get the feed data
                        var d = feeds[trans[id]],
                        //Node reference
                        inner = d.mod.query('div.inner'),
                        //Parse the JSON data
                        oRSS = Y.JSON.parse(data.responseText),
                        html = '';

                        //Did we get data?
    		            if (oRSS && oRSS.count) {
                            //Walk the list and create the news list
    			            Y.each(oRSS.value.items, function(v, k) {
                                if (k < 5) {
                                    html += '<li><a href="' + v.link + '" target="_blank">' + v.title + '</a>';
                                }
                            });
    		            }
                        //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').query('div.inner');
                            proxy_inner.set('innerHTML', '<ul>' + html + '</ul>');

                        }
                    },
    			    failure: function(id, data) {
                        //Something failed..
                        alert('Feed failed to load..' + id + ' :: ' + data);
                    }
    		    }
            });
            //Keep track of the transaction
            feeds[data.id].trans = id;
            feeds[data.id].mod = mod;
            trans[id.id] = data.id;
        };

        //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="#"></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.Node.get('#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').query('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.Node.get('#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').query('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').query('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
            });
        });

        Y.on('io:xdrReady', function() {
            //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.Node.get('#' + 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.query('div.mod').addClass('minned');
                            mod.query('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);
                    });
                });
            }
        });

    });

//Use loader to grab the modules needed YUI(yuiConfig).use('dd', 'anim', 'io', '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' }, 'techcrunch': { id: 'techcrunch', title: 'TechCrunch', url: 'feedproxy.google.com/Techcrunch' }, 'smashing': { id: 'smashing', title: 'Smashing Magazine', url: 'www.smashingmagazine.com/wp-rss.php' } }; //Setup the config for IO to use flash Y.io.transport({ id: 'flash', yid: Y.id, src: buildDir + 'io/io.swf?stamp=' + (new Date()).getTime() }); //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').query('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.query('ul'), h2 = div.query('h2'), h = h2.get('offsetHeight'), hUL = ul.get('offsetHeight'), inner = div.query('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.Node.get('#' + 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.query('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(); //The Yahoo! Pipes URL var url = 'http:/'+'/pipes.yahooapis.com/pipes/pipe.run?_id=6b7b2c6a32f5a12e7259c36967052387&_render=json&url=http:/'+'/' + data.url; //Start the XDR request var id = Y.io(url, { method: 'GET', xdr: { use:'flash' }, //XDR Listeners on: { success: function(id, data) { //On success get the feed data var d = feeds[trans[id]], //Node reference inner = d.mod.query('div.inner'), //Parse the JSON data oRSS = Y.JSON.parse(data.responseText), html = ''; //Did we get data? if (oRSS && oRSS.count) { //Walk the list and create the news list Y.each(oRSS.value.items, function(v, k) { if (k < 5) { html += '<li><a href="' + v.link + '" target="_blank">' + v.title + '</a>'; } }); } //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').query('div.inner'); proxy_inner.set('innerHTML', '<ul>' + html + '</ul>'); } }, failure: function(id, data) { //Something failed.. alert('Feed failed to load..' + id + ' :: ' + data); } } }); //Keep track of the transaction feeds[data.id].trans = id; feeds[data.id].mod = mod; trans[id.id] = data.id; }; //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="#"></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.Node.get('#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').query('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.Node.get('#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').query('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').query('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 }); }); Y.on('io:xdrReady', function() { //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.Node.get('#' + 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.query('div.mod').addClass('minned'); mod.query('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); }); }); } }); });

Copyright © 2009 Yahoo! Inc. All rights reserved.

Privacy Policy - Terms of Service - Copyright Policy - Job Openings