view rhodecode/public/js/rhodecode.js @ 2787:423a14349ef3 beta

Don't use .innerHTML for tr field, I'm looking at you IE
author Marcin Kuzminski <marcin@python-works.com>
date Mon, 03 Sep 2012 17:33:21 +0200
parents c61c2ccea2b4
children d3e5c259fe71
line wrap: on
line source

/**
RhodeCode JS Files
**/

if (typeof console == "undefined" || typeof console.log == "undefined"){
	console = { log: function() {} }
}


var str_repeat = function(i, m) {
	for (var o = []; m > 0; o[--m] = i);
	return o.join('');
};

/**
 * INJECT .format function into String
 * Usage: "My name is {0} {1}".format("Johny","Bravo")
 * Return "My name is Johny Bravo"
 * Inspired by https://gist.github.com/1049426
 */
String.prototype.format = function() {
	  
	  function format() {
	    var str = this;
	    var len = arguments.length+1;
	    var safe = undefined;
	    var arg = undefined;
	    
	    // For each {0} {1} {n...} replace with the argument in that position.  If 
	    // the argument is an object or an array it will be stringified to JSON.
	    for (var i=0; i < len; arg = arguments[i++]) {
	      safe = typeof arg === 'object' ? JSON.stringify(arg) : arg;
	      str = str.replace(RegExp('\\{'+(i-1)+'\\}', 'g'), safe);
	    }
	    return str;
	  }

	  // Save a reference of what may already exist under the property native.  
	  // Allows for doing something like: if("".format.native) { /* use native */ }
	  format.native = String.prototype.format;

	  // Replace the prototype property
	  return format;

}();

String.prototype.strip = function(char) {
	if(char === undefined){
	    char = '\\s';
	}
	return this.replace(new RegExp('^'+char+'+|'+char+'+$','g'), '');
}
String.prototype.lstrip = function(char) {
	if(char === undefined){
	    char = '\\s';
	}
	return this.replace(new RegExp('^'+char+'+'),'');
}
String.prototype.rstrip = function(char) {
	if(char === undefined){
	    char = '\\s';
	}
	return this.replace(new RegExp(''+char+'+$'),'');
}


if(!Array.prototype.indexOf) {
    Array.prototype.indexOf = function(needle) {
        for(var i = 0; i < this.length; i++) {
            if(this[i] === needle) {
                return i;
            }
        }
        return -1;
    };
}

// IE(CRAP) doesn't support previousElementSibling
var prevElementSibling = function( el ) {
    if( el.previousElementSibling ) {
        return el.previousElementSibling;
    } else {
        while( el = el.previousSibling ) {
            if( el.nodeType === 1 ) return el;
        }
    }
}




/**
 * SmartColorGenerator
 *
 *usage::
 *	var CG = new ColorGenerator();
 *  var col = CG.getColor(key); //returns array of RGB
 *  'rgb({0})'.format(col.join(',')
 * 
 * @returns {ColorGenerator}
 */
var ColorGenerator = function(){
	this.GOLDEN_RATIO = 0.618033988749895;
	this.CURRENT_RATIO = 0.22717784590367374 // this can be random
	this.HSV_1 = 0.75;//saturation
	this.HSV_2 = 0.95;
	this.color;
	this.cacheColorMap = {};
};

ColorGenerator.prototype = {
    getColor:function(key){
    	if(this.cacheColorMap[key] !== undefined){
    		return this.cacheColorMap[key];
    	}
    	else{
    		this.cacheColorMap[key] = this.generateColor();
    		return this.cacheColorMap[key];
    	}
    },
    _hsvToRgb:function(h,s,v){
        if (s == 0.0)
            return [v, v, v];
        i = parseInt(h * 6.0)
        f = (h * 6.0) - i
        p = v * (1.0 - s)
        q = v * (1.0 - s * f)
        t = v * (1.0 - s * (1.0 - f))
        i = i % 6
        if (i == 0) 
            return [v, t, p]
        if (i == 1) 
            return [q, v, p]
        if (i == 2) 
            return [p, v, t]
        if (i == 3)
            return [p, q, v]
        if (i == 4) 
            return [t, p, v]
        if (i == 5)
            return [v, p, q]            	
    },
    generateColor:function(){
        this.CURRENT_RATIO = this.CURRENT_RATIO+this.GOLDEN_RATIO;
        this.CURRENT_RATIO = this.CURRENT_RATIO %= 1;
        HSV_tuple = [this.CURRENT_RATIO, this.HSV_1, this.HSV_2]
        RGB_tuple = this._hsvToRgb(HSV_tuple[0],HSV_tuple[1],HSV_tuple[2]);
        function toRgb(v){
        	return ""+parseInt(v*256)
        }
        return [toRgb(RGB_tuple[0]),toRgb(RGB_tuple[1]),toRgb(RGB_tuple[2])];
        
    }
}





/**
 * GLOBAL YUI Shortcuts
 */
var YUC = YAHOO.util.Connect;
var YUD = YAHOO.util.Dom;
var YUE = YAHOO.util.Event;
var YUQ = YAHOO.util.Selector.query;

// defines if push state is enabled for this browser ?
var push_state_enabled = Boolean(
		window.history && window.history.pushState && window.history.replaceState
		&& !(   /* disable for versions of iOS before version 4.3 (8F190) */
				(/ Mobile\/([1-7][a-z]|(8([abcde]|f(1[0-8]))))/i).test(navigator.userAgent)
				/* disable for the mercury iOS browser, or at least older versions of the webkit engine */
				|| (/AppleWebKit\/5([0-2]|3[0-2])/i).test(navigator.userAgent)
		)
);

var _run_callbacks = function(callbacks){
	if (callbacks !== undefined){
		var _l = callbacks.length;
	    for (var i=0;i<_l;i++){
	    	var func = callbacks[i];
	    	if(typeof(func)=='function'){
	            try{
	          	    func();
	            }catch (err){};            		
	    	}
	    }
	}		
}

/**
 * Partial Ajax Implementation
 * 
 * @param url: defines url to make partial request
 * @param container: defines id of container to input partial result
 * @param s_call: success callback function that takes o as arg
 *  o.tId
 *  o.status
 *  o.statusText
 *  o.getResponseHeader[ ]
 *  o.getAllResponseHeaders
 *  o.responseText
 *  o.responseXML
 *  o.argument
 * @param f_call: failure callback
 * @param args arguments 
 */
function ypjax(url,container,s_call,f_call,args){
	var method='GET';
	if(args===undefined){
		args=null;
	}
	
	// Set special header for partial ajax == HTTP_X_PARTIAL_XHR
	YUC.initHeader('X-PARTIAL-XHR',true);
	
	// wrapper of passed callback
	var s_wrapper = (function(o){
		return function(o){
			YUD.get(container).innerHTML=o.responseText;
			YUD.setStyle(container,'opacity','1.0');
    		//execute the given original callback
    		if (s_call !== undefined){
    			s_call(o);
    		}
		}
	})()	
	YUD.setStyle(container,'opacity','0.3');
	YUC.asyncRequest(method,url,{
		success:s_wrapper,
		failure:function(o){
			console.log(o);
			YUD.get(container).innerHTML='<span class="error_red">ERROR: {0}</span>'.format(o.status);
			YUD.setStyle(container,'opacity','1.0');
		},
		cache:false
	},args);
	
};

var ajaxPOST = function(url,postData,success) {
	// Set special header for ajax == HTTP_X_PARTIAL_XHR
	YUC.initHeader('X-PARTIAL-XHR',true);
	
	var toQueryString = function(o) {
	    if(typeof o !== 'object') {
	        return false;
	    }
	    var _p, _qs = [];
	    for(_p in o) {
	        _qs.push(encodeURIComponent(_p) + '=' + encodeURIComponent(o[_p]));
	    }
	    return _qs.join('&');
	};
	
    var sUrl = url;
    var callback = {
        success: success,
        failure: function (o) {
            alert("error");
        },
    };
    var postData = toQueryString(postData);
    var request = YAHOO.util.Connect.asyncRequest('POST', sUrl, callback, postData);
    return request;
};


/**
 * tooltip activate
 */
var tooltip_activate = function(){
    function toolTipsId(){
        var ids = [];
        var tts = YUQ('.tooltip');
        for (var i = 0; i < tts.length; i++) {
            // if element doesn't not have and id 
        	//  autogenerate one for tooltip 
            if (!tts[i].id){
                tts[i].id='tt'+((i*100)+tts.length);
            }
            ids.push(tts[i].id);
        }
        return ids
    };
    var myToolTips = new YAHOO.widget.Tooltip("tooltip", {
        context: [[toolTipsId()],"tl","bl",null,[0,5]],
        monitorresize:false,
        xyoffset :[0,0],
        autodismissdelay:300000,
        hidedelay:5,
        showdelay:20,
    });
};

/**
 * show more
 */
var show_more_event = function(){
    YUE.on(YUD.getElementsByClassName('show_more'),'click',function(e){
        var el = e.target;
        YUD.setStyle(YUD.get(el.id.substring(1)),'display','');
        YUD.setStyle(el.parentNode,'display','none');
    });
};


/**
 * Quick filter widget
 * 
 * @param target: filter input target
 * @param nodes: list of nodes in html we want to filter.
 * @param display_element function that takes current node from nodes and
 *    does hide or show based on the node
 * 
 */
var q_filter = function(target,nodes,display_element){
	
	var nodes = nodes;
	var q_filter_field = YUD.get(target);
	var F = YAHOO.namespace(target);

	YUE.on(q_filter_field,'click',function(){
	   q_filter_field.value = '';
	});

	YUE.on(q_filter_field,'keyup',function(e){
	    clearTimeout(F.filterTimeout); 
	    F.filterTimeout = setTimeout(F.updateFilter,600); 
	});

	F.filterTimeout = null;

	var show_node = function(node){
		YUD.setStyle(node,'display','')
	}
	var hide_node = function(node){
		YUD.setStyle(node,'display','none');
	}
	
	F.updateFilter  = function() { 
	   // Reset timeout 
	   F.filterTimeout = null;
	   
	   var obsolete = [];
	   
	   var req = q_filter_field.value.toLowerCase();
	   
	   var l = nodes.length;
	   var i;
	   var showing = 0;
	   
       for (i=0;i<l;i++ ){
    	   var n = nodes[i];
    	   var target_element = display_element(n)
    	   if(req && n.innerHTML.toLowerCase().indexOf(req) == -1){
    		   hide_node(target_element);
    	   }
    	   else{
    		   show_node(target_element);
    		   showing+=1;
    	   }
       }	  	   

	   // if repo_count is set update the number
	   var cnt = YUD.get('repo_count');
	   if(cnt){
		   YUD.get('repo_count').innerHTML = showing;
	   }       
       
	}	
};

var tableTr = function(cls, body){
	var _el = document.createElement('table');
	
	var cont = new YAHOO.util.Element(body);
	var comment_id = fromHTML(body).children[0].id.split('comment-')[1];
	var id = 'comment-tr-{0}'.format(comment_id);
	var _html = ('tbody><tr id="{0}" class="{1}">'+
	              '<td class="lineno-inline new-inline"></td>'+
    			  '<td class="lineno-inline old-inline"></td>'+ 
                  '<td>{2}</td>'+
                 '</tr>').format(id, cls, body);
	_el.innerHTML = _html;
	return _el.children[0].children[0];
};

/** comments **/
var removeInlineForm = function(form) {
	form.parentNode.removeChild(form);
};

var createInlineForm = function(parent_tr, f_path, line) {
	var tmpl = YUD.get('comment-inline-form-template').innerHTML;
	tmpl = tmpl.format(f_path, line);
	var form = tableTr('comment-form-inline',tmpl)

	// create event for hide button
	form = new YAHOO.util.Element(form);
	var form_hide_button = new YAHOO.util.Element(YUD.getElementsByClassName('hide-inline-form',null,form)[0]);
	form_hide_button.on('click', function(e) {
		var newtr = e.currentTarget.parentNode.parentNode.parentNode.parentNode.parentNode;
		if(YUD.hasClass(newtr.nextElementSibling,'inline-comments-button')){
			YUD.setStyle(newtr.nextElementSibling,'display','');
		}
		removeInlineForm(newtr);
		YUD.removeClass(parent_tr, 'form-open');
		
	});
	
	return form
};

/**
 * Inject inline comment for on given TR this tr should be always an .line
 * tr containing the line. Code will detect comment, and always put the comment
 * block at the very bottom
 */
var injectInlineForm = function(tr){
	  if(!YUD.hasClass(tr, 'line')){
		  return
	  }
	  var submit_url = AJAX_COMMENT_URL;
	  var _td = YUD.getElementsByClassName('code',null,tr)[0];
	  if(YUD.hasClass(tr,'form-open') || YUD.hasClass(tr,'context') || YUD.hasClass(_td,'no-comment')){
		  return
	  }	
	  YUD.addClass(tr,'form-open');
	  var node = YUD.getElementsByClassName('full_f_path',null,tr.parentNode.parentNode.parentNode)[0];
	  var f_path = YUD.getAttribute(node,'path');
	  var lineno = getLineNo(tr);
	  var form = createInlineForm(tr, f_path, lineno, submit_url);
	  
	  var parent = tr;
	  while (1){
		  var n = parent.nextElementSibling;
		  // next element are comments !
		  if(YUD.hasClass(n,'inline-comments')){
			  parent = n;
		  }
		  else{
			  break;
		  }
	  }	  
	  YUD.insertAfter(form,parent);
	  
	  var f = YUD.get(form);
	  
	  var overlay = YUD.getElementsByClassName('overlay',null,f)[0];
	  var _form = YUD.getElementsByClassName('inline-form',null,f)[0];
	  
	  form.on('submit',function(e){
		  YUE.preventDefault(e);
		  
		  //ajax submit
		  var text = YUD.get('text_'+lineno).value;
		  var postData = {
	            'text':text,
	            'f_path':f_path,
	            'line':lineno
		  };
		  
		  if(lineno === undefined){
			  alert('missing line !');
			  return
		  }
		  if(f_path === undefined){
			  alert('missing file path !');
			  return
		  }
		  
		  if(text == ""){
			  return
		  }
		  
		  var success = function(o){
			  YUD.removeClass(tr, 'form-open');
			  removeInlineForm(f);			  
			  var json_data = JSON.parse(o.responseText);
	          renderInlineComment(json_data);
		  };

		  if (YUD.hasClass(overlay,'overlay')){
			  var w = _form.offsetWidth;
			  var h = _form.offsetHeight;
			  YUD.setStyle(overlay,'width',w+'px');
			  YUD.setStyle(overlay,'height',h+'px');
		  }		  
		  YUD.addClass(overlay, 'submitting');		  
		  
		  ajaxPOST(submit_url, postData, success);
	  });
	  
	  setTimeout(function(){
		  // callbacks
		  tooltip_activate();
		  MentionsAutoComplete('text_'+lineno, 'mentions_container_'+lineno, 
	                         _USERS_AC_DATA, _GROUPS_AC_DATA);
		  var _e = YUD.get('text_'+lineno);
		  if(_e){
			  _e.focus();
		  }
	  },10)
};

var deleteComment = function(comment_id){
	var url = AJAX_COMMENT_DELETE_URL.replace('__COMMENT_ID__',comment_id);
    var postData = {'_method':'delete'};
    var success = function(o){
        var n = YUD.get('comment-tr-'+comment_id);
        var root = prevElementSibling(prevElementSibling(n));
        n.parentNode.removeChild(n);

        // scann nodes, and attach add button to last one
        placeAddButton(root);
    }
    ajaxPOST(url,postData,success);
}

var updateReviewers = function(reviewers_ids){
	var url = AJAX_UPDATE_PULLREQUEST;
	var postData = {'_method':'put',
			        'reviewers_ids': reviewers_ids};
	var success = function(o){
		window.location.reload();
	}
	ajaxPOST(url,postData,success);
}

var createInlineAddButton = function(tr){

	var label = TRANSLATION_MAP['add another comment'];
	
	var html_el = document.createElement('div');
	YUD.addClass(html_el, 'add-comment');
	html_el.innerHTML = '<span class="ui-btn">{0}</span>'.format(label);
	
	var add = new YAHOO.util.Element(html_el);
	add.on('click', function(e) {
		injectInlineForm(tr);
	});
	return add;
};

var getLineNo = function(tr) {
	var line;
	var o = tr.children[0].id.split('_');
	var n = tr.children[1].id.split('_');

	if (n.length >= 2) {
		line = n[n.length-1];
	} else if (o.length >= 2) {
		line = o[o.length-1];
	}

	return line
};

var placeAddButton = function(target_tr){
	if(!target_tr){
		return
	}
	var last_node = target_tr;
    //scann	
	  while (1){
		  var n = last_node.nextElementSibling;
		  // next element are comments !
		  if(YUD.hasClass(n,'inline-comments')){
			  last_node = n;
			  //also remove the comment button from previous
			  var comment_add_buttons = YUD.getElementsByClassName('add-comment',null,last_node);
			  for(var i=0;i<comment_add_buttons.length;i++){
				  var b = comment_add_buttons[i];
				  b.parentNode.removeChild(b);
			  }
		  }
		  else{
			  break;
		  }
	  }
	  
    var add = createInlineAddButton(target_tr);
    // get the comment div
    var comment_block = YUD.getElementsByClassName('comment',null,last_node)[0];
    // attach add button
    YUD.insertAfter(add,comment_block);	
}

/**
 * Places the inline comment into the changeset block in proper line position
 */
var placeInline = function(target_container,lineno,html){
	  var lineid = "{0}_{1}".format(target_container,lineno);
	  var target_line = YUD.get(lineid);
	  var comment = new YAHOO.util.Element(tableTr('inline-comments',html))
	  
	  // check if there are comments already !
	  var parent = target_line.parentNode;
	  var root_parent = parent;
	  while (1){
		  var n = parent.nextElementSibling;
		  // next element are comments !
		  if(YUD.hasClass(n,'inline-comments')){
			  parent = n;
		  }
		  else{
			  break;
		  }
	  }
	  // put in the comment at the bottom
	  YUD.insertAfter(comment,parent);
	  
	  // scann nodes, and attach add button to last one
      placeAddButton(root_parent);

	  return target_line;
}

/**
 * make a single inline comment and place it inside
 */
var renderInlineComment = function(json_data){
    try{
	  var html =  json_data['rendered_text'];
	  var lineno = json_data['line_no'];
	  var target_id = json_data['target_id'];
	  placeInline(target_id, lineno, html);

    }catch(e){
  	  console.log(e);
    }
}

/**
 * Iterates over all the inlines, and places them inside proper blocks of data
 */
var renderInlineComments = function(file_comments){
	for (f in file_comments){
        // holding all comments for a FILE
		var box = file_comments[f];

		var target_id = YUD.getAttribute(box,'target_id');
		// actually comments with line numbers
        var comments = box.children;
        for(var i=0; i<comments.length; i++){
        	var data = {
        		'rendered_text': comments[i].outerHTML,
        		'line_no': YUD.getAttribute(comments[i],'line'),
        		'target_id': target_id
        	}
        	renderInlineComment(data);
        }
    }	
}

var removeReviewer = function(reviewer_id){
	var el = YUD.get('reviewer_{0}'.format(reviewer_id));
	if (el.parentNode !== undefined){
		el.parentNode.removeChild(el);
	}
}

var fileBrowserListeners = function(current_url, node_list_url, url_base){
	
	var current_url_branch = +"?branch=__BRANCH__";
	var url = url_base;
	var node_url = node_list_url;	

	YUE.on('stay_at_branch','click',function(e){
	    if(e.target.checked){
	        var uri = current_url_branch;
	        uri = uri.replace('__BRANCH__',e.target.value);
	        window.location = uri;
	    }
	    else{
	        window.location = current_url;
	    }
	})            

	var n_filter = YUD.get('node_filter');
	var F = YAHOO.namespace('node_filter');
	
	F.filterTimeout = null;
	var nodes = null;

	F.initFilter = function(){
	  YUD.setStyle('node_filter_box_loading','display','');
	  YUD.setStyle('search_activate_id','display','none');
	  YUD.setStyle('add_node_id','display','none');
	  YUC.initHeader('X-PARTIAL-XHR',true);
	  YUC.asyncRequest('GET',url,{
	      success:function(o){
	        nodes = JSON.parse(o.responseText).nodes;
	        YUD.setStyle('node_filter_box_loading','display','none');
	        YUD.setStyle('node_filter_box','display','');
	        n_filter.focus();
			if(YUD.hasClass(n_filter,'init')){
				n_filter.value = '';
				YUD.removeClass(n_filter,'init');
			}   
	      },
	      failure:function(o){
	          console.log('failed to load');
	      }
	  },null);            
	}

	F.updateFilter  = function(e) {
	    
	    return function(){
	        // Reset timeout 
	        F.filterTimeout = null;
	        var query = e.target.value.toLowerCase();
	        var match = [];
	        var matches = 0;
	        var matches_max = 20;
	        if (query != ""){
	            for(var i=0;i<nodes.length;i++){
	            	
	                var pos = nodes[i].name.toLowerCase().indexOf(query)
	                if(query && pos != -1){
	                    
	                    matches++
	                    //show only certain amount to not kill browser 
	                    if (matches > matches_max){
	                        break;
	                    }
	                    
	                    var n = nodes[i].name;
	                    var t = nodes[i].type;
	                    var n_hl = n.substring(0,pos)
	                      +"<b>{0}</b>".format(n.substring(pos,pos+query.length))
	                      +n.substring(pos+query.length)
	                    node_url = node_url.replace('__FPATH__',n);
	                    match.push('<tr><td><a class="browser-{0}" href="{1}">{2}</a></td><td colspan="5"></td></tr>'.format(t,node_url,n_hl));
	                }
	                if(match.length >= matches_max){
	                    match.push('<tr><td>{0}</td><td colspan="5"></td></tr>'.format(_TM['search truncated']));
	                }
	            }                       
	        }
	        if(query != ""){
	            YUD.setStyle('tbody','display','none');
	            YUD.setStyle('tbody_filtered','display','');
	            
	            if (match.length==0){
	              match.push('<tr><td>{0}</td><td colspan="5"></td></tr>'.format(_TM['no matching files']));
	            }                           
	            
	            YUD.get('tbody_filtered').innerHTML = match.join("");   
	        }
	        else{
	            YUD.setStyle('tbody','display','');
	            YUD.setStyle('tbody_filtered','display','none');
	        }
	        
	    }
	};

	YUE.on(YUD.get('filter_activate'),'click',function(){
	    F.initFilter();
	})
	YUE.on(n_filter,'click',function(){
		if(YUD.hasClass(n_filter,'init')){
			n_filter.value = '';
			YUD.removeClass(n_filter,'init');
		}
	 });
	YUE.on(n_filter,'keyup',function(e){
	    clearTimeout(F.filterTimeout); 
	    F.filterTimeout = setTimeout(F.updateFilter(e),600);
	});
};


var initCodeMirror = function(textAreadId,resetUrl){  
    var myCodeMirror = CodeMirror.fromTextArea(YUD.get(textAreadId),{
           mode:  "null",
           lineNumbers:true
         });
    YUE.on('reset','click',function(e){
        window.location=resetUrl
    });
    
    YUE.on('file_enable','click',function(){
        YUD.setStyle('editor_container','display','');
        YUD.setStyle('upload_file_container','display','none');
        YUD.setStyle('filename_container','display','');
    });
    
    YUE.on('upload_file_enable','click',function(){
        YUD.setStyle('editor_container','display','none');
        YUD.setStyle('upload_file_container','display','');
        YUD.setStyle('filename_container','display','none');
    });	
};



var getIdentNode = function(n){
	//iterate thru nodes untill matched interesting node !
	
	if (typeof n == 'undefined'){
		return -1
	}
	
	if(typeof n.id != "undefined" && n.id.match('L[0-9]+')){
			return n
		}
	else{
		return getIdentNode(n.parentNode);
	}
};

var  getSelectionLink = function(selection_link_label) {
	return function(){
	    //get selection from start/to nodes    	
	    if (typeof window.getSelection != "undefined") {
	    	s = window.getSelection();
	
	       	from = getIdentNode(s.anchorNode);
	       	till = getIdentNode(s.focusNode);
	        
	        f_int = parseInt(from.id.replace('L',''));
	        t_int = parseInt(till.id.replace('L',''));
	        
	        if (f_int > t_int){
	        	//highlight from bottom 
	        	offset = -35;
	        	ranges = [t_int,f_int];
	        	
	        }
	        else{
	        	//highligth from top 
	        	offset = 35;
	        	ranges = [f_int,t_int];
	        }
	        
	        if (ranges[0] != ranges[1]){
	            if(YUD.get('linktt') == null){
	                hl_div = document.createElement('div');
	                hl_div.id = 'linktt';
	            }
	            anchor = '#L'+ranges[0]+'-'+ranges[1];
	            hl_div.innerHTML = '';
	            l = document.createElement('a');
	            l.href = location.href.substring(0,location.href.indexOf('#'))+anchor;
	            l.innerHTML = selection_link_label;
	            hl_div.appendChild(l);
	            
	            YUD.get('body').appendChild(hl_div);
	            
	            xy = YUD.getXY(till.id);
	            
	            YUD.addClass('linktt','yui-tt');
	            YUD.setStyle('linktt','top',xy[1]+offset+'px');
	            YUD.setStyle('linktt','left',xy[0]+'px');
	            YUD.setStyle('linktt','visibility','visible');
	        }
	        else{
	        	YUD.setStyle('linktt','visibility','hidden');
	        }
	    }
	}
};

var deleteNotification = function(url, notification_id,callbacks){
    var callback = { 
		success:function(o){
		    var obj = YUD.get(String("notification_"+notification_id));
		    if(obj.parentNode !== undefined){
				obj.parentNode.removeChild(obj);
			}
			_run_callbacks(callbacks);
		},
	    failure:function(o){
	        alert("error");
	    },
	};
    var postData = '_method=delete';
    var sUrl = url.replace('__NOTIFICATION_ID__',notification_id);
    var request = YAHOO.util.Connect.asyncRequest('POST', sUrl, 
    											  callback, postData);
};	

var readNotification = function(url, notification_id,callbacks){
    var callback = { 
		success:function(o){
		    var obj = YUD.get(String("notification_"+notification_id));
		    YUD.removeClass(obj, 'unread');
		    var r_button = YUD.getElementsByClassName('read-notification',null,obj.children[0])[0];
		    
		    if(r_button.parentNode !== undefined){
		    	r_button.parentNode.removeChild(r_button);
			}		    
			_run_callbacks(callbacks);
		},
	    failure:function(o){
	        alert("error");
	    },
	};
    var postData = '_method=put';
    var sUrl = url.replace('__NOTIFICATION_ID__',notification_id);
    var request = YAHOO.util.Connect.asyncRequest('POST', sUrl, 
    											  callback, postData);
};	

/** MEMBERS AUTOCOMPLETE WIDGET **/

var MembersAutoComplete = function (divid, cont, users_list, groups_list) {
    var myUsers = users_list;
    var myGroups = groups_list;

    // Define a custom search function for the DataSource of users
    var matchUsers = function (sQuery) {
            // Case insensitive matching
            var query = sQuery.toLowerCase();
            var i = 0;
            var l = myUsers.length;
            var matches = [];

            // Match against each name of each contact
            for (; i < l; i++) {
                contact = myUsers[i];
                if (((contact.fname+"").toLowerCase().indexOf(query) > -1) || 
                   	 ((contact.lname+"").toLowerCase().indexOf(query) > -1) || 
                   	 ((contact.nname) && ((contact.nname).toLowerCase().indexOf(query) > -1))) {
                       matches[matches.length] = contact;
                   }
            }
            return matches;
        };

    // Define a custom search function for the DataSource of usersGroups
    var matchGroups = function (sQuery) {
            // Case insensitive matching
            var query = sQuery.toLowerCase();
            var i = 0;
            var l = myGroups.length;
            var matches = [];

            // Match against each name of each contact
            for (; i < l; i++) {
                matched_group = myGroups[i];
                if (matched_group.grname.toLowerCase().indexOf(query) > -1) {
                    matches[matches.length] = matched_group;
                }
            }
            return matches;
        };

    //match all
    var matchAll = function (sQuery) {
            u = matchUsers(sQuery);
            g = matchGroups(sQuery);
            return u.concat(g);
        };

    // DataScheme for members
    var memberDS = new YAHOO.util.FunctionDataSource(matchAll);
    memberDS.responseSchema = {
        fields: ["id", "fname", "lname", "nname", "grname", "grmembers", "gravatar_lnk"]
    };

    // DataScheme for owner
    var ownerDS = new YAHOO.util.FunctionDataSource(matchUsers);
    ownerDS.responseSchema = {
        fields: ["id", "fname", "lname", "nname", "gravatar_lnk"]
    };

    // Instantiate AutoComplete for perms
    var membersAC = new YAHOO.widget.AutoComplete(divid, cont, memberDS);
    membersAC.useShadow = false;
    membersAC.resultTypeList = false;
    membersAC.animVert = false;
    membersAC.animHoriz = false;    
    membersAC.animSpeed = 0.1;

    // Instantiate AutoComplete for owner
    var ownerAC = new YAHOO.widget.AutoComplete("user", "owner_container", ownerDS);
    ownerAC.useShadow = false;
    ownerAC.resultTypeList = false;
    ownerAC.animVert = false;
    ownerAC.animHoriz = false;
    ownerAC.animSpeed = 0.1;

    // Helper highlight function for the formatter
    var highlightMatch = function (full, snippet, matchindex) {
            return full.substring(0, matchindex) 
            + "<span class='match'>" 
            + full.substr(matchindex, snippet.length) 
            + "</span>" + full.substring(matchindex + snippet.length);
        };

    // Custom formatter to highlight the matching letters
    var custom_formatter = function (oResultData, sQuery, sResultMatch) {
            var query = sQuery.toLowerCase();
            var _gravatar = function(res, em, group){
            	if (group !== undefined){
            		em = '/images/icons/group.png'
            	}
            	tmpl = '<div class="ac-container-wrap"><img class="perm-gravatar-ac" src="{0}"/>{1}</div>'
            	return tmpl.format(em,res)
            }
            // group
            if (oResultData.grname != undefined) {
                var grname = oResultData.grname;
                var grmembers = oResultData.grmembers;
                var grnameMatchIndex = grname.toLowerCase().indexOf(query);
                var grprefix = "{0}: ".format(_TM['Group']);
                var grsuffix = " (" + grmembers + "  )";
                var grsuffix = " ({0}  {1})".format(grmembers, _TM['members']);

                if (grnameMatchIndex > -1) {
                    return _gravatar(grprefix + highlightMatch(grname, query, grnameMatchIndex) + grsuffix,null,true);
                }
			    return _gravatar(grprefix + oResultData.grname + grsuffix, null,true);
            // Users
            } else if (oResultData.nname != undefined) {
                var fname = oResultData.fname || "";
                var lname = oResultData.lname || "";
                var nname = oResultData.nname;
                
                // Guard against null value
                var fnameMatchIndex = fname.toLowerCase().indexOf(query),
                    lnameMatchIndex = lname.toLowerCase().indexOf(query),
                    nnameMatchIndex = nname.toLowerCase().indexOf(query),
                    displayfname, displaylname, displaynname;

                if (fnameMatchIndex > -1) {
                    displayfname = highlightMatch(fname, query, fnameMatchIndex);
                } else {
                    displayfname = fname;
                }

                if (lnameMatchIndex > -1) {
                    displaylname = highlightMatch(lname, query, lnameMatchIndex);
                } else {
                    displaylname = lname;
                }

                if (nnameMatchIndex > -1) {
                    displaynname = "(" + highlightMatch(nname, query, nnameMatchIndex) + ")";
                } else {
                    displaynname = nname ? "(" + nname + ")" : "";
                }

                return _gravatar(displayfname + " " + displaylname + " " + displaynname, oResultData.gravatar_lnk);
            } else {
                return '';
            }
        };
    membersAC.formatResult = custom_formatter;
    ownerAC.formatResult = custom_formatter;

    var myHandler = function (sType, aArgs) {
    		var nextId = divid.split('perm_new_member_name_')[1];
            var myAC = aArgs[0]; // reference back to the AC instance
            var elLI = aArgs[1]; // reference to the selected LI element
            var oData = aArgs[2]; // object literal of selected item's result data
            //fill the autocomplete with value
            if (oData.nname != undefined) {
                //users
                myAC.getInputEl().value = oData.nname;
                YUD.get('perm_new_member_type_'+nextId).value = 'user';
            } else {
                //groups
                myAC.getInputEl().value = oData.grname;
                YUD.get('perm_new_member_type_'+nextId).value = 'users_group';
            }
        };

    membersAC.itemSelectEvent.subscribe(myHandler);
    if(ownerAC.itemSelectEvent){
    	ownerAC.itemSelectEvent.subscribe(myHandler);
    }

    return {
        memberDS: memberDS,
        ownerDS: ownerDS,
        membersAC: membersAC,
        ownerAC: ownerAC,
    };
}


var MentionsAutoComplete = function (divid, cont, users_list, groups_list) {
    var myUsers = users_list;
    var myGroups = groups_list;

    // Define a custom search function for the DataSource of users
    var matchUsers = function (sQuery) {
    	    var org_sQuery = sQuery;
    	    if(this.mentionQuery == null){
    	    	return []    	    	
    	    }
    	    sQuery = this.mentionQuery;
            // Case insensitive matching
            var query = sQuery.toLowerCase();
            var i = 0;
            var l = myUsers.length;
            var matches = [];

            // Match against each name of each contact
            for (; i < l; i++) {
                contact = myUsers[i];
                if (((contact.fname+"").toLowerCase().indexOf(query) > -1) || 
                	 ((contact.lname+"").toLowerCase().indexOf(query) > -1) || 
                	 ((contact.nname) && ((contact.nname).toLowerCase().indexOf(query) > -1))) {
                    matches[matches.length] = contact;
                }
            }
            return matches
        };

    //match all
    var matchAll = function (sQuery) {
            u = matchUsers(sQuery);
            return u
        };

    // DataScheme for owner
    var ownerDS = new YAHOO.util.FunctionDataSource(matchUsers);

    ownerDS.responseSchema = {
        fields: ["id", "fname", "lname", "nname", "gravatar_lnk"]
    };

    // Instantiate AutoComplete for mentions
    var ownerAC = new YAHOO.widget.AutoComplete(divid, cont, ownerDS);
    ownerAC.useShadow = false;
    ownerAC.resultTypeList = false;
    ownerAC.suppressInputUpdate = true;
    ownerAC.animVert = false;
    ownerAC.animHoriz = false;    
    ownerAC.animSpeed = 0.1;
    
    // Helper highlight function for the formatter
    var highlightMatch = function (full, snippet, matchindex) {
            return full.substring(0, matchindex) 
            + "<span class='match'>" 
            + full.substr(matchindex, snippet.length) 
            + "</span>" + full.substring(matchindex + snippet.length);
        };

    // Custom formatter to highlight the matching letters
    ownerAC.formatResult = function (oResultData, sQuery, sResultMatch) {
		    var org_sQuery = sQuery;
		    if(this.dataSource.mentionQuery != null){
		    	sQuery = this.dataSource.mentionQuery;		    	
		    }

            var query = sQuery.toLowerCase();
            var _gravatar = function(res, em, group){
            	if (group !== undefined){
            		em = '/images/icons/group.png'
            	}
            	tmpl = '<div class="ac-container-wrap"><img class="perm-gravatar-ac" src="{0}"/>{1}</div>'
            	return tmpl.format(em,res)
            }
            if (oResultData.nname != undefined) {
                var fname = oResultData.fname || "";
                var lname = oResultData.lname || "";
                var nname = oResultData.nname;
                
                // Guard against null value
                var fnameMatchIndex = fname.toLowerCase().indexOf(query),
                    lnameMatchIndex = lname.toLowerCase().indexOf(query),
                    nnameMatchIndex = nname.toLowerCase().indexOf(query),
                    displayfname, displaylname, displaynname;

                if (fnameMatchIndex > -1) {
                    displayfname = highlightMatch(fname, query, fnameMatchIndex);
                } else {
                    displayfname = fname;
                }

                if (lnameMatchIndex > -1) {
                    displaylname = highlightMatch(lname, query, lnameMatchIndex);
                } else {
                    displaylname = lname;
                }

                if (nnameMatchIndex > -1) {
                    displaynname = "(" + highlightMatch(nname, query, nnameMatchIndex) + ")";
                } else {
                    displaynname = nname ? "(" + nname + ")" : "";
                }

                return _gravatar(displayfname + " " + displaylname + " " + displaynname, oResultData.gravatar_lnk);
            } else {
                return '';
            }
        };

    if(ownerAC.itemSelectEvent){
    	ownerAC.itemSelectEvent.subscribe(function (sType, aArgs) {

            var myAC = aArgs[0]; // reference back to the AC instance
            var elLI = aArgs[1]; // reference to the selected LI element
            var oData = aArgs[2]; // object literal of selected item's result data
            //fill the autocomplete with value
            if (oData.nname != undefined) {
                //users
            	//Replace the mention name with replaced
            	var re = new RegExp();
            	var org = myAC.getInputEl().value;
            	var chunks = myAC.dataSource.chunks
            	// replace middle chunk(the search term) with actuall  match
            	chunks[1] = chunks[1].replace('@'+myAC.dataSource.mentionQuery,
            								  '@'+oData.nname+' ');
                myAC.getInputEl().value = chunks.join('')
                YUD.get(myAC.getInputEl()).focus(); // Y U NO WORK !?
            } else {
                //groups
                myAC.getInputEl().value = oData.grname;
                YUD.get('perm_new_member_type').value = 'users_group';
            }
        });
    }

    // in this keybuffer we will gather current value of search !
    // since we need to get this just when someone does `@` then we do the
    // search
    ownerAC.dataSource.chunks = [];
    ownerAC.dataSource.mentionQuery = null;

    ownerAC.get_mention = function(msg, max_pos) {
    	var org = msg;
    	var re = new RegExp('(?:^@|\s@)([a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+)$')
    	var chunks  = [];

		
    	// cut first chunk until curret pos
		var to_max = msg.substr(0, max_pos);		
		var at_pos = Math.max(0,to_max.lastIndexOf('@')-1);
		var msg2 = to_max.substr(at_pos);

		chunks.push(org.substr(0,at_pos))// prefix chunk
		chunks.push(msg2)                // search chunk
		chunks.push(org.substr(max_pos)) // postfix chunk

		// clean up msg2 for filtering and regex match
		var msg2 = msg2.lstrip(' ').lstrip('\n');

		if(re.test(msg2)){
			var unam = re.exec(msg2)[1];
			return [unam, chunks];
		}
		return [null, null];
    };
    
    if (ownerAC.textboxKeyUpEvent){
		ownerAC.textboxKeyUpEvent.subscribe(function(type, args){
			
			var ac_obj = args[0];
			var currentMessage = args[1];
			var currentCaretPosition = args[0]._elTextbox.selectionStart;
	
			var unam = ownerAC.get_mention(currentMessage, currentCaretPosition); 
			var curr_search = null;
			if(unam[0]){
				curr_search = unam[0];
			}
			
			ownerAC.dataSource.chunks = unam[1];
			ownerAC.dataSource.mentionQuery = curr_search;
	
		})
	}	
    return {
        ownerDS: ownerDS,
        ownerAC: ownerAC,
    };
}


var PullRequestAutoComplete = function (divid, cont, users_list, groups_list) {
    var myUsers = users_list;
    var myGroups = groups_list;

    // Define a custom search function for the DataSource of users
    var matchUsers = function (sQuery) {
            // Case insensitive matching
            var query = sQuery.toLowerCase();
            var i = 0;
            var l = myUsers.length;
            var matches = [];

            // Match against each name of each contact
            for (; i < l; i++) {
                contact = myUsers[i];
                if (((contact.fname+"").toLowerCase().indexOf(query) > -1) || 
                   	 ((contact.lname+"").toLowerCase().indexOf(query) > -1) || 
                   	 ((contact.nname) && ((contact.nname).toLowerCase().indexOf(query) > -1))) {
                       matches[matches.length] = contact;
                   }
            }
            return matches;
        };

    // Define a custom search function for the DataSource of usersGroups
    var matchGroups = function (sQuery) {
            // Case insensitive matching
            var query = sQuery.toLowerCase();
            var i = 0;
            var l = myGroups.length;
            var matches = [];

            // Match against each name of each contact
            for (; i < l; i++) {
                matched_group = myGroups[i];
                if (matched_group.grname.toLowerCase().indexOf(query) > -1) {
                    matches[matches.length] = matched_group;
                }
            }
            return matches;
        };

    //match all
    var matchAll = function (sQuery) {
            u = matchUsers(sQuery);
            return u
        };

    // DataScheme for owner
    var ownerDS = new YAHOO.util.FunctionDataSource(matchUsers);

    ownerDS.responseSchema = {
        fields: ["id", "fname", "lname", "nname", "gravatar_lnk"]
    };

    // Instantiate AutoComplete for mentions
    var reviewerAC = new YAHOO.widget.AutoComplete(divid, cont, ownerDS);
    reviewerAC.useShadow = false;
    reviewerAC.resultTypeList = false;
    reviewerAC.suppressInputUpdate = true;
    reviewerAC.animVert = false;
    reviewerAC.animHoriz = false;    
    reviewerAC.animSpeed = 0.1;
    
    // Helper highlight function for the formatter
    var highlightMatch = function (full, snippet, matchindex) {
            return full.substring(0, matchindex) 
            + "<span class='match'>" 
            + full.substr(matchindex, snippet.length) 
            + "</span>" + full.substring(matchindex + snippet.length);
        };

    // Custom formatter to highlight the matching letters
    reviewerAC.formatResult = function (oResultData, sQuery, sResultMatch) {
		    var org_sQuery = sQuery;
		    if(this.dataSource.mentionQuery != null){
		    	sQuery = this.dataSource.mentionQuery;		    	
		    }

            var query = sQuery.toLowerCase();
            var _gravatar = function(res, em, group){
            	if (group !== undefined){
            		em = '/images/icons/group.png'
            	}
            	tmpl = '<div class="ac-container-wrap"><img class="perm-gravatar-ac" src="{0}"/>{1}</div>'
            	return tmpl.format(em,res)
            }
            if (oResultData.nname != undefined) {
                var fname = oResultData.fname || "";
                var lname = oResultData.lname || "";
                var nname = oResultData.nname;
                
                // Guard against null value
                var fnameMatchIndex = fname.toLowerCase().indexOf(query),
                    lnameMatchIndex = lname.toLowerCase().indexOf(query),
                    nnameMatchIndex = nname.toLowerCase().indexOf(query),
                    displayfname, displaylname, displaynname;

                if (fnameMatchIndex > -1) {
                    displayfname = highlightMatch(fname, query, fnameMatchIndex);
                } else {
                    displayfname = fname;
                }

                if (lnameMatchIndex > -1) {
                    displaylname = highlightMatch(lname, query, lnameMatchIndex);
                } else {
                    displaylname = lname;
                }

                if (nnameMatchIndex > -1) {
                    displaynname = "(" + highlightMatch(nname, query, nnameMatchIndex) + ")";
                } else {
                    displaynname = nname ? "(" + nname + ")" : "";
                }

                return _gravatar(displayfname + " " + displaylname + " " + displaynname, oResultData.gravatar_lnk);
            } else {
                return '';
            }
        };
        
    //members cache to catch duplicates
    reviewerAC.dataSource.cache = [];
    // hack into select event
    if(reviewerAC.itemSelectEvent){
    	reviewerAC.itemSelectEvent.subscribe(function (sType, aArgs) {

            var myAC = aArgs[0]; // reference back to the AC instance
            var elLI = aArgs[1]; // reference to the selected LI element
            var oData = aArgs[2]; // object literal of selected item's result data
            var members  = YUD.get('review_members');
            //fill the autocomplete with value

            if (oData.nname != undefined) {
            	if (myAC.dataSource.cache.indexOf(oData.id) != -1){
            		return
            	}

            	var tmpl = '<li id="reviewer_{2}">'+
		                      '<div class="reviewers_member">'+
		                        '<div class="gravatar"><img alt="gravatar" src="{0}"/> </div>'+
		                        '<div style="float:left">{1}</div>'+
		                        '<input type="hidden" value="{2}" name="review_members" />'+
		                        '<span class="delete_icon action_button" onclick="removeReviewer({2})"></span>'+
		                      '</div>'+
		                   '</li>'

		        var displayname = "{0} {1} ({2})".format(oData.fname,oData.lname,oData.nname);
            	var element = tmpl.format(oData.gravatar_lnk,displayname,oData.id);
            	members.innerHTML += element;
            	myAC.dataSource.cache.push(oData.id);
            	YUD.get('user').value = '' 
            }
    	});        
    }
    return {
        ownerDS: ownerDS,
        reviewerAC: reviewerAC,
    };
}


/**
 * QUICK REPO MENU
 */
var quick_repo_menu = function(){
    YUE.on(YUQ('.quick_repo_menu'),'mouseenter',function(e){
            var menu = e.currentTarget.firstElementChild.firstElementChild;
            if(YUD.hasClass(menu,'hidden')){
                YUD.replaceClass(e.currentTarget,'hidden', 'active');
                YUD.replaceClass(menu, 'hidden', 'active');
            }
        })
    YUE.on(YUQ('.quick_repo_menu'),'mouseleave',function(e){
            var menu = e.currentTarget.firstElementChild.firstElementChild;
            if(YUD.hasClass(menu,'active')){
                YUD.replaceClass(e.currentTarget, 'active', 'hidden');
                YUD.replaceClass(menu, 'active', 'hidden');
            }
        })
};


/**
 * TABLE SORTING
 */

// returns a node from given html;
var fromHTML = function(html){
	  var _html = document.createElement('element');
	  _html.innerHTML = html;
	  return _html;
}
var get_rev = function(node){
    var n = node.firstElementChild.firstElementChild;
    
    if (n===null){
        return -1
    }
    else{
        out = n.firstElementChild.innerHTML.split(':')[0].replace('r','');
        return parseInt(out);
    }
}

var get_name = function(node){
	 var name = node.firstElementChild.children[2].innerHTML; 
	 return name
}
var get_group_name = function(node){
	var name = node.firstElementChild.children[1].innerHTML;
	return name
}
var get_date = function(node){
	var date_ = YUD.getAttribute(node.firstElementChild,'date');
	return date_
}

var get_age = function(node){
	return node
}

var get_link = function(node){
	return node.firstElementChild.text;
}

var revisionSort = function(a, b, desc, field) {
	  
	  var a_ = fromHTML(a.getData(field));
	  var b_ = fromHTML(b.getData(field));
	  
	  // extract revisions from string nodes 
	  a_ = get_rev(a_)
	  b_ = get_rev(b_)
	      	  
	  var comp = YAHOO.util.Sort.compare;
	  var compState = comp(a_, b_, desc);
	  return compState;
};
var ageSort = function(a, b, desc, field) {
    var a_ = fromHTML(a.getData(field));
    var b_ = fromHTML(b.getData(field));
    
    // extract name from table
    a_ = get_date(a_)
    b_ = get_date(b_)          
    
    var comp = YAHOO.util.Sort.compare;
    var compState = comp(a_, b_, desc);
    return compState;
};

var lastLoginSort = function(a, b, desc, field) {
	var a_ = a.getData('last_login_raw') || 0;
    var b_ = b.getData('last_login_raw') || 0;
    
    var comp = YAHOO.util.Sort.compare;
    var compState = comp(a_, b_, desc);
    return compState;
};

var nameSort = function(a, b, desc, field) {
    var a_ = fromHTML(a.getData(field));
    var b_ = fromHTML(b.getData(field));

    // extract name from table
    a_ = get_name(a_)
    b_ = get_name(b_)          
    
    var comp = YAHOO.util.Sort.compare;
    var compState = comp(a_, b_, desc);
    return compState;
};

var permNameSort = function(a, b, desc, field) {
    var a_ = fromHTML(a.getData(field));
    var b_ = fromHTML(b.getData(field));
    // extract name from table

    a_ = a_.children[0].innerHTML;
    b_ = b_.children[0].innerHTML;      
    
    var comp = YAHOO.util.Sort.compare;
    var compState = comp(a_, b_, desc);
    return compState;
};

var groupNameSort = function(a, b, desc, field) {
    var a_ = fromHTML(a.getData(field));
    var b_ = fromHTML(b.getData(field));
    
    // extract name from table
    a_ = get_group_name(a_)
    b_ = get_group_name(b_)          
    
    var comp = YAHOO.util.Sort.compare;
    var compState = comp(a_, b_, desc);
    return compState;
};
var dateSort = function(a, b, desc, field) {
    var a_ = fromHTML(a.getData(field));
    var b_ = fromHTML(b.getData(field));
    
    // extract name from table
    a_ = get_date(a_)
    b_ = get_date(b_)          
    
    var comp = YAHOO.util.Sort.compare;
    var compState = comp(a_, b_, desc);
    return compState;
};

var linkSort = function(a, b, desc, field) {
	  var a_ = fromHTML(a.getData(field));
	  var b_ = fromHTML(a.getData(field));
	  
	  // extract url text from string nodes 
	  a_ = get_link(a_)
	  b_ = get_link(b_)

	  var comp = YAHOO.util.Sort.compare;
	  var compState = comp(a_, b_, desc);
	  return compState;
}

var addPermAction = function(_html, users_list, groups_list){
    var elmts = YUD.getElementsByClassName('last_new_member');
    var last_node = elmts[elmts.length-1];
    if (last_node){
       var next_id = (YUD.getElementsByClassName('new_members')).length;
       _html = _html.format(next_id);
       last_node.innerHTML = _html;
       YUD.setStyle(last_node, 'display', '');
       YUD.removeClass(last_node, 'last_new_member');
       MembersAutoComplete("perm_new_member_name_"+next_id, 
               "perm_container_"+next_id, users_list, groups_list);          
       //create new last NODE
       var el = document.createElement('tr');
       el.id = 'add_perm_input';
       YUD.addClass(el,'last_new_member');
       YUD.addClass(el,'new_members');
       YUD.insertAfter(el, last_node);
    }	
}

/* Multi selectors */

var MultiSelectWidget = function(selected_id, available_id, form_id){


	//definition of containers ID's
	var selected_container = selected_id;
	var available_container = available_id;
	
	//temp container for selected storage.
	var cache = new Array();
	var av_cache = new Array();
	var c =  YUD.get(selected_container);
	var ac = YUD.get(available_container);
	
	//get only selected options for further fullfilment
	for(var i = 0;node =c.options[i];i++){
	    if(node.selected){
	        //push selected to my temp storage left overs :)
	        cache.push(node);
	    }
	}
	
	//get all available options to cache
	for(var i = 0;node =ac.options[i];i++){
	        //push selected to my temp storage left overs :)
	        av_cache.push(node);
	}
	
	//fill available only with those not in choosen
	ac.options.length=0;
	tmp_cache = new Array();
	
	for(var i = 0;node = av_cache[i];i++){
	    var add = true;
	    for(var i2 = 0;node_2 = cache[i2];i2++){
	        if(node.value == node_2.value){
	            add=false;
	            break;
	        }
	    }
	    if(add){
	        tmp_cache.push(new Option(node.text, node.value, false, false));
	    }
	}
	
	for(var i = 0;node = tmp_cache[i];i++){
	    ac.options[i] = node;
	}
	
	function prompts_action_callback(e){
	
	    var choosen = YUD.get(selected_container);
	    var available = YUD.get(available_container);
	
	    //get checked and unchecked options from field
	    function get_checked(from_field){
	        //temp container for storage.
	        var sel_cache = new Array();
	        var oth_cache = new Array();
	
	        for(var i = 0;node = from_field.options[i];i++){
	            if(node.selected){
	                //push selected fields :)
	                sel_cache.push(node);
	            }
	            else{
	                oth_cache.push(node)
	            }
	        }
	
	        return [sel_cache,oth_cache]
	    }
	
	    //fill the field with given options
	    function fill_with(field,options){
	        //clear firtst
	        field.options.length=0;
	        for(var i = 0;node = options[i];i++){
	                field.options[i]=new Option(node.text, node.value,
	                        false, false);
	        }
	
	    }
	    //adds to current field
	    function add_to(field,options){
	        for(var i = 0;node = options[i];i++){
	                field.appendChild(new Option(node.text, node.value,
	                        false, false));
	        }
	    }
	
	    // add action
	    if (this.id=='add_element'){
	        var c = get_checked(available);
	        add_to(choosen,c[0]);
	        fill_with(available,c[1]);
	    }
	    // remove action
	    if (this.id=='remove_element'){
	        var c = get_checked(choosen);
	        add_to(available,c[0]);
	        fill_with(choosen,c[1]);
	    }
	    // add all elements
	    if(this.id=='add_all_elements'){
	        for(var i=0; node = available.options[i];i++){
	                choosen.appendChild(new Option(node.text,
	                        node.value, false, false));
	        }
	        available.options.length = 0;
	    }
	    //remove all elements
	    if(this.id=='remove_all_elements'){
	        for(var i=0; node = choosen.options[i];i++){
	            available.appendChild(new Option(node.text,
	                    node.value, false, false));
	        }
	        choosen.options.length = 0;
	    }
	
	}
	
	YUE.addListener(['add_element','remove_element',
	               'add_all_elements','remove_all_elements'],'click',
	               prompts_action_callback)
	if (form_id !== undefined) {
		YUE.addListener(form_id,'submit',function(){
		    var choosen = YUD.get(selected_container);
		    for (var i = 0; i < choosen.options.length; i++) {
		        choosen.options[i].selected = 'selected';
		    }
		});
	}
}