API Docs for:
Show:

File: generic.js

//*******************************************************************************
// * Copyright (c) 2017-2019 Genialist Software Ltd.
// * All rights reserved.
// ******************************************************************************

const c_do_nothing = function() {};

/******************************************************************************/
/* A shared callback is a function that will execute in all tabs when called. */
/******************************************************************************/

function pSharedCallback(pid, f) {
	var that = this, id = pid, func = f; 
	
	this.call = function(p_default_this, p_arg1, p_arg2, p_arg3, p_arg4, p_arg5, p_arg6, p_arg7, p_arg8, p_arg9) {
		var r = func.call(p_default_this, p_arg1, p_arg2, p_arg3, p_arg4, p_arg5, p_arg6, p_arg7, p_arg8, p_arg9);
		
		if (window.localStorage) {
			localStorage.setItem(id, JSON.stringify([ p_arg1, p_arg2, p_arg3, p_arg4, p_arg5, p_arg6, p_arg7, p_arg8, p_arg9 ]));
			localStorage.removeItem(id);
		}
	};

	if (window.localStorage)
		window.addEventListener('storage', function(e) {
			if (e.key == id && e.newValue) {
				var i_args = JSON.parse(e.newValue);
				func.call(this, i_args[0], i_args[1], i_args[2], i_args[3], i_args[4], i_args[5], i_args[6], i_args[7], i_args[8], i_args[9]);
			}
		});
};

/******************************************************************************/
/******************************************************************************/
/******************************************************************************/

function pToggleElement(p_ids, p_enabled, p_disabled) {
	var that = this;
	var id = p_ids, enabled = p_enabled, disabled = p_disabled;

	this.enable = function() {
		pDocument.show(enabled);
		pDocument.hide(disabled);
		id.forEach(pDocument.enable);
	};
	this.disable = function() {
		pDocument.hide(enabled);
		pDocument.show(disabled);
		id.forEach(pDocument.disable);
	};
	this.isEnabled = function() {
		return pDocument.isEnabled(id[0]);
	};

	/** Indicates if this object is a pToggleElement or not. */
	this.isToggleElement = function() {
		return true;
	};
};

/******************************************************************************/
/******************************************************************************/
/******************************************************************************/

function pMap(p_values, p_addid) {
	var that = this, m_keys = [], m_values = [];
	
	this.clear = function() {
		m_keys = [];
		m_values = [];
	};
	this.keys = function() {
		return pArray.clone(m_keys);
	};
	this.keyAt = function(i) {
		return m_keys[i];
	};
	
	this.nextKey = function(k) {
		var i = m_keys.indexOf(k);
		if (i>=0 && i+1<m_keys.length)
			return m_keys[i+1];
		//return null;
	};
	this.previousKey = function(k) {
		var i = m_keys.indexOf(k);
		if (i>0)
			return m_keys[i-1];
		//return null;
	};
	this.valueAt = function(i) {
		return m_values[i];
	};
	this.getValue = function(k, p_default) {
		if (null==k)
			throw "Map key cannot be null";

		var i = m_keys.indexOf(k);
		if (i>=0)
			return m_values[i];
		return p_default;
	};
	this.values = function() {
		return pArray.clone(m_values);
	}
	this.getKey = function(v, p_default) {
		if (null==v)
			throw "Map value cannot be null";

		var i = m_values.indexOf(v);
		if (i>=0)
			return m_keys[i];
		return p_default;
	};
	this.put = function(k, v) {
		if (null==k)
			throw "Map key cannot be null";
		
		if (null==v)
			return that.remove(k);
		else {
			var i = m_keys.indexOf(k);
			if (i>=0) {
				var i_old = m_values[i];
				m_values[i] = v;
				return i_old;
			}
			else {
				m_keys.push(k);
				m_values.push(v);
				//return null;
			}
		}
	};
	this.remove = function(k) {
		if (null==k)
			throw "Map key cannot be null";

		var i = m_keys.indexOf(k);
		if (i>=0) {
			var i_value = m_values[i];

			m_keys.splice(i, 1);
			m_values.splice(i ,1);

			return i_value;
		}
		//return null;
	};

	// "length" property
	Object.defineProperty(this, "length", { get: function() { return m_keys.length; } });

	// "firstKey" property
	Object.defineProperty(this, "firstKey", { get: function() { return m_keys.length>0? m_keys[0] : null; } });

	//*** INIT
	if (p_values) //TODO: test if array...
		for(var i=0 ; i<p_values.length ; i+=2) {
			this.put(p_values[i], p_values[i+1]);
			if (p_addid)
				p_values[i+1].id = p_values[i];
		}
};

var objects = new pMap();

/******************************************************************************/

function pDefinedUniqueObjectList() {
	var that;
	var v = [];
	
	this.push = function(a) {
		if (a && !v.includes(a)) { v.push(a); return true; }
		//return false;
	};

	this.forEach = function(f) {
		return v.forEach(f);
	};
	
	this.values = function(f) {
		return pArray.clone(v);
	};
	
	this.remove = function(a) {
		var p = v.indexOf(a);
		if (p>-1)
			v = v.splice(p);
	};

	// "length" property
	Object.defineProperty(this, "length", { get: function() { return v.length; } });
};

/******************************************************************************/
/******************************************************************************/
/******************************************************************************/

function pEventHandler() {
	var that = this, listeners = new pMap();
	
	this.addEventListener = function(eid, f) {
		//alert(p_function + " " + typeof p_function);
		var l = listeners.getValue(eid, null);
		if (null==l)
			listeners.put(eid, l = []);

		if (l.indexOf(f)>=0)
			return;

		l[l.length] = f;
	};
	
	this.removeEventListener = function(eid, f) {
		var l = listeners.getValue(eid, []), i = l.indexOf(f);
		if (i>=0)
			l.splice(i, 1);
	};
	
	this.fireEvent = function(eid, p_source, e) {
		listeners.getValue(eid, []).forEach(function(f) {
			try {
				f.call(p_source, e || { id: eid, source: p_source });
			}
			catch (er) {
				pConsole.error('Document', "Failed to call event listener on event " + eid + ": " + f + "\n" + er + "\n" + er.stack);
			}
		});
	};
	
	this.countListeners = function(eid) {
		return listeners.getValue(eid, []).length;
	};

	// array of all event ids
	this.events = function() {
		return listeners.keys();
	};
}

/******************************************************************************/

const pEvent = new (function() {
	this.addEventListener = function(ev, el, f) {
    	if (el.addEventListener)  // W3C DOM
    	   	el.addEventListener(ev, f, false);
	    else if (el.attachEvent) { // IE DOM
			var r = el.attachEvent("on"+ev, f);
			return r;
    	}
    	else //DOM 0
    		el["on" + ev] = f;
	};
	this.removeEventListener = function(ev, el, f) {
    	if (el.removeEventListener)  // W3C DOM
    	   	el.removeEventListener(ev, f, false);
	    else if (el.detachEvent) { // IE DOM
			var r = el.detachEvent("on"+ev, f);
			return r;
    	}
	};
	this.cancel = function(e) {
		if (e.preventDefault)
			e.preventDefault();
		return false;
	};
});

/******************************************************************************/
/******************************************************************************/
/******************************************************************************/

const pElement = new (function() {
	var that = this, cache_x = new pMap();
	
	this.x = function(x, cache) {
		if (x) {
			//return (typeof x=="string")? document.getElementById(x) : x;
			if (typeof x=="string") {
				var r = cache? cache_x.getValue(x) : null;
				if (r)
					return r;
				x = document.getElementById(x);
				if (cache && x) 
					cache_x.put(x.id, x);
			}
			return x;
		}
	};
	
	this.c = function(x, c) {
		var r = [], y = null;
		if (c) {
			if (x = that.x(x))
				y = x.getElementsByClassName(c);
		}
		else
			y = document.getElementsByClassName(x);
		
		if (y)
			for(var i=0 ; i<y.length ; i++)
				r[r.length] = y.item(i);
		return r;
	};
	
	/* search element by name, return array (can be empty but not null) */
	this.n = function(x) {
		var r = [];
		if (x = document.getElementsByName(x))
			for(var i=0 ; i<x.length ; i++)
				r[r.length] = x[i];
		return r;
	};
	/* search element by tag name, return array (can be empty but not null) */
	this.t = function(x, n) {
		var r = [];
		if (x = that.x(x))
			if (x = x.getElementsByTagName(n))
				for(var i=0 ; i<x.length ; i++)
					r[r.length] = x[i];
		return r;
	};
	this.firstByClass = function(x, c) {
		if (x = that.x(x))
			return x.getElementsByClassName(c).item(0);
	};
	this.first = function(x, t, y) {
		x = that.x(x);
		var c = x.getElementsByTagName(t);
		for(var i=0 ; i<c.length ; i++)
			if (c.item(i).id == y)
				return c.item(i);
		//return null;
	};

	this.displayBlock = function(x) {
		if (x = that.x(x))
			x.style.display = 'block';
	};
	
	this.displayNone = function(x) {
		if (x = that.x(x))
			x.style.display = 'none';
	};
	
	this.blur = function(x) {
		if (x = that.x(x)) {
			x.blur();
			return true;
		}
		return false;
	};
	this.focus = function(x) {
		if (x = that.x(x)) {
			x.focus({ preventScroll: false });//pObject.FALSE);
			return true;
		}
		return false;
	};

	this.go = function(x, y) {
		if (x = that.x(x)) {
			x.focus({ preventScroll: false });//false);
			if (!that.checkVisible(y || x))
				pDocument.scrollTo(y || x);
			return true;
		}
		return false;
	};
	this.checkVisible = function(x) {
		if (x = that.x(x)) {
			//var s = new pStyle(x);
			if (x.style.display=='none') return false;
			
			var r = x.getBoundingClientRect(), viewHeight = Math.max(document.documentElement.clientHeight, window.innerHeight);
			//console.log('check: r.top: ' + r.top + ' r.b: ' + r.bottom + ' vh: ' +viewHeight + ' wih: ' + window.innerHeight)
			//console.log('check: ' + !(r.bottom < 0 || r.top < 0 || r.top - viewHeight >= 0) && r.bottom <= viewHeight)
			return !(r.bottom < 0 || r.top < 0 || r.top - viewHeight >= 0) && r.bottom <= viewHeight;
		}
	};
	this.checkDisplayed = function(x) {
		if (x = that.x(x)) {
			if (x.style.display=='none') return false;
			
			return true;
		}
	};
	
	this.hasClassName = function(x, c) {
		if (x = that.x(x)) {
			if (c.forEach) {
				x = (x.className || '').split(' ');
				return c.find(function(c) { return x.includes(c) });
			}
			return (x.className || '').split(' ').includes(c);
		}
	};
	this.setClassName = function(x, c) {
		if (x = that.x(x))
			[].forEach.call((null==x.length)? [ x ] : x, function(x) { if (x) x.className = c; });
	};
	this.addClassName = function(x, cl) {
		if (x)
			(x.forEach? x : [ x ]).forEach(function(xx) { 
				if (xx = that.x(xx)) {
					var c = (xx.className || '').split(' ');
					if (!c.includes(cl))
						xx.className = (c.join(' ') + ' ' + cl).trim();
				}
			});
	};
	this.removeClassName = function(x, p_class) {
		if (x) {
			var p_classes = p_class.split(' ');
			(x.forEach? x : [ x ]).forEach(function(xx) { 
				if (xx = that.x(xx))
					xx.className = (xx.className || '').split(' ').filter(function(cc) { return !p_classes.includes(cc); }).join(' ').trim();
			});
		}
	};
	this.addAndRemoveClassName = function(x, p_add, p_remove) {
		if (x = that.x(x))
			x.className = ((x.className || '').split(' ').filter(function(c) { return c!=p_remove && c!=p_add }).join(' ') + ' ' + p_add).trim();
	};
	/*this.setInnerHTML = function(id, p_html) {
		var x = that.x(id); if (x) x.innerHTML = p_html;
	};*/
	this.innerHTML = function(x) {
		if (x = that.x(x)) return x.innerHTML;
	};
	this.setInnerHTML = function(x, h, pthis) {
		if (x = that.x(x)) x.innerHTML = (typeof h === "function")? h.call(pthis, x.innerHTML) : h;
	};
	this.textContent = function(x) {
		if (x = that.x(x)) return x.textContent;
	}
	this.setTextContent = function(x, t, pthis) {
		if (x = that.x(x)) x.textContent = (typeof t === "function")? t.call(pthis, x.textContent) : t;
	};
	
	this.setTitle = function(x, t, pthis) {
		if (pString.v(t))
			if (x = that.x(x)) x.title = (typeof t === "function")? t.call(pthis, x.title) : t;
	};
	
	this.setFor = function(x, f) {
		if (x = that.x(x)) x.htmlFor = f;
	};
		
	this.getSrc = function(x) {
		if (x = that.x(x)) return x.src;
	};
	this.setSrc = function(x, u, f) {
		if (x = that.x(x)) {
			u = u || '';
			if (f)
				x.src = u;
			else {
				var o = x.getAttribute('org_src');
				if (o) {
					if (o!=u)
						x.src = u
				}
				else if (x.src != u)
					x.src = u;
			}
			//if (force || || x.src != url)
			//	x.src = url;
		}
	};
	this.setHref = function(x, url, force) {
		if (x = that.x(x)) {
			url = url || '';
			if (force || x.href != url)
				x.href = url;
		}
	};
	this.setOnClick = function(x, f) {
		if (x = that.x(x)) 
			[].forEach.call((null==x.length)? [ x ] : x, function(i_x) { if (i_x) i_x.onclick = f; });
	};
	
	this.isDescendant = function(parent, child) {
		if (child) {
		     var node = child.parentNode;
		     while (node != null) {
		         if (node == parent) {
		             return true;
		         }
		         node = node.parentNode;
		     }
		}
	    return false;
	};
	
	this.dragURL = function(e) {
		e.dataTransfer.setData("text/uri-list", ''+e.currentTarget.getAttribute("attr-url"));
		e.dataTransfer.setData("text/json", ''+e.currentTarget.getAttribute("attr-url"));

		//console.log(p_event.currentTarget.id + ' : ' + p_event.currentTarget.getAttribute("attr-url") + ' : ' + p_event.dataTransfer.getData('text/json'));
	};
	var m_drops = new pMap(), m_drag_source;
	function f_dragEnter(e) {
			if (!this.id)
				return;
			
			var i_data = m_drops.getValue(this, null);
			if (null==i_data)
				return;
			
			i_data.dragin_count = (i_data.dragin_count || 0) + 1;
			
			console.log('enter: ' + this.id + ' ' + e.target.id + ' ' + m_drag_source);
			//console.log(this)
			//console.log(e.target)
			
			if (m_drag_source == this || that.isDescendant(this, m_drag_source))  {
				e.preventDefault();
				return false;
			}

			var d = m_drops.getValue(this); 
			if (d.filter && !d.filter.call(this, e)) {
				e.preventDefault();
				return false;
			}
			
			if (i_data.dragin_count==1)
				that.addClassName(this, 'dragenter');
	}
	
	function f_dragLeave(e) {
			if (!this.id)
				return;
		
			var i_data = m_drops.getValue(this, null);
			if (null==i_data)
				return;
			
			i_data.dragin_count--;
			
			console.log('leave: ' + this.id + ' ' + e.target.id + ' ' + m_drag_source);
			//console.log(this)
			//console.log(e.target)
			/*var r = x.getBoundingClientRect();
			if (e.clientX >= r.left && e.clientX <= r.right)
				if (e.clientY >= r.top && e.clientY <= r.bottom)
					return;
			
			console.log(e);*/
			
			if (m_drag_source == this)
				return;
			
			//if (that.isDescendant(this, e.target))
			//	return;

			if (i_data.dragin_count<1)
				that.removeClassName(this, 'dragenter');
		}
	
	function f_dragParentEnter(e, x) {
		console.log('parent-enter: ' + this.id + ' ' + e.target.id + ' ' + m_drag_source);
			
		if (m_drag_source == x.parentNode)
			return;

		var c = x.parentNode.cells;
		if (c)
			for(var i=0 ; i<c.length ; i++)
				that.addClassName(c[i], 'dragenter');
				//that.addClassName(this.parentNode, 'dragenter');
	}
	
	function f_dragParentLeave(e, x) {
			console.log('parent-leave ' + this.id + ' ' + e.target.id + ' ' + m_drag_source);
			
			var p = x.parentNode;
			while(p) {
				if (m_drag_source == p)
					return;
				p = p.parentNode;
			}
			//if (m_drag_source == x.parentNode)
			//	return;

			var c = x.parentNode.cells;
			for(var i=0 ; i<c.length ; i++)
				that.removeClassName(c[i], 'dragenter');
			//that.removeClassName(this.parentNode, 'dragenter');
	}
	
	function f_drop(e) {
			if (!this.id)
				return;
			
			e = e || window.event; // get window.event if e argument missing (in IE)
			if (e.preventDefault) { e.preventDefault(); } // stops the browser from redirecting off to the image.

			var dt    = e.dataTransfer;
			var files = dt.files;

			if (m_drag_source == this)
				return;
			m_drag_source = null;

			var i_data = m_drops.getValue(this, null);
			if (null==i_data)
				return;
			
			i_data.dragin_count = 0;

			that.removeClassName(this, 'dragenter');
			that.removeClassName(this.parentNode, 'dragenter');
			if (this.parentNode.cells) {
				var c = this.parentNode.cells;
				for(var i=0 ; i<c.length ; i++)
					that.removeClassName(c[i], 'dragenter');
			};

			//console.log(e.currentTarget.id + ' - ' + dt.getData('text/json'));

			if (dt.getData('text/json')) {
				var o = pJSON.parse(dt.getData('text/json'));
				i_data.callback.call(this, o, e);
			}
			else
				i_data.callback.call(this, (files.length>0)? files : null, dt.getData('URL'), e);
	}
	
	function dragStart(e) {
		console.log('dragstart: ' + e.target.id + '  - ' + e.dataTransfer.getData('URL'));//event.currentTarget.id);
		try {
			localStorage.setItem('drag.url', e.dataTransfer.getData('URL'));
			m_drag_source = e.target;//event.currentTarget;
		}
		catch (exception) {
		}
	}
	
	function dragend(e) {
		//console.log('dragsend: ' + e.target.id);//event.currentTarget.id);
		localStorage.removeItem('drag.url');
		//m_drag_source = e.target;//event.currentTarget;
	}
	
	pEvent.addEventListener('dragstart', document, dragStart);
	pEvent.addEventListener('dragend', document, dragend);
	
	this.setOnDrop = function(x, p_func, p_parent, filter) {
		if (x = that.x(x)) {
			x = x.forArray? x : [ x ];
			pArray.clone(x).forEach(function(xx) { x = x.concat(that.c(xx, 'dragenter-help')); });
			x.forEach(function(i_x) { 
				if (i_x) {
					if (m_drops.getValue(i_x))
						return;
					
					// Tells the browser that we *can* drop on this target
					pEvent.addEventListener('dragover', i_x, pEvent.cancel);
					pEvent.addEventListener('dragenter', i_x, pEvent.cancel);
		
					m_drops.put(i_x, { callback: p_func, filter: filter || ((that.getAttribute(i_x, 'drop-picture')==='true')? pMediaDrop.filterPicture : null) });
		
					pEvent.addEventListener('drop', i_x, f_drop);
					if (p_parent) {
						pEvent.addEventListener('dragenter', i_x, function(e) { return f_dragParentEnter(e, i_x); });
						//pEvent.addEventListener('dragover', i_x, function(e) { return f_dragParentEnter(e, i_x); });
						pEvent.addEventListener('dragleave', i_x, function(e) { return f_dragParentLeave(e, i_x); });
					}
					else {
						//pEvent.addEventListener('dragstart', i_x, function() { pEvent.removeEventListener('drop', this, f_drop)});
						//pEvent.addEventListener('dragend', i_x, function() { pEvent.addEventListener('drop', this, f_drop)});
						
						pEvent.addEventListener('dragenter', i_x, f_dragEnter);
						//pEvent.addEventListener('dragover', i_x, f_dragEnter);
						pEvent.addEventListener('dragleave', i_x, f_dragLeave);
					}
				}
			});
		}
	};

	var m_fileChanges = new pMap();
	this.setOnFileChange = function(x, p_callback) {
		if (x = that.x(x)) {
			m_fileChanges.put(x, { callback: p_callback });
	
			x.onchange = function(e) {
				var f = e.target.files;
	
				var d = m_fileChanges.getValue(this, null);
				if (d && f && f.length>0)
					d.callback.call(this, f);
			};
		}
	};
	
	this.remove = function(x) {
		if (x = that.x(x))
			if (x.parentNode) x.parentNode.removeChild(x);
	};
	this.removeAllChildren = function(x) {
		if (x = that.x(x))
			while (x.firstChild) x.removeChild(x.firstChild);
	};
	
	this.moveToLast = function(x, y) {
		if (x = that.x(x))
			if (y = that.x(y))
				if (y != x.lastChild) {
					that.remove(y);
					x.appendChild(y);
				}
	};
	
	
	this.getValue = function(x, p_default) {
		if (x = that.x(x)) {
			if (x.localName == 'input') {
				if (x.type == 'checkbox')
					return x.checked;
				
				if (x.type == 'radio')
					return x.checked? x.value : null;
			}

			var v = x.getAttribute('data_value');
			if (v)
				return v;

			return x.value;
		}
		return p_default;
	};
	this.updateValue = function(x, v) {
		if (x = that.x(x))
			if (!x.readOnly && !x.disabled) {
				that.setValue(x, v);
				x.dispatchEvent(new Event('input'));
			}
	};
	this.setValue = function(x, p_value, p_validate) {
		if (x = that.x(x)) {
			//x.value = p_value;
			if (x.value == p_value) return;
	
			if (x.localName == 'select') {
				var o = that.create('option', null, null, p_value);
				o.value = p_value;
				x.add(o);
	
				x.value = p_value;
			}
			else if (x.localName == 'input') {
				if (x.type == 'checkbox') {
					x.checked = p_value == true || p_value == "true" || p_value == "1";
	
					//*** VALIDATE DIALOG
					if (null==p_validate || p_validate === true) if (x.getAttribute('attr_dialog_id')) pDialog.validate(x.getAttribute('attr_dialog_id'));
					return;
				}
				if (x.type == 'date' || that.getAttribute(x, 'xtype') == 'date') {
					if (pDevice.isChrome() || pDevice.isSafari() || pDevice.isIOS()) {
						x.value = p_value.getFullYear()+'-'+((p_value.getMonth()<9)? '0':'')+(p_value.getMonth()+1)+'-'+((p_value.getDate()<10)? '0':'')+p_value.getDate();
						pConsole.debug(pElement, x.id + ': ' + p_value.getFullYear()+'-'+((p_value.getMonth()<9)? '0':'')+(p_value.getMonth()+1)+'-'+((p_value.getDate()<10)? '0':'')+p_value.getDate());
					}
					else
						x.value = pDate.toLongLocaleString(p_value);
	
					//*** VALIDATE DIALOG
					if (null==p_validate || p_validate === true) if (x.getAttribute('attr_dialog_id')) pDialog.validate(x.getAttribute('attr_dialog_id'));
					return;
				}
				if (x.type != 'datetime-local' && that.getAttribute(x, 'xtype') == 'datetime-local') {
					x.value = p_value.substring? pDate.create(p_value) : p_value;
					
					//*** VALIDATE DIALOG
					if (null==p_validate || p_validate === true) if (x.getAttribute('attr_dialog_id')) pDialog.validate(x.getAttribute('attr_dialog_id'));
					return;
				}
				if (x.type == 'datetime-local' || that.getAttribute(x, 'xtype') == 'datetime-local') {
					if (p_value && pString.v(''+p_value)) {
						if (p_value.substring) p_value = pDate.create(p_value);
						
						if (pDevice.isChrome() || pDevice.isSafari() || pDevice.isIOS()) {
							var i_time = p_value.getFullYear()+'-'+((p_value.getMonth()<9)? '0':'')+(p_value.getMonth()+1)+'-'+((p_value.getDate()<10)? '0':'')+p_value.getDate();
							i_time += 'T'+((p_value.getHours()<10)? '0':'')+p_value.getHours();
							i_time += ':'+((p_value.getMinutes()<10)? '0':'')+p_value.getMinutes();
							i_time += ':'+((p_value.getSeconds()<10)? '0':'')+p_value.getSeconds();
							x.value = i_time;
							pConsole.debug(pElement, x.id + ': ' + i_time);
						}
						else
							x.value = pDate.toLongLocaleString(p_value);
					}
					
					//*** VALIDATE DIALOG
					if (null==p_validate || p_validate === true) if (x.getAttribute('attr_dialog_id')) pDialog.validate(x.getAttribute('attr_dialog_id'));
					return;
				}
				//console.log('v: ' + p_value);
				x.value = p_value;
	
				//*** VALIDATE DIALOG
				if (null==p_validate || p_validate === true) if (x.getAttribute('attr_dialog_id')) pDialog.validate(x.getAttribute('attr_dialog_id'));
			}
			else
				x.value = p_value;
		}
	};

	// @param: p_func function or callback...
	this.setOnClick = function(x, p_func) {
		if (x = that.x(x)) {
			var i_func = p_func;
			if (p_func && p_func.isCallback)
				x.onclick = function() { i_func.call(this, this); };
			else
				x.onclick = p_func;
		}
	};
	this.setOnChange = function(x, p_func) {
		if (x = that.x(x)) {
			var i_func = p_func;
			if (p_func && p_func.isCallback)
				x.onchange = function() { i_func.call(this, this); };
			else
				x.onchange = p_func;
		}
	};

	this.click = function(x) {
		if (x = that.x(x))
			if (x.onclick && x.disabled != true) {//new pStyle(x).display() != "none") {
				x.click();
				return true;
			}
	};
	this.copy = function(x) {
		if (x = that.x(x)) { x.focus(); x.select(); document.execCommand('copy'); }
	};
	this.paste = function(x) {
		if (x = that.x(x)) { x.focus(); x.select(); document.execCommand('paste'); }
	};
	this.scrollToBottom = function(x) {
		if (x = that.x(x))
			(x.forEach? x : [ x ]).forEach(function(x) { if (x) x.scrollTop = x.scrollHeight; });
	};
	this.getOffset = function(x) {
		if (x = that.x(x)) {
			var l = 0, t = 0;
			while (x && !isNaN( x.offsetLeft ) && !isNaN( x.offsetTop ) ) {
				l += x.offsetLeft - x.scrollLeft;
				t += x.offsetTop - x.scrollTop;
				x = x.offsetParent;
			}
			return { top: t, left: l };
		}
	};
	this.setAttribute = function(x, n, v) {
		if (x = that.x(x)) {
			x.setAttribute(n, v);
			return true;
		}
	};
	this.getAttribute = function(x, n, d) {
		if (x = that.x(x))
			return x.getAttribute(n) || d;
		return d;
	};
	this.setStyleDisplay =function(x, v) {
		if (x = that.x(x)) 
			x.style.display = v;
	};
	this.nextSibling = function(x) {
		if (x = that.x(x)) return x.nextSibling;
	};
	this.nextElementSibling = function(x) {
		if (x = that.x(x)) return x.nextElementSibling;
	};
	
	this.fillDatalist = function(x) {
		if (x = that.x(x)) {
			var tmp = [], f = function(n) {
				if (!tmp.includes(n)) {
					//if (n == 'Martin Garrix') console.log(n);
					that.addToDatalist(x, n, true);
					tmp[tmp.length] = n;
				}
			};
			Array.from(arguments).forEach(function(a, i) {
				if (i>0 && a)
					(a.forEach? a : [ a ]).sort().forEach(f);
			});
		}
	};
	this.addToDatalist = function(x, n, nocheck) {
		if (n.length>100)
			return;
		
		if (x = that.x(x)) {
			if (nocheck) {
				var o = that.create('option');
				o.value = n.value || n;
				o.text = n.text || o.value; if (o.text) o.text = o.text.replace(/&amp;/, '&');
				x.appendChild(o);
			}
			else {
				var o = that.t(x, 'option');
				if (!o.some(function(oo) { return oo.value == (n.value || n) }))
					that.addToDatalist(x, n, true);
			}
		}
	};
	
	this.create = function(type, id, cl, h, s, a) {
		var x = document.createElement(type);
		if (id) x.id = id;
		if (cl && cl.forEach) cl = cl.filter(pObject.isNotNull).join(' ');
		if (cl) x.className = cl;
		if (h && h.forEach) 
			h.forEach(function(y) {
				x.appendChild(y.forEach? that.create(y[0], y[1], y[2], y[3], y[4], y[5]) : y);
			});
		else if (h) 
			x.innerHTML = h;
		if (s) x.style = s;
		if (a)
			for(var i=0 ; i<a.length ; i+=2)
				if (a[i] == 'title') {
					if (a[i+1]) that.setTitle(x, (''+a[i+1]).replace(/&#x000A;/g, '\n'));
				}
				else
					x.setAttribute(a[i], a[i+1]);
		return x;
	};
	
	function f_decorate_content(id) {
		var v = pDocument.getStyleValue('.' + id) || pDocument.getStyleValue('.' + id + '-content');
		if (pString.v(v)) {
			this.innerHTML = v;
			return true;
		}
	}
	
	function f_decorate_href(id) {
		var v = pDocument.getStyleValue('.' + id + '-href');
		if (pString.v(v)) {
			this.href = v;
			return true;
		}
	}
	
	function f_decorate_title(id) {
		var v = pDocument.getStyleValue('.' + id + '-title');
		if (pString.v(v)) {
			this.title = v;
			return true;
		}
	}
	
	this.decorate = function(x, id) {
		if (x = that.x(x)) {
			
			var ids = [ id || x.id ];
			ids = ids.concat((x.getAttribute('decorated') || '').split(' ')).filter(pString.v);
			
			ids.find(f_decorate_content, x);
			ids.find(f_decorate_href, x);
			ids.find(f_decorate_title, x);
		}
	};
	this.decorateAll = function() {
		that.c('decorated').forEach(function(x) { that.decorate(x); });
	};
	
	/** @param x Element ID. */
	this.createClearDiv = function(x) {
		return that.create('div', x, null, null, 'clear: both');
	};
	
	this.add = function(x, y) {
		if (x = that.x(x))
			(y.forEach? y : [ y ]).forEach(function(z) { 
				if (z.forEach) 
					x.appendChild(that.create(z[0], z[1], z[2], z[3], z[4], z[5])); 
				else if (z = that.x(z)) 
					x.appendChild(z); 
			});
	};
	
	this.toCanvas = function(x) {
		return new Promise(function(resolve, reject) {
			var canvas = that.create("canvas"), ctx = canvas.getContext("2d"), data = "data:image/svg+xml," +
			    "<svg xmlns='http://www.w3.org/2000/svg' width='200' height='200'>" +
			         "<foreignObject width='100%' height='100%'>" + that.x(x).outerHTML + 
			         "</foreignObject>" +
			    "</svg>";
			
			console.log(data);

			var img = new Image();
			img.onload = function() { 
				ctx.drawImage(img, 0, 0); 
				console.log('canvas ready');
				resolve(canvas); 
			}
			img.onerror = function(e) { 
				console.log('error: ' + e) }
			img.src = data;
		});
	};

	this.isOverflow = function(x) {
		if (x = that.x(x)) {
			var s = new pStyle(x), co = s.prop("overflow");

			if (!co || co === "visible")
				x.style.overflow = "hidden";

			var sh = x.scrollHeight - s.paddingHeight(), r = (x.clientWidth < x.scrollWidth && (x.scrollWidth - x.clientWidth > 4)) || (x.clientHeight < sh && (sh - x.clientHeight > 4));
			//console.log('' + x.clientWidth + ' ' + x.scrollWidth + ' ' + sh + ' ' + x.clientHeight)
			x.style.overflow = co;
			return r;
		}
	};
});

/******************************************************************************/

const pImage = new (function() {
	this.changeSrc = function(x, p_url) {
		if (x = pElement.x(x)) {
			x.onerror = null;
			pElement.setSrc(x, p_url);
		}
	};
});

/******************************************************************************/
/******************************************************************************/
/******************************************************************************/

const pKeys = (new function() {
	
	this.down = 40;
	this.escape = 27;
	this.f3 = 183;
	this.up = 38;
	this.right = 39;
	this.left = 37;
	this.enter = 13;
	this.space = 32;
	
	this.pure = function(e) {
		return !e.altKey && !e.ctrlKey && !e.shiftKey;
	};
});

/******************************************************************************/
/******************************************************************************/
/******************************************************************************/

const pDocumenti = new (function() {
	var that = this, m_onkeydown;
	
	this.addOnKeyDown = function(f_callback, p_keycodes, first, id) {
		if (!m_onkeydown)
			document.onkeydown = function(e) {
				var i_focus = that.activeElement();

				if (i_focus)
					if (i_focus.localName) {
						if (i_focus.localName.toLowerCase() === "input") {
							if (i_focus.id == 'form-search-text' && e.keyCode == pKeys.escape) {
								if (e.altKey  || e.ctrlKey || e.shiftKey) return true;
								i_focus.blur();
								return true;
							}
							return true;
						}
						if (i_focus.localName.toLowerCase() === "select") return true;
						if (i_focus.localName.toLowerCase() === "textarea") return true;
					}
				
				if (e.altKey  || e.ctrlKey || e.shiftKey ) //removed: === true
					return true;

				//console
				pConsole.debug(that, 'KeyCode: ' + e.keyCode);
				if (m_onkeydown)
					for(var i=0 ; i<m_onkeydown.length ; i++) {
						var h = m_onkeydown[i];
						if (h.keycodes && !(h.keycodes.forEach? h.keycodes : [ h.keycodes ]).includes(e.keyCode)) continue;

						if (h.fcall(e, e.keyCode)) { //removed: === true
							e.preventDefault();
							return false;
						}
					}
			};
		
		if (first)
			(m_onkeydown = m_onkeydown || []).unshift({ fcall: f_callback, keycodes: p_keycodes, id: id });
		else
			(m_onkeydown = m_onkeydown || []).push({ fcall: f_callback, keycodes: p_keycodes, id: id });
	};
	
	this.removeOnKeyDown = function(f_callback, p_keycodes, id) {
		m_onkeydown = id? (m_onkeydown || []).filter(function(k) { return id != k.id }) : pArray.remove(m_onkeydown || [], { fcall: f_callback, keycodes: p_keycodes, id: id });
	};
	
	this.activeElement = function() {
		var focused = document.activeElement;
		if (!focused || focused == document.body)
		    focused = null;
		else if (document.querySelector)
    		focused = document.querySelector(":focus");
    	return focused;
	};
});

/******************************************************************************/
/******************************************************************************/
/******************************************************************************/

const pDocument = new (function() {
	var that = this, eh = new pEventHandler();
	
	this.setTitle = function(t) {
		document.title = t;
		pElement.setTextContent('doc-title', t);
	};
	this.exec = function(x) {
		if (x = pElement.x(x)) {
			var s = pElement.create('script');
			s.innerHTML = x.innerHTML;
			document.body.appendChild(s);
		}
	};
	
	this.addOnVisibilityChange = function(f) {
		eh.addEventListener("visibilitychange", f);
	};
	this.onVisibilityChange = function() {
		eh.fireEvent("visibilitychange");
	};
	this.addOnPageShow =  function(f) {
		eh.addEventListener("pageshow", f);
	};
	this.onPageShow = function() {
		pConsole.info(this, "onPageShow...");

		that.stopwait();
		eh.fireEvent("pageshow");
	};
	this.addOnPageClose = function(f) {
		eh.addEventListener("close", f);
	};
	this.onPageClose = function() {
		eh.fireEvent("close");
	};
	this.addOnResize = function(f) {
		eh.addEventListener("resize", f);
	};
	this.removeOnResize = function(f) {
		eh.removeEventListener('resize', f);
	};
	this.onResize = function() {
		//alert("resize: " + screen.width + " x " + screen.height);
		eh.fireEvent("resize");
		eh.events().forEach(function(i_event_id) {
			if (i_event_id.indexOf("show.") === 0)
				that.onShow(i_event_id.substring("show.".length));
		});
		
		f_coverWholePage_onresize();
	};
	this.addOnShow = function(x, f) {
		if (x = pElement.x(x))
			eh.addEventListener("show."+x.id, f);
	};
	this.onShow = function(x) {
		if (x = pElement.x(x))
			eh.fireEvent("show."+x.id);
	};
	
	var m_renderEllipsisText_ids;
	this.ellipsisMenu = function(i) {
		m_renderEllipsisText_ids[i].menu.call(m_renderEllipsisText_ids[i]);
	};
	this.renderEllipsisText = function(p_id, p_cid, p_sep, p_execute) {

		var i_request = {};
		if (p_id.id) {
			i_request = p_id;
			p_id = i_request.id;
			p_cid = i_request.cid;
			p_sep = i_request.sep;
		}
				
		var x = pElement.x(p_id);
		if (!x)
			return;

		var i_texts = [], i_text = x.getAttribute('data-json');
		
		if (pString.v(i_text)) {
			i_texts = pJSON.parse(i_text); i_texts = i_texts.data || i_texts.values;
		}
		else {
			i_text = (i_request.ignoreCase === true)? x.getAttribute("data-text").toLowerCase() : x.getAttribute("data-text");
			i_texts = (i_request.parse)? i_request.parse(''+x.getAttribute("data-text"), p_sep || ',') : pString.split(pString.decodeHTML(i_text), p_sep || ',');
			
			if (i_texts.length==1 && i_texts[0].startsWith('[') && i_texts[0].endsWith(']'))
				i_texts = pString.split(i_texts[0].substring(1, i_texts[0].length-1), ',');
		}

		//*** remove duplicates
		var i_texts2 = [];
		for(var i=0 ; i<i_texts.length ; i++)
			if (i_texts[i]!=null && i_texts[i]!='' && i_texts2.indexOf(i_texts[i])<0)
				i_texts2[i_texts2.length] = i_texts[i];
		if (i_texts2.length==1 && i_texts2[0].toLowerCase() == '(unknown)')
			i_texts2 = [];

		if (i_texts2.length<1)
			that.hide(p_cid);
		else
			that.show(p_cid);

		if (!m_renderEllipsisText_ids)
			m_renderEllipsisText_ids = [];

		for(var i=0 ; i<m_renderEllipsisText_ids.length ; i++)
			if (m_renderEllipsisText_ids[i].id == x.id)
				break;

		var index = i;
		m_renderEllipsisText_ids[i] = {
			id: x.id,
			request: i_request,
			element: x,
			cid: p_cid,
			full: i_text,
			texts: i_texts2,
			href: x.getAttribute("data-href"),
			className: x.getAttribute("data-class"),
			menuClassName: x.getAttribute("menu-class"),
			reset: function() {
				var x = this.element;
				if (x!=null) {
					x.style.width = "1px"; //TODO: why?
					x.innerHTML = "";
				}
			},
			menu: function() {
				var i_name = this.texts.some(pObject.isNotNull), i_links = this.texts.map(function(t) { return (t.name)?
					{ icon: 'none', text: this.formatText(t.name), className: this.className + ' ' + (this.menuClassName || ''), url: (t.lid)? pMediaLibrary.getLibraryURL()+t.lid+'/'+t.id+'/' : null, disabled: t.disabled }:
					{ icon: 'none', text: this.formatText(t), className: this.className + ' ' + (this.menuClassName || ''), url: (this.href)? this.href+pURL.fixedEncodeURIComponent(t):null, disabled: i_name }; 
				}.bind(this));
				i_links.sort(function(p_a, p_b) { if (p_a.text < p_b.text) return -1; if (p_a.text > p_b.text) return 1; return 0; });
				pApplicationMenu.menu(this.id, i_links);
			},
			formatText: function(t) {
				return (this.request.format)? this.request.format.call(this, t, this) : t;
			},
			getURL: function(i_add) {
				if (i_add.name) 
					return pMediaLibrary.getDocURL(null, i_add.lid, i_add.id);
				if (this.href)
					return this.href+pURL.fixedEncodeURIComponent(i_add);
				return null;
			},
			format: function() {
				var x = this.element;
				if (x) {
					var c = pElement.x(this.cid);
					if (c) {
						var i_texts = this.texts, i_href = this.href, desch = c.getElementsByClassName('desch');
						if (desch!=null && desch.length>0)
							desch = desch[0];
						var i_style_c = new pStyle(c), w = that.width(c)/*c.clientWidth - i_style_c.paddingLeftWidth() - i_style_c.paddingRightWidth()*/ - that.width(desch);// - 10 - 120; //desch
						//console.log("w - " + this.cid + ": " + w);
						//console.log("w - " + this.cid + ": " + that.width(c));
						//console.log("w - " + this.cid + ": " + that.width(desch));
		
						var i_comma = ', ';
						for(var i=0 ; i<i_texts.length ; i++) {
							var i_text = x.innerHTML, i_add = i_texts[i], i_url = this.getURL(i_add), i_link = false;
							if (i_url) {
								i_add = '<a class="link '+(this.className || '')+'" href="'+i_url+'">' + pString.encodeHTML(this.formatText(i_add.name || i_add)) + '</a>';
								i_link = true;
							}
							else
								i_add = pString.encodeHTML(i_add);
								
							/*if (i_add.name) {
								i_add = '<a class="link '+((this.className)? this.className:'')+'" href="'+pMediaLibrary.getDocURL(null, i_add.lid, i_add.id)+'">' + i_add.name + '</a>';
								i_link = true;
							}
							else if (i_href) {
								i_add = '<a class="link '+((this.className)? this.className:'')+'" href="'+i_href+pURL.fixedEncodeURIComponent(i_url)+'">' + this.formatText(i_add, this) + '</a>';
								i_link = true;
							}*/
		
							if (i_text=="")
								x.innerHTML = i_add;
							else
								x.innerHTML = x.innerHTML + i_comma + i_add + "...";
		
							//console.log("w - " + this.cid + ": " + w + " " + that.width(x) + " " + i_add);
							if (that.width(x) >= w) {
								if (i+1 < i_text.length)
									x.innerHTML = i_text + ', <a class="link" href="javascript:void(pDocument.ellipsisMenu('+index+'))">...</a>';
								break;
							}
		
							if (i_text=="")
								x.innerHTML = i_add;
							else
								x.innerHTML = i_text + i_comma + i_add;
		
							i_comma = (i_link)? '<span class="'+((this.className)? this.className:'')+'">, </span>' : ", "
						}
						x.style.width = that.width(x);//TODO: why??
					}
				}
			}
		};

		if (p_execute === true) {
			m_renderEllipsisText_ids[i].reset.call(m_renderEllipsisText_ids[i]);
			m_renderEllipsisText_ids[i].format.call(m_renderEllipsisText_ids[i]);
		}
	};
	
	function f_reset(o) { o.reset.call(o); }
	function f_format(o) { o.format.call(o); }
	
	this.m_renderEllipsisTextImpl = function() {
		if (m_renderEllipsisText_ids) {
			m_renderEllipsisText_ids.forEach(f_reset);
			m_renderEllipsisText_ids.forEach(f_format);
		}
	};
	
	var m_element_hide_smooth;
	this.isShownSmooth = function(x) {
		if (x = pElement.x(x)) {
			if (!m_element_hide_smooth)
				return false;
			
			var i_data = m_element_hide_smooth.getValue(x.id, null);
			return i_data? i_data.shown : false;
		}
	};
	this.setSmoothAutoHeight = function(x) {
		if (x = pElement.x(x)) {
			if (!m_element_hide_smooth)
				return false;
			
			var i_data = m_element_hide_smooth.getValue(x.id, null);
			if (i_data)
				i_data.height = 'auto';//: (new pStyle(x)).height(), shown: false });
			else
				x.style.height = 'auto';
		}
	};
	this.hideSmooth = function(x) {
		if (x = pElement.x(x)) {
			if (!m_element_hide_smooth)
				m_element_hide_smooth = new pMap();
	
			//var i_data = m_element_hide_smooth.getValue(x.id, null);
			//if (i_data)
			//	i_data.shown = false;
			//else
				m_element_hide_smooth.put(x.id, i_data = { element: x, height: (new pStyle(x)).height(), shown: false });
	
			if (new pStyle(x).position() == "absolute")
				x.style.display = "none";
			else
				x.style.height = "0px";
			//x.style.border = "0px";
		}
	};
	this.showSmooth = function(x, p_callback) {
		if (x = pElement.x(x)) {
			if (!m_element_hide_smooth)
				m_element_hide_smooth = new pMap();
	
			var i_data = m_element_hide_smooth.getValue(x.id, null);
			if (new pStyle(x).position() == "absolute") {
				x.style.display = "block";
				m_element_hide_smooth.put(x.id, i_data = { element: x });
			}
			else if (i_data && i_data.height != "auto" && x.style.height != 'auto') {
				x.style.height = i_data.height;
				//x.style.border = i_data.border;
			}
			else {
				x.style.height = "auto";
				x.style.display = "block";
	
				var i_rect = x.getBoundingClientRect(), i_h = (i_rect.bottom - i_rect.top) + "px";
				//var i_h = (new pStyle(x)).height();
				m_element_hide_smooth.put(x.id, i_data = { element: x, height: i_h });
	
				x.style.height = "0px";
				x.style.height = i_h;
			}
			i_data.shown = true;
	
			return x.style.height;
		} // return null;
	};
	this.isShown = function(x) {
		if (x = pElement.x(x)) {
			if (m_element_hide_smooth) {
				var d = m_element_hide_smooth.getValue(x.id, null);
				return d && d.shown;
			}
		}
		return false;
	};
	var m_element_hide;
	this.isHidden = function(x) {
		if (x = pElement.x(x)) {
			if (m_element_hide) {
				if (m_element_hide.getValue(x.id))
					return true;
				/*
				var i_data = m_element_hide.getValue(x.id, null);
				if (i_data)
					return true;*/
			}
	
			if ((new pStyle(x)).display() != 'none')
				return false;
			/*
			var i_display_old = (new pStyle(x)).display();
			if (i_display_old != "none")
				return false;
				*/
			return true;
		}//return null;
	};
	
	function f_hide(x) {
		if (x = pElement.x(x)) {
			if (!m_element_hide)
				m_element_hide = new pMap();
	
			//m_element_hide.remove(x.id);
			var i_display_old = (new pStyle(x)).display();
			if (i_display_old != "none") {
				m_element_hide.put(x.id, { element: x, display: i_display_old });
				
				pElement.removeClassName(x, 'show');
			}
				
			x.style.display = "none";
		}
	}
	
	this.hide = function(y) {
		if (y)
			(y.forEach? y : [y]).forEach(f_hide);
	};
	
	/*this.hideByClass = function(x) {
		pElement.c(x).forEach(that.hide);
	};
	this.hide = function(x) {
		if (x = pElement.x(x)) {
			if (!m_element_hide)
				m_element_hide = new pMap();
	
			//m_element_hide.remove(x.id);
			var i_display_old = (new pStyle(x)).display();
			if (i_display_old != "none") {
				m_element_hide.put(x.id, { element: x, display: i_display_old });
				
				pElement.removeClassName(x, 'show');
			}
				
			x.style.display = "none";
		}
	};*/
	
	function f_show(x, d) {
		if (x = pElement.x(x)) {
			if (!m_element_hide)
				m_element_hide = new pMap();
	
			if ((new pStyle(x)).display() == "none") {
				var i_data = m_element_hide.getValue(x.id, null);
				d = i_data? i_data.display : d;
	
				/*if (!i_display)
					pElement.addClassName(x, 'visible');
				else*/
				if (d != 'auto')
					x.style.display = d || x.getAttribute('display_default') || (x.localName=='tr'? 'table-row':'block');
				else
					x.style.display = null;
				
				pElement.addClassName(x, 'show');
			}
			m_element_hide.remove(x.id);
	
			that.onShow(x);
		}
	};
	this.show = function(y, d) {
		if (y)
			(y.forEach? y : [y]).forEach(function(x) { f_show(x, d); });
	};
	
	/*this.showByClass = function(x, p_display) {
		pElement.c(x).forEach(function(y) { that.show(y, p_display); });
	};
	this.show = function(x, p_display) {
		if (x = pElement.x(x)) {
			if (!m_element_hide)
				m_element_hide = new pMap();
	
			if ((new pStyle(x)).display() == "none") {
				var i_data = m_element_hide.getValue(x.id, null), i_display = i_data? i_data.display : p_display;
	
				if (i_display != 'auto') {
					if (i_display)
						x.style.display = i_display;
					else if (x.getAttribute('display_default'))
						x.style.display = x.getAttribute('display_default');
					else
						x.style.display = ((x.localName == "tr")? "table-row" : "block");
				}
				pElement.addClassName(x, 'show');
			}
			m_element_hide.remove(x.id);
	
			that.onShow(x);
		}
	};*/
	this.showAndFadeOut = function(x) {
		if (x = pElement.x(x)) {
			pElement.addAndRemoveClassName(x, 'noanimation', 'fadeout'); 
			that.show(x); 
			that.setTimeout(x.id, function(){ pElement.addAndRemoveClassName(x, 'fadeout', 'noanimation'); }, 100);
		}
	};
	
	this.toggle = function(x, y, sih, hih) {
		if (x = pElement.x(x)) {
			if (that.isHidden(x)) {
				that.show(x);
				if (sih) pElement.setInnerHTML(y, sih)
			}
			else {
				that.hide(x);
				if (sih) pElement.setInnerHTML(y, hih)
			}
		}
	};
	
	var m_component_hide;
	/*this.componentIsShown = function(p_cid) {
		var i_container = (typeof p_cid == "string")? pElement.x(p_cid) : p_cid;
		if (null==i_container)
			return false;

		if (!m_component_hide)
			return false;

		var i_obj = m_component_hide.getValue(i_container.id, null);
		return i_obj != null;
	};*/
	this.componentIsShown = function(x) {
		if (x = pElement.x(x))
			if (m_component_hide)
				return null!=m_component_hide.getValue(x.id);
		return false;
	};
	this.showComponent = function(x, p_tohide_class, p_tohide_class2) {
		if (x = pElement.x(x)) {
			if (!m_component_hide)
				m_component_hide = new pMap();
	
			var o = m_component_hide.getValue(x.id, null);
			if (!o) {
				o = { id: x.id, elements: [], visibles: [], pageXOffset: window.pageXOffset, pageYOffset: window.pageYOffset };
				m_component_hide.put(x.id, o);
		
				if (typeof p_tohide_class=="string")
					pElement.c(p_tohide_class).forEach(function(e) {
						o.elements.push({ element: e, display: new pStyle(e).display() });
						e.style.display = "none";
					});
				else if (p_tohide_class) {
					var e = pElement.x(p_tohide_class);
					if (e) {
						o.elements.push({ element: e, display: new pStyle(e).display() });
						e.style.display = "none";
					}
				}
						
				
				if (null!=p_tohide_class2) {
					pElement.c(p_tohide_class2).forEach(function(xx) {
						o.visibles.push({ element: xx, visibility: xx.style.visibility });
						xx.style.visibility = "hidden";
					});
				}
		
				//alert(x.style.height);
				//if (x.style.height == 0 || x.style.height == "0px")
				//	x.style.height = "auto";
		
				if (x.id == 'dialog-question')
					x.style.display = 'block';//flex';
				else
					x.style.display = "block";
		
				setTimeout(function() { that.onShow(x); that.m_renderEllipsisTextImpl(); }, 500);
			}
		}
	};
	this.hideComponent = function(x) {
		if (x = pElement.x(x)) {
			/*x.style.height = x.clientHeight;
			setTimeout(function () {
				x.style.overflow = "hidden";
				x.style.height = 0;
			}; 100);*/
			//x.style.display = "none";
	
			if (!m_component_hide)
				m_component_hide = new pMap();
	
			var o = m_component_hide.getValue(x.id, null);
			if (o) {
				o.elements.forEach(function(x) {
					x.element.style.display = x.display;
				});
				o.visibles.forEach(function(x) {
					x.element.style.visibility = x.visibility;
				});
	
				window.scrollTo(o.pageXOffset, o.pageYOffset);
	
				m_component_hide.remove(x.id);
			}
	
			/*for(var i=0 ; i<m_component_hide.length ; i++)
				if (m_component_hide[i].id == p_cid) {
	
					for(var j=0 ; j<m_component_hide[i].elements.length ; j++) {
						m_component_hide[i].elements[j].element.style.display = m_component_hide[i].elements[j].display;
					}
	
					m_component_hide.splice(i, 1);
					break;
				}*/
	
			x.style.display = "none";
	
			that.m_renderEllipsisTextImpl();
		}
	};
	this.scrollWidth = function() {
		var b = document.body, h = document.documentElement;
		return Math.max( b.scrollWidth, b.offsetWidth, h.clientWidth, h.scrollWidth, h.offsetWidth );
	};
	this.scrollHeight = function() {
		var b = document.body, h = document.documentElement;
		
		//console.log([ body.scrollHeight, body.offsetHeight, html.clientHeight, html.scrollHeight, html.offsetHeight ].join('\\n'));
		return Math.max( b.scrollHeight, b.offsetHeight, h.clientHeight, h.scrollHeight, h.offsetHeight );
	};
	
	var nav;
	this.disableNav = function() {
		var cn = { x: [] };
		nav = nav || [];
		nav.push(cn);
		[ 'input', 'select', 'textarea', 'a' ].forEach(function(t) {
			pElement.t(document, t).forEach(function(x) {
				var ti = pElement.getAttribute(x, 'tabindex');
				if (ti)
					cn.x[cn.x.length] = { x: x, ti: ti };
				x.setAttribute('tabindex', '-1');
			});
		});
	};
	
	function f_remove_x_tabindex(x) { x.removeAttribute('tabindex'); };
	function f_remove_t_tabindex(t) { pElement.t(document, t).forEach(f_remove_x_tabindex); };
	function f_restore_x_tabindex(c) { c.x.setAttribute('tabindex', c.ti); };
	this.enableNav = function() {
		var cn = nav.pop();
		[ 'input', 'select', 'textarea', 'a' ].forEach(f_remove_t_tabindex);
		
		if (cn)
			cn.x.forEach(f_restore_x_tabindex);
	};
	
	var busy = false;
	this.wait = function(m) {
		if (!busy) {
			var x = pElement.x('busy-screen'), y = pElement.x('busy-screen-progress');
			if (x && y) {
				x.style.display = "block";
				y.style.display = "block";
				
				pElement.setInnerHTML('busy-screen-msg', m || '');
				
				//*** DISABLE BODY SCROLL
				pElement.addClassName(document.body, 'noscroll-busy');
	
				//*** ENABLE BACKGROUND RESIZING
				that.coverWholePage(x);
				that.coverWholePage(y);
	
				//*** DISABLE TAB NAVIGATION
				that.disableNav();
				
				busy = true;
			}
		}
		/*if (!x)
			return;
		
		var i_url = new pStyle(x).backgroundImage();
		if (i_url.startsWith("url('")) {
			var i_img = new Image();
			i_img.onload = that.waitImpl;
			i_img.src = i_url.substring("url('".length, i_url.length-2);
		}
		else	
			that.waitImpl();
	};
	this.waitImpl = function() {
		var x = pElement.x('busy-screen');
		
		//window.scrollTo(0, 0);
		x.style.display = "block";

		//*** ENABLE BACKGROUND RESIZING
		that.coverWholePage('busy-screen');

		busy = true;*/
	};
	
	this.stopwait = function() {
		if (busy) {
			var x = pElement.x('busy-screen'), y = pElement.x('busy-screen-progress');
			if (x && y) {
				x.style.display = "none";
				y.style.display = "none";
				
				//*** ENABLE BODY SCROLL
				pElement.removeClassName(document.body, 'noscroll-busy');
				
				//*** ENABLE TAB NAVIGATION
				that.enableNav();
				
				busy = false;
			}
		}
	};
	
	this.registerEvent = function(x, event, handler) {
		if (x = pElement.x(x))
			pEvent.addEventListener(event, x, handler);
	};
	
	this.scrollBy = function(x, y) {
		//window.scrollBy(x, y);
		window.scrollBy({ left: x, top: y, behavior: 'smooth' });
	};
	
	//m_scrolls
	this.scrollTo = function(x, x2) {
		if ((x = pElement.x(x)) && (!x2 || (x2 = pElement.x(x2))))
			try {
				var r = x.getBoundingClientRect(), t = r.top, b = r.bottom + (new pStyle(x)).surroundBottomHeight(), h = window.innerHeight;
				if (x2) b = x2.getBoundingClientRect().bottom + (new pStyle(x2).surroundBottomHeight());
				
				//console.log('h ' + h + ' t: ' + t + ' b: ' + b)
				
				if (t > h)
					that.scrollBy(0, t - h);
				else if (b > h) {
					
					if (b - t + 1 < h)
						that.scrollBy(0, b - h + 1);
					else if (t < h)
						that.scrollBy(0, t);
					else
						that.scrollBy(0, b - h);
				}
				else if (t < 0)
					that.scrollBy(0, t);
			}
			catch (exception) {
				//alert(exception);
			}
	};
	
	this.middle = function(x) {
		if (x = pElement.x(x)) {
			var r = x.getBoundingClientRect(); return r.left + ((r.right - r.left) / 2)
		}
		return 0;
	}
	this.top = function(x) {
		if (x = pElement.x(x))
			return x.getBoundingClientRect().top;
		return 0;
	};
	this.height = function(x) {
		if (x = pElement.x(x)) {
			var r = x.getBoundingClientRect();
			//0.9.16
			return (r.height || (r.bottom - r.top)) + (new pStyle(x)).marginHeight();
			//return ((r.height)? r.height : (r.bottom - r.top)) + (new pStyle(x)).marginHeight();
		}
		return 0;
	};
	this.innerHeight = function(x, p_style) { // width without margin, border and padding
		if (x = pElement.x(x)) {
			//0.9.16
			var r = x.getBoundingClientRect(), s = p_style || new pStyle(x);
			return (r.height || (r.bottom - r.top)) - s.paddingHeight() - s.borderHeight();//+ i_style.marginWidth();
			//var r = x.getBoundingClientRect();
			//var i_style = (p_style)? p_style : new pStyle(x);
			//return ((r.height!=null)? r.height : (r.bottom - r.top)) - i_style.paddingHeight() - i_style.borderHeight();//+ i_style.marginWidth();
		}
		return 0;
	};
	this.width = function(x, p_style) {
		if (x = pElement.x(x)) {
			var r = x.getBoundingClientRect();
			//0.9.16
			return (r.width || (r.right - r.left)) + (p_style || new pStyle(x)).marginWidth();
			//return ((r.width!=null)? r.width : (r.right - r.left)) + (p_style? p_style : new pStyle(x)).marginWidth();
		}
		return 0;
	};
	this.innerWidth = function(x, p_style) { // width without margin, border and padding
		if (x = pElement.x(x)) {
			var r = x.getBoundingClientRect();
			var i_style = (p_style)? p_style : new pStyle(x);
			return ((r.width!=null)? r.width : (r.right - r.left)) - i_style.paddingWidth() - i_style.borderWidth();//+ i_style.marginWidth();
		}
		return 0;
	};
	this.rightContent = function(x) {
		if (x = pElement.x(x)) {
			var r = x.getBoundingClientRect();
			var i_style = new pStyle(x);
	
			//console.log('left: ' + i_rect.left + ' right: ' + i_rect.right);
	
			return r.right - i_style.paddingRightWidth() - i_style.borderRightWidth();
		}
		return 0;
	};
	/*
	var m_beep;
	this.beep = function() {
	    if (!m_beep)
	    	m_beep = new Audio("data:audio/wav;base64,//uQRAAAAWMSLwUIYAAsYkXgoQwAEaYLWfkWgAI0wWs/ItAAAGDgYtAgAyN+QWaAAihwMWm4G8QQRDiMcCBcH3Cc+CDv/7xA4Tvh9Rz/y8QADBwMWgQAZG/ILNAARQ4GLTcDeIIIhxGOBAuD7hOfBB3/94gcJ3w+o5/5eIAIAAAVwWgQAVQ2ORaIQwEMAJiDg95G4nQL7mQVWI6GwRcfsZAcsKkJvxgxEjzFUgfHoSQ9Qq7KNwqHwuB13MA4a1q/DmBrHgPcmjiGoh//EwC5nGPEmS4RcfkVKOhJf+WOgoxJclFz3kgn//dBA+ya1GhurNn8zb//9NNutNuhz31f////9vt///z+IdAEAAAK4LQIAKobHItEIYCGAExBwe8jcToF9zIKrEdDYIuP2MgOWFSE34wYiR5iqQPj0JIeoVdlG4VD4XA67mAcNa1fhzA1jwHuTRxDUQ//iYBczjHiTJcIuPyKlHQkv/LHQUYkuSi57yQT//uggfZNajQ3Vmz+Zt//+mm3Wm3Q576v////+32///5/EOgAAADVghQAAAAA//uQZAUAB1WI0PZugAAAAAoQwAAAEk3nRd2qAAAAACiDgAAAAAAABCqEEQRLCgwpBGMlJkIz8jKhGvj4k6jzRnqasNKIeoh5gI7BJaC1A1AoNBjJgbyApVS4IDlZgDU5WUAxEKDNmmALHzZp0Fkz1FMTmGFl1FMEyodIavcCAUHDWrKAIA4aa2oCgILEBupZgHvAhEBcZ6joQBxS76AgccrFlczBvKLC0QI2cBoCFvfTDAo7eoOQInqDPBtvrDEZBNYN5xwNwxQRfw8ZQ5wQVLvO8OYU+mHvFLlDh05Mdg7BT6YrRPpCBznMB2r//xKJjyyOh+cImr2/4doscwD6neZjuZR4AgAABYAAAABy1xcdQtxYBYYZdifkUDgzzXaXn98Z0oi9ILU5mBjFANmRwlVJ3/6jYDAmxaiDG3/6xjQQCCKkRb/6kg/wW+kSJ5//rLobkLSiKmqP/0ikJuDaSaSf/6JiLYLEYnW/+kXg1WRVJL/9EmQ1YZIsv/6Qzwy5qk7/+tEU0nkls3/zIUMPKNX/6yZLf+kFgAfgGyLFAUwY//uQZAUABcd5UiNPVXAAAApAAAAAE0VZQKw9ISAAACgAAAAAVQIygIElVrFkBS+Jhi+EAuu+lKAkYUEIsmEAEoMeDmCETMvfSHTGkF5RWH7kz/ESHWPAq/kcCRhqBtMdokPdM7vil7RG98A2sc7zO6ZvTdM7pmOUAZTnJW+NXxqmd41dqJ6mLTXxrPpnV8avaIf5SvL7pndPvPpndJR9Kuu8fePvuiuhorgWjp7Mf/PRjxcFCPDkW31srioCExivv9lcwKEaHsf/7ow2Fl1T/9RkXgEhYElAoCLFtMArxwivDJJ+bR1HTKJdlEoTELCIqgEwVGSQ+hIm0NbK8WXcTEI0UPoa2NbG4y2K00JEWbZavJXkYaqo9CRHS55FcZTjKEk3NKoCYUnSQ0rWxrZbFKbKIhOKPZe1cJKzZSaQrIyULHDZmV5K4xySsDRKWOruanGtjLJXFEmwaIbDLX0hIPBUQPVFVkQkDoUNfSoDgQGKPekoxeGzA4DUvnn4bxzcZrtJyipKfPNy5w+9lnXwgqsiyHNeSVpemw4bWb9psYeq//uQZBoABQt4yMVxYAIAAAkQoAAAHvYpL5m6AAgAACXDAAAAD59jblTirQe9upFsmZbpMudy7Lz1X1DYsxOOSWpfPqNX2WqktK0DMvuGwlbNj44TleLPQ+Gsfb+GOWOKJoIrWb3cIMeeON6lz2umTqMXV8Mj30yWPpjoSa9ujK8SyeJP5y5mOW1D6hvLepeveEAEDo0mgCRClOEgANv3B9a6fikgUSu/DmAMATrGx7nng5p5iimPNZsfQLYB2sDLIkzRKZOHGAaUyDcpFBSLG9MCQALgAIgQs2YunOszLSAyQYPVC2YdGGeHD2dTdJk1pAHGAWDjnkcLKFymS3RQZTInzySoBwMG0QueC3gMsCEYxUqlrcxK6k1LQQcsmyYeQPdC2YfuGPASCBkcVMQQqpVJshui1tkXQJQV0OXGAZMXSOEEBRirXbVRQW7ugq7IM7rPWSZyDlM3IuNEkxzCOJ0ny2ThNkyRai1b6ev//3dzNGzNb//4uAvHT5sURcZCFcuKLhOFs8mLAAEAt4UWAAIABAAAAAB4qbHo0tIjVkUU//uQZAwABfSFz3ZqQAAAAAngwAAAE1HjMp2qAAAAACZDgAAAD5UkTE1UgZEUExqYynN1qZvqIOREEFmBcJQkwdxiFtw0qEOkGYfRDifBui9MQg4QAHAqWtAWHoCxu1Yf4VfWLPIM2mHDFsbQEVGwyqQoQcwnfHeIkNt9YnkiaS1oizycqJrx4KOQjahZxWbcZgztj2c49nKmkId44S71j0c8eV9yDK6uPRzx5X18eDvjvQ6yKo9ZSS6l//8elePK/Lf//IInrOF/FvDoADYAGBMGb7FtErm5MXMlmPAJQVgWta7Zx2go+8xJ0UiCb8LHHdftWyLJE0QIAIsI+UbXu67dZMjmgDGCGl1H+vpF4NSDckSIkk7Vd+sxEhBQMRU8j/12UIRhzSaUdQ+rQU5kGeFxm+hb1oh6pWWmv3uvmReDl0UnvtapVaIzo1jZbf/pD6ElLqSX+rUmOQNpJFa/r+sa4e/pBlAABoAAAAA3CUgShLdGIxsY7AUABPRrgCABdDuQ5GC7DqPQCgbbJUAoRSUj+NIEig0YfyWUho1VBBBA//uQZB4ABZx5zfMakeAAAAmwAAAAF5F3P0w9GtAAACfAAAAAwLhMDmAYWMgVEG1U0FIGCBgXBXAtfMH10000EEEEEECUBYln03TTTdNBDZopopYvrTTdNa325mImNg3TTPV9q3pmY0xoO6bv3r00y+IDGid/9aaaZTGMuj9mpu9Mpio1dXrr5HERTZSmqU36A3CumzN/9Robv/Xx4v9ijkSRSNLQhAWumap82WRSBUqXStV/YcS+XVLnSS+WLDroqArFkMEsAS+eWmrUzrO0oEmE40RlMZ5+ODIkAyKAGUwZ3mVKmcamcJnMW26MRPgUw6j+LkhyHGVGYjSUUKNpuJUQoOIAyDvEyG8S5yfK6dhZc0Tx1KI/gviKL6qvvFs1+bWtaz58uUNnryq6kt5RzOCkPWlVqVX2a/EEBUdU1KrXLf40GoiiFXK///qpoiDXrOgqDR38JB0bw7SoL+ZB9o1RCkQjQ2CBYZKd/+VJxZRRZlqSkKiws0WFxUyCwsKiMy7hUVFhIaCrNQsKkTIsLivwKKigsj8XYlwt/WKi2N4d//uQRCSAAjURNIHpMZBGYiaQPSYyAAABLAAAAAAAACWAAAAApUF/Mg+0aohSIRobBAsMlO//Kk4soosy1JSFRYWaLC4qZBYWFRGZdwqKiwkNBVmoWFSJkWFxX4FFRQWR+LsS4W/rFRb/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////VEFHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAU291bmRib3kuZGUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMjAwNGh0dHA6Ly93d3cuc291bmRib3kuZGUAAAAAAAAAACU=");
	     m_beep.play();
	};*/
	this.enable = function(x, s) {
		if (s === false)
			that.disable(x);
		
		if (x && x.isToggleElement)
			x.enable();
		else if (x = pElement.x(x)) {
			pElement.addAndRemoveClassName(x, 'enabled', 'disabled');
			x.disabled = false;
		}
	};
	this.disable = function(x) {
		if (x && x.isToggleElement)
			x.disable();
		else if (x = pElement.x(x)) {
			pElement.addAndRemoveClassName(x, 'disabled', 'enabled');
			x.disabled = true;
		}
	};
	this.isEnabled = function(x) {
		if (x && x.isToggleElement)
			return x.isEnabled();
		return !pElement.hasClassName(x, 'disabled');
	};
	this.pageIsHidden = function() {
		return document[hidden];
	};

	var m_history_state_handlers;
	this.getHistoryState = function(n, d) {
		if (history.replaceState) {
			var s = history.state;
			if (s)
				if (s[n])
					return s[n];
		}
		return d;
	};
	this.setHistoryState = function(n, v) {
		if (history.replaceState) {
			var s = history.state;
			if (!s)
				s = {};
			s[n] = v;
			try {
				history.replaceState(s, "");
			}
			catch (ex) {
				pConsole.warn(this, 'Failed to replace history state (data too large?)');
			}
		}
	};
	this.initHistoryState = function(n) {
		that.setHistoryState(n, that.getHistoryState(n, {}));
	};
	this.addHistoryStateHandler = function(f) {
		if (!m_history_state_handlers)
			m_history_state_handlers = [];
		m_history_state_handlers.push(f);
	};
	
	function f_handleHistoryState(f) {
		f(history.state);
	};
	this.handleHistoryState = function() {
		if (history.state && m_history_state_handlers)
			m_history_state_handlers.forEach(f_handleHistoryState);
	};
	
	this.clickOnKey = function(p_id, p_keycodes) {
		//if (pElement.x(p_id)) {
			pDocumenti.addOnKeyDown((function(e) {
				if (e.altKey || e.ctrlKey || e.shiftKey)
					return;
				
				//pConsole.debug(this.id, 'keyCode: ' + e.keyCode);
				pElement.click(this.id);
				return true;
			}).bind({ id: p_id }), p_keycodes);
		//}
	};
	this.replace = function(s, n, v) {
		var r = '${'+n+'}';
		while(s.indexOf(r)>0)
			s = pString.substring_before(s, r) + v + pString.substring_after(s, r); 
		return s;
	};
	this.replaceResourceName = function(s, n) {
		var r = '${resources.'+n+'}';
		while(s.indexOf(r)>0)
			s = pString.substring_before(s, r) + pResources.get(n) + pString.substring_after(s, r); 
		return s;
	};
	
	this.getStyleValueImpl = function(v) {
		return pPageData.resolve(v);
		//[ 'username', 'fullname', 'managed-fullname' ].forEach(function(n) { v = that.replaceResourceName(v, n); });
		//return v;
	};
	
	var style_cache;
	this.getStyleValue = function(c, n, d) {
		if (c.charAt(0)!='.' && !c.charAt(0)!='#') c = '.'+c;
		n = n || 'content';
		
		style_cache = style_cache || new pMap();
		var v = style_cache.getValue(c+'/'+n);
		if (v)
			return that.getStyleValueImpl(v);
		
		for(var i=0,l = document.styleSheets.length ; i < l ; i++) {
			var s = document.styleSheets[i];
			if (s.cssRules)
				for(var j=0, rl = s.cssRules.length ; j < rl ; j++) {
					var r = s.cssRules[j];
					if (r.type == CSSRule.STYLE_RULE)
						if (r.selectorText == c) {
							var v = r.style.getPropertyValue(n);
							if (v) {
								//if (n == "content") {
									if (v.charAt(0)=='"')
										v = v.substring(1, v.length-1);
									if (v.charAt(0)=='\'')
										v = v.substring(1, v.length-1);
								/*}
								else*/ if (n == "background-image") {
									if (v.startsWith('url("'))
										v = v.substring(5, v.length-2);
									else if (v.startsWith("url('"))
										v = v.substring(5, v.length-2);
									else if (v.startsWith("url("))
										v = v.substring(4, v.length-1);
								}
									
								style_cache.put(c+'/'+n, v);
								return that.getStyleValueImpl(v);
							}
						}
				}
		}
		return d;
	};
	
	var m_detectScrollbar = false;
	this.detectScrollbar = function() {
		if (!m_detectScrollbar) {
			
			//Create an invisible iframe
			var f = pElement.create('iframe', "hacky-scrollbar-resize-listener");
			f.style.cssText = 'height: 0; background-color: transparent; margin: 0; padding: 0; overflow: hidden; border-width: 0; position: absolute; width: 100%;';
			
			//Register our event when the iframe loads
			f.onload = function() {
				// The trick here is that because this iframe has 100% width 
				// it should fire a window resize event when anything causes it to 
				// resize (even scrollbars on the outer document)
				f.contentWindow.addEventListener('resize', function() {
					pConsole.info(pDocument, "Resize detected...");
					try {
						var evt = new UIEvent('resize');
						//var evt = document.createEvent('UIEvents');
						//evt.initUIEvent('resize', true, false, window, 0);
						window.dispatchEvent(evt);
					} 
					catch(e) { 
						pConsole.error(pDocument, "Failed to dispatch resizing event: " + e);
					}
				});
			};
			
			//Stick the iframe somewhere out of the way
			try {
				document.body.appendChild(f);
			}
			catch (e) {
				pConsole.error(this, "Failed to create resizing detector: " + e);
			}
			m_detectScrollbar = true;
		}
	};
	
	var timeouts = new pMap();
	this.setTimeout = function(id, f, ms) {
		this.clearTimeout(id);
		
		if (ms<=0)
			f.call(this);
		else {
			var tm = { id: id, ms: ms, f: f};
			
			timeouts.put(id, tm);
			tm.timeout = setTimeout(function() {
				timeouts.put(this.id, null);
				this.f.call(this);
			}.bind(tm), ms)
		}
	};
	
	this.clearTimeout = function(id) {
		var tm = timeouts.getValue(id);
		if (tm && tm.timeout)
			clearTimeout(tm.timeout);
		timeouts.put(id, null);
	};
	
	var m_coverWholePage = new pDefinedUniqueObjectList();
	function f_coverWholePage_onresize() {
		var l = m_coverWholePage;
		
		if (l && l.length>0) {
			var w = that.scrollWidth(), h = that.scrollHeight();
			l.forEach(function(x) {
				if (x = pElement.x(x)) {
					//TODO: sizing style...
					var sx = new pStyle(x);
					x.style.width = (w - sx.surroundWidth()) + 'px';
					x.style.height = (h - sx.surroundHeight()) + 'px';
				}
			});
		}
	};
	this.coverWholePage = function(id) {
		m_coverWholePage.push(id);
		f_coverWholePage_onresize(); //always refresh
	};
	this.uncoverWholePage = function(id) {
		m_coverWholePage.remove(id);
	};
	
	this.addOnPageReshow = function(f) {
		var n = f.name;
		if (!n || n == 'anonymous')
			n = f.m_name || (f.m_name = ''+Math.random());
		
		window.addEventListener('pagehide', function() { that.setHistoryState(n+'_pagehide', true); });
		that.addOnPageShow(function() {
			if (that.getHistoryState(n+'_pagehide')) //{
				//console.log('reshow: ' + n + '...');
				f.call(this);
			//}
			//else
			//	console.log('dont reshow: ' + n + '...');
			
			that.setHistoryState(n+'_pagehide', false);
		});
	};
	
	this.fire = function(n) {
		document.dispatchEvent(new Event(n));
	};
	
	this.isFullScreen = function() {
		return pDevice.isIOS()? false : (document.fullscreenElement || document.webkitFullscreenElement || document.mozFullScreenElement);
	};
	
	var fullScreens = new pMap(), x_fullScreen;
	this.fullScreen = function(x) {
		if (x = pElement.x(x)) {
			var f = [ 'requestFullScreen', 'webkitEnterFullscreen', 'webkitRequestFullScreen', 'webkitRequestFullscreen', 'mozRequestFullScreen', 'mozRequestFullScreen', 'oRequestFullScreen', 'msRequestFullScreen' ].find(function(n) { return x[n]!=null; });
			if (f) {
				x[f].call(x);
				x_fullScreen = x;
				//pElement.addClassName(x, 'fullscreen');
				
				f = fullScreens.getValue(x.id);
				if (!f)
					fullScreens.put(x.id, f = function() {
						if (that.isFullScreen()) {
							pElement.addClassName(x, 'fullscreen');
						}
						else {
							pElement.removeClassName(x, 'fullscreen');
						}
					});
				
				document.addEventListener("fullscreenchange", f);
				document.addEventListener("mozfullscreenchange", f);
				document.addEventListener("webkitfullscreenchange", f);
			}
		}
	};
	
	this.exitFullScreen = function(x) {
		if (x = document) {
			var f = [ 'exitFullScreen', 'webkitExitFullScreen', 'webkitExitFullscreen', 'mozExitFullScreen', 'mozCancelFullScreen', 'oExitFullScreen', 'msExitFullScreen' ].find(
				function(n) { return x[n]!=null; }
			);
			if (f) {
				x[f].call(x);
				x_fullScreen = null;
			}
		}
	};
	
	this.fullScreenElement = function() {
		return x_fullScreen;
	};

	// Set the name of the hidden property and the change event for visibility
	var hidden, visibilityChange;
	if (typeof document.hidden !== "undefined") { // Opera 12.10 and Firefox 18 and later support
	  hidden = "hidden";
	  visibilityChange = "visibilitychange";
	} else if (typeof document.msHidden !== "undefined") {
	  hidden = "msHidden";
	  visibilityChange = "msvisibilitychange";
	} else if (typeof document.webkitHidden !== "undefined") {
	  hidden = "webkitHidden";
	  visibilityChange = "webkitvisibilitychange";
	}
	
	// init...
	pEvent.addEventListener("resize", window, that.onResize);
	window.addEventListener("orientationchange", /*function(){console.log('orientation change');*/that.onResize);//() });
	pEvent.addEventListener("beforeunload", window, that.onPageClose);
	that.addOnResize(that.m_renderEllipsisTextImpl);
	window.onload = that.m_renderEllipsisTextImpl;
	pEvent.addEventListener(visibilityChange, document, that.onVisibilityChange);
	that.addOnPageShow(that.stopwait);

	//that.detectScrollbar();
});

//console.log('TEST: pDocument.initHistoryState(\'test\') = ' + pDocument.initHistoryState('test')); 
//console.log('TEST: JSON.stringify(pDocument.getHistoryState(\'test\')) = ' + JSON.stringify(pDocument.getHistoryState('test')));
//pDocument.getHistoryState('test')['y'] = 2;
//console.log('TEST: JSON.stringify(pDocument.getHistoryState(\'test\')) = ' + JSON.stringify(pDocument.getHistoryState('test')));

//console.log('TEST: pDocument.getStyleValue(\'fadeout\', \'opacity\') = ' + pDocument.getStyleValue('fadeout', 'opacity'));
//console.log('TEST: pDocument.getStyleValue(\'fadeout\', \'content\') = ' + pDocument.getStyleValue('fadeout', 'content'));

//console.log('TEST: pDocument.getStyleValue(\'#menu-server\', \'background-image\') = ' + pDocument.getStyleValue('#menu-server', 'background-image'));

/******************************************************************************/
/******************************************************************************/
/******************************************************************************/

const pCookieManager = new (function(){
	var that = this;
	
	this.set = function(cname, cvalue) {
		if (localStorage)
			localStorage.setItem(cname, cvalue);
		else
			document.cookie = cname + "=" + cvalue + '; path="'+location.href+'"; max-age=31536000';
	};
	this.setGlobal = function(cname, cvalue) {
		if (localStorage)
			localStorage.setItem(cname, cvalue);
		else
			that.xsetGlobal(cname, cvalue);
	};
	this.xsetGlobal = function(cname, cvalue) {
		document.cookie = cname + "=" + cvalue + "; path=\"/\"; max-age=31536000";
	};
	this.getCookie = function(n) {
		return localStorage? localStorage.getItem(n) : that.xgetCookie(n);
	};
	this.xgetCookie = function(n) {
		n += "=";
		var ca = document.cookie.split(';');
		for(var i=0; i<ca.length; i++) {
			var c = ca[i].trim();
			if (c.indexOf(n)==0) {
				if (n == "SID=") {
					var v = c.substring(n.length);
					if (v != "none")
						return v;
					return "";
				}
				return c.substring(n.length);
			}
		}
		return "";
	};
	this.remove = function(cname) {
		if (localStorage)
			localStorage.removeItem(cname);
		else
			document.cookie = cname + "=; expires=Thu, 01 Jan 1970 00:00:00 UTC";
	}
});

/******************************************************************************/
/******************************************************************************/
/******************************************************************************/

const pFormManager = (new function(){
	this.initCheckBox = function(x, d) {
		if (x = pElement.x(x)) {
			var v = pCookieManager.getCookie(x.id);
	
			x.checked = false;
			if (v=="true" || (v!="false" && d===true))
				x.checked = true;
		}
	};
	this.onCheckBoxChange = function(x) {
		if (x = pElement.x(x)) {
			pCookieManager.setGlobal(x.id, x.checked);
			return x.checked;
		}
	};
	this.onRadioChange = function(x) {
		if (x = pElement.x(x))
			if (x.checked) {
				pCookieManager.setGlobal(x.name, x.value);
				return true;
			}
	};
	this.getCheckBoxValue = function(x) {
		if (x = pElement.x(x))
			return x.checked;
	};
});

/******************************************************************************/
/******************************************************************************/
/******************************************************************************/
/*
function pNumberConverterEnglish() {
	this.ones = ['','one','two','three','four','five','six','seven','eight','nine'];
	this.tens = ['','','twenty','thirty','forty','fifty','sixty','seventy','eighty','ninety'];
	this.teens = ['ten','eleven','twelve','thirteen','fourteen','fifteen','sixteen','seventeen','eighteen','nineteen'];
	this.convert_millions = function(num){
	    if (num>=1000000){
	        return (this.convert_millions(Math.floor(num/1000000))+" million "+this.convert_thousands(num%1000000)).trim();
	    }
	    else {
	        return this.convert_thousands(num);
	    }
	};
	this.convert_thousands = function(num){
	    if (num>=1000){
	        return (this.convert_hundreds(Math.floor(num/1000))+" thousand "+this.convert_hundreds(num%1000)).trim();
	    }
	    else{
	        return this.convert_hundreds(num);
	    }
	};
	this.convert_hundreds = function(num){
	    if (num>99){
	        return (this.ones[Math.floor(num/100)]+" hundred "+this.convert_tens(num%100)).trim();
	    }
	    else{
	        return this.convert_tens(num);
	    }
	};
	this.convert_tens = function(num){
	    if (num<10)
	    	return this.ones[num];
	    else if (num>=10 && num<20)
	    	return this.teens[num-10];
	    return (this.tens[Math.floor(num/10)]+" "+this.ones[num%10]).trim();
	};
	this.convert = function(num){
	    if (num==0)
	    	return "zero";
	    else
	    	return this.convert_millions(num);
	};
}
*/
/******************************************************************************/
/*
function pNumberConverterFrench() {
	this.ones = ['','un','deux','trois','quatre','cinq','six','sept','huit','neuf'];
	this.tens = ['','','vingt','trente','quarante','cinquante','soixante','soixante-dix','quatre-vingt','quatre-vingt dix'];
	this.teens = ['dix', 'onze','douze','treize','quatorze','quinze','seize','dix sept','dix huit','dix neuf'];
	this.exceptions = [ 21, 'vingt et un', 31, 'trente et un', 41, 'quarante et un', 51, 'cinquante et un', 61, 'soixante et un',
	                    71, 'soixante et onze', 72, 'soixante douze', 73, 'soixante treize', 74, 'soixante quatorze', 75, 'soixante quize', 76, 'soixante seize',
	                    91, 'quatre vingt onze', 92, 'quatre vingt douze', 93, 'quatre vingt treize', 94, 'quatre vingt quatorze', 95, 'quatre vingt quinze', 96, 'quatre vingt seize' ];
	this.convert_millions = function(num){
	    if (num>=1000000){
	        return (this.convert_millions(Math.floor(num/1000000))+" million "+this.convert_thousands(num%1000000)).trim();
	    }
	    else {
	        return this.convert_thousands(num);
	    }
	};
	this.convert_thousands = function(num){
	    if (num>=1000){
			if (num<2000)
				return ("mille "+this.convert_hundreds(num%1000)).trim();
	        return (this.convert_hundreds(Math.floor(num/1000))+" mille "+this.convert_hundreds(num%1000)).trim();
	    }
	    else{
	        return this.convert_hundreds(num);
	    }
	};
	this.convert_hundreds = function(num){
	    if (num>99){
			if (num<200)
				return ("cent "+this.convert_tens(num%100)).trim();
	        return (this.ones[Math.floor(num/100)]+" cent "+this.convert_tens(num%100)).trim();
	    }
	    else{
	        return this.convert_tens(num);
	    }
	};
	this.convert_tens = function(num){
	    if (num<10)
	    	return this.ones[num];
	    else if (num>=10 && num<20)
	    	return this.teens[num-10];

		var i_ex = this.exceptions.indexOf(num); // IE9.x min
		if (i_ex>=0)
			return this.exceptions[i_ex+1];
	    //for(var i=0 ; i<this.exceptions.length ; i+=2)
		//	if (this.exceptions[i] == num)
	    //		return this.exceptions[i+1];

	    return (this.tens[Math.floor(num/10)]+" "+this.ones[num%10]).trim();
	};
	this.convert = function(num){
	    if (num==0)
	    	return "zero";
	    else
	    	return this.convert_millions(num);
	};
}
*/
/******************************************************************************/
/*
var pNumber = {
	en: new pNumberConverterEnglish(),
	fr: new pNumberConverterFrench(),
	convert: function(p_lang, num){
		if (p_lang == "en")
			return pNumber.en.convert(num);
		return pNumber.fr.convert(num);
	},
	test: function(){
	    var cases=[20000,0,1,2,7,10,11,12,13,15,19,20,21,25,29,30,35,50,55,69,70,99,100,101,119,510,900,1000,5001,5019,5555,10000,11000,100000,199001,1000000,1111111,190000009];
	    for (var i=0;i<cases.length;i++ ){
	        alert(cases[i]+": "+pNumber.convert("en", cases[i])+ " - " + pNumber.convert("fr", cases[i]));
	    }
	}
};
*/
/******************************************************************************/
/***  Functions to manipulate human text, articles and accents              ***/
/******************************************************************************/

const pTextIndexing = new (function() {
	var that = this, articles = [ "the ", "le ", "la ", "les ", "l'", "l&apos;", "a ", "un ", "une ", "de la ", "de ", "du ", "des " ],
		accents = { 
			a: [ '\u00C0', '\u00C1', '\u00C2', '\u00C3', '\u00C4', '\u00C5' ],
			e: [ '\u00C8', '\u00C9', '\u00CA', '\u00CB' ],
			i: [ '\u00CC', '\u00CD', '\u00CE', '\u00CF' ],
			o: [ '\u00D2', '\u00D3', '\u00D4', '\u00D5', '\u00D6' ],
			u: [ '\u00D9', '\u00DA', '\u00DB', '\u00DC' ],
			y: [ '\u00DD' ]
		},
		accents_lowercase = {
			a: [ '\u00E0', '\u00E1', '\u00E2', '\u00E3', '\u00e4', '\u00e5' ],
			e: [ '\u00E8', '\u00E9', '\u00EA', '\u00EB' ],
			i: [ '\u00EC', '\u00ED', '\u00EE', '\u00EF' ],
			o: [ '\u00F2', '\u00F3', '\u00F4', '\u00F5', '\u00F6' ],
			u: [ '\u00F9', '\u00FA', '\u00FB', '\u00FC' ],
			y: [ '\u00FD' ]
		};
	
	this.removeArticle = function(t, prefix) {
		prefix = prefix || '';
		var lt = t.toLowerCase();
		var a = articles.find(function(a) { return lt.indexOf(prefix+a)==0; });
		return a? prefix+t.substring(a.length) : t;
		/*
		for(var i = 0 ; i<pTextIndexing.m_articles.length ; i++) {
			if (i_title.indexOf(p_prefix+pTextIndexing.m_articles[i])==0) {
				return p_prefix+title.substring((p_prefix+pTextIndexing.m_articles[i]).length);
			}
		}
		return title;*/
	};
	
	this.toLowerCase = function(t) {
		t = t.toLowerCase();
		for(var a in accents)
			accents[a].forEach(function(b) { t = t.replace(b, a); });
		for(var a in accents_lowercase)
			accents_lowercase[a].forEach(function(b) { t = t.replace(b, a); });
		return t;
	};
	
	this.toSorting = function(t) {
		return that.toLowerCase(that.removeArticle(t));
	};
});

/******************************************************************************/
/******************************************************************************/
/******************************************************************************/

const pArray = new (function() {
	//+ Jonas Raoni Soares Silva
	//@ http://jsfromhell.com/array/shuffle [v1.0]
	this.shuffle = function(o){ //v1.0
	    for(var j, x, i = o.length; i; j = Math.floor(Math.random() * i), x = o[--i], o[i] = o[j], o[j] = x);
	    return o;
	};
	this.randomElement = function(a) {
		return a[Math.floor(Math.random() * a.length)];
	};
	this.remove = function(a, o) {
		return a.filter(function(x) { return x !== o; });
	};
	this.clone = function(a) {
		return a.slice(0);
	};
	this.append = function(a, o) {
		if (o.forEach)
			//a = a.concat(o);
			o.forEach(function(o) { a.push(o); });
		else if (o)
			a[a.length] = o;
		return a;
	};
	this.findValid = function(a) {
		return a.find(pString.isValid);
	};
	this.sortByValue = function(o) {
		o = o.slice(0).sort(function(a, b) { return pObject.compare(a.value, b.value); });
		return o;
	};
	this.sortByText = function(o) {
		o = o.slice(0).sort(function(a, b) {return pObject.compare(a.text, b.text); });
		return o;
	};
	this.sortByName = function(o) {
		o = o.slice(0).sort(function(a, b) { return pObject.compare(a.name, b.name); });
		return o;
	};
});

//console.log(pArray.randomElement([ 0, 1, 2, 3]));
//console.log(pArray.randomElement([ 0, 1, 2, 3]));
//console.log(pArray.randomElement([ 0, 1, 2, 3]));
//console.log(pArray.randomElement([ 0, 1, 2, 3]));
//console.log(pArray.randomElement([ 0, 1, 2, 3]));
//console.log(pArray.randomElement([ 0, 1, 2, 3]));
//console.log(pArray.randomElement([ 0, 1, 2, 3]));
//console.log(pArray.randomElement([ 0, 1, 2, 3]));

/******************************************************************************/
/******************************************************************************/
/******************************************************************************/

function lengthToDuration(len) {
	var t=0, b=1;
	len.split(':').reverse().forEach(function(l) { t += parseInt(l)*b; b = b* 60; });
	return t;
}

/******************************************************************************/
/******************************************************************************/
/******************************************************************************/

const sq = String.fromCharCode(39);

const re_surround1 = /^\(.*\)$/;
const re_surround2 = /^\[.*\]$/;
const re_surround3 = /^\{.*\}$/;

const re_html_amp = /&/g;
const re_html_lt = /</g;
const re_html_gt = />/g;
const re_html_quot = /"/g;
const re_html_apos = /'/g;

const re_dhtml_amp = /&amp;/g;
const re_dhtml_lt = /&lt;/g;
const re_dhtml_gt = /&gt;/g;
const re_dhtml_quot = /&quot;/g;
const re_dhtml_apos = /&apos;/g;

const re_space = / /g;
const re_plus = /[+]/g;
const re_26 = /%26/g;
		
const re_dot = /\./g;

const re_xml_comment = /<!--[^>]*-->/g;

/**
 * The pString object exposes functions to manipulate strings.
 * @class pString
 */
const pString = new (function() {
	var that = this, doublequote = String.fromCharCode(34), backslash_regex = new RegExp(String.fromCharCode(92, 92), 'g');
	
	this.backslash = String.fromCharCode(92);

	function f_sortByName(a, b) { 
		if (a.name==b.name) return 0; return (a.name < b.name)? -1 : 1; 
	}

	this.sq = String.fromCharCode(39);
	
	this.crlf = String.fromCharCode(13, 10);
	
	this.compareNotNull = function(x,y) {
		x = x || ''; y = y || '';
		return x < y ? -1 : x > y;
	};
	
	this.sortByName = function(a) {
		return a.sort(f_sortByName);
	};
	
	this.isValid = function(s) {
		return s && s.trim().length>0;
	};
	this.v = function(s) {
		/*if (s) {
		console.log(typeof s);
		if (typeof s!="string")
			console.log("not a string");
		}*/
		return s && s.trim().length>0;
	};
	
	/** 
	 * Selects a valid string among two.
	 * @method sv
	 * @param {String} a The first string.
	 * @param {String} b The second string. 
	 * @return a if a is not null, undefined or an empty string once trimmed, otherwise b if b is not null, undefined or en empty string once trimmed, otherwise null.
	 */ 
	this.sv = function(a, b) {
		return (a && a.trim().length>0)? a : (b && b.trim().length>0)? b : null;
	}
	
	this.validOrNull = function(s) {
		return that.v(s)? s : null;
	};
	
	this.trim = function(s) {
		return (''+s).trim();
	};
	
	this.padLeft = function(i_str, i_max, i_seq) {
		while(i_str.length<i_max)
			i_str = i_seq + i_str;
		return i_str;
	};
	this.encodeHTML = function(s) {
    	return !s? s : (''+s).replace(re_html_amp, '&amp;').replace(re_html_lt, '&lt;').replace(re_html_gt, '&gt;').replace(re_html_quot, '&quot;').replace(re_html_apos, '&apos;');
  	};
  	this.encodeHTMLLines = function(s, sp) {
  		return !s? s : (''+s).split(sp || '&#x000A').map(that.encodeHTML).join(sp || '&#x000A');
  	};
  	this.decodeHTML = function (s) {
    	return !s? s : s.replace(re_dhtml_apos, "'").replace(re_dhtml_quot, '"').replace(re_dhtml_gt, '>').replace(re_dhtml_lt, '<').replace(re_dhtml_amp, '&');
  	};
  	this.encodeURI = function(s) {
		return encodeURI(s).replace(re_space, "%20");
	};
	this.decodeURI = function(s) {
		return decodeURI(s).replace(re_plus, " ").replace(re_26, "&");
	};
	this.queryGetParam = function(p_query, p_param) {
		var i_params = p_query.split('&');
		for(var i=0 ; i<i_params.length ; i++)
			if (i_params[i].indexOf(p_param+"=") == 0)
				return pString.decodeURI(i_params[i].substring(p_param.length+1));
		return '';
	};
	this.basename = function(p_text) {
		if (p_text.indexOf('file:/')==0)
			p_text = decodeURIComponent(p_text);
		//if (p_text.indexOf('file:/')!=0)
			p_text = p_text.replace(backslash_regex, '/');///\\\\/g, '/');

		var i_pos = p_text.lastIndexOf('/');
		if (i_pos == p_text.length-1) {
			p_text = p_text.substring(0, p_text.length-1);
			i_pos = p_text.lastIndexOf('/');
		}
		if (i_pos>=0)
			return p_text.substring(i_pos+1);

		return p_text;
	};
	this.dirname = function(p_text) {
		if (p_text.indexOf('file:/')==0)
			p_text = decodeURIComponent(p_text);
		//if (p_text.indexOf('file:/')!=0)
			p_text = p_text.replace(backslash_regex, '/');///\\\\/g, '/');

		var i_pos = p_text.lastIndexOf('/');
		if (i_pos == p_text.length-1) {
			p_text = p_text.substring(0, p_text.length-1);
			i_pos = p_text.lastIndexOf('/');
		}
		if (i_pos>=0)
			return p_text.substring(0, i_pos);
		return p_text;
	};
	this.concat = function(p_array, p_sep, p_front_sep, p_func_transform) {
		const i_sep = p_sep || ',';
		var i_str = '';
		[].forEach.call(p_array, function(a) {
			if (pString.v(a)) {
				const v = (p_func_transform)? p_func_transform(a) : a.trim();
				if (pString.v(v)) {
					if (i_str.length>0)
						i_str += i_sep;
					i_str += v;
				}
			}
		});
		if (p_front_sep === false)
			return i_str;
		return ((i_sep==',' || i_sep==', ')? '':i_sep) + i_str;
	};
	this.split = function(s, p_sep) {
		return !s? [] : s.split(p_sep).map(that.trim).filter(that.v);
	};
	this.substring_before = function(s, b) {
		var i = s.indexOf(b);
		return (i>=0)? s.substring(0, i) : s;
	};
	this.substring_after = function(s, b) {
		var i = s.indexOf(b);
		return (i>=0)? s.substring(i+b.length) : s;
	};
	this.capitalize = function(s) {
	    return s.charAt(0).toUpperCase() + s.substring(1);
	};
	this.removeExtension = function(s) {
		var i = s.lastIndexOf('.');
		return (i>0)? s.substring(0, i) : s;
	};
	this.compareIgnoreCase = function(a, b) {
		return pObject.compare(a.toLowerCase(),b.toLowerCase());
	};
	this.isVowel = function(c) {
		return 'AaEeIiOoUuYy'.indexOf(c.charAt(0))>=0;
	};
});

//console.log("TEST: pString.capitalize('a') = " + pString.capitalize('a'));
//console.log("TEST: pString.capitalize('ab') = " + pString.capitalize('ab'));
//console.log("TEST: pString.basename('a\\b\\cc') = " + pString.basename('a\\b\\cc'));
//console.log("TEST: pString.dirname('a\\b\\cc') = " + pString.dirname('a\\b\\cc'));

if (!String.prototype.endsWith) {
  String.prototype.endsWith = function(searchString, position) {
      var subjectString = this.toString();
      if (typeof position !== 'number' || !isFinite(position) || Math.floor(position) !== position || position > subjectString.length) {
        position = subjectString.length;
      }
      position -= searchString.length;
      var lastIndex = subjectString.lastIndexOf(searchString, position);
      return lastIndex !== -1 && lastIndex === position;
  };
}

//console.log("TEST: pString.concat('a,b,, c, d'.split(','), '| ') = " + pString.concat('a,b,, c, d'.split(','), '| '));
//console.log("TEST: pString.concat('a,b,, c, d'.split(',')) = " + pString.concat('a,b,, c, d'.split(',')));
//console.log("TEST: pString.concat('a,b,, c, d'.split(','), ', ') = " + pString.concat('a,b,, c, d'.split(','), ', '));

/******************************************************************************/
/******************************************************************************/
/******************************************************************************/

//console.log("TEST: typeof parseFloat(1234).toFixed(2) = " + typeof parseFloat(1234).toFixed(2));
//console.log("TEST: '1234'.endsWith('.00') = " + '1234'.endsWith('.00'));

const pHumanText = new (function() {
	this.split = function(p_str) {
		if (!p_str || p_str.trim() == "")
			return [];

		var i_result = [];
		pString.split(p_str, /&| and | et /i).forEach(function(i_token) {
			pString.split(i_token, ',').forEach(i_result.push);
		});
		return i_result;
	};
	this.toFloat = function(p_number) {
		var i_str = parseFloat(''+p_number).toFixed(2);
		if (i_str.endsWith('.00'))
			return pNumber.toLocaleString(parseFloat(i_str.substring(0, i_str.length-3)));
		return pNumber.toLocaleString(parseFloat(i_str));
	};
	this.toByteSize = function(p_size) {
		var i_str = pHumanText.toFloat(p_size)/*pNumber.toLocaleString(p_size)*/+' byte' + ((p_size>1)? 's':'');
		if (p_size<1024)
			return i_str;
		if (p_size<1024*1024) {
			var i_kb = p_size / 1024;
			return i_str + ' ('+pHumanText.toFloat(i_kb)+' KB'+((i_kb>1)? 's':'')+')';
		}
		if (p_size<1024*1024*1024) {
			var i_kb = p_size / (1024*1024);
			return i_str + ' ('+pHumanText.toFloat(i_kb)+' MB'+((i_kb>1)? 's':'')+')';
		}
		if (p_size<1024*1024*1024*1024) {
			var i_kb = p_size / (1024*1024*1024);
			return i_str + ' ('+pHumanText.toFloat(i_kb)+' GB'+((i_kb>1)? 's':'')+')';
		}
		if (p_size<1024*1024*1024*1024*1024) {
			var i_kb = p_size / (1024*1024*1024*1024);
			return i_str + ' ('+pHumanText.toFloat(i_kb)+' TB'+((i_kb>1)? 's':'')+')';
		}
	};
	this.printSeconds = function(min) {
		return '' + (min<1? 1:min) + " second" + ((min>1)? 's':'');
	};
	this.printMinutes = function(min) {
		return '' + (min<1? 1:min) + " minute" + ((min>1)? 's':'');
	};
	this.toDateTime = function(d, tt) {
		var t = new Date(); //t.setTime(Date.now());
		if (d.getFullYear() == t.getFullYear())
			if (d.getDate() == t.getDate())
				if (d.getMonth() == t.getMonth()) {
					var now = Date.now();
					if (now < d.getTime() && d.getTime() <now+3600000) {
						var min = Math.round((d.getTime()-now) / 60000);
						if (min<1) {
							min = Math.round((d.getTime()-now) / 1000);
							//return (new Date().getTime()) + ' ' + now + ' ' + d.getTime() + ' ' + d.toISOString() + ' ' + tt + ' ' + (now+3600000) + ' ' + (now-3600000)+ "in " + pHumanText.printSeconds(min);
							return "in " + pHumanText.printSeconds(min);
							//return "in " + (min<1? 1:min) + " second" + ((min>1)? 's':'');
						}
						//return (new Date().getTime()) + ' ' + now + ' ' + d.getTime() + ' ' + d.toISOString() + ' ' + tt + ' ' + (now+3600000) + ' ' + (now-3600000)+ "in " + pHumanText.printMinutes(min);
						return "in " + pHumanText.printMinutes(min);
						//return "in " + (min<1? 1:min) + " minute" + ((min>1)? 's':'');
					}
					if (now-3600000 < d.getTime() && d.getTime() <now) {
						var min = Math.round((now-d.getTime()) / 60000);
						if (min<1) {
							min = Math.round((now-d.getTime()) / 1000);
							return pHumanText.printSeconds(min)+' ago';
							//return "" + (min<1? 1:min) + " second" + ((min>1)? 's ago':' ago');
						}
						return pHumanText.printMinutes(min) + ' ago';
						//return "" + (min<1? 1:min) + " minute" + ((min>1)? 's ago':' ago');
					}
						
					return pDate.toLocaleTimeString(d);
				}
		
		var tom = new Date();//Date.now();
		tom.setHours(0);
		tom.setMinutes(0)
		tom.setMilliseconds(0);
		tom.setSeconds(0);
		tom = tom.getTime() + 48*3600000;
		
		if (d.getTime() > tom)
			return "tomorrow at " + pDate.toLocaleTimeString(d);
		
		return pDate.toLocaleString(d);
	};
});

//console.log("TEST: pString.concat(pHumanText.split(\"a1&b2 and c3, d4 et e5, f6, g7\"), \"|\") = " + pString.concat(pHumanText.split("a1&b2 and c3, d4 et e5, f6, g7"), "|"));
//console.log("TEST: pHumanText.toFloat(1234) = " + pHumanText.toFloat(1234));

/******************************************************************************/
/******************************************************************************/
/******************************************************************************/

const pTable = new (function() {
	var that = this;
	
	function f_cell_content(r, i) {
		return ''+((r.cells[i])? (r.cells[i].getAttribute("data-sort") || r.cells[i].textContent || r.cells[i].innerText) : '');
	}
	
	function f_sortValues(x,y){
		if (x[0] == y[0])
			return 0;
		if (x[0] < y[0])
			return -1;
		return 1;
	}
	
	function f_sort(tid, p_col_index, p_body_index, p_inbody, transform) {
		if (tid = pElement.x(tid)) {
			if (typeof p_col_index=="string") p_col_index = pTable.columnIndex(tid, p_col_index, 0, 0);
	
			var tbl = tid.tBodies[(p_body_index)? p_body_index : 0];
			var store = [];
			var i=0;
			if (p_inbody === true)
				i++;
			for(len=tbl.rows.length; i<len; i++) {
				var row = tbl.rows[i];
				if (row.id && row.id.endsWith('-toolbar')) {
					var i_rows = store[store.length-1][1];
					i_rows[i_rows.length] = row;
				}
				else {
					var i_content = f_cell_content(row, p_col_index);
					store[store.length] = [transform(i_content), [ row ]];
				}
			}
			store.sort(f_sortValues);
	
			if (p_inbody === true)
				tbl.appendChild(tbl.rows[0]);
			for(var i=0, len=store.length; i<len; i++)
				store[i][1].forEach(/*tbl.appendChild);//*/function(row) { tbl.appendChild(row); });
	
			store = null;
		}
	}
	
	this.columnIndex = function(tid, p_col_title, p_row_index, p_body_index) {
		if (tid = pElement.x(tid)) {
			var th = tid.tHead;
			if (p_body_index>-1)
				th = tid.tBodies[p_body_index];
	
			var thr = th.rows[p_row_index];
			for(var i=0 ; i<thr.cells.length ; i++) {
				var ttitle =  ''+(thr.cells[i]? (thr.cells[i].textContent || thr.cells[i].innerText) : '');
				if (ttitle.trim()==p_col_title) {
					return i;
				}
			}
			return 0;
		}
		return -1;
	};
	
	this.sortString = function(tid, p_col_index, p_body_index, p_inbody) {
		f_sort(tid, p_col_index, p_body_index, p_inbody, pObject.trim);
	};
	
	this.sortTitle = function(tid, p_col_index, p_body_index, p_inbody) {
		f_sort(tid, p_col_index, p_body_index, p_inbody, function(c) { return pTextIndexing.removeArticle(c.trim(), "") });
	};
	
	this.sortInt = function(tid, p_col_index, p_body_index, p_inbody) {
		f_sort(tid, p_col_index, p_body_index, p_inbody, function(c) { return parseInt(c.trim()) });
	};
	
	this.sortDuration = function(tid, p_col_index, p_body_index, p_inbody) {
		f_sort(tid, p_col_index, p_body_index, p_inbody, function(c) { return lengthToDuration(c.trim()) });
	};
});

/******************************************************************************/

	function columnIndex(p_table_id, p_col_title, p_row_index) {
		var th = pElement.x(p_table_id).tHead;
		var thr = th.rows[p_row_index];
		for(var i=0 ; i<thr.cells.length ; i++) {
			var ttitle =  ""+(thr.cells[i].textContent || thr.cells[i].innerText);
			if (ttitle.trim()==p_col_title) {
				return i;
			}
		}
		return 0;
	}

	function sortTableString(p_table_id, p_col_index){
		var tbl = pElement.x(p_table_id).tBodies[0];
		var store = [];
		for(var i=0, len=tbl.rows.length; i<len; i++) {
			var row = tbl.rows[i];
			var i_content =  ""+(row.cells[p_col_index].getAttribute("data-sort") || row.cells[p_col_index].textContent || row.cells[p_col_index].innerText);
			store.push([i_content.trim(), row]);
		}
		store.sort(function(x,y){
			if (x[0] == y[0])
				return 0;
			if (x[0] < y[0])
				return -1;
			return 1;
		});
		for(var i=0, len=store.length; i<len; i++) {
			tbl.appendChild(store[i][1]);
		}
		store = null;
	}

	function sortTableTitle(p_table_id, p_col_index){
		var tbl = pElement.x(p_table_id).tBodies[0];
		var store = [];
		for(var i=0, len=tbl.rows.length; i<len; i++) {
			var row = tbl.rows[i];
			var i_content =  ""+(row.cells[p_col_index].textContent || row.cells[p_col_index].innerText);
			store.push([pTextIndexing.removeArticle(i_content.trim(), ""), row]);
		}
		store.sort(function(x,y){
			if (x[0] == y[0])
				return 0;
			if (x[0] < y[0])
				return -1;
			return 1;
		});
		for(var i=0, len=store.length; i<len; i++) {
			tbl.appendChild(store[i][1]);
		}
		store = null;
	}

	function sortTableInt(p_table_id, p_col_index){
		var tbl = pElement.x(p_table_id).tBodies[0];
		var store = [];
		for(var i=0, len=tbl.rows.length; i<len; i++) {
			var row = tbl.rows[i];
			var i_content =  ""+(row.cells[p_col_index].textContent || row.cells[p_col_index].innerText);
			store.push([parseInt(i_content.trim()), row]);
		}
		store.sort(function(x,y){
			return x[0] - y[0];
		});
		for(var i=0, len=store.length; i<len; i++) {
			tbl.appendChild(store[i][1]);
		}
		store = null;
	}

	function sortTableDuration(p_table_id, p_col_index){
		var tbl = pElement.x(p_table_id).tBodies[0];
		var store = [];
		for(var i=0, len=tbl.rows.length; i<len; i++) {
			var row = tbl.rows[i];
			var i_content =  ""+(row.cells[p_col_index].textContent || row.cells[p_col_index].innerText);
			store.push([lengthToDuration(i_content.trim()), row]);
		}
		store.sort(function(x,y){
			return x[0] - y[0];
		});
		for(var i=0, len=store.length; i<len; i++) {
			tbl.appendChild(store[i][1]);
		}
		store = null;
	}

/******************************************************************************/
/******************************************************************************/
/******************************************************************************/

const pTime = new (function() {
	var that = this;
	
	this.getUTCHours = function(d) {
		return (d.getUTCHours || d.getHours).call(d); 
	};
	this.getUTCMinutes = function(d) {
		return (d.getUTCMinutes || d.getMinutes).call(d); 
	};
	this.getUTCSeconds = function(d) {
		return (d.getUTCSeconds || d.getSeconds).call(d); 
	};
	
	this.msToDuration = function(p_ms) {
		if (p_ms<1000)
			return p_ms + 'ms';

		var d = new Date(p_ms), h=that.getUTCHours(d), m=that.getUTCMinutes(d), s=that.getUTCSeconds(d);

    	return (((h>0)? h+'hour'+((h>1)? 's ':' '):' ') + ((m>0)? m+'min'+((m>1)? 's ':' '):' ') + ((s>0)? s+'sec'+((s>1)? 's':''):'')).trim();
	};
	this.msToLongDuration = function(p_ms) {
		if (p_ms<1000)
			return p_ms + ' ms';

		var d = new Date(p_ms), h=that.getUTCHours(d), m=that.getUTCMinutes(d), s=that.getUTCSeconds(d);

    	return (((h>0)? h+' hour'+((h>1)? 's ':' '):' ') + ((m>0)? m+' minute'+((m>1)? 's ':' '):' ') + ((s>0)? s+' second'+((s>1)? 's':''):'')).trim();
	};
	
	this.pad = function(i) {
		return i<10? '0'+i : ''+i;
	};
	this.display = function(t) {
		var d = new Date(parseFloat(t)), h=that.getUTCHours(d), m=that.getUTCMinutes(d)/*that.pad(today.getMinutes())*/, s=that.pad(that.getUTCSeconds(d));

	    if (m == "NaN" || s == "NaN")
	    	return "0:00";
	    
		if (h<=0)
			return m+":"+s;
		return h+":"+that.pad(m)+":"+s;
	};
	
	var session_start = 0, clock_started = false;
	
	this.displayClock = function(s) {
		if (s!=null) that.session_start = s;
	    var today=new Date(), h= that.pad(today.getHours()), m= that.pad(today.getMinutes()), s= that.pad(today.getSeconds());

	    var i_html = h+":"+m;//+":"+s;// + " ("+this.session_start+" "+today.getTime()+")";
	    if (that.session_start>0) {
			if (today.getTime()>=that.session_start + /*20000)//*/180 * 60000)
				i_html = '<span style="color: red;">'+i_html+'</span>';
		    else if (today.getTime()>=that.session_start + /*10000)//*/120 * 60000)
		    	i_html = '<span style="color: orange;">'+i_html+'</span>';
		}
	    pElement.setInnerHTML('clock', i_html);//h+":"+m+":"+s + " ("+this.session_start+")";

	    if (!that.clock_started) {
	    	setInterval(that.displayClock, 10000);
	    	that.clock_started = true;
		}
	};
});

/******************************************************************************/
/******************************************************************************/
/******************************************************************************/

const pDevice = new (function() {
	var that = this, ua = window.navigator.userAgent;
	
	var ios = ua.indexOf('iPad') >=0 || ua.indexOf('iPhone') >=0,
		iphone = ua.indexOf('iPhone') >=0,
		ipad = ua.indexOf('iPad') >=0,
		android = ua.indexOf('Android') >=0,
		chrome = ua.indexOf('Chrome') >=0,
		ipod = ua.indexOf('iPod') >=0,
		firefox = ua.indexOf('Firefox') >=0,
		opera = ua.indexOf('Opera') >=0,
		safari = ua.indexOf('Safari') >=0;
	
	this.isIOS = function(a) {
		return a? a.indexOf('iPad') >=0 || a.indexOf('iPhone') >=0 : ios;
	};
	this.isIOS12 = function(a) {
		return that.isIOS(a) && (a || ua).indexOf('OS 12_') >=0;
	};
	
	this.isIPhone = function(a) {
		return a? a.indexOf('iPhone') >=0 : iphone;
	};
	this.isIPad = function(a) {
		return a? a.indexOf('iPad') >=0 : ipad;
	};
	this.isAndroid = function(a) {
		return a? a.indexOf('Android') >=0 : android;
	};
	this.isChrome = function(a) {
		return a? a.indexOf('Chrome') >=0 : chrome;
	};
	this.isIPod = function(a) {
		return a? a.indexOf('iPod') >=0 : ipod;
	};
	this.isFirefox = function(a) {
		return a? a.indexOf('Firefox') >=0 : firefox;
	};
	this.isOpera = function(a) {
		return a? a.indexOf('Opera') >=0 : opera;
	};
	this.isSafari = function(a) {
		return a? a.indexOf('Safari') >=0 : safari;
	};
	this.isMobile = function(a) {
		return that.isIOS(a) || that.isAndroid(a);
	};
	
	//var m_lids_cannotPlay = [ "movies.medias", "movies.movies", "tvshows.medias", "tvshows.seasons" ]; //TODO: genres, etc...
	this.canPlay = function(p_list_item) {
		var i_lid = p_list_item.lid;
		var i_provider = p_list_item.provider;
		if (null!=i_provider) {
			i_provider = pString.split(i_provider.toLowerCase(), '|');

			if (i_provider.length == 1 && i_provider[0] === "itunes") {
				if (pCookieManager.getCookie('list-show-itunes-items') == "true")
					return true;
				if (i_lid == 'movies.medias')
					return false;
				if (i_lid == 'movies.movies')
					return false;
				if (i_lid == 'tvshows.medias')
					return false;
				if (i_lid == 'tvshows.seasons')
					return false;
			}

			//if (m_lids_cannotPlay.indexOf(i_lid)>0)
			//	if (i_provider.length == 1 && i_provider[0] === "itunes")
			//		if (!isIOS())
			//			return pCookieManager.getCookie('list-show-itunes-items') == "true";
		}
		return true;
	};
	this.type = function(p_agent) {
		if (that.isIPhone(p_agent)) return 'iPhone';
		if (that.isIPad(p_agent)) return 'iPad';
		if (that.isAndroid(p_agent)) return 'Android';
		if (that.isChrome(p_agent)) return 'Chrome';
		if (that.isIPod(p_agent)) return 'iPod';
		if (that.isFirefox(p_agent)) return 'Firefox';
		if (that.isOpera(p_agent)) return 'Opera';
		if (that.isSafari(p_agent)) return 'Safari';
		return 'Unknown';
	};
	this.isRetina = function(){
		if (window.devicePixelRatio > 1)
		  return true;

		var mediaQuery = "(-webkit-min-device-pixel-ratio: 1.5),(min--moz-device-pixel-ratio: 1.5),(-o-min-device-pixel-ratio: 3/2),(min-resolution: 1.5dppx)";
		if (window.matchMedia && window.matchMedia(mediaQuery).matches)
		  return true;

		return false;
	};
	this.isSmallScreen = function() {
		return window.innerWidth < 760 || window.innerHeight < 760;
	};
	
	// "online" property
	Object.defineProperty(this, "online", { get: function() { return window.navigator.onLine; } });
});

/******************************************************************************/
/******************************************************************************/
/******************************************************************************/

const pStyleUtil = new (function() {
	this.parseWidth = function(w) {
		return pString.v(w)? parseInt(w.substring(0, w.indexOf('px'))) : 0;
	};
});

/******************************************************************************/
/******************************************************************************/
/******************************************************************************/

function pStyle(p_element) {
	var that = this;
	
	this.element = pElement.x(p_element);
	this.style = null;
	
	function getValue(p_style, p_value, p_name) {
		if (p_value && p_value!='')
			return p_value;
		return p_style.getStyle().getPropertyValue(p_name);
	}

	this.getStyle = function() {
		return that.style = that.style || window.getComputedStyle(that.element, null);
	};

	this.backgroundImage = function() {
		if (that.element.style.backgroundImage && that.element.style.backgroundImage!='')
			return that.element.style.backgroundImage;
		return that.getStyle().getPropertyValue('background-image');
	};
	this.backgroundImageURL = function() {
		var i_value = that.backgroundImage();
		if (i_value.startsWith('url("'))
			return i_value.substring(5, i_value.length-2);
		if (i_value.startsWith("url('"))
			return i_value.substring(5, i_value.length-2);
		return i_value;
	};
	this.border = function() {
		if (that.element.style.border && that.element.style.border!='')
			return that.element.style.border;
		return that.getStyle().getPropertyValue('border');
	};
	this.display = function() {
		if (that.element.style.display && that.element.style.display!='')
			return that.element.style.display;
		return that.getStyle().getPropertyValue('display');
	};
	
	this.prop = function(n) {
		if (pString.v(this.element.style[n]))
			return this.element.style[n];
		return this.getStyle().getPropertyValue(n);
	};
	
	this.height = function() {
		if (this.element.style.height && this.element.style.height!='')
			return this.element.style.height;
		return this.getStyle().getPropertyValue('height');
	};
	this.borderBottomWidth = function() {
		if (this.element.style.borderBottomWidth && this.element.style.borderBottomWidth!='')
			return pStyleUtil.parseWidth(this.element.style.borderBottomWidth);
		return pStyleUtil.parseWidth(this.getStyle().getPropertyValue("border-bottom-width"));
	};
	this.borderLeftWidth = function() {
		if (this.element.style.borderLeftWidth && this.element.style.borderLeftWidth!='')
			return pStyleUtil.parseWidth(this.element.style.borderLeftWidth);
		return pStyleUtil.parseWidth(this.getStyle().getPropertyValue("border-left-width"));
	};
	this.borderRightWidth = function() {
		if (this.element.style.borderRightWidth && this.element.style.borderRightWidth!='')
			return pStyleUtil.parseWidth(this.element.style.borderRightWidth);
		return pStyleUtil.parseWidth(this.getStyle().getPropertyValue("border-right-width"));
	};
	this.borderTopWidth = function() {
		if (this.element.style.borderTopWidth && this.element.style.borderTopWidth!='')
			return pStyleUtil.parseWidth(this.element.style.borderTopWidth);
		return pStyleUtil.parseWidth(this.getStyle().getPropertyValue("border-top-width"));
	};
	this.borderHeight = function() {
		return this.borderBottomWidth() + this.borderTopWidth();
	};
	this.borderWidth = function() {
		return this.borderLeftWidth() + this.borderRightWidth();
	};
	this.maxHeight = function() {
		return getValue(that, that.element.style.maxHeight, 'max-height');
	};
	this.maxWidth = function() {
		return getValue(that, that.element.style.maxWidth, 'max-width');
	};
	this.paddingBottomWidth = function() {
		if (this.element.style.paddingBottom && this.element.style.paddingBottom!='')
			return pStyleUtil.parseWidth(this.element.style.paddingBottom);
		return pStyleUtil.parseWidth(this.getStyle().getPropertyValue("padding-bottom"));
	};
	this.paddingLeftWidth = function() {
		if (this.element.style.paddingLeft && this.element.style.paddingLeft!='')
			return pStyleUtil.parseWidth(this.element.style.paddingLeft);
		return pStyleUtil.parseWidth(this.getStyle().getPropertyValue("padding-left"));
	};
	this.paddingTopWidth = function() {
		if (this.element.style.paddingTop && this.element.style.paddingTop!='')
			return pStyleUtil.parseWidth(this.element.style.paddingTop);
		return pStyleUtil.parseWidth(this.getStyle().getPropertyValue("padding-top"));
	};
	this.paddingRightWidth = function() {
		if (this.element.style.paddingRight && this.element.style.paddingRight!='')
			return pStyleUtil.parseWidth(this.element.style.paddingRight);
		return pStyleUtil.parseWidth(this.getStyle().getPropertyValue("padding-right"));
	};
	this.paddingHeight = function() {
		return this.paddingBottomWidth() + this.paddingTopWidth();
	};
	this.paddingWidth = function() {
		return this.paddingLeftWidth() + this.paddingRightWidth();
	};
	this.marginBottomWidth = function() {
		if (this.element.style.marginBottom && this.element.style.marginBottom!='')
			return pStyleUtil.parseWidth(this.element.style.marginBottom);
		return pStyleUtil.parseWidth(this.getStyle().getPropertyValue("margin-bottom"));
	};
	this.marginTopWidth = function() {
		if (this.element.style.marginTop && this.element.style.marginTop!='')
			return pStyleUtil.parseWidth(this.element.style.marginTop);
		return pStyleUtil.parseWidth(this.getStyle().getPropertyValue("margin-top"));
	};
	this.marginHeight = function() {
		return this.marginTopWidth() + this.marginBottomWidth();
	};
	this.marginLeftWidth = function() {
		if (this.element.style.marginLeft && this.element.style.marginLeft!='')
			return pStyleUtil.parseWidth(this.element.style.marginLeft);
		return pStyleUtil.parseWidth(this.getStyle().getPropertyValue("margin-left"));
	};
	this.marginRightWidth = function() {
		if (this.element.style.marginRight && this.element.style.marginRight!='')
			return pStyleUtil.parseWidth(this.element.style.marginRight);
		return pStyleUtil.parseWidth(this.getStyle().getPropertyValue("margin-right"));
	};
	this.marginWidth = function() {
		return this.marginLeftWidth() + this.marginRightWidth();
	};
	this.position = function() {
		if (this.element.style.position && this.element.style.position!='')
			return this.element.style.position;
		return this.getStyle().getPropertyValue("position");
	};
	this.surroundWidth = function() {
		return this.paddingLeftWidth() + this.marginLeftWidth() + this.borderLeftWidth() + this.paddingRightWidth() + this.marginRightWidth() + this.borderRightWidth();
	};
	this.surroundLeftWidth = function() {
		return this.paddingLeftWidth() + this.marginLeftWidth() + this.borderLeftWidth();
	};
	this.surroundRightWidth = function() {
		return this.paddingRightWidth() + this.marginRightWidth() + this.borderRightWidth();
	};
	this.surroundHeight = function() {
		return this.paddingTopWidth() + this.marginTopWidth() + this.borderTopWidth() + this.paddingBottomWidth() + this.marginBottomWidth() + this.borderBottomWidth();
	};
	this.surroundTopHeight = function() {
		return this.paddingTopWidth() + this.marginTopWidth() + this.borderTopWidth();
	};
	this.surroundBottomHeight = function() {
		return this.paddingBottomWidth() + this.marginBottomWidth() + this.borderBottomWidth();
	};
	this.width = function(p_element) {
		if (p_element.style.width && p_element.style.width!='')
			return p_element.style.width;
		return this.getStyle().getPropertyValue('width');
	};
};

/******************************************************************************/
/******************************************************************************/
/******************************************************************************/

const pLocation = new (function() {
	var that = this, localhost = [ "localhost", "127.0.0.1", "" ]	
	
	this.isLocalhost = function() {
		return localhost.includes(window.location.hostname);// === "localhost" || location.hostname === "127.0.0.1" || location.hostname === "";
	};
	
	this.getHostname = function() {
		return window.location.hostname;
	};
	this.getPath = function() {
		return window.location.pathname;
	};
	this.getPathAndQuery = function() {
		if (window.location.search!='')
			return window.location.pathname+'?'+window.location.search;
		return window.location.pathname;
	};
	
	var m_query;
	this.getQueryParameter = function(p_name, p_default) {
		if (!m_query) {
			m_query = new pMap();
			var i_str = window.location.search;
			if (i_str.charAt(0)=='?') {
				i_str.substring(1).split('&').forEach(function(a) {
					var pos = a.indexOf('=');
					if (pos<0)
						m_query.put(decodeURIComponent(a), "");
					else if (pos>0)
						m_query.put(decodeURIComponent(a.substring(0, pos)), decodeURIComponent(a.substring(pos+1).replace(/\\+/g, ' ')));
				});
			}
		}
		return m_query.getValue(p_name, p_default);
	};
	this.param = function(p_name, p_default) {
		return that.getQueryParameter(p_name, p_default);
	};
	this.assign = function(p_url) {
		if (!p_url || p_url=='')
			return;

		pDocument.wait();
		setTimeout(function() { window.location.assign(p_url) }, 0);
	};
	this.assignOnKey = function(p_url, p_keycodes) {
		pDocumenti.addOnKeyDown((function(e) {
			//pConsole.debug(that, 'keyCode: ' + e.keyCode);
			pLocation.assign(this.url);
			return true;
		}).bind({ url: p_url }), p_keycodes);
	};
	this.back = function() {
		var h = window.location.href;
		
		pDocument.wait();
		setTimeout(function() { 
			window.history.back(); 

			//*** STOP WAIT IF HISTORY DID NOT CHANGE
			setTimeout(function() { 
		        if (window.location.href == h)
		        	pDocument.stopwait();
		    }, 500);
		}, 0);
	};
	this.backAndReload = function() {
		pCookieManager.set('reload-'+window.name, 'true');
		that.back();
	};
	this.reload = function() {
		window.location.reload(true);
	};
	this.forward = function() {
		var h = window.location.href;
		
		pDocument.wait();
		setTimeout(function(){ 
			window.history.forward();

			//*** STOP WAIT IF HISTORY DID NOT CHANGE
			setTimeout(function() { 
		        if (window.location.href == h)
		        	pDocument.stopwait();
		    }, 500);
		}, 0);
	};
	this.removeParam = function(n) {
		if (history.state)
			history.replaceState(history.state, '', new pLocationInstance().removeParam(n).toString());
	};
	
	//0.9.19
	this.host = function(u) {
		if (u) {
			var i_pos = u.indexOf('://');
			if (u.startsWith('data:'))
				return null;

			if (i_pos>=0) {
				u = u.substring(i_pos+3);
				i_pos = u.indexOf('/');
				if (i_pos < 0)
					return u;
				
				return u.substring(0, i_pos);
			}
		}
		return window.location.host;
	};
	
	//0.9.19
	this.hostname = function(u) {
		if (u) {
			var i_pos = u.indexOf('://');
			if (u.startsWith('data:'))
				return null;

			if (i_pos>=0) {
				u = u.substring(i_pos+3);
				i_pos = u.indexOf('/');
				if (i_pos >= 0)
					u = u.substring(0, i_pos);
				
				return u.split(':')[0]
			}
		}
		return window.location.hostname;
	};
	
	//0.9.19
	this.path = function(u) {
		if (u) {
			var i_pos = u.indexOf('://');
			if (u.startsWith('data:'))
				return u.substring(5);

			if (i_pos>=0) {
				u = u.substring(i_pos+3);
				i_pos = u.indexOf('/');
				if (i_pos < 0)
					return '/';
				
				u = u.substring(i_pos);
			}

			var s = u.split('?');
			if (s.length==2)
				return s[0];
					
			return u;
		}
		return this.getPath();
	};
	
	//0.9.19
	this.param = function(u, n, d) {
		if (u) {
			var i_pos = u.indexOf('://');
			if (u.startsWith('data:'))
				return null;

			if (i_pos>=0) {
				u = u.substring(i_pos+3);
				i_pos = u.indexOf('/');
				if (i_pos < 0)
					return d;
				
				u = u.substring(i_pos);
			}
			
			var s = u.split('?');
			if (s.length==2) {
				n += '=';
				var v = s[1].split('&').find(function(p) {
					return p.startsWith(n);
				})
				if (v)
					return decodeURIComponent(v.substring(n.length).replace(/\\+/g, ' ')); 
			}
		}
		return d;
	};
});

//console.log("TEST: = pLocation.getQueryParameter('a.x', 'xxx', '?bar=&a.x=yyy&a=b')", pLocation.getQueryParameter('a.x', 'xxx', '?bar=&a.x=yyy&a=b'));

function pLocationInstance(u) {
	var that = this;
	
	this.isLocationInstance = true;
	
	this.url = u = u || (''+window.location);

	var i_pos = u.indexOf('://');
	if (u.startsWith('data:')) {
		this.protocol = 'data';
		this.path = u.substring(5);
	}
	else if (i_pos<0) {
		this.protocol = window.location.protocol;
		this.host = window.location.host;
		this.path = u;
	}
	else {
		this.protocol = u.substring(0, i_pos+1);

		u = u.substring(i_pos+3);
		i_pos = u.indexOf('/');
		this.host = u.substring(0, i_pos);
		this.path = u.substring(i_pos);
	}

	var m_query;
	this.getQueryParameter = function(p_name, p_default) {
		if (!m_query) {
			m_query = new pMap();
			var i_str = this.path.split('?');
			if (i_str.length==2) {
				this.path = i_str[0];
				i_str[1].split('&').forEach(function(a) {
					var pos = a.indexOf('=');
					if (pos<0)
						m_query.put(decodeURIComponent(a), "");
					else if (pos>0)
						m_query.put(decodeURIComponent(a.substring(0, pos)), decodeURIComponent(a.substring(pos+1).replace(/\\+/g, ' ')));
				});
			}
		}
		return m_query.getValue(p_name, p_default);
	};

	this.getQueryParameter('foo'); //force parsing of params...
	
	this.removeParam = function(name) {
		m_query.remove(name);
		return this;
	};
	
	this.toString = function() {
		var u = this.path;
		m_query.keys().forEach(function(n) {
			u = pURL.addQueryParameter(u, n, m_query.getValue(n, ''));
		});
		return u;
	};
}

function h_isHTTPURL(p_url) {
	return (p_url)? p_url.startsWith("http://") || p_url.startsWith("https://") : false;
}

/*
console.log("TEST: pLocation.path(\"http://www/a/b/c\") = " + pLocation.path("http://www/a/b/c"));
console.log("TEST: pLocation.path(\"http://www/a/b/c?foo=bar&foo2=wiz&foo3=jiz\") = " + pLocation.path("http://www/a/b/c?foo=bar&foo2=wiz&foo3=jiz"));
console.log("TEST: pLocation.path(\"/a/b/c\") = " + pLocation.path("/a/b/c"));
console.log("TEST: pLocation.path(\"/a/b/c?foo=bar&foo2=wiz&foo3=jiz\") = " + pLocation.path("/a/b/c?foo=bar&foo2=wiz&foo3=jiz"));

[ "http://www/a/b/c", "http://www:81/a/b/c", "/a/b/c", "http://www", "http://www:83" ].forEach(function(u) {
	console.log("TEST: pLocation.host(\""+u+"\") = " + pLocation.host(u));
	console.log("TEST: pLocation.hostname(\""+u+"\") = " + pLocation.hostname(u));
});

console.log("TEST: pLocation.host(\"http://www/a/b/c\") = " + pLocation.host("http://www/a/b/c"));
console.log("TEST: pLocation.host(\"http://www:81/a/b/c\") = " + pLocation.host("http://www:81/a/b/c"));
console.log("TEST: pLocation.host(\"/a/b/c\") = " + pLocation.host("/a/b/c"));
console.log("TEST: pLocation.host(\"http://www\") = " + pLocation.host("http://www"));
console.log("TEST: pLocation.host(\"http://www:83\") = " + pLocation.host("http://www:83"));


[ 'foo', 'foo2', 'foo3', 'xxx' ].forEach(function(n) {
console.log("TEST: pLocation.param(\"http://www/a/b/c\", \""+n+"\") = " + pLocation.param("http://www/a/b/c", n));
console.log("TEST: pLocation.param(\"http://www/a/b/c?foo=bar&foo2=wiz&foo3=jiz\", \""+n+"\") = " + pLocation.param("http://www/a/b/c?foo=bar&foo2=wiz&foo3=jiz", n));
console.log("TEST: pLocation.param(\"/a/b/c\", \""+n+"\") = " + pLocation.param("/a/b/c", n));
console.log("TEST: pLocation.param(\"/a/b/c?foo=bar&foo2=wiz&foo3=jiz\", \""+n+"\") = " + pLocation.param("/a/b/c?foo=bar&foo2=wiz&foo3=jiz", n));
});*/

/******************************************************************************/
/******************************************************************************/
/******************************************************************************/

//console.log("TEST: decodeURIComponent(\"a+b%2Bc\") = " + decodeURIComponent("a+b%2Bc"));
//console.log("TEST: decodeURIComponent(\"a+b%2Bc\".replace(/[\+]/g, ' ')) = " + decodeURIComponent("a+b%2Bc".replace(/[\+]/g, ' ')));

const pURL = new (function() {
	this.fixedEncodeURIComponent  = function(str) {
	  return encodeURIComponent(str).replace(/[!'()*#=]/g, function(c) {
	    return '%' + c.charCodeAt(0).toString(16);
	  });
	};
	this.fixedDecodeURIComponent  = function(str) {
	  return decodeURIComponent(str.replace(/[\+]/g, ' '));
	};
	this.replaceParam = function(u, name, value, encode) {
		return this.addQueryParameter(new pLocationInstance(u).removeParam(name).toString(), name, value, encode);
	};
	
	this.addBodyParameter = function(p_url, p_name, p_value, p_encode) {
		if (p_name && p_name.forEach) {
			for(var i=0 ; i<p_name.length ; i+=2)
				p_url = pURL.addBodyParameter(p_url, p_name[i], p_name[i+1]);
		}
		else if (p_name && p_name != '') {
			if (p_url.length > 0)
				p_url += '&';
			p_url += p_name + '=';
			if (null!=p_value)
				p_url += (p_encode === false)? p_value : pURL.fixedEncodeURIComponent(p_value);
		}
		return p_url;
	};
	this.addQueryParameter = function(u, p_name, p_value, p_encode) {
		return pURL.addParams(u, pURL.addBodyParameter('', p_name, p_value, p_encode));
	};
	this.addParam = function(u, p_name, p_value, p_encode) {
		return pURL.addParams(u, pURL.addBodyParameter('', p_name, p_value, p_encode));
	};
	this.addQueryParameters = function(u, p_params) {
		u = u || window.location.pathname;
		if (p_params != '')
			u += ((u.indexOf('?') <= 0)? '?':'&') + p_params;
		return u;
	};
	this.addParams = function(u, p_params) {
		u = u || window.location.pathname;
		if (u.indexOf('data:')==0)
			return u;
		
		if (p_params != '')
			u += ((u.indexOf('?') <= 0)? '?':'&') + p_params;
		return u;
	};
	this.toFormSubmitBody = function(p_map, p_callback_transform_value, p_callback_transform_name, trim) {
		var i_body = '';
		if (p_map.forEach)
			for(var i=0 ; i<p_map.length ; i+=2) {
				//console.log(p_map.keyAt(i) + ': ' + p_map.valueAt(i));
	
				var i_name = p_map[i];
				if (p_callback_transform_name)
					i_name = p_callback_transform_name.call(this, i_name);
	
				var i_value = p_map[i+1];
				if (p_callback_transform_value)
					i_value = p_callback_transform_value.call(this, i_name, i_value);
	
				if (trim === true)
					i_value = (''+i_value).trim();
				
				if (null!=i_value)
					i_body = pURL.addBodyParameter(i_body, i_name, ''+i_value);
			}
		else
			for(var i=0 ; i<p_map.length ; i++) {
				//console.log(p_map.keyAt(i) + ': ' + p_map.valueAt(i));
	
				var i_name = p_map.keyAt(i);
				if (p_callback_transform_name)
					i_name = p_callback_transform_name.call(this, i_name);
	
				var i_value = p_map.valueAt(i);
				if (p_callback_transform_value)
					i_value = p_callback_transform_value.call(this, i_name, i_value);
	
				if (trim === true)
					i_value = (''+i_value).trim();
				
				if (null!=i_value)
					i_body = pURL.addBodyParameter(i_body, i_name, ''+i_value);
			}
		return i_body;
	};
	this.addSizeParameters = function(u) {
		if (pApplicationUI.OPTION_SCALE_BACKDROP) {
			u = pURL.addQueryParameter(u, [ 'sw', screen.width, 'sh', screen.height ]);
			if (pDevice.isRetina()) u = pURL.addQueryParameter(u, 'sr', '1');
			if (pDevice.isMobile()) u = pURL.addQueryParameter(u, 'srt', '1');
		}
		return u;
	};
	
	this.pathEqual = function(a, b) {
		return (a.isLocationInstance? a.path : pLocation.path(a)) == (b.isLocationInstance? b.path : pLocation.path(b));
	};
});

//console.log(pURL.addQueryParameter('http://foo.bar/test?ok=3', 'name', 'value=this and that!!'));

/******************************************************************************/
/***  JSON                                                                  ***/
/******************************************************************************/

const pJSON = new (function() {
	this.pretty = function(p_str) {
		return (typeof p_str == "string")? JSON.stringify(pJSON.parse(p_str), undefined, 2) : JSON.stringify(p_str, undefined, 2);
	};
	this.syntaxHighlight = function(p_str) {
	    p_str = pJSON.pretty(p_str).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
	    return p_str.replace(/("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)/g, function (match) {
	        var cls = 'number';
	        if (/^"/.test(match)) {
	            if (/:$/.test(match)) {
	                cls = 'key';
	            } else {
	                cls = 'string';
	            }
	        } else if (/true|false/.test(match)) {
	            cls = 'boolean';
	        } else if (/null/.test(match)) {
	            cls = 'null';
	        }
	        return '<span class="json-value-' + cls + '">' + match + '</span>';
	    });
	};

	this.parse = function(p_str) {
		try {
			if (p_str.indexOf("while(1);")==0)
				p_str = p_str.substring("while(1);".length);
			return JSON.parse(p_str);
		}
		catch (exception) {
			pConsole.error(this, "Failed to parse JSON: " + ((p_str.length>1000)? p_str.substring(0, 1000)+'...' : p_str), exception);
		}
		return null;
	};
});

/******************************************************************************/
/******************************************************************************/
/******************************************************************************/
//TODO: username, password, timeout, events...

const pHTTPRequest = new (function() {
	this.tracePrefix = 'HTTPRequest';
	var that = this, m_xml_requests = new pMap();
	
	function f_create(p_object) {
		var i_request = new XMLHttpRequest();
		i_request.p_id = Math.random();
		m_xml_requests.put(i_request.p_id, p_object);
		return i_request;
	}
	
	function f_remove(p_request) {
		return m_xml_requests.remove(p_request.p_id);
	}
	
	function f_send(i_xml_http_request, p_request, p_data) {

		i_xml_http_request.onerror = function(e) {
			//console.log(e);
			//console.log(e.target);
				
			var i_now = Date.now();
			var i_xml_http_request = e.target;
			var i_data = f_remove(i_xml_http_request);
			if (!i_data)
				return;

			i_xml_http_request.x_end = i_now;
			pConsole.error(pHTTPRequest, 'Request failed: URL: ' + i_data.request.url + '...');

			if (i_data.request.onerror)
				i_data.request.onerror.call(i_xml_http_request, i_xml_http_request, i_data.data);
		};

		if (p_request.timeout) i_xml_http_request.timeout = p_request.timeout;
		i_xml_http_request.ontimeout = function(p_event) {
			var i_now = Date.now();
			var i_xml_http_request = p_event.target;
			var i_data = f_remove(i_xml_http_request);
			if (!i_data)
				return;

			i_xml_http_request.x_end = i_now;
			pConsole.error(pHTTPRequest, 'Request timed out: URL: ' + i_data.request.url + ' after ' +
				(!i_xml_http_request.timeout || i_xml_http_request.timeout==0)? pTime.msToDuration(i_xml_http_request.x_end-i_xml_http_request.x_start) : pTime.msToDuration(i_xml_http_request.timeout) + '...');

			if (i_data.request.ontimeout)
				i_data.request.ontimeout.call(i_xml_http_request, i_xml_http_request, i_data.data);
		};

		if (p_request.headers && p_request.headers.length>0)
			for(var i=0 ; i<p_request.headers.length ; i+=2)
				if (p_request.headers[i] && p_request.headers[i+1])
					i_xml_http_request.setRequestHeader(p_request.headers[i], p_request.headers[i+1]);

		i_xml_http_request.setRequestHeader("x-content-type", "application/json");
		i_xml_http_request.setRequestHeader("x-csrf-token", pCookieManager.xgetCookie("SID"));

		i_xml_http_request.x_start = Date.now();
		i_xml_http_request.send(p_request.body);
		return i_xml_http_request;
	}
	
	/**
	 * @param p_onreadystatechange: callback...
	 * @param p_method Default to "GET"
	 */
	this.get = function(p_url, p_onreadystatechange, p_sync, p_data, p_method) {
		if (p_url.url) {
			p_url.method = p_url.method || 'GET';
			return that.invoke(p_url);
		}
		return that.invoke({ url: p_url, onreadystatechange: p_onreadystatechange, sync: p_sync, data: p_data, method: p_method });
	};
	this.head = function(p_url, p_onreadystatechange, p_sync, p_data) {
		if (p_url.url) {
			p_url.method = p_url.method || 'HEAD';
			p_url.responseType = p_url.responseBuffer || 'arraybuffer';
			return that.invoke(p_url);
		}
		return that.invoke({ url: p_url, onreadystatechange: p_onreadystatechange, sync: p_sync, data: p_data, method: "HEAD", responseType: 'arraybuffer' });
	};
	/** p_onreadystatechange: callback... */
	this.post = function(p_url, p_onreadystatechange, p_sync, p_data) {
		if (p_url.url) {
			p_url.method = p_url.method || 'POST';
			return that.invoke(p_url);
		}
		return that.invoke({ url: p_url, onreadystatechange: p_onreadystatechange, sync: p_sync, data: p_data, method: "POST", responseType: 'text' });
	};
	this.sendDELETE = function(p_url, p_onreadystatechange, p_sync, p_data) {
		if (p_url.url) {
			p_url.method = p_url.method || 'DELETE';
			return that.invoke(p_url);
		}
		return that.invoke({ url: p_url, onreadystatechange: p_onreadystatechange, sync: p_sync, data: p_data, method: "DELETE", responseType: 'text' });
	};
	this.invoke = function(p_request) {
		return new Promise(function(resolve, reject) {
			var i_data = { request: p_request, data: p_request.data || p_request.url, fcall: p_request.onreadystatechange };
			var i_xml_http_request = f_create(i_data);
			i_xml_http_request.open(p_request.method || "GET", p_request.url, (p_request.sync === true)? false : true);
			if (p_request.responseType) 
				i_xml_http_request.responseType = p_request.responseType; 
			else if (p_request.method == "HEAD" || !i_data.fcall) 
				p_request.responseType = "arraybuffer";
			i_xml_http_request.onreadystatechange = function(p_event) {
				var i_now = Date.now();
				var i_xml_http_request = p_event.target;

				if (i_xml_http_request.readyState === 4 || (i_xml_http_request.readyState>=2 && i_xml_http_request.method == "HEAD")) {// When response is complete

					if (i_xml_http_request.status < 100) // failure, var the onerror handler handle it...
						return;

					var i_data = f_remove(i_xml_http_request);
					if (!i_data)
						return;

					i_xml_http_request.x_end = i_now;
					pConsole.debug(pHTTPRequest, 'Request completed: URL: ' + i_data.request.url + ', ' + i_xml_http_request.statusText);

					if (i_xml_http_request.statusText.indexOf('Error:')==0) {
						pConsole.error(pHTTPRequest, 'Request failed: URL: ' + i_data.request.url + ', ' + i_xml_http_request.statusText);
						if (i_data.ignore_status_error === false)
							return;
					}

					var f = i_data.request["f"+i_xml_http_request.status];
					if (f) {
						(f.forEach? f : [ f ]).forEach(function(f) {
							f.call(i_xml_http_request, i_xml_http_request, i_data.data);
						});
						return;
					}
					
					if (i_data.fcall) {
						i_data.fcall.call(i_xml_http_request, i_xml_http_request, i_data.data, i_data.request);
						return;
					}
					
					resolve(i_xml_http_request);
				}
			};
			f_send(i_xml_http_request, p_request, i_data);
		});
	};
	
	this.headers_post = [ "Content-type", "application/x-www-form-urlencoded" ];
});

/******************************************************************************/
/******************************************************************************/
/******************************************************************************/

/******************************************************************************/
/***  Configuration using local storage                                     ***/
/******************************************************************************/

const pConfiguration = new (function() {
	//var that = this;
	
	this.reset = function() {
		return pDialog.question({ id: 'reset-config' }).then(function() {
			if (localStorage)
				while(localStorage.length>0)
					localStorage.removeItem(localStorage.key(0));
			f_init();
		});
	};
	
	function f_init() {
		if (localStorage)
			if (null==localStorage.getItem("init"))
				localStorage.setItem("init", "1");
	}

	//*** CALL INIT
	f_init();
});

/******************************************************************************/
/******************************************************************************/
/******************************************************************************/

const pConsole = new (function() {
	var that = this, m_debug = false;
	
	this.debugging = function() {
		return m_debug;
		//var d = pCookieManager.getCookie('view-user-advanced-debug');
		//return d=='true' || d=='1';
	};
	
	this.prefix = function(o, d) {
		return (typeof o=="string")? o.trim() : ((o || {}).tracePrefix || d || 'Console');
	};
		
	function message(lv, o, t) {
		try {
			return lv + that.prefix(o) + ': ' + (''+t).trim();
		}
		catch (e) {
			console.log(e);
		}
	};
	
	function trace(lv, o, t) {
		var d = new Date();
		t = pDate.toLOGDateTime(d) + ' ' + message(lv, o, t);

		console.log(t);

		if (m_debug) {
			var cs = document.main;//pElement.x('console-content');
			if (cs) {
				var x = document.createElement('div');
				x.className = 'console-line';
				x.innerHTML = t.replace(/&/g, '&amp;');
				cs.appendChild(x);
				
				//document.main.append(x.innerHTML);
			}
		}
		return t;
	};
	
	this.debug = function(o, t) {
		return m_debug? trace('DEBG: ', o, t) : message('DEBG: ', o, t);
	};
	this.info = function(o, t) {
		return trace('INFO: ', o, t);
	};
	this.warn = function(o, t) {
		return trace('WARN: ', o, t);
	};
	this.error = function(o, t, e) {
		if (e) console.log(e); //on chrome, trace all exception data...
		return trace('ERR : ', o, t + ((e)? (': ' + e + ' ' + ((e.stack)? e.stack : '')):''));
	};
});

/******************************************************************************/
/******************************************************************************/
/******************************************************************************/

Function.prototype.bindToEventHandler = function bindToEventHandler() {
  var handler = this;
  var boundParameters = Array.prototype.slice.call(arguments);
  //create closure
  return function(e) {
      e = e || window.event; // get window.event if e argument missing (in IE)
      boundParameters.unshift(e);
      handler.apply(this, boundParameters);
  }
};

const pFile = new (function() {
	var that = this, m_files = new pMap();

	this.readAsDataURL = function(p_file, p_callback) {
		m_files.put(p_file, { callback: p_callback });

		var i_reader = new FileReader();

		pEvent.addEventListener('loadend', i_reader, function(e, p_file) {
			var i_data = m_files.getValue(p_file, null);
			i_data.callback.call(this, p_file, this.result);
		}.bindToEventHandler(p_file));

		i_reader.readAsDataURL(p_file);
	};
});

const pFiles = new (function() {
	var that = this;
	
	this.isDirectory = function(e) {
		return e.isDirectory;
	};
	
	this.readAsURL = function(f, c) {
		var r = new FileReader();
		r.addEventListener('loadend', function() {
			c(f, r.result);
		}, false);
		r.readAsDataURL(f);
	};
	
	this.read = function(f, c) {
		var d = {
			total: f.length,
			ended: 0, 
			files: new pMap() 
		};
		
		for(var i= 0 ; i<d.total ; i++) {
			var ff = f[i];
			d.files.put(ff, { file: ff });
			
			that.readAsURL(ff, function(ff, result) {
				d.files.getValue(ff).data = result;
				
				if (++d.ended == d.total)
					c.call(c, d.files.values());
			});
		}
	};
	
	this.toFiles = function(e, c, filter) {
		if (!e)
			c.call(c, null);
		else if (e.dataTransfer && e.dataTransfer.items) {
			var done = 0, dt = 0, ff = [], l = e.dataTransfer.items.length;
			for(var i=0 ; i<l; i++)
				if (e.dataTransfer.items[i].webkitGetAsEntry) {
					var d = e.dataTransfer.items[i].webkitGetAsEntry();
					if (d) {
						done++;
						
						//var f = e.dataTransfer.items[i].getAsFile();
						//f = f.path;
						
						pFiles.toFiles(d, function(fs) {
							pArray.append(ff, fs);
							dt++;
							if (dt == l)
								c.call(c, ff);
						}, filter);
					}
					//else
					//	console.log("Failed to get webkit entry...");
				}
			
			if (done<1)
				c.call(c, null);
		}
		else if (filter && !filter.call(filter, e))
			c.call(c, null);
		else if (e.isDirectory) {
			
			/*var d = {
				name: e.name,
				isDirectory: true,
				//total: 0,
				ended: 0,
				files: []
			};
			e.createReader().readEntries(function(es) {
				d.total = 0;
				if (es && es.length) es.forEach(function(e) {
					if (!filter || filter.call(filter, e))
						if (e.isFile || e.isDirectory)
							d.total++;
				});
				if (d.total==0)
					c.call(c, [ d ]);
				
				if (es && es.length) es.forEach(function(e) {
					if (!filter || filter.call(filter, e)) {
						if (e.isFile)
							e.file(function(f) {
								f.isFile = true;
								d.files[d.files.length] = f;
								
								if (++d.ended == d.total)
									c.call(c, [ d ]);
							});
						else if (e.isDirectory) {
							pFiles.toFiles(e, function(ff) {
								d.files[d.files.length] = ff[0];
								
								if (++d.ended == d.total)
									c.call(c, [ d ]);
							}, filter);
						}
					}
				});
			});*/
			
			var d = {
				name: e.name,
				isDirectory: true,
				total: 0,
				ended: 0,
				files: []
			}, r  = e.createReader();
			
			var f_list = function(es) {
				if (es && es.length) {
					es.forEach(function(e) {
						if (!filter || filter.call(filter, e)) {
							if (e.isFile)
								e.file(function(f) {
									f.isFile = true;
									d.files[d.files.length] = f;
									
									if (++d.ended == d.total)
										c.call(c, [ d ]);
								});
							else if (e.isDirectory) {
								pFiles.toFiles(e, function(ff) {
									d.files[d.files.length] = ff[0];
									
									if (++d.ended == d.total)
										c.call(c, [ d ]);
								}, filter);
							}
						}
					});
					
					r.readEntries(f_list);
				}
			};
			var f_count = function(es) {
				if (es && es.length && es.length>0) {
					es.forEach(function(e) {
						if (!filter || filter.call(filter, e))
							if (e.isFile || e.isDirectory)
								d.total = d.total+1;
					});
					
					r.readEntries(f_count);
				}
				else { //finished
					if (d.total==0)
						c.call(c, [ d ]);
					else {
						r = e.createReader();
						r.readEntries(f_list);
					}
				}
			};
			
			r.readEntries(f_count);
		}
		else if (e.isFile)
			e.file(function(f) { f.isFile = true; c.call(c, [ f ]); });
		else
			c.call(c, null);
	};
});

/******************************************************************************/
/******************************************************************************/
/******************************************************************************/

const pNumber = new (function() {
	var that = this, toLocaleString = (!!(typeof Intl == 'object' && Intl && typeof Intl.NumberFormat == 'function'));

	this.toLocaleString = function(n) {
		return toLocaleString? n.toLocaleString() : n;
	};
});

/******************************************************************************/
/******************************************************************************/
/******************************************************************************/

const options_long_ym = { year: 'numeric', month: 'long' };
const options_full_date = { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' };

const pDate = new (function() {
	var that = this;
	this.ftoLocaleString = false;
	this.ftoLocaleDateString = false;
	this.ftoLocaleTimeString = false;
	var options_long = { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' },
		options_month_long = { year: 'numeric', month: 'long' },
		pattern1 = /^([0-9]{4})-([0-9]{2})-([0-9]{2}) ([0-9]{2}):([0-9]{2}):([0-9]{2})$/,
		pattern2 = /^([0-9]{4}):([0-9]{2}):([0-9]{2}) ([0-9]{2}):([0-9]{2}):([0-9]{2})$/;

	try {
		new Date().toLocaleString('i');
	} catch (e) {
		this.ftoLocaleString = (e.name === 'RangeError');
	}
	try {
		new Date().toLocaleDateString('i');
	} catch (e) {
		this.ftoLocaleDateString = (e.name === 'RangeError');
	}
	try {
		new Date().toLocaleTimeString('i');
	} catch (e) {
		this.ftoLocaleTimeString = (e.name === 'RangeError');
	}
	
	this.toLocaleString = function(p_date, p_locales, p_options) {
		return that.ftoLocaleString? p_date.toLocaleString(p_locales, p_options) : p_date;
	};
	this.toLongLocaleString = function(p_date, p_locales) {
		return that.ftoLocaleString? p_date.toLocaleString(p_locales, options_long) : p_date;
	};
	this.toLocaleDateString = function(p_date, p_locales, p_options, p_default) {
		return that.ftoLocaleDateString? p_date.toLocaleDateString(p_locales || {}, p_options || {}) : (p_default || p_date); //TODO
	};
	this.toLocaleTimeString = function(p_date, p_locales, p_options, p_default) {
		return that.ftoLocaleTimeString? p_date.toLocaleTimeString(p_locales || {}, p_options || {}) : (p_default || p_date);
	};
	this.toMonth = function(p_date) {
		return that.ftoLocaleDateString? p_date.toLocaleDateString([], options_month_long) : p_date.getMonth();
	};
	
	this.create = function(s) {
		var i_date = null;
		//pConsole.debug(this, "pDate.create(" + s + "), " + parseInt(s));
		if ((''+s).indexOf('-')<0 && parseInt(s)!="NaN") {
			i_date = new Date(parseInt(s));
			//i_date.setTime(parseInt(s));
		}
		else
			i_date = new Date(((s+'').indexOf('T')>0)? s : (s+'T00:00:00'));
		
		if (i_date == 'Invalid Date') {
			if (/^.*[-+][0-9]{4}$/.test(s))
				return new Date(s.substring(0, s.length-2) + ':' + s.substring(s.length-2));
			var e = pattern1.exec(s) || pattern2.exec(s);
			if (e)
				return new Date(e[1], e[2], e[3], e[4], e[5], e[6]);
		}
		return i_date;
	};
	
	this.msToInputTime = function(ms) {
		var h = Math.round(ms / 3600000), m = Math.round((ms % 3600000) / 60000);
		return (h<10? '0':'')+h+':'+(m<10? '0':'')+m;
	};
	this.inputTimeToMS = function(v) {
		var p = v.indexOf(':');
		if (p>0)
			return parseInt(v.substring(0, p))*3600000 + parseInt(v.substring(p+1)) * 60000;
		return v;
	};
	this.isValid = function(d) {
		if (d && d != 'NaN') {
			lm = parseInt(''+d);
			return d!='NaN' && d>0;
		}
	};
	this.toLOGDateTime = function(d) {
		var /*m = d.getUTCMonth()+1, dd = d.getUTCDate(),*/h = d.getHours(), mm = d.getMinutes(), s = d.getSeconds(), ms = d.getMilliseconds();
		//m = m<10? '0'+m : mm;
		//dd = dd<10? '0'+dd : dd;
		//h = h<10? '0'+h : h;
		//mm = mm<10? '0'+mm : mm;
		//s = s<10? '0'+s : s;
		//ms = ms<10? '00'+ms : (ms<100? '0'+ms : ms);
		return /*d.getUTCFullYear()+'-'+m+'-'+dd+' '+*/(h<10? '0'+h : h)+':'+(mm<10? '0'+mm : mm)+':'+(s<10? '0'+s : s)+'.'+(ms<10? '00'+ms : (ms<100? '0'+ms : ms));
	};
});

if (!Date.now) {
  Date.now = function now() {
    return new Date().getTime();
  };
}

/******************************************************************************/

const pImages = new (function() {
	var that = this;
	
	// create image object from url or image
	this.createImage = function(u, p_option, img) {
		
		var r = (typeof u=='string')? { uri: u } : u;
		
		u = r.uri.trim();
		if (u=='')
			return null;

		if (u.indexOf('http')==0 && u.indexOf(url_prefix)>0 && u.indexOf('play_url=')>0 && u.indexOf('view-item')>0) {
			var i_play_url = pLocation.param(u, 'play_url');
			if (i_play_url && i_play_url!='')
				u = i_play_url;
		}

		var l = new pLocationInstance(u);

		if (!r.width || !r.height) {
			var w = parseInt(r.width? r.width : l.getQueryParameter('img_w'));
			if ((!w || w == 'NaN') && img) w = img.naturalWidth;
			
			var h = parseInt(r.height? r.height : l.getQueryParameter('img_h'));
			if ((!h || h == 'NaN') && img) h = img.naturalHeight; 
	
			if (w & h) {
				r.width = w;
				r.height = h;
			}
		}
		
		if (!r.size) {
			var s = parseInt(r.size || l.getQueryParameter('img_s'));
			if (s == 'Nan') s = undefined;
			r.size = s;
		}
		
		if (!r.date) {
			var d = parseInt(r.date || l.getQueryParameter('img_d'));
			if (pDate.isValid(d))
				r.date = d;
		}
		
		if (!r.lastModified) {
			var lm = parseInt(r.lastModified || l.getQueryParameter('img_lm'));
			if (pDate.isValid(lm))
				r.lastModified = lm;
		}
		
		if (!r.thumbs) { 
			var o = l.getQueryParameter('thumb');
			if (pString.v(o))
				r.thumbs = [ { uri: o } ];
		}

		if (p_option && p_option.image)
			for(var i in p_option.image)
				if (i!='uri') r[i] = p_option.image[i];
		
		return r;
	};
	
	this.toURL = function(i) {
		return pURL.addParam(i.uri, [ 'v', i.lastModified, 'img_d', i.date, 'img_w', i.width, 'img_h', i.height, 'img_lm', i.lastModified, 'img_s', i.size, 'thumb', ((i.thumbs || [])[0] || {}).uri ]);
	};
	
	this.createDialogOption = function(i, options, img) {
		//console.log(i.uri + ' - ' + JSON.stringify(i));
		i = that.createImage(i, options, img);
		return { id: 'img', image: i, disabled: true,
			style: 'display: block; width: 90%; min-width: 90%; max-width: 90%;', className: 'columns2',
			text_after: '<span class="desc">'+(i.name || pString.basename(pLocation.path(i.uri))) + 
				((i.width && i.height)? '<br/><nobr>Dimensions: ' + i.width + ' x ' + i.height + '</nobr>' : '') +
				(i.size? '<br/><nobr>Size: '+pHumanText.toByteSize(i.size)+'</nobr>' : '') +
				(pDate.isValid(i.date)? '<br/><nobr>Date Taken: ' + pDate.toLocaleDateString(pDate.create(parseInt(i.date))) + '</nobr>' : '') +
				(pDate.isValid(i.lastModified)? '<br/><nobr>Last Modified: ' + pDate.toLocaleDateString(pDate.create(parseInt(i.lastModified))) + '</nobr>' : '') +
				'</span>'
		};
	};
	
	this.download = function(du, u) {

		//test with http://www.imagebam.com/image/0e892a74410806/
		//test with http://www.imagebam.com/image/2f7808538720614

		return new Promise(function(resolve, reject) {
			u = pURL.addQueryParameter(du, [ 'action', 'download-url', 'url', u, 'v', ''+Math.random() ]);
	
			pConsole.info(that, 'Download: ' + u);
			pHTTPRequest.get(u, function(p_request) {
	
				var x = pElement.create('div', null, null, p_request.responseText), w=0, h=0, img = null;
				pElement.t(x, 'img').forEach(function(x) {
					if (x.naturalWidth > w && x.naturalHeight > h) {
						img = x;
						w = x.naturalWidth;
						h = x.naturalHeight;
					}
				})
				if (img) {
					pConsole.info(that, 'Image: '+img.src);
					
					//*** RESOLVE
					resolve(img.src);
				}
			});
		});
	};
});

/******************************************************************************/
/******************************************************************************/
/******************************************************************************/

const pObject = new (function() {
	var that = this;
	
	this.FALSE = new Boolean(false);
	this.TRUE = new Boolean(true);
	
	function f_pathname(p) {
		p = (''+p).replace('.', '_');
		var c = p.charAt(0);
		if (c>='0' && c<='9') return '_'+p;
		return p;
	};
	
	this.isNotNull = function(o) { return o!=null; };
	this.value = function(o, d) { return o.value || d; };
	this.name = function(o,d ) { return o.name || d; };
	this.isEnabled = function(o) { return o.enabled === true };

	this.compare = function(x,y) { return x < y ? -1 : x > y; };
	this.compareReverse = function(x,y) { return x < y ? 1 : (x > y ? -1 : 0); };
	
	this.trim = function(o) {
		return (o && o.trim)? o.trim() : o;
	};
	this.removeMember = function(o, n) {
		if (o)
			o[n] = undefined;
		return o;
	};
	this.getMember = function(o, n, d) {
		return o? (o[n] || d) : d;
	};
	//*** add member to obj, return obj, if obj is null, it is created...
	this.setMember = function(o, n, v) {
		if (!o)
			o = {};
		o[n] = v;
		return o;
	};
	//*** add member to obj, return obj member, if obj or member is null, it is created...
	this.createMember = function(o, n, v) {
		return (o[n])? o[n] : o[n] = v;
	};
	
	this.replace = function(o, p, v) {
		o = o || {};
		p = pArray.clone(p);
		var lp = f_pathname(p.pop()), c = o;
		p.forEach(function(pp) {
			pp = f_pathname(pp);
			c = c[pp] || (c[pp] = {});
			//if (!c[pp])
			//	c[pp] = {};
			//c = c[pp];
		});
		c[lp] = v;
		return o;
	};
	
	this.query = function(o, p) {
		for(var i=0 ; i<p.length ; i++) {
			var pp = f_pathname(p[i]);
			if (!o[pp])
				return null;
			o = o[pp];
		}
		return o;
	};
});

//console.log(JSON.stringify(pObject.replace(null, [ 'a' ], 3)));
//console.log(JSON.stringify(pObject.replace(null, [ 'a', 'b', 'c'], 3)));
//console.log(JSON.stringify(pObject.replace(null, [ 'a', 'b', 'c'],  { d: 3 })));
//console.log(JSON.stringify(pObject.replace(null, [ 'a', 'b', 'c'],  [ 1, 2, 3 ])));

//console.log(JSON.stringify(pObject.replace({ a: { b: {c: 3 }}}, [ 'a', 'b', 'c'], 4)));
//console.log(JSON.stringify(pObject.replace({ a: { b: {c: 3 }}}, [ 'a', 'b', 'd'], 4)));
//console.log(JSON.stringify(pObject.replace({ a: { b: {c: 3 }}}, [ 'a', 'e'], 4)));

//console.log(JSON.stringify(pObject.query({ a: { b: {c: 3 }}}, [ 'a', 'e'])));
//console.log(JSON.stringify(pObject.query({ a: { b: {c: 3 }}}, [ 'a', 'b'])));
//console.log(JSON.stringify(pObject.query({ a: { b: {c: 3 }}}, [ 'a', 'b', 'c'])));

//console.log(JSON.stringify(pObject.replace({ a: { _1: {c: 3 }}}, [ 'a', 1, 'c'], 4)));
//console.log(JSON.stringify(pObject.replace({ a: { _1: {c: 3 }}}, [ 'a', 1, 'd'], 4)));
//console.log(JSON.stringify(pObject.replace({ a: { _1: {c: 3 }}}, [ 'a', 'e'], 4)));

//console.log(JSON.stringify(pObject.query({ a: { _1: {c: 3 }}}, [ 'a', 'e'])));
//console.log(JSON.stringify(pObject.query({ a: { _1: {c: 3 }}}, [ 'a', 1 ])));
//console.log(JSON.stringify(pObject.query({ a: { _1: {c: 3 }}}, [ 'a', 1, 'c'])));

/******************************************************************************/
/******************************************************************************/
/******************************************************************************/

// left: 37, up: 38, right: 39, down: 40,
// spacebar: 32, pageup: 33, pagedown: 34, end: 35, home: 36
var keys = {37: 1, 38: 1, 39: 1, 40: 1};

function preventDefault(e) {
  e = e || window.event;
  if (e.preventDefault)
      e.preventDefault();
  e.returnValue = false;
}

function preventDefaultForScrollKeys(e) {
    if (keys[e.keyCode]) {
        preventDefault(e);
        return false;
    }
}

function disableScroll() {
  if (window.addEventListener) // older FF
      window.addEventListener('DOMMouseScroll', preventDefault, false);
  window.onwheel = preventDefault; // modern standard
  window.onmousewheel = document.onmousewheel = preventDefault; // older browsers, IE
  window.ontouchmove  = preventDefault; // mobile
  document.onkeydown  = preventDefaultForScrollKeys;
}

function enableScroll() {
    if (window.removeEventListener)
        window.removeEventListener('DOMMouseScroll', preventDefault, false);
    window.onmousewheel = document.onmousewheel = null;
    window.onwheel = null;
    window.ontouchmove = null;
    document.onkeydown = null;
}

function UUID() {
    //------------------
    var S4 = function () {
        return(
                Math.floor(
                        Math.random() * 0x10000 /* 65536 */
                    ).toString(16)
            );
    };
    //------------------

    return (
            S4() + S4() + "-" +
            S4() + "-" +
            S4() + "-" +
            S4() + "-" +
            S4() + S4() + S4()
        );
}

if (!window.name)
	window.name = UUID();
else if (pCookieManager.getCookie('reload-'+window.name) === 'true') {
	pCookieManager.remove('reload-'+window.name);
	pLocation.reload();
}
//console.log("UUID: " + window.name);

/******************************************************************************/
/***  onClick event listener                                                ***/
/******************************************************************************/

document.addEventListener('click', function(e) {

	if (e.button != 0)
		return;

	if (e.altKey && e.altKey)
		return;

	if (e.ctrlKey && e.ctrlKey)
		return;

	var curnode = e.target;//, location=document.location;//, stop=/^(a|html)$/i;
	//if (!curnode)
	//	return true;

	while (curnode) {//} && curnode.nodeName.toLowerCase()!='a' && curnore.nodeName != true) {
		var n = curnode.nodeName.toLowerCase();
		if (n=='a' || n=='html') break;
		curnode = curnode.parentNode;
	}
	//console.log(curnode.href);
	//console.log(window.location.href);

	if (curnode && 'href' in curnode) {
		//alert('here-href :' + curnode.href);
		if (curnode.href==window.location.href) return;
		if (curnode.href==window.location.href+'#') return;
		if (curnode.target) return;

		if (curnode.getAttribute("nowait") === "true") return;

		if (curnode.href.indexOf('http')==0) {
			//alert('here :' + curnode.href);
			e.preventDefault();

			pDocument.wait();
			//if (history.replaceState)
			//	history.replaceState({ click: "yes" }, "");
			document.location.href = curnode.href;
		}
	}
}, false);

/******************************************************************************/

// creates a global "addWheelListener" method
// example: addWheelListener( elem, function( e ) { console.log( e.deltaY ); e.preventDefault(); } );
(function(window,document) {

    var prefix = "", _addEventListener, onwheel, support;

    // detect event model
    if ( window.addEventListener ) {
        _addEventListener = "addEventListener";
    } else {
        _addEventListener = "attachEvent";
        prefix = "on";
    }

    // detect available wheel event
    support = "onwheel" in document.createElement("div") ? "wheel" : // Modern browsers support "wheel"
              document.onmousewheel !== undefined ? "mousewheel" : // Webkit and IE support at least "mousewheel"
              "DOMMouseScroll"; // let's assume that remaining browsers are older Firefox

    window.addWheelListener = function( elem, callback, useCapture ) {
        _addWheelListener( elem, support, callback, useCapture );

        // handle MozMousePixelScroll in older Firefox
        if( support == "DOMMouseScroll" ) {
            _addWheelListener( elem, "MozMousePixelScroll", callback, useCapture );
        }
    };

    function _addWheelListener( elem, eventName, callback, useCapture ) {
        elem[ _addEventListener ]( prefix + eventName, support == "wheel" ? callback : function( originalEvent ) {
            !originalEvent && ( originalEvent = window.event );

            // create a normalized event object
            var event = {
                // keep a ref to the original event object
                originalEvent: originalEvent,
                target: originalEvent.target || originalEvent.srcElement,
                type: "wheel",
                deltaMode: originalEvent.type == "MozMousePixelScroll" ? 0 : 1,
                deltaX: 0,
                deltaZ: 0,
                preventDefault: function() {
                    originalEvent.preventDefault ?
                        originalEvent.preventDefault() :
                        originalEvent.returnValue = false;
                }
            };

            // calculate deltaY (and deltaX) according to the event
            if ( support == "mousewheel" ) {
                event.deltaY = - 1/40 * originalEvent.wheelDelta;
                // Webkit also support wheelDeltaX
                originalEvent.wheelDeltaX && ( event.deltaX = - 1/40 * originalEvent.wheelDeltaX );
            } else {
                event.deltaY = originalEvent.detail;
            }

            // it's time to fire the callback
            return callback( event );

        }, useCapture || false );
    }

})(window,document);

/******************************************************************************/
/******************************************************************************/
/******************************************************************************/

const pLang = new (function() {
	var that = this, countries = new pMap(), langs = new pMap();
	
	this.nativex = new pMap();
	
	//0.9.9
	var i_p = [ "Albany", "", "Belarus", "Bulgaria", "", "China", "Croatia","","Denmark",
	      "Netherlands|Holand",
	      "United States|US|USA|England|United Kingdom|UK|Australia|Canada",
	      "Estonia","Finland","France","Germany","Greece","Israel","India","Hungary","Iceland","Indonesia","Ireland","Italy","Japan",
	      "Korea|South Koera","","Lithuania","Macedonia","Malaysia","Malta","Norway","Poland",
	      "Brasil|Portugal",
	      "Romania","Russia","Serbia","Slovakia","Slovenia","Spain","Sweeden","Thailand","Turkey","","Vietnam"];
	var i_c = [ "Albanian","Arabic","Belarusian","Bulgarian","Catalan","Chinese","Croatian","Czech","Danish","Dutch","English","Estonian","Finnish","French","German","Greek","Hebrew","Hindi","Hungarian","Icelandic","Indonesian","Irish","Italian","Japanese","Korean","Latvian","Lithuanian","Macedonian","Malay","Maltese","Norwegian","Polish","Portuguese","Romanian","Russian","Serbian","Slovak","Slovenian","Spanish","Swedish","Thai","Turkish","Ukrainian","Vietnamese" ];
	var i_i = [ "sq","ar","be","bg","ca","zh","hr","cs","da","nl","en","et","fi","fr","de","el","iw","hi","hu","is","in","ga","it","ja","ko","lv","lt","mk","ms","mt","no","pl","pt","ro","ru","sr","sk","sl","es","sv","th","tr","uk","vi" ];
	for(var i = 0 ; i<i_c.length ; i++) 
		langs.put(i_i[i], i_c[i].toLowerCase());
	for(var i = 0 ; i<i_p.length ; i++)
		if (pString.v(i_p[i]))
			i_p[i].split('|').forEach(function(p) { countries.put(p.toLowerCase(), i_i[i])});
	
	this.getLongName = function(id) {
		id = id.toLowerCase();
		return langs.getValue(id, id);
	};
	this.getNativeName = function(id) {
		id = id.toLowerCase();
		return that.nativex.getValue(id, id);
	};
	/*this.getName = function(id) {
		return pApplicationUI.OPTION_SHOW_NATIVE_LANGS? pLang.getNativeName(id) : pLang.getLongName(id);
	};*/
	
	this.getISOName = function(p_long) {
		p_long = (''+p_long).toLowerCase(); //IE9

		var i = p_long.indexOf('-');
		if (i>0)
			if (langs.getValue(p_long.substring(0, i), null))
				return p_long.substring(0, i);

		if (langs.getValue(p_long, null))
			return p_long;

		return langs.getKey(p_long, p_long);
	};
	this.getLanguage = function() {
		return window.navigator.language;
	};
	this.getLanguageByCountry =  function(p_country) {
		return countries.getValue(p_country.toLowerCase(), null);
	};
	
	this.getLangs = function() {
		return langs.values();
	};
	
	this.display = function(l) {
		l = l || '';
		if (l.indexOf('|')>0)
			return l.split('|').map(pLang.display).join(', ');
		
		l = that.getLongName(l);
		if (pApplicationUI.OPTION_SHOW_NATIVE_LANGS) {
			var iso = that.getISOName(l), nat = iso? that.nativex/*i_nat*/.getValue(iso) : null;
			if (nat && nat!=l.toLowerCase()) {
				if (pApplicationUI.OPTION_SHOW_NATIVE_AND_ENGLISH_LANGS) {
					var cn = pString.capitalize(nat), ci = pString.capitalize(that.getLongName(iso));
					return cn==ci? cn : cn + ' ('+ci+')';
				}
				return pString.capitalize(nat);
			}
		}
		return l;
	};
});

/******************************************************************************/

const pLocalStorage = new (function() {
	var that = this, listeners = new pMap();
	
	function f_listener(e) {
		//var ls = listeners.getValue(e.key, []);
		//ls.forEach(function(l) { l(e); });
		listeners.getValue(e.key, []).forEach(function(l) { l(e); });
	}
	
	this.addListener = function(id, f) {
		var ls = listeners.getValue(id, []);
		listeners.put(id, ls);
		ls[ls.length] = f;
	};
	
	this.removeListener = function(id, f) {
		//var ls = listeners.getValue(id, []);
		//listeners.put(id, ls.filter(function(e)  { return e!= f }));
		listeners.put(id, listeners.getValue(id, []).filter(function(e) { return e!= f }));
	};
	
	this.notify = function(id, v) {
		if (localStorage) {
			localStorage.setItem(id, v);
			localStorage.removeItem(id);
		}
	};
	
	window.addEventListener('storage', f_listener);
});

/******************************************************************************/

const pConst = new (function() {
	
});

/******************************************************************************/

window.pDrag = new (function() {
	this.setURL = function(e, u, c) {
		if (u = u || e.currentTarget.getAttribute("attr-url")) {
			if (u.charAt(0)=='/') u = window.location.protocol + '//' + window.location.host + u;
			
			e.dataTransfer.setData("text/uri-list", u);
			e.dataTransfer.setData("text/plain", u);
			
			var i = pElement.firstByClass(e.currentTarget.parentNode, c);
			if (i)
				e.dataTransfer.setDragImage(i, 20, 20);
		}
	};
});

/******************************************************************************/
/***  RANGE  ******************************************************************/
/******************************************************************************/

pRange = function(r) {
	var that = this, m_value = 0, m_min = 0, m_max = 100, eh = new pEventHandler(), m_enabled = true, m_buffered = 0;

	var x_range = pElement.x(r), x_line, x_buffered, x_mouseline, x_progress, x_thumb, drag; 
	
	if (x_range) {
	
		x_range.setAttribute('draggable', 'false');
		
		pElement.removeAllChildren(x_range);
		x_range.appendChild(x_line = pElement.create('div', x_range.id + '-line', x_range.className+'-line line', '', null, [ 'draggable', 'false']));
		x_range.appendChild(x_buffered = pElement.create('div', x_range.id + '-buffered', x_range.className+'-buffered line', '', null, [ 'draggable', 'false']));
		x_range.appendChild(x_mouseline = pElement.create('div', x_range.id + '-mouseline', x_range.className+'-mouseline line', '', null, [ 'draggable', 'false']));
		x_range.appendChild(x_progress = pElement.create('div', x_range.id + '-progress', x_range.className+'-progress line', '', null, [ 'draggable', 'false']));
		x_range.appendChild(x_thumb = pElement.create('div', x_range.id + '-thumb', x_range.className+'-thumb line', '', null, [ 'draggable', 'false']));
		
		function f_change(x) {
			var o = m_value;
			m_value = f_draw(x);
			if (m_value != o)
				eh.fireEvent('change', this, { id: 'change', value: m_value });
		}
		
		function f_draw_value(v, r, w) {
			//r = r || x_range.getBoundingClientRect();
			w = w || pDocument.width(x_range);
			var p = (w / m_max) * v, tw = pDocument.width(x_thumb);
	
			x_progress.style.width = (p - (tw / 2)) + 'px';
			x_thumb.style.left = (p - (tw / 2)) + 'px';
		}
		
		function f_draw_buffered(v, r, w) {
			m_buffered = v;
			
			//r = r || x_range.getBoundingClientRect();
			w = w || pDocument.width(x_range);
			var p = (w / m_max) * v, tw = pDocument.width(x_thumb);
	
			x_buffered.style.width = Math.min(w, p) + 'px';
		}
		
		function f_value(x, r, w) { // x: position in range div (0 -> ...)
			//r = r || x_range.getBoundingClientRect();
			w = w || pDocument.width(x_range);
			
			return Math.max(0, Math.min(m_max, parseInt((m_max / w) * x)));// if (v>this.max) v = this.max; if (v<0) v = 0;
		};
	
		function f_draw(x) {
			var r = x_range.getBoundingClientRect(), w = pDocument.width(x_range), v = f_value(x - r.left, r, w);
	
			f_draw_value(v, r, w)
			return v;
		}
		
		function f_mousemove(e, xx) {
			if (!m_enabled)
				return;
			
			var r = x_range.getBoundingClientRect(), w = pDocument.width(x_range), x = xx -r.left;
			
			x_mouseline.style.width = x + 'px';
			if (drag)
				f_change(xx);
				//f_draw_value(f_value(x));
			
			eh.fireEvent('move', this, { id: 'move', value: f_value(x, r, w) });
		}
		
		function f_mousedown(e) {
			if (m_enabled) {
				pElement.addClassName(document.body, 'noselect');
				pElement.addClassName(x_thumb, 'drag');
				drag = true;
			}
		}
		
		x_thumb.addEventListener('mousedown', f_mousedown, {passive: true});
		x_thumb.addEventListener('touchstart', function(e) { 
			if (e.changedTouches.length==1) 
				f_mousedown();
			
			e.preventDefault();
		});
		
		function f_mouseup(e, x) {
			//console.log(e);
			if (m_enabled && drag) {
				pElement.removeClassName(document.body, 'noselect');
				pElement.removeClassName(x_thumb, 'drag');
				drag = false;
				f_change(x);//e.clientX);
				//console.log('value : ' + m_value);
			}
		}
		document.addEventListener('mouseup', function(e) { return f_mouseup(e, e.clientX); });
		document.addEventListener('touchend', function(e) { 
			if (e.changedTouches.length==1)
				f_mouseup(e, e.changedTouches[0].clientX); 
		});
		
		[ x_range, x_progress, x_line, x_mouseline ].forEach(function(x) { 
			x.addEventListener('mousemove', function(e) { return f_mousemove(e, e.clientX); }, {passive: true});
			x.addEventListener('touchmove', function(e) { 
				if (e.changedTouches.length==1)
					f_mousemove(e, e.changedTouches[0].clientX); 
	
				if (drag)
					e.preventDefault();
	
			});
		});
		//x_range.addEventListener('mousemove', f_mousemove);
		//x_progress.addEventListener('mousemove', f_mousemove);
		//x_line.addEventListener('mousemove', f_mousemove);
		//x_mouseline.addEventListener('mousemove', f_mousemove);
		function f_doc_mousemove(e, xx) {
			if (!m_enabled)
				return;
			
			var r = x_range.getBoundingClientRect(), w = pDocument.width(x_range), x = xx -r.left;
			
			if (drag)
				f_change(xx);
				//f_draw_value(f_value(x));
			
			eh.fireEvent('move', this, { id: 'move', value: f_value(x, r, w) });
		}
		document.addEventListener('mousemove', function(e) { return f_doc_mousemove(e, e.clientX); });
		document.addEventListener('touchmove', function(e) { 
			if (e.changedTouches.length==1) 
				f_doc_mousemove(e, e.changedTouches[0].clientX); 
		
			if (drag)
				e.preventDefault();
			
		});
		
		function f_mouseout() {
			if (!drag)
				x_mouseline.style.width = '0px';
		}
		x_range.addEventListener('mouseout', f_mouseout);
		
		x_range.addEventListener('click', function(e) {
			if (that.enabled) f_change(e.clientX);
		});
	} //if (x_range)
	
	this.addEventListener = function(eid, f) {
		eh.addEventListener(eid, f);
	};

	// "enabled" property
	Object.defineProperty(this, "enabled", { get: function() { return m_enabled; }, set: function(v) { 
		m_enabled = v;
		if (x_range)
			x_thumb.style.display = m_enabled? 'block':'none'; 
	}});

	// "value" property
	Object.defineProperty(this, "value", { get: function() { return m_value; }, set: function(v) { 
		if (x_range)
			if (!drag) {
				if (v<m_min) v = m_min;
				if (v>m_max) v = m_max;
				f_draw_value(m_value = v); 
			}
	}});

	// "min" property
	Object.defineProperty(this, "min", { get: function() { return m_min; }, set: function(v) { m_min = v; }});

	// "max" property
	Object.defineProperty(this, "max", { get: function() { return m_max; }, set: function(v) { m_max = v; }});

	// "buffered" property
	Object.defineProperty(this, "buffered", { get: function() { return m_buffered; }, set: function(v) { 
		if (x_range)
			f_draw_buffered(v); 
	}});
};

/******************************************************************************/

const pPageData = (new function() {
	var that = this, eh = new pEventHandler();
	this.tracePrefix = 'PageData';
	
	this.setup = function(n, v) {
		pConsole.info(that, 'Setup: ' + n + ', ' + v);
		
		var s = pDocument.getHistoryState('pPageData', {});
		if (!s[n])
			that.put(n, v, true);
		
		if (n.startsWith('s-'))
			pLocalStorage.addListener('pPageData-'+n, function(e) {
				if (e.newValue) {
					var u = e.key.substring('pPageData-'.length);
					that.put(u, e.newValue, true);
				}
			});
		
	};
	
	this.put = function(n, v, noglobal) {
		pConsole.debug(that, 'Put: ' + n + ', ' + v);
		
		var s = pDocument.getHistoryState('pPageData', {});
		s[n] = v;
		pDocument.setHistoryState('pPageData', s);
		eh.fireEvent(n, that, { id: n, value: v });
		
		if (!noglobal && n.startsWith('s-')) {
			localStorage.setItem('pPageData-'+n, v);
			localStorage.removeItem('pPageData-'+n);
		}
	};
	
	this.getValue = function(n) {
		var s = pDocument.getHistoryState('pPageData', {});
		return s[n];
	};
	
	this.init = function() {
		pElement.c(document, 'pdata').forEach(function(x) {
			if (!pString.v(x.getAttribute('pdata-init'))) {
				var a = x.getAttribute('pdata-ids');
				if (pString.v(a))
					pString.split(a, ',').forEach(function(n, i, a) {
						eh.addEventListener(n, function() {
							pElement.setTextContent(x, pPageData.getValue(n));
						});
						
						var v = that.getValue(n);
						if (v)
							pElement.setTextContent(x, v);
						else if (a.length==1)
							that.setup(n, pElement.textContent(x));
						
					});
				
				if (pString.v(a = x.getAttribute('pdata-content'))) {
					var b = [];
					a.split('{').forEach(function(aa) {
						b = b.concat(aa.split('}'));
					})
					//console.log(a);
					b.forEach(function(w, i) {
						if (i % 2 == 1) {
							if (w.indexOf(':')>0)
								w = w.substring(w.indexOf(':')+1);
							
							eh.addEventListener(w, function() {
								pElement.setTextContent(x, that.join(b));
							});
						}
					});
					pElement.setTextContent(x, that.join(b));
				}
				
				x.setAttribute('pdata-init', '1');
			}
		})
	};
	
	this.initDocumentTitle = function(a) {
		if (pString.v(a)) {
			var b = [];
			a.split('{').forEach(function(aa) {
				b = b.concat(aa.split('}'));
			})
			//console.log(a);
			b.forEach(function(w, i) {
				if (i % 2 == 1) {
					if (w.indexOf(':')>0)
						w = w.substring(w.indexOf(':')+1);
					
					eh.addEventListener(w, function() {
						document.title = that.join(b);
					});
				}
			});
			document.title = that.join(b);
		}
	};
	
	this.resolve = function(a) {
		var b = [];
		a.split('{').forEach(function(aa) {
			b = b.concat(aa.split('}'));
		})
		return that.join(b);
	};
		
	this.join = function(b) {
		var s = '';
		b.forEach(function(w, i) {
			if (i % 2 == 1) {
				if (w == 'resources.username')
					s += that.getValue('s-fullname-'+pResources.get('login'));
				else if (w == 'resources.managed-fullname')
					s += that.getValue('s-fullname-'+pResources.get('managed-username'));
				else if (w.startsWith('basename:'))
					s += pString.basename(''+that.getValue(w.substring('basename:'.length)));
				else
					s += that.getValue(w);
			}
			else if (w.endsWith('$'))
				s += w.substring(0, w.length -1);
			else
				s += w;
		});
		return s;
		
	};
});

/******************************************************************************/

/*function x(a) {
	var b = [];
	a.split('{').forEach(function(aa) {
		b = b.concat(aa.split('}'));
	})
	return b;
}

console.log(x('{a} {b}'));
console.log(x('A: {a} {b}'));
console.log(x('A: {a}{b}'));
console.log(x('A: {a} B'));*/

//console.log('a'.split('|')[0]);
//console.log('a|b'.split('|')[1]);

//console.log('pDrag: ' + typeof window['pDrag']);

/******************************************************************************/
/***  END OF FILE  ************************************************************/
/******************************************************************************/