/*!
 * Regio MapCat Components
 * Copyright Regio AS
 * http://mapcat.regio.ee
 */ 

/*!
 * Build: Fri Sep 3 5:36:25 EEST 2010
 * URL: #units=Regio.Utils,MapCat.Namespace,MapCat.Listener,MapCat.Listeners,Regio.ListView,Regio.SearchList,Regio.Session,Regio.SearchList.ProviderRegioJGCJSONP,MapCat.Log,MapCat.Component,Debug
 */
if (typeof(Regio) == "undefined") {
	Regio = {};
}

;(function($) {


/**
 * Component: Regio.Utils
 * Some everywhere used functions.
 * 
 * Version: 2.5
 * 
 * Author: Alexandr Smirnov (alex@regio.ee)
 */

Regio.Utils = {};

/**
 * Function: Regio.Utils.format
 * Formats string according to template. Can work in two ways:
 * - Regio.Utils.format('{0}_{1}_{2}', 1, 2, 3) will return string "1_2_3"
 * - Regio.Utils.format('{a}_{b}_{c}', { a: 1, b: 2, c: 3 }) will return string "1_2_3"
 * 
 * Parameters:
 * str - format string
 * obj OR ... - either object ({objectKey1}...{objectKeyN} will be substituted) 
 * or any number of parameters ({0}..{arguments count-2} will be substituted)
 */
Regio.Utils.format = function(str, obj) {
	if (typeof(obj) == "object") {
		for(var i in obj) {
			var re = new RegExp('\\{' + (i) + '\\}','gmi');
			str = str.replace(re, obj[i]);
		}
	} else {
		for(var i=1;i<arguments.length;i++) {
			var re = new RegExp('\\{' + (i-1) + '\\}','gmi');
			str = str.replace(re, arguments[i]);
		}
	}	
	return str;
}

// same as Regio.Utils.format('{a}_{b}_{c}', { a: 1, b: 2, c: 3 }) but will also work with multilayered queryes (delimeted by ".") like: {a.b.c}
Regio.Utils.formatEx = function(str, obj) {
	var matches = str.match(/{([a-zA-Z0-9\.]+)}/gi);
	if(matches) {
		for(var i=0; i<matches.length; i++) {
			var match = matches[i].replace(/{|}/g, "");
			var path = match.split(".");
			
			var key = obj, found = true;
			
			for(var j=0; j<path.length; j++) {
				if(typeof key != "object") {
					found = false;
					break;
				} else {
					key = key[path[j]];
				}
			}
			
			if(found && (typeof key != "undefined")) {
				str = str.replace("{"+match+"}", key);
			}
		}
	}
	
	return str;
}

/**
 * Function: Regio.Utils.removeSubstr
 * Removes all substring from specified string
 * 
 * Parameters:
 * str - string to remove substrings from
 * ... - strings, any number of arguments containg substrings to remove
 */
Regio.Utils.removeSubstr = function(str) {
	// TODO: make replace function and use it here
	for (var i=1;i<arguments.length;i++) {
		var re = new RegExp(arguments[i], 'g');
		str = str.replace(re, '');
	}
	
	return str;
}

/**
 * Function: Regio.Utils.initTemplate
 * Take inner contents of div and return it formatted accroding to specifyed *type*
 * 
 * Parameters:
 * div - DOM element (or element id), whose contents should be taken
 * type - if ="html" then div.innerHTML will be returned unmodifyed. If ="object", then eval(div.innerHTML) will be returned.
 * 
 * Throws:
 * - Error if specified div does not exists or is not DOM element.
 * - Error if type="object" and eval failed with exception.
 */
Regio.Utils.initTemplate = function(div, type) {
	div = Regio.Utils.getElement(div);
	if (!div) {
		throw new Error('Regio.Utils.initTemplate, incorrect div');
	}
	
	var s = Regio.Utils.removeSubstr(new String(div.innerHTML), '<!--', '-->', ' *\n *', ' *\r *', '\t');
	Regio.Utils.empty(div);
	
	if (type == "html") {
		// treat it as html/string template
		return s;
	} else {
		// default type == "object"
		// try it as object
		if (s == '') {
			return {};
		}
		try {
			return eval('('+s+')');
		} catch(err) {
			var msg = (err.description ? err.description : err);
			throw new Error('Regio.Utils.initTemplate('+(div.id ? div.id : div.name)+') failed with message: '+msg);
		}
	}
}

Regio.Utils.empty = function(div) {
	while(div.childNodes.length > 0) {
		div.removeChild(div.childNodes[0]);
	}
	return div;
}

/**
 * Function: Regio.Utils.applyDefaults
 * Apply default values for given object. If key exists in second specified object (values) but does not exists in first specified object (cfg) then add that key/value pair to the first object.
 * 
 * Parameters:
 * cfg - object to be modifyed
 * values - objects, containing default values
 */
Regio.Utils.applyDefaults = function(cfg, values) {
	for (var key in values) {
		if (cfg[key] == null) {
			cfg[key] = values[key];
		}
	}
	return cfg;
}

/**
 * Function: Regio.Utils.getElement
 * If provided div is string, then this function will return DOM element with ID=div, else this function will return provided div untouched
 * 
 * Parameter:
 * div - DOM element OR string (div ID)
 */
Regio.Utils.getElement = function(div) {
	if (typeof(div) == "string") {
		div = document.getElementById(div);
	}
	return div;
}

/**
 * Function: Regio.Utils.isArray
 * Return true if _obj_ is array
 * 
 * Parameters:
 * obj - object to be tested
 */
Regio.Utils.isArray = function(obj) {
   return ((typeof(obj) == "object") && (obj.constructor == Array));
}

/**
 * Function: Regio.Utils.getElementsByClassName
 * Return all childs of _element_ with class name=clsName.
 * 
 * Parameters:
 * element - parent element which childs to search
 * clsName - class name to search for
 * 
 * Returns:
 * Array of all element childs with specified class name
 */
Regio.Utils.getElementsByClassName = function(element, clsName){
	// http://www.netlobo.com/javascript_getelementsbyclassname.html
	
    var retVal = new Array();
    var elements = element.getElementsByTagName("*");
    for(var i = 0;i < elements.length;i++){
        if(elements[i].className.indexOf(" ") >= 0){
            var classes = elements[i].className.split(" ");
            for(var j = 0;j < classes.length;j++){
                if(classes[j] == clsName)
                    retVal.push(elements[i]);
            }
        }
        else if(elements[i].className == clsName)
            retVal.push(elements[i]);
    }
    return retVal;
}

Regio.Utils.createUniqueId = function(prefix) {
	return prefix + Math.random().toString().split(".")[1];
}

// strategy:
// FIRST_IMMEDIATE - (default) first call will be executed immediately, and wait will be triggered
// FIRST_WAIT - first call will trigger wait and will be executed only after updateOncePer ms
Regio.Utils.createDelayedFunction = function(callback, updateOncePer, strategy) {
	// preiodical updater code (make function run not more that once per "updateOncePer" ms)
	return function() {
		var args = arguments;
		
		var func = arguments.callee;
		var lastTime = func.last, curTime = Number(new Date());
		if((typeof lastTime == "undefined") && (strategy == "FIRST_WAIT")) {
			func.last = lastTime = curTime;
		}
		clearTimeout(func.timer);
		if (curTime - lastTime < updateOncePer) {
			func.timer = setTimeout(function() { func.apply(this, args) }, updateOncePer/4);
			return;
		}
		func.last = curTime;
		
		callback.apply(this, args);
	}
}


/**
 * Component: MapCat.Namespace
 * MapCat FlashTile API Namespace
 * 
 * Author: Alexandr Smirnov (alex@regio.ee)
 * 
 * Group: MapCat FlashTile API
 * 
*/

window.MapCat = { // namespace
	Version: '2.x'
}




/**
 * Component: MapCat.Listener
 * Listener descriptor object. Basically contains: callback, contest and set of functions to manipulate (ie. remove, call)
 * 
 * Group: MapCat FlashTile API
 * 
 * Version: 1.0
 * 
 * Requires: MapCat.Namespace
 * 
 * Author:
 * Alexandr Smirnov (alex@regio.ee)
 */
MapCat.Listener = function(listeners, callback, contest) {
	this.callback = callback;
	this.contest = contest;
	
	this.call = function(/*args for callback*/) {
		return callback.apply(contest, arguments);
	}
	
	this.remove = function() {
		listeners.removeByFunction(callback);
	}
}


/**
 * Component: MapCat.Listeners
 * Class implementing publish/subscribe pattern.
 * 
 * Group: MapCat FlashTile API
 * 
 * Version: 1.4.2
 * 
 * Requires: MapCat.Namespace, MapCat.Listener
 * 
 * Author:
 * Alexandr Smirnov (alex@regio.ee)
 */
MapCat.Listeners = function() {
	this.list = {};
}

MapCat.Listeners.prototype = {
	/**
	 * Method: add
	 * Add new listener to array.
	 * Parameters:
	 * eventName - String, Name of event that this listener is interested in
	 * callback - Function
	 * contest - "this"
	 * Returns: 
	 * This MapCat.Listeners object again. So, it could be used as follows: listeners.add(xxx).add(xxx)...
	 */
	add: function(eventName, callback, contest) {
		this.addEx.apply(this, arguments);
		return this;
	},
	/**
	 * Method: addEx
	 * Same as <add> but returns <MapCat.Listener>.
	 * Parameters:
	 * eventName - String, Name of event that this listener is interested in
	 * callback - Function
	 * contest - "this"
	 * Returns: 
	 * <MapCat.Listener>
	 */
	addEx: function(eventName, callback, contest) { // ver 1.4
		var me = this;
		
		var rec = new MapCat.Listener(this, callback, contest)
		
		if (this.list[eventName]) {
			this.list[eventName].push(rec);
		} else {
			this.list[eventName] = [rec];
		};
		
		return rec;
	},
	/**
	 * Method: removeByFunction
	 * Remove event listener by function pointer.
	 * 
	 * Parameters:
	 * func - Function, pointer to callback function defined in <add> or <addEx>
	 * 
	 * Returns: 
	 * This MapCat.Listeners object again.
	 */
	removeByFunction: function(func) { // ver 1.4
		for(var a in this.list) {
			var arr = this.list[a];
			for(var i=arr.length-1; i>=0; i--) {
				var rec = arr[i];
				if(rec.callback == func) {
					arr.splice(i, 1);
				}
			}
			if(arr.length == 0) {
				delete this.list[a];
			}
		}
		return this;
	},
	/**
	 * Method: broadcast
	 * 
	 * Call all listeners that are interested in this event. Function can have any number of arguments. 
	 * All of them, except first (eventName) will be transfered to callback function. 
	 * 
	 * Parameters:
	 * eventName - String, Name of event that this listener is interested in
	 * ... - set of parameters to be passed to event handler
	 * 
	 * Returns:
	 * Result from last event handler that returned something different from "undefined"
	 */
	broadcast: function() {
		var args = [undefined];
		for(var i=0; i<arguments.length; i++) {
			args.push(arguments[i]);
		}
		return this.broadcastEx.apply(this, args);
	},
	/**
	 * Method: broadcastEx
	 * Same as <broadcast> but has additional (first) parameter "filter".
	 * 
	 * Parameters:
	 * filter - Function, that accepts one argument <MapCat.Listener>. Should return true if this Listener should be processes for this broadcast or false if not. If argument is ommited, then function acts exzatly like regular <broadcast>.
	 * eventName - String, Name of event that this listener is interested in
	 * ... - set of parameters to be passed to event handler
	 * 
	 * Returns:
	 * Result from last event handler that returned something different from "undefined"
	 */
	broadcastEx: function(filter, eventName /*, params*/) {
		var result, l, args;
		
		function processQueue(l, args) {
			for(var i=0;i<l.length;i++) {
				var rec = l[i];
				if (filter && !filter(rec)) {
					continue;
				}
				var r = rec.call.apply(rec, args);
				if (typeof(r) != "undefined" && (r != null)) {
					result = r;
				}
			}
		}
		
		// with arguments without first (filter)
		args = [];
		for(var i=1;i<arguments.length;i++) {
			args.push(arguments[i]);
		}
		
		if(l = this.list["*"]) {
			processQueue(l, args);
		}
		
		// remove eventName argument from arguments list
		args.shift();
		
		if(l = this.list[eventName]) {
			processQueue(l, args);
		}
		
		return result;
	},
	/**
	 * Method: broadcast
	 * 
	 * Same as <broadcast>
	 */
	call: function() { // ver 1.4
		return this.broadcast.apply(this, arguments);
	}
}



/**
 * Component: Regio.ListView
 * Formats data array using provided templates.
 * Supports filtering of data, etc.
 * 
 * Requires: Regio.Utils
 * 
 * Version: 1.40
 *
 * Author: 
 * Alexandr Smirnov (Regio)
 * 
 * Usage:
 * | list = Regio.ListView('list', {
 * |   rowTemplate: '<div>{a} {b}</div>'
 * | });
 * | 
 * | list.setData([
 * |   { a: 'first', b: 1 },
 * |   { a: 'second', b: 2 },
 * |   { a: 'third', b: 3 }
 * | ]);
 * | 		
 * | list.setFilter(function(arr) {
 * |   return arr.b > 2;
 * | });
 * This sample, will fill div (with id="list") with following HTML:
 * | <div>first 1</div>
 * | <div>second 2</div>
 * | <div>third 3</div>
 * And after that will apply filter (see <setFilter>) which will set div innerHTML to:
 * | <div>third 3</div>
 */



/**
 * Constructor: Regio.ListView
 * Create and initialize ListView component.
 * 
 * Parameters:
 * div - DOM element, where list items will be drawn
 * cfg - Configuration
 * 
 * Configuration:
 * rowTemplate - template of each row (should contain substitution tags like {aaa}, see <Regio.Utils.format>). Before drawing each row, component will take this template, fill it with information provided with <setData> function and then, will add resulting HTML to DOM (into provided div). Can be something like: "<div class="searchList_row">{text}</div>" (this will work, if <setData> will be called with something like setData([{text: 'aaa'}, {text: 'bbb'}])
 * filter - optional, function, filter that will be applyed be default (intially) (default: function() { return true })
 */
Regio.ListView = function(div, cfg) {
	cfg = cfg || {};
	div = Regio.Utils.getElement(div);
	
	var me = this;
	var data = [];
	var filterObj = { filter: function() { return true } }; // show all by deafult
	
	Regio.Utils.applyDefaults(cfg, {
		rowTemplate: ''
	});
	
	if (typeof(cfg.filter) != "undefined") {
		this.setFilter(cfg.filter);
	}
	
	clear();
	
	// end of init
	
	function clear() {
		if (me.beforeClear) me.beforeClear();
		Regio.Utils.empty(div);
	}
	
	function drawRow(row, i, cnt) {
		var elem = document.createElement(div.tagName);
		
		// The innerHTML property of the TABLE, TFOOT, THEAD, and TR elements are read-only in IE. :(
		if(window.jQuery) {
			jQuery(elem).append(jQuery(Regio.Utils.format(cfg.rowTemplate, row)));
		} else {
			elem.innerHTML = Regio.Utils.format(cfg.rowTemplate, row);
		}
		
		if (this.beforeDrawRow)
			this.beforeDrawRow(row, elem.childNodes, i, cnt);
		
		var childs = [];
		
		while(elem.childNodes.length > 0) {
			var c = elem.childNodes[0];
			if (this.beforeDrawRowChildNode)
				this.beforeDrawRowChildNode(c, div, row);
			
		    childs.push(div.appendChild(c));
			
			if (this.afterDrawRowChildNode)
				this.afterDrawRowChildNode(c, div, row);
		}
		
		if (this.afterDrawRow)
			this.afterDrawRow(row, childs, i, cnt);
	}
	
	function redraw() {
		if(me.beforeRedraw) {
			me.beforeRedraw();
		}
		
		clear();
		execute('current', drawRow, me);
		
		if(me.afterRedraw) {
			me.afterRedraw();
		}
	}
	
	/**
	 * Function: execute
	 * Take current ListView data, apply filter to it and do callback for all accepted rows.
	 * If filterObject == string 'current' then take current listView filter.
	 * This function does not change contents/data of ListView. 
	 * 
	 * Parameters:
	 * filterObject - filtering *object* (see <setFilter>)
	 * callback - callback for each resulting row data
	 * me - "this" for callback function
	 */
	function execute(filterObject, callback, me) {
		if (filterObject == 'current') {
			filterObject = filterObj;
		}
		
		if (filterObject.start) filterObject.start();
		
		var cnt = 0;
		for (var i=0; i<data.length; i++) {
			if (filterObject.filter ? filterObject.filter(data[i], cnt) : true) { // draw only allowed rows
				callback.call(me, data[i], i, cnt);
				cnt++;
			}
		}
		
		if (filterObject.finish) filterObject.finish();
	}
	
	// see execute()
	this.execute = execute; // public
	
	/**
	 * Function: setData
	 * Set data of listView. This data will be filtered, then merged with provided rowTemplate (see <Regio.ListView>) and then, resulting HTML will be applyed to DOM element div.
	 * 
	 * Parameter:
	 * arr - array of objects. For each line - one object. Keys from this object will be used for substitution into rowTemplate.
	 */
	this.setData = function(arr) { // public
		if (typeof(arr) != "object") {
			throw new Error('Regio.ListView.setData, arr is not array');
		}
		data = arr;
		redraw();
	}
	
	/**
	 * Function: getData
	 * Returns data, previously set by setData
	 */
	this.getData = function() {
		return data;
	}
	
	/**
	 * Function: clear
	 * Clears ListView data. Equal to setData([])
	 */
	this.clear = function() { // public
		this.setData([]);
	}
	
	/**
	 * Function: setFilter
	 * Sets filtering of data. Filtering can be implemented for displaying paged results for example.
	 * 
	 * Parameters:
	 * callback - *function* OR *object*. If *function* provided, then this function will be applyed for each row of listView data. And if this function will return TRUE, then this row will be displayed in div. If *object* is provided, then this object must contain "filter" method that will act same way. Also, this object can optionally contain "start" and "finish" methods that will be called before filtering start or after finltering finished.
	 */
	// can accept either function (to be treated as filter function) or
	// object (in this case object should contain "filter" callback/method
	// and optionally "start" and "finish" callbacks)
	this.setFilter = function(callback) { // public
		if (typeof(callback) == "function") {
			filterObj.filter = callback;
		} else if (typeof(callback) == "object") {
			if (!callback.filter) {
				throw new Error('ListView.setFilter callback seems to be filter object but doe not contain "filter" method');
			}
			filterObj = callback;
		}
		redraw();
	}
	
	/**
	 * Function: getFilter
	 * Returns currently active filter *object*. See <setFilter>
	 */
	this.getFilter = function() {
		return filterObj;
	}
	
	/**
	 * Function: refresh
	 * Clears DIV and redraws its contents.
	 */
	this.refresh = function() { // public
		redraw();
	}
	
	return this;
}


/**
 * Component: Regio.SearchList
 * Search component base. Fetches data from one of providers and 
 * displays a paged list of results using ListView component.
 * 
 * Group: Search
 * 
 * Requires: Regio.Utils, Regio.ListView, MapCat.Listeners
 * 
 * Version: 1.29
 * 
 * Other Requirements:
 * jQuery - OPTIONAL !! Animation is disabled without it AND providers MUST be functions without it.
 * 
 * See also:
 * <Regio.SearchList.createFromMarkup> - for simpler use of search component
 * 
 * Author:
 * Alexandr Smirnov (alex@regio.ee)
 */



/**
 * Constructor: Regio.SearchList
 * Creates SearchList component in provided DIV (DOM element).
 * 
 * Parameters:
 * div - DOM element, that will contain search results
 * cfg - Configuration
 * 
 * Configuration:
 * providers - object, describing available providers (see <Regio.SearchList.ProviderRegioJGC>). Sample: *{ jgc: Regio.SearchList.ProviderRegioJGC() }*
 * rowsPerPage - number of rows to be shown per one page
 * rowTemplate - see <Regio.ListView> for format description
 * tabFilter - optional, function(tabNum, rowObject) to apply to each row for particular tab
 */
/**
 * Event: query
 */
/**
 * Event: row
 */
/**
 * Event: clear
 */
/**
 * Event: page
 */
/**
 * Event: tab
 */
/**
 * Event: message
 * Fired when message should be shown to user.
 * 
 * Parameters:
 * type - "empty", "wait", "error" or "clear"
 * searchType - optional, provider name
 * errorDesc - optional, error description
 */
/**
 * Event: items
 */
/**
 * Event: submit
 */
Regio.SearchList = function(div, cfg) {
	/**
	 * Property: listeners
	 * Events dispatcher.
	 */
	var listeners = new MapCat.Listeners();
	var div = Regio.Utils.getElement(div);
	if (!div) {
		throw new Error('Regio.SearchList, provided DIV is incorrect!');
	}
	
	var submitting = false;
	var lastAjax = null, lastQuery, lastProvider;
	
	Regio.Utils.applyDefaults(cfg, {
		rowsPerPage: 20,
		tabFilter: function() {}
	});
	
	if (!cfg.rowTemplate) {
		cfg.rowTemplate = Regio.Utils.initTemplate(div, 'html');
	}
	
	if (typeof(cfg.providers) != "object") {
		throw new Error('Regio.SearchList, no providers defined!');
	}
	
	var list = new Regio.ListView(div, cfg);
	list.beforeDrawRow = function(obj, childNodes, i, cnt) {
		listeners.broadcast("row", obj, childNodes, i, cnt, tabs.curTab);
	}
	list.beforeClear = function() {
		listeners.broadcast("clear");
	}
	list.afterRedraw = function() {
		listeners.broadcast("redraw");
	}
	// does everything related to paging. only filter actually matters here.
	var paging = {
		curPage: 0, // read only, == 0 even if no data
		pageCount: 0, // read only
		filter: function(obj, cnt, num) {
			// cnt = number of items already in list
			// num = current item number in data array
			return (num > this.curPage * cfg.rowsPerPage) && (num <= (this.curPage + 1) * cfg.rowsPerPage);
		},
		setPageCount: function(cnt) {
			if (this.pageCount != cnt) {
				this.pageCount = cnt;
				if (this.curPage > cnt-1) {
					this.curPage = cnt-1;
				}
				if (this.curPage < 0) {
					this.curPage = 0;
				}
				listeners.broadcast("page", this.pageCount == 0 ? 0 : this.curPage+1, this.pageCount);
			}
		},
		resetToRowsCount: function(cnt) {
			var newPageCnt = Math.ceil(cnt / cfg.rowsPerPage);
			if (this.pageCount != newPageCnt) {
				this.setPageCount(newPageCnt);
				this.curPage = 0;
			}
		},
		setPage: function(p) {
			if ((this.curPage != p) && (p >= 0) && (p < this.pageCount)) {
				this.curPage = p;
				
				list.refresh();
				listeners.broadcast("page", this.curPage+1, this.pageCount);
			}
		},
		nextPage: function() { this.setPage(this.curPage+1) },
		prevPage: function() { this.setPage(this.curPage-1) }
	}
	paging.setPageCount(0);
	
	// does everything about tabs. you need only filter from here.
	var tabs = {
		curTab: 0, // read only
		filter: function(obj, cnt) {
			// cnt = number of items already in list
			var res = cfg.tabFilter(this.curTab, obj, cnt);
			if (typeof(res) == "undefined") {
				res = true;
			}
			return res;
		},
		setTab: function(num) {
			if (this.curTab != num) {
				this.curTab = num;
				paging.setPage(0);
				list.refresh();
				listeners.broadcast("tab", this.curTab);
			}
		}
	}
	
	var filter = {
		num: 0,
		start: function() {
			this.num = 0;
		},
		filter: function(obj, cnt) {
			var tabAccepts = tabs.filter(obj, cnt);
			if (tabAccepts) {
				this.num++;
			}
			obj.num = this.num; // process {num} tags
			return paging.filter(obj, cnt, this.num) && tabAccepts;
		},
		finish: function() {
			paging.resetToRowsCount(this.num);
		}
	}
	list.setFilter(filter);
	
	function showMessage(msgType, searchType, desc) {
		listeners.broadcast("message", msgType, searchType, desc);
	}
	
	function setListData(arr) {
		list.setData(arr);
		listeners.broadcast("items", arr);
		if (arr.length == 0) {
			showMessage("empty");
		}
	}
	
	/**
	 * Function: submit
	 * Submit search to particular provider
	 * 
	 * Parameters:
	 * query - query can be string OR object (ie. { query: 'tartu' }). it will be transfered to provider
	 * type - optional, string, provider name (see _providers_ in configuration). if omitted, then first provider from cfg will be used.
	 */
	// query can be string OR object (ie. { query: 'tartu' }). it will be transfered to provider
	this.submit = function(query, type, onSuccess, onError) { // public
		var q = listeners.broadcast("query", query, type);
		
		if(q) {
			query = q;
		}
		
		if (submitting && lastAjax) {
			lastAjax.abort();
		}
		submitting = true;
		
		clear(true /*we do not show message "clear" because message "wait" will come immediately*/);
		
		// get first providewr if type is not defined
		if(!type) {
			for(var t in cfg.providers) {
				type = t;
				break;
			}
		}
		
		var askType = listeners.call("chooseProvider", type);
		if(typeof askType!="undefined") {
			type = askType;
		}
		
		lastProvider = type;
		lastQuery = query;
		
		function parseData(arr) {
			// install quick animation...just for fun
			var oldAfter = list.afterDrawRow;
			var oldBefore = list.beforeDrawRow;
			
			if (window.jQuery && cfg.animation) {
				list.beforeDrawRow = function(obj, nodes, i, cnt) {
					if(oldBefore) oldBefore.apply(list, arguments);
					if((nodes.length > 0) && (nodes[0].tagName)) {
						var jj = jQuery(nodes[0]);
						jj.css("display", "none");
					}
				}
				
				list.afterDrawRow = function(obj, nodes, i, cnt) {
					if(oldAfter) oldAfter.apply(list, arguments);
					if((nodes.length > 0) && (nodes[0].tagName)) {
						var jj = jQuery(nodes[0]);
						window.setTimeout(function() {
							jj.fadeIn(300);
						}, cnt * 100);
					}
				}
			}
			
			setListData(arr);
			
			// remove animation
			list.afterDrawRow = oldAfter;
			list.beforeDrawRow = oldBefore;
			
			return arr;
		}
		
    	showMessage("wait", type);
    	
    	var provider = cfg.providers[type];
    	if (typeof(provider) == "function" || !jQuery) {
    		// assume provider is handling request itself
    		var onProviderSuccess = function(data) {
				var arr = parseData(data);
				if(onSuccess) onSuccess(arr);
			}
    		var onProviderError = function(txt) {
		    	showMessage("error", type, txt);
				if(onError) onError(type, txt);
			}
			provider(query, listeners, onProviderSuccess, onProviderError);
    	} else {
    		var opts = {
			    url: Regio.Utils.format(provider.uri, {
			    	query: (typeof(query) == 'object') ? encodeURIComponent(query.query) : /*string*/ encodeURIComponent(query)
			    }),
			    error: function(req, desc) {
			    	showMessage("error", type, desc);
					if(onError) onError(type, desc);
			    },
				success: function(xml) {
					var arr = parseData(provider.parse(xml));
					if(onSuccess) onSuccess(arr);
				}
			};
    		listeners.broadcast("submit", opts);
			lastAjax = jQuery.ajax(jQuery.extend(provider, opts));
		}
	};
	
	/**
	 * Function: queryRowCountInTab
	 * temporary filter just to answer question "how many rows will 
	 * be in particular tab if i will switch to it?"
	 * Does not change searchList contents!
	 * 
	 * Parameters:
	 * tab - tab to look in
	 * 
	 * Returns:
	 * number - count of rows
	 */
	this.queryRowCountInTab = function(tab) { // public
		// temporary filter just to answer question "how many rows will 
		// be in particular tab if i will switch to it?" => see search.queryRowCountInTab
		var tabsFilter = {
			possibleRows: 0,
			start: function() {
				this.possibleRows = 0;
			},
			filter: function(obj, num) {
				var tabAccepts = cfg.tabFilter(tab, obj, num);
				if (tabAccepts || (typeof(tabAccepts) == "undefined")) {
					this.possibleRows++;
				}
				return tabAccepts;
			},
			finish: function() {
			}
		}
		
		list.execute(tabsFilter, function() {});
		
		return tabsFilter.possibleRows;
	};
	
	function clear(noShowMessage) {
		lastQuery = undefined;
		lastProvider = undefined;
		
		list.clear() 
		listeners.broadcast("items", []);
		if(!noShowMessage) {
			showMessage("clear");
		}
	}
	
	/**
	 * Function: clear
	 * Clear search results list.
	 */
	this.clear = function() {
		clear()
	}; // public
	
	/**
	 * Function: refresh
	 * See <Regio.ListView.refresh>
	 */
	this.refresh = function(tab) { // public
		listeners.broadcast("refresh", tab);
		if ((typeof(tab) == "undefined") || (tab == tabs.curTab)) {
			list.refresh();
		}
	};
	
	/**
	 * Function: nextPage
	 * Go to next page of search results.
	 */
	this.nextPage = function() { paging.nextPage() };
	/**
	 * Function: prevPage
	 * Go to previous page of search results.
	 */
	this.prevPage = function() { paging.prevPage() };
	
	/**
	 * Function: setTab
	 * Set tab. For this to work, _tabFilter_ must be provided in configuration.
	 */
	this.setTab = function() { return tabs.setTab.apply(tabs, arguments) }; // public
	
	this.setPage = function() { return paging.setPage.apply(paging, arguments) }; // public
	
	/**
	 * Function: getDisplayedData
	 * Returns array of objects (see <Regio.ListView.getData>) of currently displayed data.
	 */
	this.getDisplayedData = function() {
		var rows = [];
		list.execute('current', function(row) {
			rows.push(row);
		});
		return rows;
	}
	
	/**
	 * Function: getCurPage
	 * Returns current page number.
	 */
	this.getCurPage = function() { return paging.curPage > paging.pageCount-1 ? paging.pageCount-1 : paging.curPage+1 };
	/**
	 * Function: getPageCount
	 * Returns current page count.
	 */
	this.getPageCount = function() { return paging.pageCount };
	/**
	 * Function: getCurPage
	 * Returns current tab (see <setTab>).
	 */
	this.getCurTab = function() { return tabs.curTab };
	
	
	/**
	 * Function: getState
	 * Returns state of component. Only for use with <setState>. Internal structure returned by this function can change in future!
	 */
	this.getState = function() {
		var state = {
			data: list.getData(),
			tab: tabs.curTab,
			page: paging.curPage,
			lastQuery: lastQuery,
			lastProvider: lastProvider
		}
		// maybe somone wants to modify state record?
		listeners.call("saveState", state);
		return state;
	}
	
	/**
	 * Function: setState
	 * Restore state of component (see <getState>)
	 * 
	 * Parameters:
	 * state - state returned by <getState>
	 */
	this.setState = function(state) {
		if(state.data.length == 0) {
			// show default message by default
			clear();
		} else {
			setListData(state.data);
		}
		tabs.setTab(state.tab);
		paging.setPage(state.page);
		lastQuery = state.lastQuery;
		lastProvider = state.lastProvider;
		// searchList itself does not handle query input and provider select...so we just broadcast them to siblings
		listeners.broadcast("restoreState", state);
	}
	
	this.listeners = listeners; // public
}


/**
 * Component: Regio.Session
 * Session generator/holder
 * 
 * Version 1.0
 *
 * Author:
 * Jevgeni Kiski (jevgeni@regio.ee)
 */

Regio.Session = {
	getID: function(){
		function _generateSessionID(){
			var a = new Array ("f","e","d","c","b","a","9","8","7","6","5","4","3","2","1","0");
			var h = "";
			for (var i = 0; i < 32; i++) h += a[Math.round(Math.random() * (a.length-1))];
			return h;
		}
		if(!this.sid) this.sid = _generateSessionID();
		return this.sid;
	},
	setID: function(sid){
		this.sid = sid;
	}
};

/*Regio.SessionID = function(){
	function get(){
		if(!Regio.SessionID.sid) Regio.SessionID.sid = _generateSessionID();
		return Regio.SessionID.sid;
	}
	
	function set(sid){
		Regio.SessionID.sid = sid;
	}
	
	function _generateSessionID(){
		var a = new Array ("f","e","d","c","b","a","9","8","7","6","5","4","3","2","1","0"), h = "";
		for (var i = 0; i < 32; i++) h += a[Math.round(Math.random() * a.length)];
		return h;
	}
	
	this.get = get;
	this.set = set;
	
	return this;
}*/


/**
 * Component: Regio.SearchList.ProviderRegioJGCJSONP
 * SearchList provider (Regio JavaGeoCoding)
 * Data provider for <SearchList> component. Uses Regio JavaGeoCoding serivce >2.7.1 (directly). Default SRS is WGS84 here!
 * 
 * Group: Search
 * 
 * Requires: Regio.Session, Regio.Utils, Regio.SearchList, jQuery
 * 
 * See also:
 * <Regio.SearchList>
 * 
 * Version: 1.35
 * 
 * Author:
 * Alexandr Smirnov (alex@regio.ee)
 */

/**
 * Function: Regio.SearchList.createProviderRegioJGCJSONP
 * Creates SearchList provider.
 * 
 * Parameters:
 * key - user ID (required!!!)
 * cfg - configuration
 * 
 * Configuration:
 * params - optional, array of template vars to be substituted into URI (eg. { a: 123, b: 321 }) default to {}
 * type, dataType - ajax options (default 'GET' and 'jsonp')
 * srs - default is epsg:4326 (use epsg:3301 for Estonian Mercator)
 * 
 * Returns:
 * SearchList Provider
 */
Regio.SearchList.createProviderRegioJGCJSONP = function(key, cfg) {
	cfg = cfg || {};
	
	Regio.Utils.applyDefaults(cfg, {
		uri: "http://kaardid.regio.ee/jgc/geocode?gl=et&output=json&q={query}&key={key}&srs={srs}&sid={sid}&rid={rid}&maxcount={maxCount}",
		srs: 'epsg:4326',
		maxCount: 10
	});
	
	var lastAjax;
	var rid = 0;
	
	return function(query, listeners, onSuccess, onError) {
		var q, z;
		if (typeof(query) == "object") {
			q = query.query;
			z = query.zoom;
		} else {
			q = query;
		}
		if(!z) z = 10;
		
		var opts = {
			type: 'GET',
			dataType: 'jsonp',
			url: Regio.Utils.format(cfg.uri, jQuery.extend({
				key: key,
				query: encodeURIComponent(q),
				srs: cfg.srs,
				rid: rid++,
				sid: Regio.Session.getID(),
				maxCount: cfg.maxCount
			}, cfg.params)),
			error: function(req, desc) {
				onError(desc);
			},
			success: function(json) {
				if(json.Status && (json.Status.code != 200)) {
					onError(json.Status.code);
				} else {
					if(Regio.Utils.isArray(json)) { // new version returns array of results
						json = json[0];
					}
					var arr = json.placemark || [];
					
					var n = 1;
					for(var i in arr) {
						arr[i].e = arr[i].point.coordinates[0]; // for compatibility with GCA etc (regular JGC provider format)
						arr[i].n = arr[i].point.coordinates[1];
						arr[i].z = z;
						arr[i].serial = n++;
					}
					onSuccess(arr);
				}
			}
		};
		
		opts = jQuery.extend(opts, cfg);
		
		listeners.broadcast("submit", opts);
		
		if (lastAjax) lastAjax.abort();
		lastAjax = jQuery.ajax(opts);
	}
}



/**
 * Component: MapCat.Log
 * Provides basic routines for logging (debugging). Uses FireBug console if available. 
 * If not available, passes strings into window.status. 
 * 
 * *NB! In non-debug builds, this functions will do nothing*
 * 
 * Requires: MapCat.Namespace
 * 
 * Group: MapCat FlashTile API
 */
MapCat.Log = {
	/**
	 * Function: MapCat.Log.add
	 * Static function. Add string to the log.
	 * 
	 * Parameters:
	 * str - string
	 */
	add: function(str) {
		
	}
}


/**
 * Component: MapCat.Component
 * Creates and initializises MapCat FlashTile component.
 * 
 * Group: MapCat FlashTile API
 * 
 * Version: 2.8.9
 * 
 * Requires: MapCat.Namespace, MapCat.Listeners, MapCat.Log
 * 
 * Parameters:
 * divid - Maybe string (id of div) or actual div object itself where to put Flash embed HTML
 * cfg - Configuration
 * listeners - optional, Specifies MapCat.Listeners object to be connected to component (if not specified, object will be created internally)
 * 
 * Author: 
 * Alexandr Smirnov (alex@regio.ee)
 * 
 * Usage: 
 * |var mapcat = new MapCat.Component('map', {
 * |    src: "mapcat/map.swf",
 * |    vars: "confUrl=mapcat/conf.xml%3F&bgcolor=0x738aa0&cur_map=0&printing=0"
 * |}); 
 */
MapCat.Component = function(){
	var result = this.initialization.apply(this, arguments);
    
    return result;
}

// from TagHTML.js
MapCat._getTagHTML = function(cfg) {
	cfg.id = cfg.id ? cfg.id : "";
	cfg.vars = cfg.vars ? cfg.vars : "";
	cfg.src = cfg.src ? cfg.src: "map.swf";
	cfg.width = typeof(cfg.width) == "undefined" ? "100%" : cfg.width;
	cfg.height = typeof(cfg.height) == "undefined" ? "100%" : cfg.height;
	cfg.wmode = typeof(cfg.wmode) == "undefined" ? "window" : cfg.wmode;
	cfg.bgcolor = typeof(cfg.bgcolor) == "undefined" ? "738aa0" : cfg.bgcolor;
	cfg.scale = typeof(cfg.scale) == "undefined" ? "noscale" : cfg.scale;
	cfg.quality = typeof(cfg.quality) == "undefined" ? "high" : cfg.quality;
	if(typeof(cfg.protocol) == "undefined") {
		cfg.protocol = (''+window.location).match('^https\:\/\/') ? "https" : "http";
	}
	if((cfg.protocol != "https") && (cfg.protocol != "http")) {
		cfg.protocol = "http";
	}
	
	with(cfg) {
		var html =
			'<object codebase="'+cfg.protocol+'://fpdownload.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=8,0,0,0" '+
				'width="'+width+'" height="'+height+'"'+
				'classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" id="'+cfg.id+'">'+
			'<param name="quality" value="'+quality+'" /> '+
			'<param name="scale" value="'+scale+'" /> '+
			'<param name="allowScriptAccess" value="always" /> '+
			'<param name="bgcolor" value="'+bgcolor+'" /> '+
			'<param name="flashvars" value="jsApiID='+apiID+'&'+vars+'" /> '+
			'<param name="movie" value="'+src+'" /> '+
			'<param name="wmode" value="'+wmode+'" /> '+
			'<embed quality="'+quality+'" scale="'+scale+'" width="'+width+'" height="'+height+'"'+
				'allowscriptaccess="always" bgcolor="'+bgcolor+'" '+
				'flashvars="jsApiID='+apiID+'&'+vars+'" '+
				'pluginspage="'+cfg.protocol+'://www.macromedia.com/go/getflashplayer" '+
				'src="'+src+'" wmode="'+wmode+'" type="application/x-shockwave-flash" id="'+cfg.id+'" >'+
			'</embed>'+
			'</object>';
	}
	
	return html;
}

// from APIList.js
MapCat._apiList = {}; // APIs list

MapCat._apiList.register = function (api) {
	MapCat._apiList[api.id] = api;
}

// from FlashGateway.js
MapCat._flashGateway = function(apiID, func, args) {
	if (MapCat && MapCat._apiList) {
		var api = MapCat._apiList[apiID];
		if (api) {
			return api[func].apply(api, args);
		}
	}
	return null;
}

MapCat.Component.prototype = {
    initialization: function(divid, cfg, listeners /*optional*/) {
    	cfg = cfg || {};
    	
        this.id = "mapcat_api_" + Math.random().toString().split(".")[1];
        cfg.apiID = this.id;
        if (!cfg.id) 
            cfg.id = this.id; // IE requires that flash embeds must have some ID for ExternalInterface to work properly
        MapCat._apiList.register(this);
        
		/**
		 * Property: listeners
		 * Refers to <MapCat.Listeners> class of component
		 */
        if (!listeners) {
            this.listeners = new MapCat.Listeners();
        }
        else {
            this.listeners = listeners;
        }
        
        var flashListener = this.listeners.addEx("*", function(){
            return this._callFlash.apply(this, arguments);
        }, this);
        
        // used in _callLocal
        this._flashListenerFilter = function(listener) {
    		return (listener != flashListener);
		}
        
        var div = false;
        if (typeof divid == "string") {
            div = document.getElementById(divid);
        }
        else {
            div = divid;
        }
        this.div = div;
        this.cfg = cfg;
        
        // if Safari or wmode is opaque or transparent
        if(navigator.userAgent.match(/safari/i) || (cfg.wmode || '').match(/opaque|transparent/i)) {
        	this.initMouseWheel();
        }
        
        // "ready" functionality
    	this.stack = [];
	    this.addCallback(cfg.loadedEvent ? cfg.loadedEvent : "mapcat.loaded", this._flashLoadedEvent, this);
    	
        return this.restart();
    },
    initMouseWheel: function() { // ver 2.7.5
		var me = this;
		
		// http://javascript.about.com/library/bldom21.htm
		function addEvent(el, eType, fn, uC) {
			if (el.addEventListener) {
				el.addEventListener(eType, fn, uC);
				return true;
			} else if (el.attachEvent) {
				return el.attachEvent('on' + eType, fn);
			} else {
				el['on' + eType] = fn;
			}
		}
		
		// http://webdev.org.ua/node/424
		function cancelEvent(e) {
			e = e ? e : window.event;
			if (e.stopPropagation) {
				e.stopPropagation();
			}
			if(e.preventDefault) {
				e.preventDefault();
			}
			e.cancelBubble = true;
			e.cancel = true;
			e.returnValue = false;
			
			return false;
		}
		
		function wheelCallback(e) {
			e = e ? e : window.event;
			var wheelData = (e.detail ? e.detail * -1 : e.wheelDelta / 40) / 3;
			var x = typeof e.offsetX != "undefined" ? e.offsetX : e.layerX;
			var y = typeof e.offsetY != "undefined" ? e.offsetY : e.layerY;
			
			me.ready(function() {
				me.broadcast(me.cfg.mouseWheelEvent ? me.cfg.mouseWheelEvent : "map.zoom", { x: x, y: y, delta: wheelData });
			});
			
			return cancelEvent(e);
		}
		
		addEvent(this.div, "DOMMouseScroll", wheelCallback, false); // FF
		addEvent(this.div, "mousewheel", wheelCallback, false); // all other
    },
    /*
     Method: restart
     Available from version 2.4.
     Clear HTML and re-initialize component. Also clears "flashLoaded" flag, so that 
     ready function will wait for "loaded" event from FlashTile again. This function 
     needs to be called any time flash component reinitializes for some reason (especially usefull 
     for Firefox browser, as it reloads Flash embeds any time you change its "position" or "display" style)
     */
    restart: function(){ // ver 2.4
        this.flashLoaded = false;
        
        var div = this.div, cfg = this.cfg;
        var result = false;
        
        if (div) {
            div.innerHTML = MapCat._getTagHTML(cfg);
            
            //this.flash = div.firstChild;
            //result = true;
            
            var embeds = div.getElementsByTagName("embed");
            // for ie, there are no embeds
            if (!embeds || embeds.length == 0) {
                embeds = div.getElementsByTagName("object");
            }
            result = embeds.length > 0;
            if (result) {
                this.flash = embeds[0];
            }
            
            if(this.flash) {
            	window[cfg.id] = this.flash; // fix Adobe Flash 9.0.45.0 ExternalInterface bug (?)
            }
        }
        else {
            result = false;
        }
        
        return result;
    },
    /**
     * Method: addCallback
     * Add observer (subscriber) of event. Shortcut to listeners.add.
     * 
     * See:
     * <MapCat.Listeners.add>
     */
    addCallback: function(eventName, callback, me){
        this.listeners.add(eventName, callback, me);
    },
    /**
     * Method: broadcast
     * Broadcast (publish) some event to listeners. NB: This is asynchroneous method, which means that 
     * it MAY not neccessary execute immediately (currently it does, but in feature versions it will NOT).
     * 
     * See:
     * <MapCat.Listeners.broadcast>
     */
    broadcast: function(){
        this.call.apply(this, arguments);
    },
	broadcastOnReady: function() {
		var args = arguments, self = this;
		this.ready(function() {
			self.broadcast.apply(self, args);
		});
	},
    /**
     * Method: call
     * Same as broadcast, but will return result from callbacks. Synchroneous, executes immediately and 
     * does not return until all handlers will process event.
     * 
	 * Returns:
	 * Result from last event handler that returned something different from "undefined"
     * 
     * See: 
     * <broadcast>
     */
    call: function(){
		var res = this.listeners.broadcast.apply(this.listeners, arguments);
		return res;
    },
    /**
     * Execute func only if (or only after) flash completely loaded and inited. NB: "this" will refer to function itself.
     * @param {Object} func
     * @method
     */
    ready: function(func){
        if (this.flashLoaded) {
            func.apply(func);
        }
        else {
            this.stack.push(func);
        }
    },
    _flashListener: undefined,
    _flashListener: undefined, // defined in init
    _callLocal: function() {
    	// call all listeners except OUR/CURRENT FlashTile
		var args = [this._flashListenerFilter];
		for(var i=0; i<arguments.length; i++) {
			args.push(arguments[i]);
		}
		return this.listeners.broadcastEx.apply(this.listeners, args);
    },
    _callFlash: function(){
        if (this.flash && this.flash._javascriptGateway) {
            // for some reason, flash interface do not understand arguments in it original contest,
            // recreate it as Array
            var args = [];
            for (var i = 0; i < arguments.length; i++) {
                args.push(arguments[i]);
            }
            
            var flashresult = null;
            try {
                flashresult = this.flash._javascriptGateway(this.id, "callLocal", args);
            } 
            catch (e) {
                MapCat.Log.add('Exception while calling Flash callback: ' + e, MapCat.Log.ERROR);
            }
            return flashresult;
        }
        return null;
    },
    _flashLoadedEvent: function(){
        this.flashLoaded = true;
        while (this.stack.length > 0) {
            var f = this.stack.shift();
            f.apply(f);
        }
    }
}




/**
 * Component: Debug
 * Routines to debug MapCat API2.
 * 
 * Requires: MapCat.Log
 * 
 * Version: 2.4.5
 * 
 * Usage: 
 * Include in html and call 
 * | InitializeAPIDebug(mapcat_component_object, {
 * |     'component1 name': component1,
 * |     'component2 name': component2,
 * |     'componentN name': componentN
 * | }), 
 * that will output all MapCat events with all arguments.
 * If _logCallback_ is not specified, then output will be done using <MapCat.Log.add>
 * 
 * NB:
 * Creates global window.JSON 
 * 
 * Author: 
 * Alexandr Smirnov (alex@regio.ee)
 */

if(!this.JSON){
	JSON=function(){function f(n){return n<10?'0'+n:n;}
	Date.prototype.toJSON=function(){return this.getUTCFullYear()+'-'+
	f(this.getUTCMonth()+1)+'-'+
	f(this.getUTCDate())+'T'+
	f(this.getUTCHours())+':'+
	f(this.getUTCMinutes())+':'+
	f(this.getUTCSeconds())+'Z';};var m={'\b':'\\b','\t':'\\t','\n':'\\n','\f':'\\f','\r':'\\r','"':'\\"','\\':'\\\\'};function stringify(value,whitelist){var a,i,k,l,r=/["\\\x00-\x1f\x7f-\x9f]/g,v;switch(typeof value){case'string':return r.test(value)?'"'+value.replace(r,function(a){var c=m[a];if(c){return c;}
	c=a.charCodeAt();return'\\u00'+Math.floor(c/16).toString(16)+
	(c%16).toString(16);})+'"':'"'+value+'"';case'number':return isFinite(value)?String(value):'null';case'boolean':case'null':return String(value);case'object':if(!value){return'null';}
	if(typeof value.toJSON==='function'){return stringify(value.toJSON());}
	a=[];if(typeof value.length==='number'&&!(value.propertyIsEnumerable('length'))){l=value.length;for(i=0;i<l;i+=1){a.push(stringify(value[i],whitelist)||'null');}
	return'['+a.join(',')+']';}
	if(whitelist){l=whitelist.length;for(i=0;i<l;i+=1){k=whitelist[i];if(typeof k==='string'){v=stringify(value[k],whitelist);if(v){a.push(stringify(k)+':'+v);}}}}else{for(k in value){if(typeof k==='string'){v=stringify(value[k],whitelist);if(v){a.push(stringify(k)+':'+v);}}}}
	return'{'+a.join(',')+'}';}}
	return{stringify:stringify,parse:function(text,filter){var j;function walk(k,v){var i,n;if(v&&typeof v==='object'){for(i in v){if(Object.prototype.hasOwnProperty.apply(v,[i])){n=walk(i,v[i]);if(n!==undefined){v[i]=n;}}}}
	return filter(k,v);}
	if(/^[\],:{}\s]*$/.test(text.replace(/\\./g,'@').replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(:?[eE][+\-]?\d+)?/g,']').replace(/(?:^|:|,)(?:\s*\[)+/g,''))){j=eval('('+text+')');return typeof filter==='function'?walk('',j):j;}
	throw new SyntaxError('parseJSON');}};}();
}

/**
 * Function: InitializeAPIDebug
 * Starts outputing all events (with names and parameters) to Firebug console or into provided logCallback
 * 
 * Parameters:
 * listeners - any component that provides listeners property (events dispatcher) (see <MapCat.Component>, <Regio.SearchList>, etc...)
 * components - optional, object containing names of components as Keys and components objects as values ({ "comp1 name": comp1, ... }). Use this, if you have several components on one page and need to see events flow from all of them at the same time. Log will look like: 
 * | Comp1 Name: eventName (eventParam1, ...)
 * | Comp1 Name: eventName (eventParam1, ...)
 * | Comp2 Name: eventName (eventParam1, ...)
 * logCallback - optional, function, if you need to test  components in some other browser (that does not contain FireBug) or you just have implemented some other debugging console and want to see output there, you can use this function. If specified, then this callback will be called for every output line. Function should accept one parameter - string (line of output). 
 */
window.InitializeAPIDebug = function(listeners/*or MapCat.Component*/, components, logCallback) {
	if (listeners && listeners.listeners) {
		listeners = listeners.listeners;
	}
	var log = logCallback ? logCallback : MapCat.Log.add;
	
	
	function stringifyArgument(a) {
		var ret;
		if (a && typeof(a) == 'object' && a.length && a.item) {
			ret = '<DOM Objects Array?>';
		} else {
			try {
				ret = JSON.stringify(a);
			} catch(e) {
				ret = "<" + e + ">";
			}
		}
		return ret;
	}
	
	function createLogger(componentName) {
		return function(filter, eventName) {
			var a = [];
			for(var i=2;i<arguments.length;i++) {
				var s = stringifyArgument(arguments[i]);
				a.push(s);
			}
			var argsStr = a.join(', ');
			log(componentName+': "'+eventName+'"'+(argsStr ? ', '+argsStr : ''));
		}
	}
	
	function installLoggingCode(key, listeners) {
		var logger = createLogger(key);
		
		var old = listeners.broadcastEx;
		
		listeners.broadcastEx = function(filter, componentName) {
			logger.apply(undefined, arguments);
			
			var ret = old.apply(listeners, arguments);
			
			if(typeof ret != "undefined") {
				log(key+": \""+componentName+"\", ...returning: "+stringifyArgument(ret));
			}
			
			return ret;
		};
	}
	
	if (listeners && (typeof(listeners) == "object")) {
		installLoggingCode('MapCat FlashTile', listeners);
	};
	if (typeof(components) == "object") {
		for(var key in components) {
			var c = components[key];
			installLoggingCode(key, c.listeners);
		}
	}
}



})(window.jQuery);




/* Generation time: 0.154607057571 */