/*    usemedia.com . joes koppers . 12.2010
    thnx for reading this code */

/*    sziget dynamic grid
*/

function Grid(itemsOnRow)
{
    //settings
    this.margin = 16;        //spacing (px) between items, should be an even number
    this.snap = 15;            //distance (px) to snap to default grid
    this.cornerw = 16;        //width (px) of draggable corner
    this.cornerh = 16;        //height (px) of draggable corner
    this.labelw = .75;        //label width, percentage of default item width
    this.itemfade = 300;    //item fadeout time (ms)

    //define grid layout
    this.div = $('#grid'); 
    var items = $('#grid > .item');
    
	this.maximized = undefined;
    this.cols = itemsOnRow || 4;
    this.rows = Math.ceil(items.length/this.cols);
    this.col = new Object();
    this.row = new Object();
    
    this.resize(); //initial position
    this.w = this.div.width();
    this.h = this.div.height();
    this.itemw = (this.w - ((this.cols+1)*this.margin))/this.cols;
    this.itemh = (this.h - ((this.rows+1)*this.margin))/this.rows;
    
    //add items
    var obj = this;
    this.items = [];
    items.each(function(i) {
        obj.items.push(new Item(obj,i,$(this)))
    });
    
    //event handling
    $(window).resize(function() { obj.resize() });
    this.div.bind($.browser.msie? 'selectstart':'mousedown', function(e) {
        //prevents text select while dragging
        return false;
    })

    //->development, double click anywhere to reset
    $(document).dblclick(function(e) { obj.rset() });
}

Grid.prototype.resize = function()
{
    /*    get grid div page offset
    */
    var offset = this.div.offset();
    this.x = Math.round(offset.left);
    this.y = Math.round(offset.top);
}

Grid.prototype.click = function(i){  //dbl
	if (this.maximized != i) {
		this.maximized = i;
		this.items[i].maximize();		
		return false;   		
   	}
}

Grid.prototype.maximize = function(i){
	this.items[i].maximize();
	return false;
}

Grid.prototype.rset = function()
{
    /*    reset entire Grid
    */      
	this.maximized = undefined;
	this.active = undefined;
	$("#grid .container").removeClass("active");                                           
	this.dimm();

    for (var i in this.items) {
		this.items[i].defaults();
	}
	
}

Grid.prototype.release = function()
{
    /*    stop item resizing
    */
    $(document).unbind('.resize');
}

Grid.prototype.dimm = function(index)
{
    /*    dimm all grid items, except index
        leave index undefined to reset     */
      
    var t = $.support.opacity? this.itemfade:0;
    if (index==undefined)
    {
        this.div.find('.dimm').fadeOut(t);
		this.current = null;
    }
    else
    {
		this.current = index;
        this.div.find('.container').removeClass('active');
        
        this.div.find('.dimm:not(:eq('+index+'))').fadeIn(t);
        this.div.find('.dimm:eq('+index+')').fadeOut(t).prevAll('.container').addClass('active');
    }
}

Grid.prototype.snapTo = function(type,val)
{
    /*    snap to default grid
    */
    var m = this.margin;
    var s = this.snap;
    
    switch(type) 
    {
        case 'right':
        case 'left':
            for (var i=0; i<this.cols; i++)
            {
                var x = i*(this.itemw+m);
                if (type=='left') x+= m;
                if (val>=x-s && val<=x+s) return x;
            }
            break;
            
        case 'bottom':
        case 'top':
            for (var i=0; i<this.rows; i++)
            {
                var y = i*(this.itemh+m);
                if (type=='top') y+= m;
                if (val>=y-s && val<=y+s) return y;
            }
            break;
    }

    return val;
}

Grid.prototype.update = function(type,n,p,s)
{
    /*    update all items in a row or column
    */
    var t = this[type][n];
    for (var i=0; i<t.length; i++)
    {
        var item = this.items[t[i]];
            item[type=='col'? 'x':'y'] = p;
            item[type=='col'? 'w':'h'] = s;
            item.apply();
    }
}

Grid.prototype.push = function(type,dir,n,v)
{
    /*    update adjacent row or column (recursive)
    */
    if (n<0 || n>=this[type+'s']) return;

    var xy = type=='col'? 'x':'y';
    var wh = type=='col'? 'w':'h';

    var item = this.items[this[type][n][0]];
    var s = dir=='next'? item[wh] - (v-item[xy]):v - this.margin - item[xy];
    
    if (s<item['min'+wh]) s = item['min'+wh];
    if (s>item['max'+wh]) s = item['max'+wh];
    
    if (dir=='next')
    {
        this.update(type,n,v,s);
        
        if (s==item['min'+wh] || s==item['max'+wh])
        {
            //continue with next row or col
            this.push(type,dir,n+1,item[xy] + item[wh] + this.margin);
        }
    }
    else
    {
        if (s==item['min'+wh] || s==item['max'+wh])
        {
            var v = v - this.margin - s;
            this.update(type,n,v,s);

            //continue with next row or col
            this.push(type,dir,n-1,v);
        }
        else
        {
            this.update(type,n,item[xy],s);
        }
    }    
}


/*    grid item
*/

function Item(grid,index,elm)
{
    this.grid = grid;
    this.index = index;
    this.div = elm;

    //get row and col
    this.row = Math.floor(index/grid.cols);
    this.col = index - (this.row*grid.cols);
    
    if (!grid.col[this.col]) grid.col[this.col] = [];
    if (!grid.row[this.row]) grid.row[this.row] = [];
    
    grid.col[this.col].push(index);
    grid.row[this.row].push(index);
    
    //containment
    var m = grid.margin;
    this.minw = grid.itemw/2 - m/2; 
    this.maxw = grid.itemw*2 + m;
    if(grid.cols == 1)
       this.maxw = grid.itemw;
    this.minh = 40;
    this.maxh = grid.h - ((grid.rows-1)*this.minh) - ((grid.rows+1)*m);
    
    this.minx = this.col*(this.minw+m) + m;
    this.maxx = grid.w - ((grid.cols-this.col-1)*(this.minw+m)) - m;
    this.miny = this.row*(this.minh+m) + m;
    this.maxy = grid.h - ((grid.rows-this.row-1)*(this.minh+m)) - m;

    //reverse limits
    this.maxRx = this.col*(this.maxw+m) + m;
    this.minRx = grid.w - ((grid.cols-this.col-1) * (this.maxw+m) + m);
    this.minRy = grid.h - ((grid.rows-this.row-1) * (this.maxh+m) + m);
    
    //contents, single and double column width
    var p = parseInt(this.div.find('.content').css('padding-left'));
    var sw = grid.itemw-p;
    var dw = grid.itemw*2 + m - p - 16;
	if(grid.cols == 1)
		dw = grid.itemw - 30;
    var s = this.div.find('.content.single-column').css('width',sw);
    var d = this.div.find('.content.double-column').css('width',dw);
    this.div.children()
        .wrapAll('<div class="container" style="min-width:'+((s.length? sw:dw)+30)+'px"/>')
        .parent()
        .bind($.browser.msie? 'selectstart':'mousedown', function(e) {
            //allow text select
            e.stopImmediatePropagation();
        });
	// this.div.get(0).i = index;

	$(this.div).click(function(e) { return grid.click(obj.index); });
    this.title = this.div.find('.title');
    
    //set default position, dimension
    this.defaults();

    /*    add resizing, set label layout
    */
    var obj = this;
    this.div
        .mousedown(function(e) {
            obj.resize(e);
            //prevent drag-select    
            e.preventDefault();
        })
        .find('.label')
        .each(function(i) {
            $(this).css({
                top:grid.itemh - $(this).outerHeight(),
                width:grid.labelw * grid.itemw
            })
        });

    //add resize corners (nw, ne, se or sw)
    var corners = [];
         corners.push('se'); //only se (right-bottom) corners are used in final design
//      if (this.col==grid.cols-1 && this.row<grid.rows-1) corners.push('sw');
//      if (this.row==grid.rows-1 && this.col<grid.cols-1) corners.push('ne');
    
    this.corners = new Object();
    for (var i in corners)
    {
        $('<div/>').addClass('resize '+corners[i]).appendTo(this.div);
        this.corners[corners[i]] = true;
    }

    //add dimm
    this.fade = $('<div/>').addClass('dimm').appendTo(this.div).click(function() { 
        obj.grid.dimm(obj.index);
        obj.grid.active = obj.index;
    });
}

Item.prototype.defaults = function()
{
    /*    default position and size (can be used for reset)
    */    
    var h = this.grid.itemh;
    var w = this.grid.itemw, m = this.grid.margin;
    var c = this.col, r = this.row;
	$(this.div).find(".container").scrollTop(0);
    this.x = m + (c * w) + (c * m);
    this.y = m + (r * h) + (r * m);
    this.w = w;
    this.h = h;
    
    this.apply();
}

Item.prototype.maximize = function()
{
    // var type;
    //        var ox = e.clientX - this.x - this.grid.x + $(document).scrollLeft();
    //        var oy = e.clientY - this.y - this.grid.y + $(document).scrollTop();
    //        var w = this.grid.cornerw;
    //        var h = this.grid.cornerh;
    //        
    //        if (this.corners.nw && ox<w && oy<h)
    //        {
    //            //left top
    //            //type = 'nw'; //->not used
    //        }
    //        else if (this.corners.ne && ox>this.w-w && oy<h)
    //        {
    //            //right top
    //             type = 'ne';
    //             this.offset = { x:ox - this.w, y:oy };
    //        }
    //        else if (this.corners.se && ox>this.w-w && oy>this.h-h)
    //        {
    //            //right bottom
    //            type = 'se';
    //            this.offset = { x:ox - this.w, y:oy - this.h };
    //        }
    //        else if (this.corners.sw && ox<w && oy>this.h-h)
    //        {
    //            //left bottom
    //            type = 'sw';
    //            this.offset = { x:ox, y:oy - this.h };
    //        }
    //        else
    //        {
    //            return;
    //        } 

    
     
     //do not resize if values haven't changed  
//	 obj.div.find(".container").addClass("active");
     obj = this;
	 obj.offset = { x:obj.x, y:obj.y};
	
	 var type = 'se';
	
     var x = obj.x + this.maxw;
     var y = obj.y + this.maxh;

    
     obj.resizing(type,x,y);
}

Item.prototype.apply = function()
{
    /*    update display with current values
    */
    this.div.css({
        left:this.x,
        top:this.y,
        width:this.w,
        height:this.h
    });                                               
    
    //wrap title
    this.title.css('max-width',this.w);
}

Item.prototype.limit = function(type,v)
{
    /*    keep width or height within limits
    */
    if (v<this['min'+type]) v = this['min'+type];
    if (v>this['max'+type]) v = this['max'+type];
    return v;
}

Item.prototype.resize = function(e)
{
    /*    start resize, determine which corner is dragged
    */
    var type;
    var ox = e.clientX - this.x - this.grid.x + $(document).scrollLeft();
    var oy = e.clientY - this.y - this.grid.y + $(document).scrollTop();
    var w = this.grid.cornerw;
    var h = this.grid.cornerh;
    
    if (this.corners.nw && ox<w && oy<h)
    {
        //left top
        //type = 'nw'; //->not used
    }
    else if (this.corners.ne && ox>this.w-w && oy<h)
    {
        //right top
         type = 'ne';
         this.offset = { x:ox - this.w, y:oy };
    }
    else if (this.corners.se && ox>this.w-w && oy>this.h-h)
    {
        //right bottom
        type = 'se';
        this.offset = { x:ox - this.w, y:oy - this.h };
    }
    else if (this.corners.sw && ox<w && oy>this.h-h)
    {
        //left bottom
        type = 'sw';
        this.offset = { x:ox, y:oy - this.h };
    }
    else
    {
        return;
    }
    
    //capture document mouse events
    var obj = this;
    $(document)
        .bind('mousemove.resize',function(e) {

            e.stopImmediatePropagation();
            e.preventDefault();
        
            var x = e.clientX - obj.grid.x - obj.offset.x + $(this).scrollLeft();
            var y = e.clientY - obj.grid.y - obj.offset.y + $(this).scrollTop();
            
            //do not resize if values haven't changed
            if (x==obj.px && y==obj.py) return;

            obj.px = x;
            obj.py = y;
            obj.resizing(type,x,y);
            
        })
        .bind('mouseup.resize',function() { 
            obj.grid.release();
        });

    //IE, trigger release when mouse leaves window (prevent buggy mouseup behavior)        
    if ($.browser.msie) $(document).bind('mouseleave.resize',function(e) { obj.grid.release() })
}

Item.prototype.resizing = function(type,x,y)
{
    /*    apply resize
    */
    
    //highlight, fade other items
    if (this.grid.active!=this.index)
    {
        this.grid.active = this.index;
        this.grid.dimm(this.index);
    }

    var m = this.grid.margin;    

    /*    drag RIGHT/BOTTOM, update width/height
    */
    var types = ['right','bottom'];
    for (var i=0; i<types.length; i++)
    {
        var t = types[i];
        
        if ((t=='right' && type.indexOf('e')==-1) || (t=='bottom' && type.indexOf('s')==-1)) continue;
    
        var xy = t=='right'? 'x':'y';
        var wh = t=='right'? 'w':'h';
        var r = false;
        var p = t=='right'? x:y;
        var t = t=='right'? 'col':'row';
        
        p = p<this['max'+xy]? this.grid.snapTo(types[i],p):p;
        s = this.limit(wh,p - this[xy]);

        //restrictions, reverse dragging
        if (p<this['minR'+xy]) continue;
        if (p>this['max'+xy] && this[xy]>this['max'+xy]-this['max'+wh])
        {
            //reverse, move item left or up, update drag offset
            r = this['max'+xy] - s;
            this.offset[xy] += (this[xy] - r);
        }

        this.grid.update(t,this[t],r || this[xy],s);
        
        //push next col or row
        var p = this[xy] + s + m;
        this.grid.push(t,'next',this[t]+1,p);

        //reverse dragging, push prev col or row
        if (r) this.grid.push(t,'prev',this[t]-1,r);
    }

    /*    DRAG LEFT/UP, update left+width/top+height (without reverse dragging)
    */
    var types = ['left','top'];
    for (var i=0; i<types.length; i++)
    {
        var t = types[i];
        
        if (t=='left' && type.indexOf('w')==-1 || (t=='top' && type.indexOf('n')==-1)) continue;
        
        var xy = t=='left'? 'x':'y';
        var wh = t=='left'? 'w':'h';
        var ds;
        var p = t=='left'? x:y;
        var t = t=='left'? 'col':'row';

        p = this.grid.snapTo(types[i],p);
        s = this.limit(wh,ds = this[wh]+(this[xy]-p));

        //restrictions
        if (p<this['min'+xy] || p>this['maxR'+xy] || (ds<s && s==this['min'+wh]) || (ds>s && s==this['max'+wh])) return; //no reverse dragging
    
        this.grid.update(t,this[t],p,s);
            
        //push prev col or row
        this.grid.push(t,'prev',this[t]-1,p);
    }
}
