/**
 * The YACS AJAX shared library.
 *
 * This file extends prototype, etc., to enhance interactions with the end-user
 *
 * @see users/heartbit.php
 * @see users/notifications.php
 * @see users/view.php
 *
 * @author Bernard Paques [email]bernard.paques@bigfoot.com[/email]
 * @reference
 * @license http://www.gnu.org/copyleft/lesser.txt GNU Lesser General Public License
 */

var Yacs = {

	/**
	 * alert the surfer
	 *
	 * @param message to display
	 */
	alert: function(message) {

		return alert(message);

	},

	/**
	 * reflect last sort in table
	 *
	 * @param target table
	 * @param sorted column
	 */
	beautifyTable: function(tblEl, col) {

		// look for class names in styles
		var rowTest = new RegExp("odd", "gi");
		var colTest = new RegExp("sorted", "gi");

		// alternate row appearance
		var i, j;
		var rowEl, cellEl;

		for (i = 0; i < tblEl.rows.length; i++) {
			rowEl = tblEl.rows[i];
			Element.removeClassName(rowEl, 'odd');
			if (i % 2 != 0)
				Element.addClassName(rowEl, 'odd');

			// set style classes on each column
			for (j = 2; j < tblEl.rows[i].cells.length; j++) {
				cellEl = rowEl.cells[j];
				Element.removeClassName(cellEl, 'sorted');
				// highlight the one that was sorted
				if (j == col)
					Element.addClassName(cellEl, 'sorted');
			}
		}

		// find the table header and highlight the column that was sorted
		var el = tblEl.parentNode.tHead;
		rowEl = el.rows[el.rows.length - 1];

		// set style classes for each column
		for (i = 0; i < rowEl.cells.length; i++) {
			cellEl = rowEl.cells[i];
			Element.removeClassName(cellEl, 'sorted');

			// highlight the header of the sorted column
			if (i == col)
				Element.addClassName(cellEl, 'sorted');
		}
	},

	/**
	 * clear one cookie
	 *
	 * @param cookie name
	 */
	clearCookie: function(name) {
		if(Yacs.getCookie(name)) {
			document.cookie = name + "=; expires=Thu, 01-Jan-70 00:00:01 GMT";
		}
	},

	/**
	 * ask for confirmation
	 *
	 * @param message to display
	 * @return true if this has been confirmed, false otherwise
	 */
	confirm: function(message) {

		return confirm(message);

	},

	/**
	 * compare two values
	 *
	 * @param first value
	 * @param second value
	 * @return comparison value
	 */
	compareValues: function(v1, v2) {

		// if the values are numeric, convert them to floats
		var f1 = parseFloat(v1);
		var f2 = parseFloat(v2);
		if(!isNaN(f1) && !isNaN(f2)) {
			v1 = f1;
			v2 = f2;
		}

		// compare the two values
		if(v1 == v2)
			return 0;
		if(v1 > v2)
			return 1;
		return -1;
	},

	/**
	 * detect Flash on client-side
	 *
	 * The script used to detect Flash under Firefox, etc. This is complemented
	 * by server-side detection for IE.
	 *
	 * This function also save findings in a cookie for transmission to server.
	 *
	 * @return 'yes' or 'no'
	 */
	detectFlash: function () {

		var detected = 'no';

		// cookie has already been set
		if(detected = Yacs.getCookie("FlashIsAvailable"))
			return detected;

		// detect in plugins
		if(navigator.plugins != null && navigator.plugins.length > 0) {
			if(navigator.plugins["Shockwave Flash 2.0"] || navigator.plugins["Shockwave Flash"]) {
				detected = 'yes';
			}
		} else if(navigator.userAgent.toLowerCase().indexOf("webtv") != -1) {
				detected = 'yes';
		}

		// remember this in a cookie
		Yacs.setCookie("FlashIsAvailable", detected);

		// return the result, if useful
		return detected;

	},

	/**
	 * compute and record surfer time zone
	 *
	 * This function computes the surfer time zone, and save it in a cookie to
	 * share this with the back-end
	 *
	 * @return the time zone
	 */
	detectTimeZone: function () {

		var timeZone;

		// cookie has already been set
		if(timeZone = Yacs.getCookie("TimeZone"))
			return timeZone;

		// compute time shift
		var now = new Date();
		timeZone = (-now.getTimezoneOffset() /60);
// 		var gmtDate = new Date(now.getFullYear(), 0, 1, 0, 0, 0, 0);
// 		var gmtString = gmtDate.toGMTString();
// 		var localDate = new Date(gmtString.substring(0, gmtString.lastIndexOf(" ")-1));
// 		var timeZone = (gmtDate - localDate) / (1000 * 60 * 60);

		// remember this in a cookie
		Yacs.setCookie("TimeZone", timeZone);

		// return the result, if useful
		return timeZone;

	},

	/**
	 * get the value of one cookie
	 *
	 * @param cookie name
	 * @return cookie value
	 */
	getCookie: function(name) {
		var prefix = name + "=";
		var begin = document.cookie.indexOf(prefix);
		if(begin == -1) { return null; }
		var end = document.cookie.indexOf(";", begin);
		if(end == -1) { end = document.cookie.length; }
		return unescape(document.cookie.substring(begin + prefix.length, end));

// 		var arg = name + "=";
// 		var alen = arg.length;
// 		var clen = document.cookie.length;
// 		var i = 0;
// 		var j = 0;
// 		while(i < clen){
// 			j = i + alen;
// 			if (document.cookie.substring(i, j) == arg) {
// 				var endstr = document.cookie.indexOf(";", j);
// 				if(endstr == -1){
// 				   endstr = document.cookie.length;
// 				}
// 				return unescape(document.cookie.substring(j, endstr));
// 			}
// 			i = document.cookie.indexOf(" ", i) + 1;
// 			if(i == 0)
// 				break;
// 		}
// 		return null;
	},

	/**
	 * get a sortable string
	 *
	 * This function finds and concatenate the values of all text nodes
	 *
	 * @param the target DOM element
	 * @return a sortable string
	 */
	getTextValue: function(el) {

		// the returned string
		var s = "";

		// look at every child node
		var i;
		for(i = 0; i < el.childNodes.length; i++) {

			// a text node
			if(el.childNodes[i].nodeType == 3)
				s += el.childNodes[i].nodeValue;

			// an element node which is a break
			else if((el.childNodes[i].nodeType == 1) && (el.childNodes[i].tagName == "br"))
				s += " ";

			// use recursion to get text within sub-elements
			else
				s += getTextValue(el.childNodes[i]);

		}

		return s;
	},

	/**
	 * we have received an 'alert' notification
	 *
	 * @see users/notifications.php
	 */
	handleAlertNotification: function(response) {

		// build a message in English if no localized message has been provided by the server
		if(typeof response['dialog_text'] != 'string')
			response['dialog_text'] = 'New at ' + response['title'] + "\nby:" + response['nick_name'] + "\n" + 'Would you like to browse this page?';

		// switch to the offered address, if accepted by surfer
		if(typeof response['address'] == 'string') {
			if(Yacs.confirm(response['dialog_text'])) {
				window.open(response['address']);
			}
		}
	},

	/**
	 * we have received a 'browse' notification
	 *
	 * @see users/notifications.php
	 */
	handleBrowseNotification: function(response) {

		// build a message in English if no localized message has been provided by the server
		if(typeof response['dialog_text'] != 'string')
			response['dialog_text'] = 'From ' + response['nick_name'] + ':' + "\n" + response['message'] + "\n" + 'Would you like to browse the provided link?';

		// switch to the offered address, if already in tracker, or if accepted by surfer
		if(typeof response['address'] == 'string') {
			if((window.name == 'yacs_tracker') || Yacs.confirm(response['dialog_text'])) {
				window.open(response['address'], 'yacs_tracker');
			}
		}
	},

	/**
	 * we have received a 'hello' notification
	 *
	 * @see users/notifications.php
	 */
	handleHelloNotification: function(response) {

		// build a message in English if no localized message has been provided by the server
		if(typeof response['dialog_text'] != 'string')
			response['dialog_text'] = 'From ' + response['nick_name'] + ':' + "\n" + response['message'] + "\n" + 'Would you like to chat with this person?';

		// only show the message to the end-user
		if(typeof response['address'] != 'string') {
			Yacs.alert(response['dialog_text']);

		// else switch to the offered address, if accepted by surfer
		} else {
			if(Yacs.confirm(response['dialog_text'])) {
				window.open(response['address']);
			}
		}
	},

	/**
	 * general initialization on window successful load
	 */
	onWindowLoad: function() {

		// resize according to surfer preferences
		Yacs.textSize();

		// subscribe to notifications
//		Yacs.subscribe('fast');
		setTimeout(function(){ Yacs.subscribe('fast') }, 10000);

		// compute and record surfer time zone
		Yacs.detectTimeZone();

		// detect Flash on client side
		Yacs.detectFlash();

		// pre-load the spinning image used during ajax updates
		Yacs.spinningImage = new Image();
		Yacs.spinningImage.src = url_to_root + 'skins/_reference/ajax_spinner.gif';

		// pre-load the image used at the working overlay
		Yacs.workingImage = new Image();
		Yacs.workingImage.src = url_to_root + 'skins/_reference/ajax_working.gif';

		// do not stop page loading
		setTimeout(function(){

			// change the behavior of buttons used for data submission, except those with style 'no_spin_on_click'
			var buttons = document.getElementsByTagName('button');
			for(var index = 0; index < buttons.length; index++) {
				var button = buttons[index];
				var buttonType = String(button.getAttribute('type'));
				if(buttonType.toLowerCase().match('submit') && !Element.hasClassName(button, 'no_spin_on_click')) {
					button.onclick = function () { Yacs.startWorking(); return true; };
				}
			}

 		}, 500);

	},

	/**
	 * open a popup window
	 */
	popup: function(options) {

		// default options
		this.options = {
			url: '#',
			width: 600,
			height: 500,
			name:"_blank",
			location:"no",
			menubar:"no",
			toolbar:"no",
			status:"yes",
			scrollbars:"yes",
			resizable:"yes",
			left:"",
			top:"",
			normal:false
		}

		// use provided options, if any
    	Object.extend(this.options, options || {});

    	// sanity check
		if (this.options.normal){
			this.options.menubar = "yes";
			this.options.status = "yes";
			this.options.toolbar = "yes";
			this.options.location = "yes";
		}

		// some computations
		this.options.width = this.options.width < screen.availWidth?this.options.width:screen.availWidth;
		this.options.height=this.options.height < screen.availHeight?this.options.height:screen.availHeight;
		var openoptions = 'width='+this.options.width+',height='+this.options.height+',location='+this.options.location+',menubar='+this.options.menubar+',toolbar='+this.options.toolbar+',scrollbars='+this.options.scrollbars+',resizable='+this.options.resizable+',status='+this.options.status
		if (this.options.top!="")openoptions+=",top="+this.options.top;
		if (this.options.left!="")openoptions+=",left="+this.options.left;

		// open the popup
	    window.open(this.options.url, this.options.name, openoptions);
		return false;
	},

	/**
	 * set a new cookie
	 *
	 * @param cookie name
	 * @param cookie value
	 * @param days before expiration (optional)
	 */
	setCookie: function(name, value) {
		var argv = arguments;
		var argc = arguments.length;
		var expires = (argc > 2) ? argv[2] : null;
		if(expires > 0) {
			var today = new Date();
			var future = today.getTime() + 3600000*24*expires;
 			expires = new Date();
			expires.setTime(future);
		}
 		var path = (argc > 3) ? argv[3] : '/';
		var domain = (argc > 4) ? argv[4] : null;
		var secure = (argc > 5) ? argv[5] : false;
		document.cookie = name + "=" + escape (value) +
			((expires == null) ? "" : ("; expires=" + expires.toGMTString())) +
			((path == null) ? "" : ("; path=" + path)) +
			((domain == null) ? "" : ("; domain=" + domain)) +
			((secure == true) ? "; secure" : "");
	},

	/**
	 * load some opaque overlay during back-end processing
	 */
	startWorking: function() {

		if(Yacs.workingOverlay) {
			Element.setStyle(Yacs.workingOverlay, { display: 'block' });
			return;
		}

		// insert some html at the bottom of the page that looks similar to this:
		//
		// <div id="yacsWorkingOverlay">
		//	<div>
		//		<img src="/yacs/skins/_reference/ajax_working.gif"/>
		//	</div>
		// </div>

		var objWorkingImage = document.createElement("img");
		objWorkingImage.setAttribute('src', url_to_root + 'skins/_reference/ajax_working.gif');

		var objCentered = document.createElement("div");
		Element.setStyle(objCentered, { position: 'absolute', top: '30%', left: '0%', height: '25%', width: '100%', textAlign: 'center', lineHeight: '0' });
		objCentered.appendChild(objWorkingImage);

		Yacs.workingOverlay = document.createElement("div");
		Yacs.workingOverlay.setAttribute('id','yacsWorkingOverlay');
		Element.setStyle(Yacs.workingOverlay, { position: 'fixed', top: '0', left: '0', zIndex: '1000', width: '100%', height: '100%', minHeight: '100%', backgroundColor: '#000', filter: 'alpha(opacity=20)', opacity: '0.2', display: 'block' });
		Yacs.workingOverlay.onclick = function() { Element.setStyle(Yacs.workingOverlay, { display: 'none' });};
		Yacs.workingOverlay.appendChild(objCentered);

		var objBody = document.getElementsByTagName("body").item(0);
		objBody.appendChild(Yacs.workingOverlay);

	},

	/**
	 * hide the working overlay
	 */
	stopWorking: function() {

		var handle = $('yacsWorkingOverlay');
		if(handle)
			Element.setStyle(handle, { display: 'none' });

	},

	/**
	 * subscribe to notifications sent by the back-end asynchronously
	 *
	 * @see users/heartbit.php
	 */
	subscribe: function(rate) {

		// not less than 3 seconds between two successive calls
		var now = new Date();
		if((typeof Yacs.subscribeStamp == "object") && (now.getTime() < (Yacs.subscribeStamp.getTime() + 3000)))
			return;
		Yacs.subscribeStamp = now;

		// a transaction is taking place
		if(Yacs.subscribeAjax)
			return;

		// change the pace
		Yacs.subscribeRate = rate;

		// clear on-going action, if any
		if((typeof Yacs.subscribeTimer == "number") && (Yacs.subscribeTimer > 0)) {
			clearTimeout(Yacs.subscribeTimer);
			Yacs.subscribeTimer = 0;
		}

		// start an ajax transaction
		Yacs.subscribeAjax = new Ajax.Request(url_to_root + 'users/heartbit.php', {
			method: 'get',
			parameters: { },
			onSuccess: function(transport) {

				// dispatch received notification
				var response = transport.responseText.evalJSON();
				switch(response['type']) {
				case 'alert':
					Yacs.handleAlertNotification(response);
					break;
				case 'browse':
					Yacs.handleBrowseNotification(response);
					break;
				case 'hello':
					Yacs.handleHelloNotification(response);
					break;
				}

				// minimum time between two successive notifications
				Yacs.subscribeTimer = setTimeout(function(){ Yacs.subscribe('fast') }, 10000);
				Yacs.subscribeAjax = null;
			},
			onFailure: function(transport) {
				// regular idle cycle
				if(Yacs.subscribeRate == "fast") {
					Yacs.subscribeTimer = setTimeout(function(){ Yacs.subscribe('fast') }, 15000);
				} else {
					Yacs.subscribeTimer = setTimeout(function(){ Yacs.subscribe('slow') }, 120000);
				}
				Yacs.subscribeAjax = null;
			}
		});
	},

	// remember polling rate
	subscribeRate: "fast",

	// on-going timer, if any
	subscribeTimer: 0,

	/**
	 * sort a table
	 *
	 * @param id of the table, tbody or tfoot element to be sorted
	 * @param index of the column to sort, starting at 0
	 * @param if true, sort in reverse order
	 */
	sortTable: function(id, column, rev) {

		// get the table or table section to sort
		var handle = document.getElementById(id);

		// on first sort set up an array of reverse sort flags
		if (handle.reverseSort == null) {
			handle.reverseSort = new Array();
		}

		// set the initial sort direction.
		if (handle.reverseSort[column] == null)
			handle.reverseSort[column] = rev;

		// if this column was the last one sorted, reverse its sort direction
		if(column == handle.lastColumn)
			handle.reverseSort[column] = !handle.reverseSort[column];

		// remember this column as the last one sorted
		handle.lastColumn = column;

		// hide the table during operations
		var oldDsply = handle.style.display;
		handle.style.display = "none";

		// use a selection sort algorithm
		var tmpEl;
		var i, j;
		var minVal, minIdx;
		var testVal;
		var cmp;
		for(i = 0; i < handle.rows.length - 1; i++) {

			// assume the current row has the minimum value
			minIdx = i;
			minVal = Yacs.getTextValue(handle.rows[i].cells[column]);

		    // look for a smaller values in following rows
		    for(j = i + 1; j < handle.rows.length; j++) {
				testVal = Yacs.getTextValue(handle.rows[j].cells[column]);
				cmp = Yacs.compareValues(minVal, testVal);

				// the case of reverse ordering
				if(handle.reverseSort[column])
					cmp = -cmp;

				// sort by the first column if those values are equal
				if(cmp == 0 && column != 0)
				cmp = Yacs.compareValues(Yacs.getTextValue(handle.rows[minIdx].cells[0]), Yacs.getTextValue(handle.rows[j].cells[0]));

				// this is the new minimum
				if (cmp > 0) {
					minIdx = j;
					minVal = testVal;
				}
			}

			// move the minimum row just below the current row
			if (minIdx > i) {
				tmpEl = handle.removeChild(handle.rows[minIdx]);
				handle.insertBefore(tmpEl, handle.rows[i]);
			}
		}

		// beautify the table
		Yacs.beautifyTable(handle, column);

		// Set team rankings.
		//  setRanks(handle, column, rev);

		// show the table again
		handle.style.display = oldDsply;

		return false;
	},

	/**
	 * @link http://ajaxcookbook.org/
	 */
	syslog: function(message) {
	    if (!Yacs.window_ || Yacs.window_.closed) {
    	    var win = window.open("", null, "width=400,height=200," +
        	                      "scrollbars=yes,resizable=yes,status=no," +
            	                  "location=no,menubar=no,toolbar=no");
        	if (!win) return;
        	var doc = win.document;
        	doc.write("<html><head><title>Debug Log</title></head>" +
                  "<body></body></html>");
        	doc.close();
        	Yacs.window_ = win;
    	}
    	var logLine = Yacs.window_.document.createElement("div");
    	logLine.appendChild(Yacs.window_.document.createTextNode('=> ' + message));
    	Yacs.window_.document.body.appendChild(logLine);
	},

	/**
	 * This handler adds code to tabbing items, to process further clicks.
	 *
	 * It has to be called once, after the construction of proper DOM elements.
	 * You can either build the DOM manually, then place a call to this function,
	 * or, alternatively, just call Skin::build_tabs() from within you PHP code
	 * to have everything done automatically.
	 *
	 * @see skins/skin_skeleton.php
	 *
	 * More information for those who would like to do it the hard way:
	 *
	 * @param tabs A list of tabs related to panels and URLs
	 * @param args This corresponds to the Ajax options supported in prototype.js
	 *
	 * A live example featured in users/view.php:
	 *
	 *	<script type="text/javascript"><!--
	 *	// animate tabs after page loading
	 *	Event.observe(window, 'load', function() { Yacs.tabs({
	 *		'contributions_tab': [ 'contributions_panel' ],
	 *		'activities_tab': [ 'activities_panel', '/yacs/user-element/2-activities' ],
	 *		'actions_tab': [ 'actions_panel', '/yacs/user-element/2-actions' ],
	 *		'contact_tab': [ 'contact_panel', '/yacs/user-element/2-contact' ],
	 *		'information_tab': [ 'information_panel', '/yacs/user-element/2-information' ],
	 *		'preferences_tab': [ 'preferences_panel', '/yacs/user-element/2-preferences' ]}, {})
	 *		});
	 *	// -->
	 *	</script>
	 *
	 * @see users/view.php
	 *
	 * @link http://actsasflinn.com/Ajax_Tabs/index.html AJAX Tabs (Rails redux)
	 * @link http://20bits.com/2007/05/23/dynamic-ajax-tabs-in-20-lines/
	 */
	tabs: function(tabs, args) {

		// attach behavior to each item
		for(id in tabs) {

			// react to clicks
			Event.observe($(id), 'click', function(e) {

				// target the clicked tab
				var clicked = Event.element(e);

				// if we click on a link, move upwards to list item -- 'a' is for XHTML strict, 'A' for other cases
				if((clicked.tagName == 'a') || (clicked.tagName == 'A'))
					clicked = clicked.parentNode;

				// trigger custom behavior, if any
				if(typeof(args.onClick) == 'function')
					args.onClick(clicked);

				// activate the clicked tab -- see skins/_reference/ajax.css
				for(iterator in tabs) {
					if(clicked.id == $(iterator).id) {
					 	$(iterator).className = 'tab-foreground';
				 	} else {
					 	$(iterator).className = 'tab-background';
				 	}
			 	}

				// activate the related panel -- see skins/_reference/ajax.css
				for(iterator in tabs) {
					if(clicked.id == $(iterator).id) {
					 	$(tabs[iterator][0]).className = 'panel-foreground';

					 	// load panel content, if any
					 	if(tabs[iterator].length > 1) {
						 	Yacs.updateOnce(tabs[iterator][0], tabs[iterator][1], args);
					 	}

				 	} else {
					 	$(tabs[iterator][0]).className = 'panel-background';
				 	}
			 	}

			 	// do not propagate event
				Event.stop(e);
			})

		}
	},

	/**
	 * change text size
	 *
	 * Use this function in links, to allow surfer to adjust text size.
	 *
	 * @param target container in page
	 * @param size increment or decrement
	 */
	textResize: function(handle, increment) {

		// get current size
		var current = Yacs.getCookie('TextSize');
		if(current == null)
			currentSize = 2;
		else {
			eval("current = "+current);
			currentSize = current['size'];
		}

		// change it
		currentSize += increment;
		if(currentSize < 0 ) currentSize = 0;
		if(currentSize > 6 ) currentSize = 6;

		// save it for 6 months = 6 * 30 = 180 days
		Yacs.setCookie('TextSize', '{ handle: "' + handle + '", size: ' + currentSize + ' }', 180);

		// actual style update
		Yacs.textSize();
	},

	/**
	 * enforce current text settings
	 *
	 * This is invoked each time a page is loaded
	 */
	textSize: function() {

		// use data from cookie
		var current = Yacs.getCookie('TextSize');
		if(current == null)
			return;
		eval('current = ' + current);

		// do nothing on standard size
		if(current == 2)
			return;

		// get actual style
		var allSizes = [ 'xx-small', 'x-small', 'small', 'medium', 'large', 'x-large', 'xx-large' ];
		var currentSize = allSizes[ current['size'] ];

		// resize the target container
		$(current['handle']).style.fontsize = currentSize;

		// also resize poorly inherited items
		allTags = $(current['handle']).getElementsByTagName('div');
		for(index = 0; index < allTags.length; index++ ) { allTags[index].style.fontSize = currentSize; }
		allTags = $(current['handle']).getElementsByTagName('td');
		for(index = 0; index < allTags.length; index++ ) { allTags[index].style.fontSize = currentSize; }
		allTags = $(current['handle']).getElementsByTagName('tr');
		for(index = 0; index < allTags.length; index++ ) { allTags[index].style.fontSize = currentSize; }
	},

	/**
	 * toggle a folded box
	 *
	 * @param the box
	 * @param string URL of the extending icon
	 * @param string URL of the collapsing icon
	 */
	toggle_folder: function(node, plus_href, minus_href) {

		// unfold the branch if it is not visible
		if(node.nextSibling.style.display == 'none') {
			node.nextSibling.style.display = 'block';

			// change the image (if there is an image)
			if(node.childNodes.length > 0) {
				if(node.childNodes.item(0).nodeName == "IMG") {
					node.childNodes.item(0).src = minus_href;
				}
			}

		// collapse the branch if it is visible
		} else {
			node.nextSibling.style.display = 'none';

			// change the image (if there is an image)
			if(node.childNodes.length > 0) {
				if(node.childNodes.item(0).nodeName == "IMG") {
					node.childNodes.item(0).src = plus_href;
				}
			}

		};

	},

	/**
	 * update content asynchronously
	 *
	 * This function displays a nice spinning image while loading the page.
	 *
	 * @param string id of the target CSS container
	 * @param string web address to fetch new snippet
	 * @param mixed additional parameters to transmit to Ajax
	 *
	 */
	update: function(panel, address, args) {

		new Ajax.Updater(panel, address, $H({
			asynchronous: true,
			method: 'get',
			evalScripts: true,
	  		onLoading: function(request) { $(panel).innerHTML = '<img alt="*" src="' + Yacs.spinningImage.src + '" style="vertical-align:-3px" />' }
			}).merge(args)
		);

	},

	/**
	 * set content only once
	 *
	 * This function is similar to Yacs.update(), except it does nothing if the target
	 * item already contains something.
	 *
	 * Use this function for example to populate tabbed panels of a complex page.
	 * Look at Yacs.tabs() above for a practical example of use
	 *
	 * @param string id of the target CSS container
	 * @param string web address to fetch new snippet
	 * @param mixed additional parameters to transmit to Ajax
	 *
	 */
	updateOnce: function(panel, address, args) {

		// do nothing if the panel contains something
		if(!$(panel).innerHTML || ($(panel).innerHTML == '') || ($(panel).innerHTML == '<img alt="*" src="' + Yacs.spinningImage.src + '" style="vertical-align:-3px" />')) {
			Yacs.update(panel, address, args);
		}

	}

}

// ready to receive new notifications
Event.observe(window, 'load', function() { Yacs.onWindowLoad() });

// that's a hack for Firefox --yes, I know, Yacs should always be defined here...
// if(typeof Yacs == 'object') {

// 	// slow down notifications on window blur -- not active on IE7?
// 	Event.observe(document, 'blur', function() { Yacs.subscribe('slow'); });

// 	// boost notifications on window focus
// 	Event.observe(document, 'focus', function() { Yacs.subscribe('fast'); });

// }

