/* -*- coding: utf-8 tab-width: 3 -*- */

/****************************************************************************\
* Original classes                  fWMKq,           sg                      *
*                                   @N  'H|          W&                      *
*                                   Mj  ;W: ,gwq.s;  M|     d7^"Yb,  ,vgbk,y *
*                                  ,#EMWV',W/` 7MP' :Q;    jML_,jQ' lW`  7P  *
*                                  ;M'    WK  ,jL.  |M\_   VK^""'` :$#omvM'  *
*                                  jM     'QMKV HK  #HKEMX? "QDnsY  ?3xc;,   *
*                                                                  G7,__.J)  *
*                                                                   '"*""'   *
\****************************************************************************/



/*****************************************************************************
 * CONSTANTS
 */


var PL_FA_CLONE  = 0x00000001,
	 PL_FA_DELETE = 0x00000002;
var PL_MM_EXCLUSIVE = -1,
	 PL_MM_SINGLE    =  0,
	 PL_MM_ARRAY     =  1;



/*****************************************************************************
 * CLASSES
 */


/********************
 * AutoScrollerListH
 *
// Elenco orizzontale i cui elementi scorrono a intervalli regolari.
//
// Proprietà in tag:
//   scrollDelay: intervallo fra uno scorrimento e l'altro.
//   scrollRate: velocità di uno scorrimento completo.
//   scrollDir: "ltr" o "rtl", è la direzione dello scorrimento.
 */
PaLeg.AutoScrollerListH = function() {
	if (!this.elemName)
		return PaLeg.entangleElement(document.createSubtree(
			"div", {
				"class": "pl_aslisth"
			},
				["table", null,
					["tbody", null,
						["tr"]
					]
				]
		), arguments.callee);

	this.items = Object.merge([], arguments.callee.Items);
	this.items.parentNode = this;

	this.setStyle("overflow", "hidden");
	this.setStyle("position", "relative");
	this.addEventListener("mouseover", this._onMouseOver, false);
	this.addEventListener("mouseout",  this._onMouseOut,  false);

	with (this._m_oScroller = this.getChildren("table")[0]) {
		setStyle("position", "relative");
		setStyle("top",      "0");
		setStyle("left",     "0");
		addEventListener("mouseover", this._onScrollerMouseOver.bind(this), false);
		addEventListener("mouseout",  this._onScrollerMouseOut.bind(this),  false);
	}
	this._m_oRow = this._m_oScroller.tBodies[0].rows[0];

	with (this._m_aScrollL = document.createSubtree.apply(document, PaLeg._aaXJT["ASLHArrow"])) {
		setAttribute("href",  "#");
		setAttribute("title", Loc.PL_LOC_ASL_SCROLL_RTL)
		setStyle("display", "none");
		setStyle("left",    "0");
		addEventListener("click", this._onScrollArrowsClick.bind(this), false);
		appendChild(document.createTextNode("«"));
	};
	this.appendChild(this._m_aScrollL);
	with (this._m_aScrollR = document.createSubtree.apply(document, PaLeg._aaXJT["ASLHArrow"])) {
		setAttribute("href",  "#");
		setAttribute("title", Loc.PL_LOC_ASL_SCROLL_LTR)
		setStyle("display", "none");
		setStyle("right",   "0");
		addEventListener("click", this._onScrollArrowsClick.bind(this), false);
		appendChild(document.createTextNode("»"));
	};
	this.appendChild(this._m_aScrollR);
};
PaLeg.AutoScrollerListH.prototype.items = null;
PaLeg.AutoScrollerListH.prototype._m_aScrollL = null;
PaLeg.AutoScrollerListH.prototype._m_aScrollR = null;
PaLeg.AutoScrollerListH.prototype._m_bMouseOver = false;
PaLeg.AutoScrollerListH.prototype._m_bMouseOverScroller = false;
PaLeg.AutoScrollerListH.prototype._m_bScrollInvert = null;
PaLeg.AutoScrollerListH.prototype._m_idScrollTimeout = null;
PaLeg.AutoScrollerListH.prototype._m_iRepeatInterval = null;
PaLeg.AutoScrollerListH.prototype._m_iScrollDuration = null;
PaLeg.AutoScrollerListH.prototype._m_oArrowsAnim = null;
PaLeg.AutoScrollerListH.prototype._m_oRow = null;
PaLeg.AutoScrollerListH.prototype._m_oScrollAnim = null;
PaLeg.AutoScrollerListH.prototype._m_oScroller = null;


// Classe astratta che definisce i metodi per l'elenco di elementi.
//
PaLeg.AutoScrollerListH.Items = {

	parentNode: null,


	/* Creates a new item.
	 *
	 * Element return
	 *   Newly created item. Current implementation returns an
	 *   HTMLTableCellElement.
	 */
	create: function() {
		return document.createSubtree(
			"td", {
				"class": "pl_aslisth_oItem"
			}
		);
	},


	/* Adds a previously created item.
	 *
	 * Element oItem
	 *   Item to be added. Must have been created by the create() method.
	 * [int i]
	 *   Position at which the item should be inserted, zero based. If omitted,
	 *   the item will be appended.
	 */
	add: function(oItem, i /*= -1*/) {
		if (i === undefined || i == -1)
			i = this.length;
		else if (i < 0 || i > this.length)
			throw new /*Range*/Error("Item index out of bounds");
		oItem.index = i;
		this.parentNode._m_oRow.insertBefore(oItem, i < this.length ? this[i] : null);
		this.insertAt(i, oItem);
		return oItem;
	},


	/* Removes an element.
	 *
	 * int i
	 *   Zero-based index of the item to be removed.
	 */
	remove: function(i) {
		if (i < 0 || i >= this.length)
			throw new /*Range*/Error("Item index out of bounds");
		var oItem = this.removeAt(i);
		this.parentNode._m_oRow.removeChild(oItem);
		for (var j = i; j < this.length; ++j)
			this[j].index = j;
		return oItem;
	}
};


/* Start scrolling the list.
 *
 * [String sDirection]
 *   Either "rtl" or "ltr", with obvious meanings. Defaults to "rtl".
 * [int iDuration]
 *   Duration of a single scrolling step, in milliseconds. Defaults to
 *   Math.UI_MEDIUM_DELAY
 * [int iRepeatInterval]
 *   Delay between two consecutive scrollings. Defaults to Math.UI_LONG_DELAY.
 */
PaLeg.AutoScrollerListH.prototype.startScrolling = function(sDirection /*= "rtl"*/, iDuration /*= Math.UI_MEDIUM_DELAY*/, iRepeatInterval /*= Math.UI_LONG_DELAY*/) {
	this._m_bScrollInvert   = (sDirection == "ltr");
	this._m_iScrollDuration = (iDuration || Math.UI_MEDIUM_DELAY);
	this._m_iRepeatInterval = (iRepeatInterval || Math.UI_LONG_DELAY);
	this._m_idScrollTimeout = window.setTimeout(this._scroll.bind(this), this._m_iRepeatInterval);
};


/* Stops scrolling the list.
 */
PaLeg.AutoScrollerListH.prototype.stopScrolling = function() {
	if (this._m_bScrollInvert !== null) {
		/* null means "don't scroll". */
		this._m_bScrollInvert = null;
		if (this._m_idScrollTimeout) {
			window.clearTimeout(this._m_idScrollTimeout);
			this._m_idScrollTimeout = null;
		}
	}
};


/* See Object.toString().
 */
PaLeg.AutoScrollerListH.prototype.toString = function() {
	return "[object PaLeg.AutoScrollerListH]";
};


/* Shows the user scrolling controls.
 */
PaLeg.AutoScrollerListH.prototype._onMouseOut = function(e) {
	/* The following single-line statement has been broken down into a
	 * step-by-step check; the reason follows. */
//	if (this._m_bMouseOver && !(e.relatedTarget && (e.relatedTarget == this || $(e.relatedTarget).isAncestor(this)))) {
	if (this._m_bMouseOver) {
		/* Avoid FX error message for browser-owned (chrome) elements:
		 * Permission denied to access property 'isAncestor' from a non-chrome context */
		var bRelated;
		try {
			bRelated = (e.relatedTarget && (e.relatedTarget == this || $(e.relatedTarget).isAncestor(this)));
		} catch (x) {
			bRelated = false;
		}
		if (!bRelated) {
			this._m_bMouseOver = false;
			if (this._m_oArrowsAnim)
				this._m_oArrowsAnim.stop();
			if (this.items.length > 1 && this._m_oScroller.offsetWidth > this.offsetWidth)
				this._m_oArrowsAnim = new PaLeg.Animation(
					[
						[this._m_aScrollL, "display", null, "none"],
						[this._m_aScrollR, "display", null, "none"]
					],
					[
						[this._m_aScrollL, "opacity", 0.8, 0.0],
						[this._m_aScrollR, "opacity", 0.8, 0.0]
					],
					Math.UI_SHORT_DELAY / 2
				);
		}
	}
};


/* Hides the user scrolling controls.
 */
PaLeg.AutoScrollerListH.prototype._onMouseOver = function(e) {
	if (!this._m_bMouseOver && (e.target == this || $(e.target).isAncestor(this))) {
		this._m_bMouseOver = true;
		if (this._m_oArrowsAnim)
			this._m_oArrowsAnim.stop();
		if (this.items.length > 1 && this._m_oScroller.offsetWidth > this.offsetWidth)
			this._m_oArrowsAnim = new PaLeg.Animation(
				[
					[this._m_aScrollL, "display", "block", null],
					[this._m_aScrollR, "display", "block", null]
				],
				[
					[this._m_aScrollL, "opacity", 0.0, 0.8],
					[this._m_aScrollR, "opacity", 0.0, 0.8]
				],
				Math.UI_SHORT_DELAY / 2
			);
	}
};


/* Changes the scrolling direction.
 */
PaLeg.AutoScrollerListH.prototype._onScrollArrowsClick = function(e) {
	e.preventDefault();
	this._m_bScrollInvert = (e.target == this._m_aScrollL);
	/* If no scrolling is taking place, execute immediately. */
	if (!this._m_oScrollAnim) {
		/* _scroll() will restart the timeout. */
		if (this._m_idScrollTimeout)
			window.clearTimeout(this._m_idScrollTimeout);
		this._scroll();
	}
}


/* Restores the animation to full speed.
 */
PaLeg.AutoScrollerListH.prototype._onScrollerMouseOut = function(e) {
	/* The following single-line statement has been broken down into a
	 * step-by-step check; the reason follows. */
//	if (this._m_bMouseOverScroller && !(e.relatedTarget && (e.relatedTarget == this._m_oScroller || $(e.relatedTarget).isAncestor(this._m_oScroller)))) {
	if (this._m_bMouseOverScroller) {
		/* Avoid FX error message for browser-owned (chrome) elements:
		 * Permission denied to access property 'isAncestor' from a non-chrome context */
		var bRelated;
		try {
			bRelated = (e.relatedTarget && (e.relatedTarget == this._m_oScroller || $(e.relatedTarget).isAncestor(this._m_oScroller)));
		} catch (x) {
			bRelated = false;
		}
		if (!bRelated) {
			this._m_bMouseOverScroller = false;
			if (this._m_oScrollAnim)
				this._m_oScrollAnim.setSpeed(1);
		}
	}
};


/* Slows the scrolling animation down, to allow the user to aim and click.
 */
PaLeg.AutoScrollerListH.prototype._onScrollerMouseOver = function(e) {
	if (!this._m_bMouseOverScroller && (e.target == this._m_oScroller || $(e.target).isAncestor(this._m_oScroller))) {
		this._m_bMouseOverScroller = true;
		if (this._m_oScrollAnim)
			this._m_oScrollAnim.setSpeed(0.15);
	}
};


/* Perform one scrolling step.
 */
PaLeg.AutoScrollerListH.prototype._scroll = function() {
	var bInvert         = this._m_bScrollInvert,
		 iDuration       = this._m_iScrollDuration,
		 iRepeatInterval = this._m_iRepeatInterval;
	/* Skip animating if theres one item or no concealed items anyway. */
	if (this.items.length > 1 && this._m_oScroller.offsetWidth > this.offsetWidth) {
		this._m_idScrollTimeout = null;
		if (bInvert) {
			var o = this.items[this.items.length - 1];
			this._m_oScrollAnim = new PaLeg.Animation(
				null,
				[
					[this._m_oScroller, "left", -o.offsetWidth, 0, "px", "smoothscroll"]
				],
				iDuration,
				(function() {
					this._m_oScrollAnim = null;
					/* Scrolling might have been stopped during the animation, so
					 * check it now. */
					if (this._m_bScrollInvert !== null)
						/* If the direction was toggled, scroll immediately. This
						 * improves user feedback. */
						if (this._m_bScrollInvert != bInvert)
							window.setTimeout(this._scroll.bind(this), 0);
						else
							this._m_idScrollTimeout = window.setTimeout(this._scroll.bind(this), iRepeatInterval);
				}).bind(this)
			);
			this.items.add(this.items.remove(o.index), 0);
		} else
			this._m_oScrollAnim = new PaLeg.Animation(
				null,
				[
					[this._m_oScroller, "left", 0, -this.items[0].offsetWidth, "px", "smoothscroll"]
				],
				iDuration,
				(function() {
					this.items.add(this.items.remove(0));
					this._m_oScroller.setStyle("left", "0");
					this._m_oScrollAnim = null;
					/* Scrolling might have been stopped during the animation, so
					 * check it now. */
					if (this._m_bScrollInvert !== null)
						/* If the direction was toggled, scroll immediately. This
						 * improves user feedback. */
						if (this._m_bScrollInvert != bInvert)
							window.setTimeout(this._scroll.bind(this), 0);
						else
							this._m_idScrollTimeout = window.setTimeout(this._scroll.bind(this), iRepeatInterval);
				}).bind(this)
			);
		if (this._m_bMouseOverScroller)
			this._m_oScrollAnim.setSpeed(0.15);
	}
	else
		/* No scrolling was necessary, but will check later. */
		this._m_idScrollTimeout = window.setTimeout(this._scroll.bind(this), iRepeatInterval);
};


// BarGraphH //////////////////////////////////////////////////////////////////
//
// Grafico a barre orizzontali.
//
PaLeg.BarGraphH = function() {
	if (!this.elemName)
		return PaLeg.entangleElement(document.createSubtree(
			"table", {
				"class": "graphh"
			},
				["tbody"]
		), arguments.callee);
};


// Crea le barre del grafico.
//
PaLeg.BarGraphH.prototype.createBars = function(arrBars, vNumbers, sMaxLength) {
	var arrMatch   = sMaxLength.match(/^([0-9.]+)([%a-z]*)$/),
		 fWidth     = parseFloat(arrMatch[1]),
		 fWidthUnit = arrMatch[2],
		 fMaxValue  = 0,
		 fTotValue  = 0;
	arrBars.forEach(function(oBar) {
		if (oBar) {
			if (fMaxValue < oBar.value)
				fMaxValue = oBar.value;
			fTotValue += oBar.value;
		}
	});
	arrBars.forEach(function(oBar) {
		var tr = this.insertRow(-1),
			 th = document.createElement("th"),
			 td = document.createElement("td");
		tr.appendChild(th);
		tr.appendChild(td);
		th.setAttribute("scope", "row");
		PaLeg.appendChildAny(th, oBar !== false ? oBar.label : " ");
		if (typeof(oBar) != "object")
			td.appendChild(document.createTextNode(" "));
		else {
			var oColorBar = document.createElement("div");
			oColorBar.setAttribute("class", "graphbar");
			oColorBar.setStyle("width",            (Math.round(fWidth / Math.max(fMaxValue, 1) * oBar.value * 100) / 100) + fWidthUnit);
			oColorBar.setStyle("height",           "1.3em");
			oColorBar.setStyle("background-color", oBar.color);
			td.appendChild(oColorBar);
			if (vNumbers) {
				var s;
				switch (vNumbers) {
					case "%":  s = Number.format(oBar.value / Math.max(fTotValue, 1) * 100) + "%"; break;
					case true: s = Number.format(oBar.value); break;
				}
				td.appendChild(document.createTextNode(s));
			}
		}
	}, this);
};


// Standard toString().
//
PaLeg.BarGraphH.prototype.toString = function() {
	return "[object PaLeg.BarGraphH]";
};


// BarGraphV //////////////////////////////////////////////////////////////////
//
// Grafico a barre verticali.
//
PaLeg.BarGraphV = function() {
	if (!this.elemName)
		return PaLeg.entangleElement(document.createSubtree(
			"table", {
				"class": "graphv"
			},
				["tfoot", null,
					["tr"]
				],
				["tbody", null,
					["tr"]
				]
		), arguments.callee);
};


// Crea le barre del grafico.
//
PaLeg.BarGraphV.prototype.createBars = function(arrBars, vNumbers, sMaxLength, sBarWidth /*= "1.5em"*/) {
	var trBars    = $(this.tBodies[0].rows[0]),
		 trFooters = $(this.tFoot.rows[0]);
	var arrMatch   = sMaxLength.match(/^([0-9.]+)([%a-z]*)$/),
		 fWidth     = parseFloat(arrMatch[1]),
		 fWidthUnit = arrMatch[2],
		 fMaxValue  = 0,
		 fTotValue  = 0;
	arrBars.forEach(function(oBar) {
		if (oBar) {
			if (fMaxValue < oBar.value)
				fMaxValue = oBar.value;
			fTotValue += oBar.value;
		}
	});
	sBarWidth = (sBarWidth || "1.5em");
	arrBars.forEach(function(oBar) {
		var td = document.createElement("td"),
			 th = document.createElement("th");
		trBars.appendChild(td);
		trFooters.appendChild(th);
		th.setAttribute("scope", "col");
		PaLeg.appendChildAny(th, oBar !== false ? oBar.label : " ");
		if (typeof(oBar) != "object")
			td.appendChild(document.createTextNode(" "));
		else {
			if (vNumbers) {
				var s;
				switch (vNumbers) {
					case "%":   s = Number.format(oBar.value / Math.max(fTotValue, 1) * 100) + "%"; break;
					case true:  s = Number.format(oBar.value); break;
				}
				td.appendChild(document.createTextNode(s));
			}
			var oColorBar = document.createElement("div");
			oColorBar.setAttribute("class", "graphbar");
			oColorBar.setStyle("width",            sBarWidth);
			oColorBar.setStyle("height",           (Math.round(fWidth / Math.max(fMaxValue, 1) * oBar.value * 100) / 100) + fWidthUnit);
			oColorBar.setStyle("background-color", oBar.color);
			td.appendChild(oColorBar);
		}
	});
};


// Standard toString().
//
PaLeg.BarGraphV.prototype.toString = function() {
	return "[object PaLeg.BarGraphV]";
};


// BusyIndicator //////////////////////////////////////////////////////////////
//
// Indicatore di attesa.
//
PaLeg.BusyIndicator = function() {
	if (!this.elemName)
		return PaLeg.entangleElement(document.createSubtree(
			"div", {
				"class": "pl_busyindicator",
				"style": "position: absolute;"
			},
				["span"]
		), arguments.callee);
};


// Assegna l'elemento vicino al quale comparirà l'indicatore di attesa, e avvia
// l'animazione.
//
PaLeg.BusyIndicator.prototype.setBuddy = function(m, cxTrack /*= o.offsetWidth*/, iHDir /*= undefined*/, iVDir /*= undefined*/) {
	var o = $(m);
	if (cxTrack === undefined)
		cxTrack = o.offsetWidth;
	this.setStyle("width", cxTrack + "px");
	this.firstChild.setStyle("width", (cxTrack * 0.1) + "px");
	PaLeg.positionElement(this, o, iHDir, iVDir);
	document.getBody().appendChild(this);
	this.removeStyle("visibility");
	this._onBounce(true);
};


// Standard toString().
//
PaLeg.BusyIndicator.prototype.toString = function() {
	return "[object PaLeg.BusyIndicator]";
};


// Fine corsa; alterna il movimento.
//
PaLeg.BusyIndicator.prototype._onBounce = function(bLtR) {
	if (this.parentNode) {
		var oThumb = this.firstChild;
		new PaLeg.Animation(
			null,
			[
				[oThumb, "left", oThumb.offsetLeft, bLtR ? this.offsetWidth - oThumb.offsetWidth : 0, "px", "braked"]
			],
			Math.UI_MEDIUM_DELAY,
			this._onBounce.bind(this, !bLtR)
		);
	}
};


// Calendar ///////////////////////////////////////////////////////////////////
//
// Immissione di data/ora con calendario.
//
PaLeg.Calendar = function() {
	if (!this.elemName)
		return PaLeg.entangleElement(document.createSubtree(
			"input text", {
				"class": "calendar"
			}
		), arguments.callee);

	this.parentNode.insertBefore(document.createSubtree(
		"a", {
			"class": "tinybtn",
			"href":  "#",
			"title": Loc.PL_LOC_CAL_SETTODAY_TIP,

			"click": this._onSetTodayClick.bind(this)
		},
			["img", {
				"class": "si",
				"src":   location.RROOTDIR + "gfx/calendar_today.png",
				"alt":   Loc.PL_LOC_CAL_TODAY
			}]
	), this.nextSibling);
};


// Standard toString().
//
PaLeg.Calendar.prototype.toString = function() {
	return "[object PaLeg.Calendar]";
};


// Inserisce la data di oggi.
//
PaLeg.Calendar.prototype._onSetTodayClick = function(e) {
	e.preventDefault();
	if (!this.disabled) {
		this.value = (new Date).format(Loc.PL_LOC_FMT_TS_DATE_S + " " + Loc.PL_LOC_FMT_TS_TIME_S);
		this.focus();
	}
};


// ColUL //////////////////////////////////////////////////////////////////////
//
// Renderizza un elenco tabulare.
//
PaLeg.ColUL = function() {
	if (!this.elemName)
		return PaLeg.entangleElement(document.createSubtree(
			"table", {
				"class": "pl_colul"
			},
				["tbody"]
		), arguments.callee);

	this.items = Object.merge(this.tBodies[0], arguments.callee.Items);

	if (this.tHead)
		// Le colonne sono già presenti; controlla se c'è una colonna per cui
		// ordinare.
		Array.forEach(this.tHead.rows[0].cells, function(th, i) {
			if (th.isCssClass("sortasc")) {
				th.removeCssClass("sortasc");
				this._m_iSortMode = i + 1;
			} else if (th.isCssClass("sortdesc")) {
				th.removeCssClass("sortdesc");
				this._m_iSortMode = -(i + 1);
			}
		}, this);
};
PaLeg.ColUL.prototype.items = null;
PaLeg.ColUL.prototype._m_iSortMode = 0;


// Classe astratta che definisce i metodi per l'elenco di schede.
//
PaLeg.ColUL.Items = {

	// Crea un nuovo elemento (riga), con il numero di celle opportuno.
	//
	create: function() {
		// IE BUG: deve aggiungere i <tr> e i <td> in questo modo, perché IE
		// genera la collezione cells solo DOPO che la riga è stata inserita in
		// una tabella.
		var tr = document.createElement("tr");
		Array.forEach(this.parentNode.tHead.rows[0].cells, function(th, i) {
			var td = document.createSubtree(
				"td", {
					"class": th.getAttribute("class") + " col" + (i + 1)
				}
			);
			tr.appendChild(td);
		});
		return tr;
	},


	/* Adds a previously created row, or it creates a new one from the provided
	 * cell contents.
	 *
	 * var vRow
	 *   Either a pre-created HTMLTableRowElement, or an array of HTMLElement
	 *   specifying the contents of each cell.
	 * [int i]
	 *   Index the new row should be inserted at; if omitted, the row will be
	 *   inserted in the appropriate sorted position (for a sorted table), or
	 *   appended at the bottom of the table (for an unsorted table).
	 * [int iWeight]
	 *   Weight of the row. Sorted tables sort by weight first, and then by the
	 *   actual cell contents.
	 * HTMLTableRowElement return
	 *   Either the row passed as the vRow argument, or the new row created
	 *   from the array specified in vRow.
	 */
	add: function(vRow, i /*= -1*/, iWeight /*= 0*/) {
		var cul = this.parentNode,
			 tr;
		if (vRow instanceof Array) {
			tr = this.create();
			for (var iC = 0; iC < vRow.length; ++iC)
				tr.childNodes[iC].appendChildM(vRow[iC]);
		} else
			tr = vRow;
		tr._m_iWeight = (iWeight || 0);
		if (cul.isEmpty())
			this.deleteRow(0);
		var arrRows = this.rows;
		if (i === undefined || i == -1)
			if (cul._m_iSortMode) {
				// TODO: per celle contenenti dati tipo Date, cerca tag
				// pl_params col valore per l'ordinamento.
				var iCell = Math.abs(cul._m_iSortMode) - 1;
				/* In the sort order, the actual string comparison only matters
				 * for same-weight rows; otherwise, a positive weight makes the
				 * rows sink, while a negative one makes them float above the
				 * rest. */
				i = PaLeg.Sorting.insertionIndex(
					[tr._m_iWeight || 0, tr.childNodes[iCell].getTextContent().trim()],
					arrRows.length,
					function(iRow) {
						var tr = arrRows[iRow];
						return [tr._m_iWeight || 0, tr.cells[iCell].getTextContent().trim()];
					},
					cul._m_iSortMode < 0
						? function(arr1, arr2) {
							return arr1[0] == arr2[0] ? -arr1[1].natCompareNoCase(arr2[1]) : arr1[0] - arr2[0];
						}
						: function(arr1, arr2) {
							return arr1[0] == arr2[0] ? arr1[1].natCompareNoCase(arr2[1]) : arr1[0] - arr2[0];
						}
				);
			} else
				i = arrRows.length;
		this.insertBefore(tr, i < arrRows.length ? arrRows[i] : null);
		cul._recolorRows(i);
		return tr;
	},


	/* Adds a row, with an animation effect.
	// TODO: non funziona, perché nessuna proprietà height (offsetHeight,
	// height CSS istantanea) viene calcolata se l'elemento non è visibile,
	// quindi non si può sapere a priori che altezza dovrà raggiungere
	// l'animazione.
	 */
	animAdd: function(vRow, i /*= -1*/, onComplete /*= null*/) {
		var tr;
		if (vRow instanceof Array) {
			tr = this.create();
			for (var iC = 0; iC < vRow.length; ++iC)
				tr.childNodes[iC].appendChildM(vRow[iC]);
		} else
			tr = vRow;
		tr.setStyle("display", "none");
		this.add(tr, i);
		tr.removeStyle("display");
		if (onComplete)
			onComplete(tr);
		return tr;
	},


	/* Removes a row, with an animation effect.
	 *
	 * int i
	 *   Index of the row to be removed.
	 * [Function onComplete]
	 *   Function to be called when the animation completes.
	 */
	animRemove: function(i, onComplete /*= null*/) {
		/* <tr>s and <td>s don't have an own height; to make this work, the
		 * contents of each <td> are moved into a temporary <div>, whose height
		 * is directly modifiable. */
		var tr = this.rows[i],
			 arrSetups = [],
			 arrTargets = [];
		if (tr._bDeleting)
			return;
		tr._bDeleting = true;
		arrSetups.length = arrTargets.length = tr.cells.length;
		Array.forEach(tr.cells, function(td, i) {
			var div = document.createElement("div"),
				 o;
			while (o = td.firstChild)
				div.appendChild(o);
			td.appendChild(div);
			arrSetups[i] = [div, "overflow", "hidden", null];
			arrTargets[i] = [div, "height", div.offsetHeight, 0, "px", "accelerated"];
		});
		new PaLeg.Animation(
			arrSetups,
			arrTargets,
			Math.UI_SHORT_DELAY,
			(function() {
				this.remove(tr.sectionRowIndex);
				if (onComplete)
					onComplete(tr);
			}).bind(this)
		);
	},


	/* Removes a row.
	 *
	 * int i
	 *   Index of the row to be removed.
	 */
	remove: function(i) {
		this.deleteRow(i);
		if (this.rows.length) {
			if (i != -1)
				this.parentNode._recolorRows(i);
		} else
			this.parentNode._createEmptyListBanner();
	},


	/* Removes all the rows, clearing the list.
	 */
	removeAll: function() {
		var o;
		while (o = this.firstChild)
			this.removeChild(o);
		this.parentNode._createEmptyListBanner();
	},


	// Ordina gli elementi in base ad una colonna.
	// TODO: per celle contenenti dati tipo Date, cerca tag pl_params col
	// valore per l'ordinamento.
	//
	sort: function(iSortCol /*= 0*/, iSortDir /*= +1*/) {
		if (iSortCol === undefined)
			iSortCol = 0;
		if (iSortDir === undefined)
			iSortDir = +1;
		var arrRows = this.rows;
		/* In the sort order, the actual string comparison only matters for
		 * same-weight rows; otherwise, a positive weight makes the rows sink,
		 * while a negative one makes them float above the rest. */
		PaLeg.Sorting.stableSort(
			arrRows.length,
			function(iRow) {
				var tr = arrRows[iRow];
				return [tr._m_iWeight, tr.cells[iSortCol].getTextContent().trim(), tr];
			},
			iSortDir < 0
				? function(arr1, arr2) {
					return arr1[0] == arr2[0] ? -arr1[1].natCompareNoCase(arr2[1]) : arr1[0] - arr2[0];
				}
				: function(arr1, arr2) {
					return arr1[0] == arr2[0] ? arr1[1].natCompareNoCase(arr2[1]) : arr1[0] - arr2[0];
				},
			(function(iRow, arr) {
				this.insertBefore(arr[2], arrRows[iRow]);
			}).bind(this)
		);
		this.parentNode._recolorRows();
	},


	// Scambia due righe, opzionalmente con un'animazione.
	//
	swap: function(iSrc, iDst, bAnimate /*= false*/, onComplete /*= null*/) {
		if (Math.min(iSrc, iDst) < 0 || Math.max(iSrc, iDst) >= this.rows.length)
			return false;
		if (iSrc == iDst)
			return true;
		var trSrc = this.rows[iSrc], trDst = this.rows[iDst], tblAnim;


		var fnComplete = (function() {
			if (bAnimate)
				;//trDst.setStyle("visibility", "hidden");
			this.removeChild(trSrc);
			this.insertBefore(trDst, iSrc < this.rows.length ? this.rows[iSrc] : null);
			this.insertBefore(trSrc, iDst < this.rows.length ? this.rows[iDst] : null);
			if (bAnimate) {
				/*trSrc.removeStyle("visibility");
				tblAnim.fxFadeOut(Math.SMOOTH_RATE * Math.SMOOTH_STEPS, function() {
					tblAnim.parentNode.removeChild(tblAnim);
				});
				trDst.fxFadeIn(Math.SMOOTH_RATE * Math.SMOOTH_STEPS);*/
			}
			this.parentNode._recolorRows(Math.min(iSrc, iDst), Math.max(iSrc, iDst));
			if (onComplete)
				onComplete(trSrc);
		}).bind(this);


		/*if (bAnimate) {
			var arrSteps, iStep = Math.SMOOTH_STEPS;

			var onMoveStep = (function() {
				if (iStep-- > 0) {
					tblAnim.setStyle("top", arrSteps[iStep] + "px");
					window.setTimeout(onMoveStep, Math.SMOOTH_RATE);
				} else
					fnComplete();
			}).bind(this);

			var xySrc = trSrc.getPosition(???), cyDst = trDst.getPosition(???)[1];
			tblAnim = this.parentNode._getDetachedRowClone(trSrc.sectionRowIndex);
			tblAnim.setStyle("left", xySrc[0] + "px");
			tblAnim.setStyle("top",  xySrc[1] + "px");
			tblAnim.rows[0].addCssClass("pl_colul_oHighlighted");
			document.getBody().appendChild(tblAnim);
			trSrc.setStyle("visibility", "hidden");
			// Se si muove verso il basso, dovrà toccare il margine inferiore.
			if (iSrc < iDst)
				cyDst += trDst.offsetHeight - trSrc.offsetHeight;

			arrSteps = Array.fillAnimSteps("smoothscroll", iStep);
			dy = xySrc[1] - cyDst;
			for (var i = 0; i < iStep; ++i)
				arrSteps[i] = Math.round(cyDst + arrSteps[i] * dy, 2);
			window.setTimeout(onMoveStep, Math.SMOOTH_RATE);
		} else*/
			fnComplete();
		return true;
	},


	// Standard toString().
	//
	toString: function() {
		return "[object PaLeg.ColUL.Items]";
	}
};


// Attiva l'interfaccia utente per l'ordinamento delle colonne.
//
PaLeg.ColUL.prototype.enableSortHeaders = function(iSortCol /*= undefined*/, iSortDir /*= +1*/) {
	if (iSortCol !== undefined)
		this._m_iSortMode = (iSortDir < 0 ? -(iSortCol + 1) : iSortCol + 1);
	Array.forEach(this.tHead.rows[0].cells, function(th, i) {
		var sHeaderTip = Loc.PL_LOC_LIST_SORTBY_TIP.asFormat(th.getTextContent().trim());
		th.getTextNodes(/\S/).forEach(function(tn) {
			// TODO: se trova un link con href != "#", gli aggancia
			// this._onSortHeaderClick; se invece href == "#", lo salta, come
			// fa ora con tutti i link.
			if (!$._aaMethods.getAncestor.call(tn, "a")) {
				var aSort = document.createSubtree(
					"a", {
						"href": "#",
						"title": sHeaderTip,

						"click": this._onSortHeaderClick
					}
				);
				tn.parentNode.replaceChild(aSort, tn);
				aSort.appendChild(tn);
			}
		}, this);

		var oSortArrow = document.createElement("big");
		oSortArrow.appendChild(document.createTextNode(""));
		if (Math.abs(this._m_iSortMode) - 1 == i)
			oSortArrow.firstChild.nodeValue = (this._m_iSortMode < 0 ? " ↑ " : " ↓ ");
		else
			oSortArrow.setStyle("display", "none");
		if (th.isCssClass("ar"))
			th.insertBefore(oSortArrow, th.firstChild);
		else
			th.appendChild(oSortArrow);
		th._oSortArrow = oSortArrow;
	}, this);
};


// Restituisce true se l'elenco è vuoto (cioè contiene solo il banner).
//
PaLeg.ColUL.prototype.isEmpty = function() {
	return this.items.rows.length == 1 && this.items.rows[0].isCssClass("pl_colul_oEmptyBanner");
};


// Crea le intestazioni e i pedici delle colonne.
//
PaLeg.ColUL.prototype.setColumns = function(arrHeaders, arrFooters /*= []*/) {
	this.deleteTHead();
	this.deleteTFoot();

	var tr = this.createTHead().insertRow(0);
	arrHeaders.forEach(function(vHeader) {
		var th = document.createElement("th"),
			 o;
		if (typeof(vHeader) == "object") {
			if (vHeader["header"] !== undefined)
				if (vHeader["header"].nodeType !== undefined)
					o = vHeader["header"];
				else
					o = document.createTextNode(vHeader["header"].toString());
			else
				o = document.createTextNode(" ");
			if (vHeader["align"] !== undefined)
				th.setAttribute("class", {"-1": "al", "0": "ac", "1": "ar"}[vHeader["align"].toString()]);
		} else
			o = document.createTextNode(vHeader.toString());

		th.appendChild(o);
		th.setAttribute("scope", "col");
		tr.appendChild(th);
	});

	var td = this.createTFoot().insertRow(0).insertCell(0);
	td.setAttribute("class",   "pl_colul_oFootLine");
	td.setAttribute("colspan", arrHeaders.length);
	td.appendChild(document.createTextNode(" "));

	if (arrFooters && arrFooters.length) {
		var tr = this.tFoot.insertRow(1),
			 iActualColumn = 0;
		arrFooters.forEach(function(vFooter, i) {
			var td = tr.insertCell(i),
				 o;
			if (typeof(vFooter) == "object") {
				if (typeof(vFooter["footer"]) == "object" && vFooter["footer"].nodeType !== undefined)
					o = vFooter["footer"];
				else
					o = document.createTextNode(vFooter["footer"].toString());
				if (vFooter["align"] !== undefined)
					td.setAttribute("class", {"-1": "al", "0": "ac", "1": "ar"}[vFooter["align"].toString()]);
				if (vFooter["colspan"] !== undefined) {
					td.setAttribute("colspan", vFooter["colspan"]);
					iActualColumn += vFooter["colspan"] - 1;
				}
			} else
				o = document.createTextNode(vFooter.toString());
			if (!td.hasAttribute("class") && typeof(arrHeaders[iActualColumn]) == "object" && arrHeaders[iActualColumn]["align"] !== undefined)
				td.setAttribute("class", {"-1": "al", "0": "ac", "1": "ar"}[arrHeaders[iActualColumn]["align"].toString()]);
			td.appendChild(o);
			++iActualColumn;
		});
	}

	this._createEmptyListBanner();
};


/* See Object.toString().
 */
PaLeg.ColUL.prototype.toString = function() {
	return "[object PaLeg.ColUL]";
};


// Crea il banner per la lista vuota.
//
PaLeg.ColUL.prototype._createEmptyListBanner = function() {
	document.createSubtree(
		this.items.insertRow(0), {
			"class": "pl_colul_oEmptyBanner"
		},
			["td", {
				"class":   "ac",
				"colspan": this.tHead.rows[0].cells.length
			},
				["small", null,
					Loc.PL_LOC_LIST_NOITEMS
				]
			]
	);
};


// Crea una tabella temporanea contenente la riga specificata, identica in
// tutto e per tutto all'originale.
//
PaLeg.ColUL.prototype._getDetachedRowClone = function(i) {
	var trSrc = this.items.rows[i],
		 trClone = $(trSrc.cloneNode(true)), arrCells = (trClone.cells.length ? trClone.cells : trClone.getChildren("td"));
	Array.forEach(trSrc.cells, function(th, i) {
		// Sembra che chi non restituisce la larghezza in pixel qui, permette
		// di usare offsetWidth.
		var cx = th.getCurrentStyle("width");
		if (cx == "auto")
			cx = th.offsetWidth + "px";
		arrCells[i].setStyle("width", cx);
	});
	var table = document.createSubtree(
			"table", {
				"class": this.getAttribute("class"),
				"style": "position: absolute; margin-left: 0; margin-top: 0;"
			},
				["tbody", null,
					["tr", {
						"class": trClone.getAttribute("class")
					}]
				]
		),
		tr = table.rows[0];
	while (trClone.firstChild)
		tr.appendChild(trClone.firstChild);
	return table;
};


// Crea una tabella temporanea contenente la riga specificata, identica in
// tutto e per tutto all'originale.
//
PaLeg.ColUL.prototype._onSortHeaderClick = function(e) {
	e.preventDefault();
	var th = this.getAncestor("th"),
		 cul = th.parentNode.parentNode.parentNode;
	if (Math.abs(cul._m_iSortMode) - 1 == th.cellIndex)
		cul._m_iSortMode = -cul._m_iSortMode;
	else {
		var thOld = th.parentNode.cells[Math.abs(cul._m_iSortMode) - 1];
		thOld._oSortArrow.setStyle("display", "none");
		cul._m_iSortMode = th.cellIndex + 1;
		th._oSortArrow.removeStyle("display");
	}
	th._oSortArrow.firstChild.nodeValue = (cul._m_iSortMode < 0 ? " ↑ " : " ↓ ");
	cul.items.sort(th.cellIndex, cul._m_iSortMode < 0 ? -1 : +1);
};


// Riassegna alle righe i colori alternati.
//
PaLeg.ColUL.prototype._recolorRows = function(iFirst /*= undefined*/, iLast /*= undefined*/) {
	var arrRows = this.items.rows;
	if (iFirst === undefined)
		iFirst = 0;
	if (iLast === undefined)
		iLast = arrRows.length - 1;
	for (var i = iFirst; i <= iLast; ++i)
		arrRows[i].setAttribute("class", ((" " + arrRows[i].getAttribute("class") + " ").replace(/ c[12] /, " ") + " c" + (1 + (i & 1))).lTrim());
};


// Form ///////////////////////////////////////////////////////////////////////
//
// Modulo asincrono.
//
// Il supporto per i campi di immissione personalizzati è automatico, finché
// sono presenti un metodo getValue() per accedere all'<input type="hidden"> e
// un getInput() per accedere alla casella di immissione vera e propria.
//
PaLeg.Form = function() {
	if (!this.elemName)
		// Va creato usando l'apposita classe sul server.
		return null;

	this._m_fieldDefs = eval(this.getAttribute("id") + "_arrFieldDefs");
	this._m_arrDisabledFields = eval(this.getAttribute("id") + "_arrDisabledFields");
	this._m_arrDisabledFields.forEach(function(sName) {
		/* Don't call this.disableField() directly, as that may be redefined. */
		this._disableField(sName, true);
	}, this);

	this.addEventListener("submit", this._onSubmit, false);
	this.addEventListener("reset",  this._onReset,  false);
}
PaLeg.Form.prototype.busy = false;
PaLeg.Form.prototype._m_arrDisabledFields = null;
PaLeg.Form.prototype._m_fieldDefs = {};

// Notifica l'avvenuto aggiornamento del modulo.
//
// e.asyncResponse (object)
//   Risultato della validazione del modulo.
//
PaLeg.Form.declareEvent("asyncupdate");


/* Disables/enabled a field.
 *
 * String sName
 *   Name of the field.
 * bool bDisable
 *   If true, the field will be disabled, otherwise it'll be enabled.
 */
PaLeg.Form.prototype.disableField = function(sName, bDisable) {
	var iDis = this._m_arrDisabledFields.indexOf(sName);
	if (bDisable) {
		if (iDis == -1) {
			this._m_arrDisabledFields.push(sName);
			this._disableField(sName, true);
		}
	} else
		if (iDis != -1) {
			this._m_arrDisabledFields.removeAt(iDis);
			this._disableField(sName, false);
		}

};


// Restituisce il valore precedente di un campo.
//
PaLeg.Form.prototype.getFieldOldValue = function(sName) {
	return $(this.getAttribute("id") + "_old_" + sName).value.jsonDecode();
};


// Restituisce il valore di un campo.
//
PaLeg.Form.prototype.getFieldValue = function(sName) {
	return this._fieldAction(sName, PaLeg.Form._getFieldCB);
};


/* Returns whether the specified field is currently disabled.
 *
 * String sName
 *   Name of the field.
 * bool return
 *   true if the field is be disabled, false otherwise.
 */
PaLeg.Form.prototype.isFieldDisabled = function(sName) {
	return this._m_arrDisabledFields.indexOf(sName) >= 0;
};


// Restituisce il valore precedente di un campo.
//
PaLeg.Form.prototype.setFieldOldValue = function(sName, vValue) {
	$(this.getAttribute("id") + "_old_" + sName).value = String.jsonEncode(vValue);
};


// Cambia il valore di un campo, in seguito ad un evento esterno.
//
PaLeg.Form.prototype.setFieldValue = function(sName, vUnfmt, vValue, sValueLabel /*= undefined*/) {
	this._fieldAction(sName, PaLeg.Form._setFieldCB, [true, true], [vValue, sValueLabel]);
	$(this.getAttribute("id") + "_old_" + sName).value = String.jsonEncode(vUnfmt);
};


// Standard toString().
//
PaLeg.Form.prototype.toString = function() {
	return "[object PaLeg.Form]";
};


/* Disables/enabled a field, regardless of _m_arrDisabledFields.
 *
 * String sName
 *   Name of the field.
 * bool bDisable
 *   If true, the field will be disabled, otherwise it'll be enabled.
 */
PaLeg.Form.prototype._disableField = function(sName, bDisable) {
	this._fieldAction(sName, function() {
		this.disable(bDisable);
	});
};


/* Invokes a callback on every HTMLElement making up a field, be it composite,
 * array, or both.
 *
 * String sName
 *   Name of the field, as per form definition.
 * Function fnCallback
 *   Callback to be invoked on each field HTMLElement.
 * [Array(var) arrScalarArgs]
 *   Arguments to be passed to the callback on each invocation.
 * [Array(Object) arrAssocArgs]
 *   Array containing key-mapped arguments, where each item contains one
 *   argument for each key in an array field.
 * var return
 *   Result of the callback. For composite fields, the return values of the
 *   invocation on each field are assembled in an array; for array fields, the
 *   return values are collected in an associative array, with each key being
 *   the one identifying each array field element. So, for array composite
 *   fields, the return value is Object(Array(var)).
 */
PaLeg.Form.prototype._fieldAction = function(sName, fnCallback, arrScalarArgs /*= []*/, arrAssocArgs /*= []*/) {
	try {
		if (!arrScalarArgs)
			arrScalarArgs = [];
		if (!arrAssocArgs)
			arrAssocArgs = [];
		var cSubFields = this._m_fieldDefs[sName][1 /*subfields*/];
		var fnSubFieldsOp = (cSubFields == 1
			? function(sIDBase, arrArgs) {
				return fnCallback.apply($(sIDBase), arrArgs);
			}
			: function(sIDBase, arrArgs) {
				return Array.fill(cSubFields, function(i, c) {
					/* Reassign the first argument, using the slot created below
					 * (that is, before this). */
					arrArgs[0] = i;
					return fnCallback.apply($(sIDBase + "_" + i), arrArgs);
				});
			});
		/* Make room for the subfield index, in case that'll need to be
		 * passed. */
		arrScalarArgs.unshift(null);
		var sIDBase = this.getAttribute("id") + "_" + sName;
		switch (this._m_fieldDefs[sName][0 /*multmode*/]) {
			case PL_MM_SINGLE:
				/* Just join all the arguments. */
				return fnSubFieldsOp(sIDBase, arrScalarArgs.concat(arrAssocArgs));

			case PL_MM_EXCLUSIVE:
				/* Invoke the callback on each field like it was PL_MM_SINGLE, but
				 * only return the value from the one call that won't return
				 * undefined. */
				var vValue = null;
				this.getDescendants("*", "id", new RegExp("^" + sIDBase + "_")).forEach(function(oField) {
					/* Just join all the arguments. */
					var m = fnSubFieldsOp(oField.getAttribute("id"), arrScalarArgs.concat(arrAssocArgs));
					if (m !== undefined)
						vValue = m;
				});
				return vValue;

			case PL_MM_ARRAY:
				var reIDBase = new RegExp("^(" + sIDBase + "_(.*))" + (cSubFields == 1 ? "$" : "_\d+$")),
					 aaValues = {};
				this.getDescendants("*", "id", reIDBase).forEach(function(oField) {
					var arrIDParts = oField.getAttribute("id").match(reIDBase),
						 arrArgs = arrScalarArgs.clone();
					/* arrIDParts[2] contains the key to this very field in the
					 * array field; use it to extract arguments related to this
					 * field, from each item in arrAssocArgs. */
					arrAssocArgs.forEach(function(aa) {
						arrArgs.push((aa || {})[arrIDParts[2]]);
					});
					/* Also use it to put together the return value. */
					aaValues[arrIDParts[2]] = fnSubFieldsOp(arrIDParts[1], arrArgs);
				});
				return aaValues;
		}
	} catch (x) {
		PaLeg.logException(x, "PaLeg.Form._fieldAction", {
			"sName":         sName,
			"arrScalarArgs": arrScalarArgs,
			"arrAssocArgs":  arrAssocArgs
		});
	}
};


// Restituisce il valore di un campo.
// this è il campo.
//
PaLeg.Form._getFieldCB = function(i, arrActiveFields /*= undefined*/) {
	try {
		var vValue, input;
		if (["input", "textarea", "select"].indexOf(this.elemName) != -1)
			input = this;
		else
			input = this.getValue();
		switch (input.elemName + (input.elemName == "input" ? " " + input.getAttribute("type") : "")) {
			case "input checkbox":
				vValue = (input.checked ? "1" : "0");
				break;
			case "input radio":
				if (input.checked)
					vValue = input.value;
				break;
			default:
				vValue = input.value;
				break;
		}
		if (arrActiveFields !== undefined && !this.disabled) {
			arrActiveFields.push(this);
			this.disable(true);
		}
		return vValue;
	} catch (x) {
		PaLeg.logException(x, "PaLeg.Form._getFieldCB", {
			"elemName":        this.elemName,
			"type":            (this.getAttribute && this.getAttribute("type")),
			"id":              (this.getAttribute && this.getAttribute("id")),
			"i":               i,
			"arrActiveFields": arrActiveFields
		});
	}
};


// Ripristina i valori precedenti di tutti i campi del modulo.
//
PaLeg.Form.prototype._onReset = function(e) {
	// onreset mantiene la semantica di ripristinare i campi del modulo al
	// valore dell'ultimo salvataggio.
	// Sotto FX e IE, setAttribute("value") va a modificare proprio il valore
	// recuperato da reset(). Sotto OP anche, ma cambia anche il contenuto
	// attuale del campo; questo non è un problema quando ciò che ci scrive è
	// proprio il valore del campo in quel momento (come fa _onSubmit()).
	if (!confirm(Loc.PL_LOC_WARN_FORM_RESET))
		e.preventDefault();
};


// Gestisce il submit asincrono.
//
PaLeg.Form.prototype._onSubmit = function(e) {
	e.preventDefault();
	if (this.busy)
		return;
	this.busy = true;

	var aaOldValues = {},
		 aaNewValues = {},
		 arrActiveFields = [],
		 arrButtons = [],
		 arrBusyIndicators = [];
	this.getDescendants("button").forEach(function(button) {
		if (button.getAttribute("type") == "submit") {
			var bi = new PaLeg.BusyIndicator;
			bi.setBuddy(button);
			arrBusyIndicators.push(bi);
		}
		if (!button.disabled) {
			button.disable(true);
			arrButtons.push(button);
		}
	});
	for (var sName in this._m_fieldDefs)
		try {
			aaNewValues[sName] = this._fieldAction(sName, PaLeg.Form._getFieldCB, [arrActiveFields]);
			aaOldValues[sName] = $(this.getAttribute("id") + "_old_" + sName).value.jsonDecode();
		} catch (x) {
			PaLeg.logException(x, "PaLeg.Form._onSubmit$_m_fieldDefs", {
				"sName": sName,
				"o":     ($(this.getAttribute("id") + "_old_" + sName) || {elemName: ""}).elemName
			});
		}
	(new PaLeg.AsyncRequest).submit(
		"POST",
		this.getAttribute("action"),
		{
			"a":         "ar_apply_" + this.getAttribute("id"),
			"oldvalues": String.jsonEncode(aaOldValues),
			"newvalues": String.jsonEncode(aaNewValues)
		},
		(function(ar, oResult) {
			var xSaved = null,
				 bSuccess,
				 sName,
				 i;
			if (oResult !== undefined)
				try {
					// Aggiorna i valori dei campi dopo la riformattazione.
					bSuccess = (oResult["errmsg"] == "");
					for (sName in oResult["values"]) {
						var arrValue = oResult["values"][sName];
						// Nella chiamata a _setFieldCB() gli array saranno uniti,
						// e creeranno quindi bValid nella stessa posizione.
						if (this._m_fieldDefs[sName][0 /*multmode*/] >= PL_MM_ARRAY)
							this._fieldAction(sName, PaLeg.Form._setFieldCB, [bSuccess], [arrValue["itemsvalid"], arrValue["value"], arrValue["label"]]);
						else
							this._fieldAction(sName, PaLeg.Form._setFieldCB, [bSuccess, arrValue["valid"]], [arrValue["value"], arrValue["label"]]);
					}
					sName = null;
				} catch (x) {
					xSaved = x;
					PaLeg.logException(x, "PaLeg.Form._onSubmit$fieldsupdate", {
						"oResult": oResult,
						"sName":   sName
					});
				}
			else
				alert(Loc.PL_LOC_ERR_ASYNC_FAILED);
	
			// Pulisce tutto, poi eventualmente propaga l'eccezione.
			for (i = 0; i < arrActiveFields.length; ++i)
				arrActiveFields[i].disable(false);
			for (i = 0; i < arrButtons.length; ++i)
				arrButtons[i].disable(false);
			for (i = 0; i < arrBusyIndicators.length; ++i)
				arrBusyIndicators[i].parentNode.removeChild(arrBusyIndicators[i]);
			this.busy = false;
			if (xSaved)
				throw xSaved;
	
			if (oResult !== undefined)
				try {
					if (bSuccess) {
						for (sName in oResult["values"])
							// L'aggiornamento è stato completato, quindi
							// sincronizza anche i valori precedenti.
							$(this.getAttribute("id") + "_old_" + sName).value = String.jsonEncode(oResult["values"][sName]["unfmt"]);
						sName = null;
	
						// Notifica del completato aggiornamento.
						var e = document.createEvent("Events");
						e.initEvent("asyncupdate", true, false);
						e.asyncResponse = oResult;
						this.dispatchEvent(e);
	
						if (oResult["resmsg"])
							alert(oResult["resmsg"]);
						if (oResult["endurl"])
							location.href = oResult["endurl"];
					} else {
						alert(oResult["errmsg"] + oResult["resmsg"]);
						for (sName in oResult["values"])
							if (!oResult["values"][sName]["valid"]) {
								var sIDBase = this.getAttribute("id") + "_" + sName,
									 o;
								if (this._m_fieldDefs[sName][0 /*multmode*/] == PL_MM_EXCLUSIVE)
									// In un campo esclusivo, seleziona il
									// primo valore che trova.
									o = this.getDescendants("*", "id", new RegExp("^" + sIDBase + "_"))[0];
								else {
									if (this._m_fieldDefs[sName][0 /*multmode*/] >= PL_MM_ARRAY)
										// In un campo multiplo, seleziona il
										// primo valore non valido.
										for (var sKey in oResult["values"][sName]["itemsvalid"])
											if (!oResult["values"][sName]["itemsvalid"][sKey]) {
												sIDBase += "_" + sKey;
												break;
											}
									if (this._m_fieldDefs[sName][1 /*subfields*/] > 1)
										sIDBase += "_0";
									o = $(sIDBase);
								}
								if (o.select)
									o.select();
								o.focus();
								break;
							}
						sName = null;
					}
				} catch (x) {
					PaLeg.logException(x, "PaLeg.Form._onSubmit$userinteraction", {
						"oResult": oResult,
						"sName":   sName
					});
					throw x;
				}
		}).bind(this)
	);
};


// Aggiorna il valore e lo stato di validità di un campo.
// this è il campo.
//
PaLeg.Form._setFieldCB = function(i, bAllValid, bValid, vValue, sValueLabel) {
	try {
		var sInvalidClass = "invalid", input;
		if (["input", "textarea", "select"].indexOf(this.elemName) != -1)
			input = this;
		else
			input = this.getValue();
		switch (input.elemName + (input.elemName == "input" ? " " + input.getAttribute("type") : "")) {
			case "input checkbox":
				// Se è specificato un indice (i) ma vValue non è un array, lo
				// tratta come un array di bit; così fa funzionare bool_array.
				input.checked = (i !== null ? vValue && vValue.constructor == Array ? vValue[i] : vValue & (1 << i) : vValue);
				if (bAllValid)
					if (input.checked)
						input.setAttribute("checked", "checked");
					else
						input.removeAttribute("checked");
				sInvalidClass += "_chk";
				break;
			case "input radio":
				input.checked = (input.value == vValue);
				if (bAllValid)
					if (input.checked)
						input.setAttribute("checked", "checked");
					else
						input.removeAttribute("checked");
				sInvalidClass += "_chk";
				break;
			case "input password":
				if (bAllValid) {
					input.value = (vValue ? i !== null ? vValue[i] : vValue : "");
					input.setAttribute("value", input.value);
				}
				break;
			default:
				input.value = (vValue ? i !== null ? vValue[i] : vValue : "");
				if (bAllValid)
					input.setAttribute("value", input.value);
				break;
		}
		// Per i campi non elementari, usa il campo di immissione vero e
		// proprio.
		if (input != this) {
			input = this.getInput();
			// Già che c'è, approfitta per aggiornare la parte testuale del
			// campo; la parte valore l'ha appena sistemata.
			if (bValid) {
				input.value = sValueLabel;
				if (bAllValid)
					input.setAttribute("value", input.value);
			}
		}
		input[bValid ? "removeCssClass" : "addCssClass"](sInvalidClass);
	} catch (x) {
		PaLeg.logException(x, "PaLeg.Form._setFieldCB", {
			"elemName":    this.elemName,
			"type":        (this.getAttribute && this.getAttribute("type")),
			"id":          (this.getAttribute && this.getAttribute("id")),
			"i":           i,
			"bAllValid":   bAllValid,
			"bValid":      bValid,
			"vValue":      vValue,
			"sValueLabel": sValueLabel
		});
	}
};


// FormattedInput /////////////////////////////////////////////////////////////
//
// Casella di testo con input formattato.
//
PaLeg.FormattedInput = function() {
	if (!this.elemName)
		return PaLeg.entangleElement(document.createElement("textarea"), arguments.callee);

	this._m_oPreview = document.createSubtree(
		"div", {
			"class": "formattedinput_editor",
			"style": "display: none;"
		}
	);
	this._m_oPreview.copyStylesFrom(this, true, ["position", "left", "top", "width", "height"]);
	this.parentNode.insertBefore(this._m_oPreview, this.nextSibling);

	this._m_oToolBar = this.parentNode.insertBefore(document.createSubtree(
		"ul", {
			"class": "formattedinput_toolbar toolbarh"
		},
			["li", null,
				["input checkbox", {
					"class": "chk",
					"id":    "chkPreview_" + this.getAttribute("id"),

					"click": this._onPreviewClick
				}],
				" ",
				["label", {
					"for":   "chkPreview_" + this.getAttribute("id"),
					"title": Loc.PL_LOC_FMTINPUT_TOGGLEPREVIEW_TIP
				},
					Loc.PL_LOC_FMTINPUT_TOGGLEPREVIEW
				]
			],
			["li", null,
				["a", {
					"href":  location.RROOTDIR + "plt_help_p.php",
					"title": Loc.PL_LOC_FMTINPUT_TAGHELP_TIP,

					"click": this._onPLTHelpClick
				},
					Loc.PL_LOC_FMTINPUT_TAGHELP
				]
			]
	), this);
	this._m_oToolBar.copyStylesFrom(this, true, ["width"]);
	$("chkPreview_" + this.getAttribute("id"))._fi = this;
};
PaLeg.FormattedInput.prototype._m_oPreview = null;
PaLeg.FormattedInput.prototype._m_oToolBar = null;


// Standard toString().
//
PaLeg.FormattedInput.prototype.toString = function() {
	return "[object PaLeg.FormattedInput]";
};


// Mostra la guida ai tag.
//
PaLeg.FormattedInput.prototype._onPLTHelpClick = function(e) {
	e.preventDefault();
	PaLeg.popup(this, 700, 500);
};


// Attiva/disattiva la modalità anteprima.
//
PaLeg.FormattedInput.prototype._onPreviewClick = function(e) {
	var fi = this._fi;
	if (this.checked) {
		this.disable(true);
		PaLeg.asyncExec(
			location.RROOTDIR + "pl_ar.php",
			{
				"a":    "ar_pltpreview",
				"text": fi.value,
				"root": location.RROOTDIR
			},
			(function(bSuccess, oResult) {
				if (bSuccess) {
					fi._m_oPreview.innerHTML = oResult["plt"];
					fi.setStyle("display", "none");
					fi._m_oPreview.removeStyle("display");
				} else
					this.checked = false;
				this.disable(false);
				this.blur();
			}).bind(this)
		);
	} else {
		fi._m_oPreview.setStyle("display", "none");
		fi.removeStyle("display");
		fi.focus();
	}
};


// IncrSearchInput ////////////////////////////////////////////////////////////
//
// Casella di testo con ricerca in un elenco che può essere caricato
// dinamicamente.
//
PaLeg.IncrSearchInput = function() {
	if (!this.elemName)
		return PaLeg.entangleElement(document.createSubtree(
			"div", null,
				["input text"],
				["input hidden"]
		), arguments.callee);

	this._m_arrFixedItems = [];
	this._m_arrItems      = [];
	this._m_inputValue = this.getChildren("input");
	this._m_inputIS    = this._m_inputValue[0];
	this._m_inputValue = this._m_inputValue[1];
	this._m_sPrevIS    = this._m_inputIS.value;
	this._m_inputIS.setAttribute("autocomplete", "off");
	this._m_inputIS._isi = this;
	this._m_inputIS.addEventListener("keydown",  this._onInputISKeyDown,  false);
	this._m_inputIS.addEventListener("keyup",    this._onInputISKeyUp,    false);
	this._m_inputIS.addEventListener("dblclick", this._onInputISDblClick, false);
	this._m_inputIS.addEventListener("focus",    this._onInputISFocus,    false);
	this._m_inputIS.addEventListener("blur",     this._onInputISBlur,     false);

	this.disabled = this._m_inputIS.disabled;

	this.insertBefore(document.createSubtree(
		"a", {
			"class": "tinybtn",
			"href":  "#",

			"click": this._onShowListClick.bind(this)
		},
			"▼"
	), this._m_inputValue);
};
PaLeg.IncrSearchInput.prototype._m_aaDLParams = null;
PaLeg.IncrSearchInput.prototype._m_arrFixedItems = null;
PaLeg.IncrSearchInput.prototype._m_arrItems = null;
PaLeg.IncrSearchInput.prototype._m_bDLPartial = null;
PaLeg.IncrSearchInput.prototype._m_bMouseOverDD = false;
PaLeg.IncrSearchInput.prototype._m_inputIS = null;
PaLeg.IncrSearchInput.prototype._m_inputValue = null;
PaLeg.IncrSearchInput.prototype._m_liHl = null;
PaLeg.IncrSearchInput.prototype._m_sDLUrl = null;
PaLeg.IncrSearchInput.prototype._m_sLastIS = null;
PaLeg.IncrSearchInput.prototype._m_sPrevIS = null;
PaLeg.IncrSearchInput.prototype._m_oDropDown = null;

// Segnala il cambiamento della selezione da parte dell'utente.
//
PaLeg.IncrSearchInput.declareEvent("selchange");


// Aggiunge degli elementi all'elenco.
//
PaLeg.IncrSearchInput.prototype.addItems = function(arr) {
	this._m_arrFixedItems = this._m_arrFixedItems.concat(arr);
	if (this._m_arrItems !== null)
		this._m_arrItems = this._m_arrFixedItems;
};


// Allontana il focus.
//
PaLeg.IncrSearchInput.prototype.blur = function() {
	this._m_inputIS.blur();
};


// Reinizializza i campi.
//
PaLeg.IncrSearchInput.prototype.clear = function() {
	this._m_inputIS.value = "";
	this._m_inputValue.value = "";
};


// Disabilita il campo.
//
PaLeg.IncrSearchInput.prototype.disable = function(bDisable) {
	this._m_inputIS.disable(bDisable);
	this.disabled = (bDisable || false);
};


// Abilita il caricamento su richiesta degli elementi.
//
PaLeg.IncrSearchInput.prototype.enableDynamicLoad = function(sUrl, aaParams) {
	this._m_arrItems = null;
	this._m_sDLUrl = sUrl;
	this._m_aaDLParams = aaParams;
};


// Assegna il focus.
//
PaLeg.IncrSearchInput.prototype.focus = function() {
	this._m_inputIS.focus();
};


// Restituisce la casella di immissione.
//
PaLeg.IncrSearchInput.prototype.getInput = function() {
	return this._m_inputIS;
};


// Restituisce la casella con il valore.
//
PaLeg.IncrSearchInput.prototype.getValue = function() {
	return this._m_inputValue;
};


// Elimina l'elenco.
//
PaLeg.IncrSearchInput.prototype.hideList = function() {
	if (this._m_oDropDown) {
		this._m_oDropDown.dismissPopup();
		this._m_oDropDown.parentNode.removeChild(this._m_oDropDown);
		this._m_oDropDown = null;
		this._m_liHl = null;
	}
};


// Restituisce true se l'elenco è visibile.
//
PaLeg.IncrSearchInput.prototype.isDropDownVisible = function() {
	return this._m_oDropDown != null;
};


// Elimina eventuali elementi dinamici memorizzati.
//
PaLeg.IncrSearchInput.prototype.purgeDLCache = function() {
	this._m_arrItems = null;
};


// Carica nell'elenco gli elementi corrispondenti alla chiave di ricerca.
//
PaLeg.IncrSearchInput.prototype.showList = function() {
	this._m_oDropDown = document.createSubtree(
		"ul", {
			"class": "pl_isinput_oDropDown",

			"click":     this._onDropDownClick.bind(this),
			"mouseout":  this._onDropDownMouseOut.bind(this),
			"mouseover": this._onDropDownMouseOver.bind(this),
			"scroll":    this._onDropDownScroll.bind(this)
		}
	);
	this._m_oDropDown.copyStylesFrom(this._m_inputIS, true, ["color", "background-color", "background-image", "border-color", "border-style", "font-size", "font-name", "font-weight", "font-style"]);
	document.getBody().appendChild(this._m_oDropDown);

	if (this._m_sDLUrl && (this._m_arrItems == null || this._m_bDLPartial)) {
		var li = document.createElement("li");
		li.setAttribute("class", "lightcolor");
		li.appendChild(document.createTextNode(Loc.PL_LOC_LIST_LOADING));
		this._m_oDropDown.appendChild(li);

		var aaDLP = {
			"ss":    (location.SSID != "" ? location.SSID.substr(3) : ""),
			"match": this._m_inputIS.value
		};
		for (var sParam in this._m_aaDLParams)
			aaDLP[sParam] = (typeof(this._m_aaDLParams[sParam]) == "object" ? String.jsonEncode(this._m_aaDLParams[sParam]) : this._m_aaDLParams[sParam]);
		PaLeg.asyncExec(
			this._m_sDLUrl,
			aaDLP,
			(function(bSuccess, oResult) {
				// Il risultato interessa solo se l'elenco è ancora visibile.
				if (this._m_oDropDown)
					if (bSuccess) {
						if (li.parentNode == this._m_oDropDown)
							this._m_oDropDown.removeChild(li);
						this._m_bDLPartial = oResult["partial"];
						this._m_arrItems = this._m_arrFixedItems.concat(oResult["items"]);
						this._fillList(false);
						if (oResult["partial"]) {
							li.firstChild.nodeValue = Loc.PL_LOC_LIST_PARTIAL;
							this._m_oDropDown.appendChild(li);
						}
					} else
						this.hideList();
			}).bind(this)
		);
	} else
		this._fillList(false);

	PaLeg.popupElement(this._m_oDropDown, this._m_inputIS, {
		dismissOnClickOut: false
	}, +1, +2);
};


// Standard toString().
//
PaLeg.IncrSearchInput.prototype.toString = function() {
	return "[object PaLeg.IncrSearchInput]";
};


// Rabbocca l'elenco.
//
PaLeg.IncrSearchInput.prototype._fillList = function(bRefilter) {
	var reMatch = new RegExp("^" + RegExp.escapeLike(this._m_inputIS.value), "i"), li;
	if (bRefilter) {
		var o = this._m_oDropDown.firstChild;
		while (li = o) {
			o = li.nextSibling;
			if (!reMatch.test(li.firstChild.nodeValue))
				this._m_oDropDown.removeChild(li);
		}
		if (this._m_liHl && this._m_liHl.parentNode != this._m_oDropDown)
			this._highlightItem(null);
	} else
		this._m_arrItems.forEach(function(arrItem) {
			if (reMatch.test(arrItem[1])) {
				li = document.createElement("li");
				li._isiValue = arrItem[0];
				li.appendChild(document.createTextNode(arrItem[1]));
				this._m_oDropDown.appendChild(li);
			}
		}, this);
	if (!this._m_oDropDown.firstChild)
		this._m_oDropDown.appendChild(document.createSubtree(
			"li", {
				"class": "lightcolor"
			},
				Loc.PL_LOC_LIST_NOMATCHES
		));
};


// Evidenzia la selezione.
//
PaLeg.IncrSearchInput.prototype._highlightItem = function(li) {
	if (this._m_liHl)
		this._m_liHl.removeCssClass("pl_isinput_oHighlighted");
	if ((this._m_liHl = li) && !this._m_liHl.isCssClass("lightcolor")) {
		this._m_liHl.addCssClass("pl_isinput_oHighlighted");
		var y = li.getPosition()[1],
			 cy = li.offsetHeight,
			 yScroll = this._m_oDropDown.scrollTop,
			 cyClient = this._m_oDropDown.clientHeight;
		if (y < yScroll)
			this._m_oDropDown.scrollTop = y;
		else if (y + cy > yScroll + cyClient)
			this._m_oDropDown.scrollTop = y + cy - cyClient;
	}
};


// Seleziona gli elementi dalla lista.
//
PaLeg.IncrSearchInput.prototype._onDropDownClick = function(e) {
	if (e.target.elemName == "li") {
		this._selectItem(e.target);
		this._m_inputIS.focus();
	}
};


// Segnala che il mouse è fuori dall'elenco.
//
PaLeg.IncrSearchInput.prototype._onDropDownMouseOut = function(e) {
	this._m_bMouseOverDD = false;
};


// Segnala che il mouse è nell'elenco, ed evidenzia l'elemento sotto il mouse.
//
PaLeg.IncrSearchInput.prototype._onDropDownMouseOver = function(e) {
	this._m_bMouseOverDD = true;
	if (e.target.elemName == "li")
		this._highlightItem(e.target);
};


// Lo scorrimento può essere stato effettuato cliccando sull'area non-client
// della lista, che quindi non è un click nella lista ma sposta comunque il
// focus dall'input.
//
PaLeg.IncrSearchInput.prototype._onDropDownScroll = function(e) {
	this._m_inputIS.focus();
};


// Se viene deselezionata la casella di testo, nasconde l'elenco.
//
PaLeg.IncrSearchInput.prototype._onInputISBlur = function(e) {
	var isi = this._isi;
	if (!isi._m_bMouseOverDD) {
		isi.hideList();
		if (isi._m_inputIS.value == "" && isi._m_sLastIS !== null) {
			isi._m_inputIS.value = isi._m_sLastIS;
			isi._m_sLastIS = null;
		}
	}
};


// Doppio clic per mostrare l'elenco.
//
PaLeg.IncrSearchInput.prototype._onInputISDblClick = function(e) {
	var isi = this._isi;
	if (!isi._m_oDropDown) {
		e.preventDefault();
		isi.showList();
	}
};


// Se viene attivata la casella di testo, seleziona tutto il contenuto.
//
PaLeg.IncrSearchInput.prototype._onInputISFocus = function(e) {
	var isi = this._isi;
	if (isi._m_inputIS.select)
		isi._m_inputIS.select();
};


// Gestisce i tasti per selezionare gli elementi nell'elenco.
//
PaLeg.IncrSearchInput.prototype._onInputISKeyDown = function(e) {
	var isi = this._isi;
	switch (e.keyCode) {
		case 0x09: // Tab
			if (isi._m_liHl && isi._m_liHl.isCssClass("pl_isinput_oHighlighted")) {
				e.preventDefault();
				isi._m_sPrevIS = this.value = isi._m_liHl.firstChild.nodeValue;
			}
			break;

		case 0x0A: // LF
		case 0x0D: // CR
			if (isi._m_liHl) {
				e.preventDefault();
				e.stopPropagation();
				isi._selectItem(isi._m_liHl);
			}
			break;

		case 0x1B: // Esc
			if (isi._m_oDropDown) {
				e.preventDefault();
				e.stopPropagation();
				isi.hideList();
			}
			break;

		case 0x25: // Freccia sx
			break;

		case 0x26: // Freccia su
			e.preventDefault();
			if (isi._m_oDropDown)
				isi._highlightItem((isi._m_liHl || {}).previousSibling || isi._m_oDropDown.lastChild);
			break;

		case 0x27: // Freccia dx
			break;

		case 0x28: // Freccia giù
			e.preventDefault();
			if (isi._m_oDropDown)
				isi._highlightItem((isi._m_liHl || {}).nextSibling || isi._m_oDropDown.firstChild);
			else
				isi.showList();
			break;
	}
};


// Gestisce i tasti per selezionare gli elementi nell'elenco.
//
PaLeg.IncrSearchInput.prototype._onInputISKeyUp = function(e) {
	switch (e.keyCode) {
		case 0x0A: // LF
		case 0x0D: // CR
		case 0x1B: // Esc
		case 0x25: // Freccia sx
		case 0x26: // Freccia su
		case 0x27: // Freccia dx
		case 0x28: // Freccia giù
			e.preventDefault();
			return;
	}
	var isi = this._isi;
	if (isi._m_sPrevIS == this.value.substr(0, isi._m_sPrevIS.length)) {
		// La stringa è stata solo allungata: se l'elenco era già visibile, per
		// mantenerlo coerente non deve ricrearlo, ma solo filtrarlo, a meno
		// che non era dinamico e incompleto.
		if (isi._m_oDropDown)
			if (isi._m_sDLUrl && isi._m_bDLPartial)
				isi.hideList();
			else
				isi._fillList(true);
	} else {
		// Deve aspettare la richiesta dell'utente per mostrare nuovamente
		// l'elenco.
		isi.hideList();
		if (isi._m_sDLUrl)
			// Deve scartare tutto m_arrItems.
			isi._m_arrItems = null;
	}
	isi._m_sPrevIS = this.value;
};


// Clic sulla freccia per mostrare l'elenco.
//
PaLeg.IncrSearchInput.prototype._onShowListClick = function(e) {
	e.preventDefault();
	if (!this._m_inputIS.disabled) {
		// Salva l'etichetta della selezione attuale, per mostrarla nuovamente
		// se l'utente non dovesse selezionare nulla.
		this._m_sLastIS = this._m_inputIS.value;
		this._m_inputIS.value = "";
		this.showList();
	}
	this._m_inputIS.focus();
};


// Aggiorna la selezione.
//
PaLeg.IncrSearchInput.prototype._selectItem = function(li) {
	if (li.isCssClass("pl_isinput_oHighlighted")) {
		this._m_inputIS.value = li.firstChild.nodeValue;
		this._m_inputValue.value = li._isiValue;
		this.hideList();
		var e = document.createEvent("UIEvents");
		e.initUIEvent("selchange", true, true, window, 0);
		this.dispatchEvent(e);
	}
};


// PwSecurityChecker //////////////////////////////////////////////////////////
//
// Controllo del livello di sicurezza di password.
//
PaLeg.PwSecurityChecker = function() {
	if (!this.elemName)
		return PaLeg.entangleElement(document.createSubtree(
			"div", {
				"class": "pl_pwsecchk"
			}
		), arguments.callee);

	document.createSubtree(
		this, null,
			["div", {
				"class": "pl_pwsecchk_oLabel"
			},
				""
			],
			["div", {
				"class": "pl_pwsecchk_oBar"
			}]
	);
	this._m_oLabel = this.lastChild.previousSibling.firstChild;
	this._m_oBar   = this.lastChild;
	this._m_arrBannedWords = [];
};
PaLeg.PwSecurityChecker.prototype._m_arrBannedWords = null;
PaLeg.PwSecurityChecker.prototype._m_cchMin = 0;
PaLeg.PwSecurityChecker.prototype._m_oBar = null;
PaLeg.PwSecurityChecker.prototype._m_oLabel = null;
PaLeg.PwSecurityChecker.prototype._m_oPassword = null;


// Assegna il campo di immissione ed i criteri di validità della password.
//
PaLeg.PwSecurityChecker.prototype.setCriteria = function(oPassword, cchMin, arrBannedWords /*= []*/) {
	if (this._m_oPassword) {
		this._m_oPassword.removeEventListener("keyup",  this._onUpdate, false);
		this._m_oPassword.removeEventListener("change", this._onUpdate, false);
	}
	this._m_oPassword = oPassword;
	this._m_oPassword._pwsc = this;
	this._m_oPassword.addEventListener("keyup",  this._onUpdate, false);
	this._m_oPassword.addEventListener("change", this._onUpdate, false);
	this._m_cchMin = cchMin;
	this._m_arrBannedWords = (arrBannedWords || []);
	this._onUpdate.call(this._m_oPassword);
};


// Standard toString().
//
PaLeg.PwSecurityChecker.prototype.toString = function() {
	return "[object PaLeg.PwSecurityChecker]";
};


// Aggiorna il livello di sicurezza visualizzato.
//
PaLeg.PwSecurityChecker.prototype._onUpdate = function(e) {
	var pwsc = this._pwsc;
	var s = pwsc._m_oPassword.value.toLowerCase(),
		 iLevel = 0;
	if (s.length >= pwsc._m_cchMin) {
		++iLevel;
		for (var i = 0; i < pwsc._m_arrBannedWords.length; ++i)
			if (s.indexOf(pwsc._m_arrBannedWords[i]) != -1 || s.indexOf(pwsc._m_arrBannedWords[i].reverse()) != -1) {
				--iLevel;
				break;
			}
		if (iLevel > 0)
			if ("abcdefghijklmnopqrstuvwxyz".indexOf(s) == -1 && "01234567890".indexOf(s) == -1 && !(new RegExp("^" + RegExp.escape(s.charAt(0)) + "+$")).test(s)) {
				if (s.match(/[^a-z\s].*[^a-z\s]/))
					++iLevel;
				if (s.length >= 8)
					++iLevel;
			}
	}
	pwsc._m_oLabel.nodeValue = [Loc.PL_LOC_PWSEC_INVALID, Loc.PL_LOC_PWSEC_LOW, Loc.PL_LOC_PWSEC_MEDIUM, Loc.PL_LOC_PWSEC_HIGH][iLevel];
	pwsc._m_oBar.setAttribute("class", "pl_pwsecchk_oBar " + ["", "negativeb", "neutralb", "positiveb"][iLevel]);
	pwsc._m_oBar.setStyle("width", ["0%", "33%", "67%", "100%"][iLevel]);
};


// <img> centrata rispetto a larghezza e altezza definite.
//
PaLeg.SizedImg = function(width, height) {
	if (!this.elemName)
		// Costruttore.
		//
		return (function() {
			var img = document.createElement("img");
			img.setAttribute("src", "about:blank");
			img.setAttribute("alt", "");
			var table = document.createElement("table");
			table.setAttribute("class", "sizedimg");
			table.setStyle("width",  width);
			table.setStyle("height", height);
			table.insertRow(0).insertCell(0).appendChild(img);
			return PaLeg.SizedImg.call(table);
		})();

	var m_img = this.getDescendants("img")[0],
		 m_imgPreload = document.createElement("img");
	m_imgPreload.setStyle("visibility", "hidden");
	m_imgPreload.setAttribute("alt", m_img.getAttribute("alt"));
	this.setAttribute("src", m_img.getAttribute("src"));
	this.setAttribute("alt", m_img.getAttribute("alt"));


	// Carica immediatamente un'altra immagine.
	//
	this.load = function(sUrl, sAltText /*= ""*/) {
		m_img.setAttribute("src", sUrl);
		m_img.setAttribute("alt", sAltText || "");
		this.setAttribute("src", sUrl);
		this.setAttribute("alt", sAltText || "");
	};


	// Avvia il caricamento di un'altra immagine.
	//
	this.preload = function(sUrl, sAltText /*= ""*/) {
		m_imgPreload.setAttribute("src", sUrl);
		m_imgPreload.setAttribute("alt", sAltText || "");
	};


	// Effettua una transizione verso l'immagine precaricata.
	//
	this.usePreloaded = function(iFadeDuration) {
		/*if (iFadeDuration > 0)
			if ("filter" in m_img.style) {
				var sFilterOld = this.getStyle("filter");
//				this.disablePngFix(true);
				this.setStyle("filter", sFilterOld + " progid:DXImageTransform.Microsoft.Fade(Duration=" + (iFadeDuration / 1000) + ")");
				this.filters["DXImageTransform.Microsoft.Fade"].apply();
				m_img.parentNode.replaceChild(m_imgPreload, m_img);
				m_imgPreload.setStyle("visibility", "visible");
				this.filters["DXImageTransform.Microsoft.Fade"].play();
				window.setTimeout((function() {
					this.setStyle("filter", sFilterOld);
//					this.disablePngFix(false);
				}).bind(this), iFadeDuration);
			} else {
				var xyImg = m_img.getPosition(???);
				m_img.parentNode.replaceChild(m_imgPreload, m_img);
				m_imgPreload.removeCssClass("sizedimgx_fade");
				m_imgPreload.fxFadeIn(iFadeDuration);
				m_img.addCssClass("sizedimgx_fade");
				m_img.setStyle("left", xyImg[0] + "px");
				m_img.setStyle("top",  xyImg[1] + "px");
				document.getBody().appendChild(m_img);
				m_img.fxFadeOut(iFadeDuration);
			}
		else {*/
			m_img.parentNode.replaceChild(m_imgPreload, m_img);
			m_imgPreload.removeCssClass("sizedimgx_fade");
			m_imgPreload.setStyle("visibility", "visible");
			m_img.setStyle("visibility", "hidden");
		//}
		var o = m_img;
		m_img = m_imgPreload;
		m_imgPreload = o;
		this.setAttribute("src", m_img.getAttribute("src"));
		this.setAttribute("alt", m_img.getAttribute("alt"));
	};


	// Restituisce l'elemento <img> con l'immagine attualmente visibile.
	//
	this.getImg = function() {
		return m_img;
	};


	return this;
};


// TableForm //////////////////////////////////////////////////////////////////
//
// Modulo asincrono per la manipolazione di tabelle di un database.
//
PaLeg.TableForm = function() {
	if (!this.elemName)
		// Va creato usando l'apposita classe sul server.
		return null;

	/* This is needed before PaLeg.Form's constructor is invoked, since that
	 * might call this._disableField, which needs this._m_arrFilters to be
	 * defined. */
	this._m_arrFilters = eval(this.getAttribute("id") + "_arrFilters");

	this.parentClass.constructor.call(this);
	this._m_frmItemSel_id     = $(this.getAttribute("id") + "ItemSel_id");
	this._m_oFieldOptsPopup   = PaLeg.createInplacePopup(Loc.PL_LOC_FORM_TITLE_FLDOPTS, this.getAttribute("id") + "_oFieldOptsPopup");
	this._m_chkEnableFilter   = $(this.getAttribute("id") + "_chkEnableFilter");
	this._m_sltFilterMode     = $(this.getAttribute("id") + "_sltFilterMode");
	this._m_oFilterValue      = $(this.getAttribute("id") + "_oFilterValue");
	this._m_sItemLabel        = this._m_frmItemSel_id.getInput().value;
	if (!this._m_oFilterValue.firstChild)
		this._m_oFilterValue.appendChild(document.createTextNode(""));
	this._m_arrFilters.forEach(function(arrFilter) {
		this.parentClass._disableField.call(this, arrFilter[0], true);
	}, this);
	this.iIDItem = this._m_frmItemSel_id.getValue().value;
	this.iIDItem = (this.iIDItem != "" ? parseInt(this.iIDItem) : null);


	// Crea i controlli per la creazione dei filtri.
	this.getDescendants("div", "class", "oLabel").forEach(function(oLabel) {
		// Salta i campi non appartenenti al PLForm PHP, e salta anche i campi
		// multipli, che non possono essere filtrati.
		var sFieldID = oLabel.getAttribute("id");
		sFieldID = sFieldID.substr(0, sFieldID.length - 7 /* "_oLabel".length */);
		var sFieldName = sFieldID.substr(this.getAttribute("id").length + 1),
			 onPopupFieldOptsClick = this._onPopupFieldOptsClick.bind(this);
		if (sFieldName in this._m_fieldDefs && this._m_fieldDefs[sFieldName][0 /*multmode*/] == PL_MM_SINGLE)
			oLabel.insertBefore(document.createSubtree(
				"a", {
					"class": "aPopupFieldOpts tinybtn",
					"id":    "aPopupFieldOpts_" + sFieldID,
					"href":  "#",

					"click": onPopupFieldOptsClick
				},
					"▼"
			), oLabel.firstChild);
	}, this);


	this._m_chkEnableFilter.addEventListener("click", this._onFilterChange.bind(this), false);
	this._m_sltFilterMode.addEventListener("change", this._onFilterChange.bind(this), false);
	$(this.getAttribute("id") + "_oTFNavBarClearFlt").getChildren("a")[0].addEventListener("click", this._onTFNavBarClearFltClick.bind(this), false);
	this.addEventListener("asyncupdate", this._onAsyncUpdate, false);
};
PaLeg.TableForm.inheritFrom(PaLeg.Form);
PaLeg.TableForm.prototype.iIDItem = null;
PaLeg.TableForm.prototype._m_arrFilters = null;
PaLeg.TableForm.prototype._m_chkEnableFilter = null;
PaLeg.TableForm.prototype._m_frmItemSel_id = null;
PaLeg.TableForm.prototype._m_oFieldOptsPopup = null;
PaLeg.TableForm.prototype._m_oFilterValue = null;
PaLeg.TableForm.prototype._m_sItemLabel = null;
PaLeg.TableForm.prototype._m_sltFilterMode = null;
PaLeg.TableForm.prototype._m_sOptsFieldName = null;


// Modifica un filtro per il modulo.
//
PaLeg.TableForm.prototype.setFormFilter = function(sFieldName, sMode, vValue /*= null*/) {
	if (this._m_fieldDefs[this._m_sOptsFieldName][0 /*multmode*/] != PL_MM_SINGLE)
		throw new Error("Can't set form filter for non-single field '" + sFieldName + "'");
	if (this.busy)
		return false;
	this.busy = true;
	PaLeg.asyncExec(
		this.getAttribute("action"),
		{
			"a":  "ar_setfilter_" + this.getAttribute("id"),
			"f":  sFieldName,
			"mv": String.jsonEncode([sMode, vValue === undefined ? null : vValue])
		},
		(function(bSuccess, oResult) {
			if (bSuccess) {
				this._m_frmItemSel_id.purgeDLCache();
				// Aggiorna l'interfaccia.
				var i = this._m_arrFilters.indexOfByProp(0, sFieldName);
				if (i != -1)
					this._m_arrFilters.removeAt(i);
				if (sMode !== null) {
					this._m_arrFilters.push([sFieldName, sMode, oResult["value"]]);
					this.parentClass._disableField.call(this, sFieldName, true);
				} else
					if (this._m_arrDisabledFields.indexOf(sFieldName) == -1)
						this.parentClass._disableField.call(this, sFieldName, false);
				$(this.getAttribute("id") + "_oTFNavBarClearFlt").setStyle("display", this._m_arrFilters.length ? "" : "none");
			}
			this.busy = false;
		}).bind(this)
	);
	return true;
};


// Standard toString().
//
PaLeg.TableForm.prototype.toString = function() {
	return "[object PaLeg.TableForm]";
};


/* See PaLeg.Form._disableField(). Also takes into account fields that are
 * currently being used as filters, so as to leave them disabled.
 */
PaLeg.TableForm.prototype._disableField = function(sName, bDisable) {
	if (this._m_arrFilters.indexOfByProp(0, sName) == -1)
		this.parentClass._disableField.call(this, sName, bDisable);
};


// Aggiorna la barra di navigazione.
//
PaLeg.TableForm.prototype._onAsyncUpdate = function(e) {
	if (this.isCssClass("defaultpageform")) {
		document.title = e.asyncResponse["itemlabel"] + document.title.substr(this._m_sItemLabel.length);
		this._m_sItemLabel = e.asyncResponse["itemlabel"];
	}
	this._m_frmItemSel_id.getInput().value = e.asyncResponse["itemlabel"];
	$(this.getAttribute("id") + "_oTFNavBarClone").setStyle("display", e.asyncResponse["itemactions"] & PL_FA_CLONE  ? "" : "none");
	$(this.getAttribute("id") + "_oTFNavBarDel"  ).setStyle("display", e.asyncResponse["itemactions"] & PL_FA_DELETE ? "" : "none");
};


// Attiva o disattiva un filtro.
//
PaLeg.TableForm.prototype._onFilterChange = function(e) {
	if (e.target == this._m_chkEnableFilter || this._m_chkEnableFilter.checked)
		this._m_oFieldOptsPopup.dismissPopup();
	if (this._m_chkEnableFilter.checked) {
		var vValue = this._fieldAction(this._m_sOptsFieldName, PaLeg.Form._getFieldCB);
		if (!this.setFormFilter(this._m_sOptsFieldName, this._m_sltFilterMode.value, vValue))
			e.preventDefault();
	} else
		if (!this.setFormFilter(this._m_sOptsFieldName, null))
			e.preventDefault();
};


// Apre lo pseudo-popup con le opzioni per il campo.
//
PaLeg.TableForm.prototype._onPopupFieldOptsClick = function(e) {
	e.preventDefault();
	this._m_sOptsFieldName = e.target.getAttribute("id").substr(16 /* "aPopupFieldOpts_".length */ + this.getAttribute("id").length + 1 /* "_".length */);
	var iFilter = this._m_arrFilters.indexOfByProp(0, this._m_sOptsFieldName);
	this._m_chkEnableFilter.checked = (iFilter != -1);
	this._m_sltFilterMode.value = (iFilter != -1 ? this._m_arrFilters[iFilter][1] : "eq");
	this._m_oFilterValue.firstChild.nodeValue = (iFilter != -1 ? this._m_arrFilters[iFilter][2] : " ");
	PaLeg.popupElement(this._m_oFieldOptsPopup, e.target, {
		dismissOnClickOut: true
	}, +1, +2);
};


// Elimina tutti i filtri.
//
PaLeg.TableForm.prototype._onTFNavBarClearFltClick = function(e) {
	e.preventDefault();
	if (this.busy)
		return false;
	this.busy = true;
	PaLeg.asyncExec(
		this.getAttribute("action"),
		{
			"a": "ar_clearfilters_" + this.getAttribute("id")
		},
		(function(bSuccess, oResult) {
			this._m_frmItemSel_id.purgeDLCache();
			if (bSuccess) {
				// Aggiorna l'interfaccia.
				this._m_arrFilters.forEach(function(arrFilter) {
					if (this._m_arrDisabledFields.indexOf(arrFilter[0]) == -1)
						this.parentClass._disableField.call(this, arrFilter[0], false);
				}, this);
				this._m_arrFilters = [];
				e.target.parentNode.setStyle("display", "none");
			}
			this.busy = false;
		}).bind(this)
	);
};


// TabSheet ///////////////////////////////////////////////////////////////////
//
// Gruppo di schede con linguette in una <dl class="tabsheet">.
//
PaLeg.TabSheet = function() {
	if (!this.elemName)
		return PaLeg.entangleElement(document.createSubtree(
			"dl", {
				"class": "pl_tabsheet"
			}
		), arguments.callee);

	this.items = Object.merge([], arguments.callee.Items);
	this.items.parentNode = document.createSubtree(
		"dd", {
			"class": "pl_tabsheet_oContainer"
		}
	);
	this.appendChild(this.items.parentNode);
};
PaLeg.TabSheet.prototype.items = null;
PaLeg.TabSheet.prototype.selectedIndex = -1;


// Classe astratta che definisce i metodi per l'elenco di schede.
//
PaLeg.TabSheet.Items = {

	parentNode: null,


	// Crea una nuova coppia linguetta/scheda.
	//
	create: function() {
		return {
			tab: document.createSubtree(
				"dt", {
					"class": "pl_tabsheet_oTab"
				}
			),
			sheet: document.createSubtree(
				"div", {
					"class": "pl_tabsheet_oSheet"
				}
			)
		};
	},


	// Aggiunge una scheda.
	//
	add: function(oItem, i /*= -1*/) {
		var ts = this.parentNode.parentNode;
		if (i === undefined || i == -1)
			i = this.length;
		else if (i < 0 || i > this.length)
			throw new /*Range*/Error("Sheet index out of bounds");
		oItem.tab.sheetIndex = i;
		oItem.sheet.sheetIndex = i;

		// Rende cliccabile il testo nella linguetta.
		var sTabHref = (oItem.tab.hasAttribute("id") ? "#" + oItem.tab.getAttribute("id") : "#");
		oItem.tab.getTextNodes(/\S/).forEach(function(tn) {
			if (!$._aaMethods.getAncestor.call(tn, "a")) {
				var aSelect = document.createSubtree(
					"a", {
						"href": sTabHref,

						"click": ts._onTabClick
					}
				);
				tn.parentNode.replaceChild(aSelect, tn);
				aSelect.appendChild(tn);
			}
		});

		oItem.sheet.setStyle("display", "none");

		// Inserisce linguetta, scheda ed elemento.
		ts.insertBefore(oItem.tab, i < this.length ? this[i].tab : this.parentNode);
		this.parentNode.insertBefore(oItem.sheet, i < this.length ? this[i].sheet : null);
		this.insertAt(i, oItem);

		if (this.length == 1)
			// Effettua la prima selezione.
			ts.select(0);
		return oItem;
	},


	// Aggiunge una scheda, con animazione.
	// Nota: l'effetto è gradevole solo se la linguetta viene inserita
	// nell'ultima posizione.
	//
	animAdd: function(oItem, i /*= -1*/, onComplete /*= null*/) {
		oItem.tab.setStyle("display", "none");
		this.add(oItem, i);
		new PaLeg.Animation(
			[
				[oItem.tab, "display", "", null],
				[oItem.tab, "opacity", null, ""]
			],
			[
				[oItem.tab, "opacity", 0.00, 1.00]
			],
			Math.UI_SHORT_DELAY,
			(function() {
				if (onComplete)
					onComplete(oItem);
			}).bind(this)
		);
		return oItem;
	},


	// Rimuove una scheda, con animazione.
	// Nota: l'effetto è gradevole solo se la linguetta rimossa è l'ultima.
	//
	animRemove: function(i, onComplete /*= null*/) {
		if (i < 0 || i >= this.length)
			throw new /*Range*/Error("Sheet index out of bounds");
		var ts = this.parentNode.parentNode;
		if (i == ts.selectedIndex)
			// Seleziona un'altra scheda, poiché l'animazione è solo per la
			// linguetta.
			ts.select(i < this.length - 1 ? i + 1 : i - 1);
		var oItem = this[i];
		new PaLeg.Animation(
			null,
			[
				[oItem.tab, "opacity", 1.00, 0.00]
			],
			Math.UI_SHORT_DELAY,
			(function() {
				this.remove(i);
				if (onComplete)
					onComplete(oItem);
			}).bind(this)
		);
		return oItem;
	},


	// Rimuove una scheda.
	//
	remove: function(i) {
		if (i < 0 || i >= this.length)
			throw new /*Range*/Error("Sheet index out of bounds");
		var ts = this.parentNode.parentNode;
		if (i == ts.selectedIndex)
			// Seleziona prima un'altra scheda.
			ts.select(i < this.length - 1 ? i + 1 : i - 1);
		var oItem = this.removeAt(i);
		ts.removeChild(oItem.tab);
		this.parentNode.removeChild(oItem.sheet);
		for (var j = i; j < this.length; ++j) {
			this[j].tab.sheetIndex = j;
			this[j].sheet.sheetIndex = j;
		}
		return oItem;
	},


	// Standard toString().
	//
	toString: function() {
		return "[object PaLeg.TabSheet.Items]";
	}
};


// Passa ad una determinata scheda.
//
PaLeg.TabSheet.prototype.select = function(i) {
	if (i != this.selectedIndex) {
		if (i >= 0 && i < this.items.length) {
			var oItem = this.items[i];
			oItem.tab.addCssClass("pl_tabsheet_oSelTab");
			oItem.sheet.removeStyle("display");
		}
		if (this.selectedIndex >= 0 && this.selectedIndex < this.items.length) {
			var oItem = this.items[this.selectedIndex];
			oItem.tab.removeCssClass("pl_tabsheet_oSelTab");
			oItem.sheet.setStyle("display", "none");
		}
		this.selectedIndex = i;
	}
};


// Standard toString().
//
PaLeg.TabSheet.prototype.toString = function() {
	return "[object PaLeg.TabSheet]";
};


// Cambia scheda cliccando sulla linguetta.
//
PaLeg.TabSheet.prototype._onTabClick = function(e) {
	if (this.getAttribute("href") == "#")
		e.preventDefault();
	var tab = this.getAncestor("dt");
	tab.parentNode.select(tab.sheetIndex);
};


// Struttura ad albero in una <ul class="treeview">.
//
PaLeg.TreeView = function() {
	// Espande/comprime un nodo di treeview.
	//
	var onToggleNodeClick = function(e) {
		e.preventDefault();
		var treenode = this.parentNode.treenode;
		if (treenode.expanded)
			treenode.collapse();
		else
			treenode.expand();
	};


	// Nodo simil-DOM della struttura.
	//
	function treenode(tv, nodeExisting) {
		var m_ulSubItems,
			 m_img    = document.createElement("img"),
			 m_aLabel = document.createElement("a");
		m_img.setAttribute("class", "treeview_pm");
		m_img.setAttribute("src",   location.RROOTDIR + "gfx/treel_p.gif");
		m_img.setAttribute("alt",   "[+]");
		m_aLabel.setAttribute("href",  "#");
		m_aLabel.setAttribute("title", Loc.PL_LOC_TREE_EXPAND);
		m_aLabel.setStyle("visibility", "hidden");
		m_aLabel.addEventListener("click", onToggleNodeClick, false);
		m_aLabel.appendChild(m_img);
		// Questa serve solo per la durata di questo costruttore; dopo va
		// annullata perché bisogna seguire la procedura per l'aggiunta
		// corretta.
		this.tv = tv;
		this.firstChild = null;
		this.lastChild = null;
		this.parentNode = null;
		this.previousSibling = null;
		this.nextSibling = null;
		this.expanded = false;


		this.setLabel = function(vLabel) {
			// Rimuove tutto ciò che segue m_aLabel, incluso m_ulSubItems.
			var o;
			while (o = m_aLabel.nextSibling)
				this.item.removeChild(o);
			PaLeg.appendChildAny(this.item, vLabel, "li");
			this.item.appendChild(m_ulSubItems);
		};


		this.appendChild = function(child) {
			if (child.parentNode)
				child.parentNode.removeChild(child);
			else if (child.tv)
				child.tv.nodes.remove(child);
			// Collega child a this e tv.
			child.parentNode = this;
			child.tv = this.tv;
			// Collega child ai suoi fratelli.
			child.previousSibling = this.lastChild;
			// Collega this a child.
			this.lastChild = child;
			if (!this.firstChild) {
				this.firstChild = child;
				m_aLabel.removeStyle("visibility");
			}
			// Aggiorna la struttura DOM.
			// Questo controllo serve per quando questo metodo viene chiamato
			// dal codice poco sotto, per non inserire come figlio di
			// m_ulSubItems un nodo che è già figlio di m_ulSubItems.
			if (child.item.parentNode != m_ulSubItems)
				m_ulSubItems.appendChild(child.item);
			return true;
		};


		this.removeChild = function(child) {
			if (child.parentNode != this || child.tv != this.tv)
				return false;
			// Scollega child da this e tv.
			child.parentNode = null;
			child.tv = null;
			// Scollega this da child.
			if (this.firstChild == child)
				this.firstChild = child.nextSibling;
			if (this.lastChild == child)
				this.lastChild = child.previousSibling;
			// Scollega child dai suoi fratelli.
			if (child.previousSibling)
				child.previousSibling.nextSibling = child.nextSibling;
			child.previousSibling = null;
			if (child.nextSibling)
				child.nextSibling.previousSibling = child.previousSibling;
			child.nextSibling = null;
			// Aggiorna la struttura DOM.
			m_ulSubItems.removeChild(child.item);
			if (!this.firstChild) {
				this.collapse();
				m_aLabel.setStyle("visibility", "hidden");
			}
			return true;
		};


		this.hasChildNodes = function() {
			return this.firstChild != null;
		};


		this.expand = function() {
			if (!this.expanded) {
				m_img.setAttribute("src", location.RROOTDIR + "gfx/treel_m.gif");
				m_img.setAttribute("alt", "[-]");
				m_aLabel.setAttribute("title", Loc.PL_LOC_TREE_COLLAPSE);
				m_ulSubItems.removeStyle("display");
				this.expanded = true;
			}
		};


		this.collapse = function() {
			if (this.expanded) {
				m_img.setAttribute("src", location.RROOTDIR + "gfx/treel_p.gif");
				m_img.setAttribute("alt", "[+]");
				m_aLabel.setAttribute("title", Loc.PL_LOC_TREE_EXPAND);
				m_ulSubItems.setStyle("display", "none");
				this.expanded = false;
			}
		};


		if (nodeExisting) {
			this.item = $(nodeExisting);
			// Trova l'<ul> dei figli e fa sparire tutto ciò che lo segue.
			for (var o = nodeExisting.firstChild, oNext; o; o = oNext) {
				oNext = o.nextSibling;
				if (m_ulSubItems)
					nodeExisting.removeChild(o);
				else if (o.nodeType == document.ELEMENT_NODE && $eN(o).elemName == "ul")
					m_ulSubItems = $(o);
			}
			if (m_ulSubItems)
				// Inserisce i figli nella struttura, ed elimina tutto il
				// resto.
				for (var o = m_ulSubItems.firstChild, oNext; o; o = oNext) {
					oNext = o.nextSibling;
					if (o.nodeType == document.ELEMENT_NODE && $eN(o).elemName == "li")
						this.appendChild(new treenode(tv, o));
					else
						m_ulSubItems.removeChild(o);
				}
		} else
			this.item = document.createElement("li");
		this.item.treenode = this;
		if (!m_ulSubItems) {
			m_ulSubItems = document.createElement("ul");
			this.item.appendChild(m_ulSubItems);
		}
		m_ulSubItems.setStyle("display", "none");
		this.item.insertBefore(m_aLabel, this.item.firstChild);

		// Mostra la selezione iniziale.
		if (nodeExisting && nodeExisting.isCssClass("selected")) {
			nodeExisting.removeCssClass("selected");
			this.expand();
		}
		// Ora appendChild() è stato chiamato, quindi aspetta di essere
		// collegato da fuori.
		this.tv = null;
	}


	if (!this.elemName)
		// Costruttore.
		//
		return (function() {
			var ul = document.createElement("ul");
			ul.setAttribute("class", "treeview");
			return PaLeg.TreeView.call(ul);
		})();

	var m_tv = this;
	this.nodes = {
		first: null,
		last: null,


		create: function(vLabel) {
			var node = new treenode(m_tv);
			if (vLabel)
				node.setLabel(vLabel);
			return node;
		},


		append: function(node) {
			if (node.parentNode)
				node.parentNode.removeChild(node);
			else if (node.tv)
				node.tv.nodes.remove(node);
			// Collega node a m_tv.
			node.tv = m_tv;
			// Collega node ai suoi fratelli.
			node.previousSibling = this.last;
			// Collega this a node.
			this.last = node;
			if (!this.first)
				this.first = node;
			// Aggiorna la struttura DOM.
			// Questo controllo serve per quando questo metodo viene chiamato
			// dal codice poco sotto, per non inserire come figlio di m_tv un
			// nodo che è già figlio di m_tv.
			if (node.item.parentNode != m_tv)
				m_tv.appendChild(node.item);
		},


		remove: function(node) {
			if (node.parentNode || node.tv != m_tv)
				return false;
			// Scollega node da m_tv.
			node.tv = null;
			// Scollega this da node.
			if (this.first == node)
				this.first = node.nextSibling;
			if (this.last == node)
				this.last = node.previousSibling;
			// Scollega this dai suoi fratelli.
			if (node.previousSibling)
				node.previousSibling.nextSibling = node.nextSibling;
			node.previousSibling = null;
			if (node.nextSibling)
				node.nextSibling.previousSibling = node.previousSibling;
			node.nextSibling = null;
			// Aggiorna la struttura DOM.
			m_tv.removeChild(node.item);
			return true;
		}
	};


	for (var o = this.firstChild, oNext; o; o = oNext) {
		oNext = o.nextSibling;
		if (o.nodeType == document.ELEMENT_NODE && $eN(o).elemName == "li")
			this.nodes.append(new treenode(this, o));
		else
			this.removeChild(o);
	}


	return this;
};


// ZoomableImage //////////////////////////////////////////////////////////////
//
// <img> con zoom al volo.
//
PaLeg.ZoomableImage = function() {
	if (!this.elemName)
		return PaLeg.entangleElement(document.createSubtree(
			"img", {
				"class": "zoomable"
			}
		), arguments.callee);

	this._m_bLoadScaledEnabled = ("scale" in this.getAttribute("src").urlSplitQA());
	this._m_imgPreloadScaled = document.createElement("img");
	this._m_imgPreloadScaled.setStyle("display", "none");
	this.parentNode.insertBefore(this._m_imgPreloadScaled, this);
	this._m_oToolBar = this.parentNode.insertBefore(document.createSubtree(
		"div", {
			"class": "zoomableimage_toolbar"
		},
			["a", {
				"href":  "#",
				"title": Loc.PL_LOC_ZOOM_REALSIZE,

				"click": this._onRealSizeClick.bind(this)
			},
				["small", null,
					"1:1"
				]
			],
			["a", {
				"href":  "#",
				"title": Loc.PL_LOC_ZOOM_FIT,

				"click": this._onShrinkToFitClick.bind(this)
			},
				"↔"
			],
			["a", {
				"href":  "#",
				"title": Loc.PL_LOC_ZOOM_ENLARGE,

				"click": this._onEnlargeClick.bind(this)
			},
				"+"
			],
			["a", {
				"href":  "#",
				"title": Loc.PL_LOC_ZOOM_SHRINK,

				"click": this._onShrinkClick.bind(this)
			},
				"−"
			],
			["a", {
				"href":  "#",
				"title": Loc.PL_LOC_ZOOM_HIDETOOLBAR,

				"click": this._onHideToolBarClick.bind(this)
			},
				"×"
			]
	), this);

	this.addEventListener("load",      this._onLoad,             false);
	this.addEventListener("mouseover", this._onToolBarMouseOver, false);
	this.addEventListener("mouseout",  this._onToolBarMouseOut,  false);
	window.setTimeout(this._raiseImgResize.bind(this), Math.MIN_TIMEOUT);
	this._m_imgPreloadScaled.addEventListener("load", this._onPreloadLoad.bind(this), false);
};
PaLeg.ZoomableImage.prototype.srcWidth = null;
PaLeg.ZoomableImage.prototype.srcHeight = null;
PaLeg.ZoomableImage.prototype._m_bLoadScaledEnabled = false;
PaLeg.ZoomableImage.prototype._m_fScale = 1.0;
PaLeg.ZoomableImage.prototype._m_imgPreloadScaled = null;

// Informa che le dimensioni dell'immagine (cioè la sua scala) sono cambiate.
//
PaLeg.ZoomableImage.declareEvent("imgresize");


// Restituisce la scala di rimensionamento.
//
PaLeg.ZoomableImage.prototype.getScale = function() {
	return this._m_fScale;
};


// Cambia la scala di rimensionamento.
//
PaLeg.ZoomableImage.prototype.setScale = function(fScale, bLoadScaled /*= undefined*/) {
	if (this._m_fScale != fScale) {
		this._m_fScale = fScale;
		this.setStyle("width",  Math.round(this.srcWidth  * fScale) + "px");
		this.setStyle("height", Math.round(this.srcHeight * fScale) + "px");
		this._raiseImgResize();
		if (bLoadScaled !== undefined)
			this._m_bLoadScaledEnabled = bLoadScaled;
		if (this._m_bLoadScaledEnabled)
			this._preloadScaled();
	}
};


// Standard toString().
//
PaLeg.ZoomableImage.prototype.toString = function() {
	return "[object PaLeg.ZoomableImage]";
};


// Ingrandisce l'immagine.
//
PaLeg.ZoomableImage.prototype._onEnlargeClick = function(e) {
	e.preventDefault();
	this.setScale(Math.min(this._m_fScale / 0.75, 1.0));
};


// Nasconde la barra degli strumenti.
//
PaLeg.ZoomableImage.prototype._onHideToolBarClick = function(e) {
	e.preventDefault();
	this._m_oToolBar.setStyle("visibility", "hidden");
	e.target.blur();
};


// Al caricamento di una nuova immagine, avvia il caricamento di una nuova se
// nel frattempo è cambiata la scala.
//
PaLeg.ZoomableImage.prototype._onLoad = function(e) {
	if (this._m_bLoadScaledEnabled && this.getAttribute("src").urlSplitQA()["scale"] != this._m_fScale)
		this._preloadScaled();
};


// Al caricamento di una immagine ridimensionata, cambia anche quella visibile.
//
PaLeg.ZoomableImage.prototype._onPreloadLoad = function(e) {
	if (this._m_bLoadScaledEnabled && this._m_imgPreloadScaled.getAttribute("src").urlSplitQA()["scale"] == this._m_fScale)
		this.setAttribute("src", this._m_imgPreloadScaled.getAttribute("src"));
}


// Annulla l'ingrandimento.
//
PaLeg.ZoomableImage.prototype._onRealSizeClick = function(e) {
	e.preventDefault();
	this.setScale(1.0);
	e.target.blur();
};


// Rimpicciolisce l'immagine.
//
PaLeg.ZoomableImage.prototype._onShrinkClick = function(e) {
	e.preventDefault();
	this.setScale(Math.max(this._m_fScale * 0.75, 0.1));
};


// Ingrandisce l'immagine al massimo visualizzabile.
//
PaLeg.ZoomableImage.prototype._onShrinkToFitClick = function(e) {
	// Cambia al volo la destinazione del link, per scorrere la finestra come
	// necessario.
	if (!this.hasAttribute("id"))
		this.setAttribute("id", "pl_zoomableimage_" + (new Date).getTime());
	e.target.setAttribute("href", "#" + this.getAttribute("id"));

	window.setTimeout((function() {
		var xyThis = this.getPosition(document.getBody());
		this.setScale(Math.min((document.documentElement.clientWidth - xyThis[0]) / this.srcWidth, document.documentElement.clientHeight / this.srcHeight, 1.0));
	}).bind(this), Math.MIN_TIMEOUT);
};


// Nasconde la barra degli strumenti, se opportuno.
//
PaLeg.ZoomableImage.prototype._onToolBarMouseOut = function(e) {
	if (this._m_oToolBar.getStyle("display") == "block" && !(e.relatedTarget && (e.relatedTarget == this._m_oToolBar || e.relatedTarget.parentNode == this._m_oToolBar))) {
		this._m_oToolBar.removeStyle("display");
		this._m_oToolBar.removeStyle("visibility");
	}
};


// Mostra la barra degli strumenti.
//
PaLeg.ZoomableImage.prototype._onToolBarMouseOver = function(e) {
	if (this._m_oToolBar.getStyle("display") != "block") {
		this._m_oToolBar.setStyle("display", "block");
		var arrPos = this.getPosition(document.getBody());
		this._m_oToolBar.setStyle("left", arrPos[0] + "px");
		this._m_oToolBar.setStyle("top",  arrPos[1] + "px");
	}
};


// Avvia il caricamento dell'immagine con la scala appropriata.
//
PaLeg.ZoomableImage.prototype._preloadScaled = function() {
	if (arguments.callee.idTimeout)
		window.clearTimeout(arguments.callee.idTimeout);
	arguments.callee.idTimeout = window.setTimeout((function() {
		this._m_imgPreloadScaled.setAttribute("src", this.getAttribute("src").urlReplaceQA("scale", this._m_fScale));
	}).bind(this), Math.UI_MEDIUM_DELAY);
};


// Genera l'evento di notifica del ridimensionamento completato.
//
PaLeg.ZoomableImage.prototype._raiseImgResize = function() {
	var e = document.createEvent("HTMLEvents");
	e.initEvent("imgresize", true, false);
	this.dispatchEvent(e);
};



/*****************************************************************************
 * FUNCTIONS
 */


// Attiva le funzionalità PaLeg per la pagina.
//
PaLeg.addModuleInit(function() {
	// Applica a tutte le <div class="pl_aslisth">.
	document.getDescendants("div", "class", "pl_aslisth").forEach(function(div) {
		var tr = div.getChildren("table")[0].tBodies[0].rows[0],
			 o,
			 arrItems = [];
		while (o = tr.firstChild) {
			if (o.nodeType == document.ELEMENT_NODE && $eN(o).elemName == "td")
				arrItems[arrItems.length] = o;
			tr.removeChild(o);
		}
		PaLeg.entangleElement(div, PaLeg.AutoScrollerListH);
		for (var i = 0; i < arrItems.length; ++i)
			div.items.add(arrItems[i]);
	});


	// Applica a tutte le <div class="autoscrollerlistv">.
	document.getDescendants("div", "class", "autoscrollerlistv").forEach(function(div) {
		var arrOldItems = [];
		var container = div.getChildren("div")[0], o;
		// Fa pulizia: elimina tutto, mettendo da parte i tag utili.
		while (o = container.firstChild) {
			if (o.nodeType == document.ELEMENT_NODE && $eN(o).elemName == "div")
				arrOldItems.push(o);
			container.removeChild(o);
		}
		PaLeg.entangleElement(div, PaLeg.AutoScrollerListV);
		arrOldItems.forEach(function(o) {
			div.items.add(o);
		});
	});


	// Applica a tutti gli <input type="text" class="calendar">.
	document.getDescendants("input", "class", "calendar").forEach(function(input) {
		PaLeg.entangleElement(input, PaLeg.Calendar);
	});


	// Applica a tutte le <table class="pl_colul">.
	document.getDescendants("table", "class", "pl_colul").forEach(function(table) {
		$(table.tHead);
		$(table.tFoot);
		$(table.tBodies[0]);
		Array.forEach(table.rows, function(tr) {
 			Array.forEach($(tr).cells, $);
		});
		PaLeg.entangleElement(table, PaLeg.ColUL);
	});


	// Applica a tutti i <textarea class="plt_formatted">.
	document.getDescendants("textarea", "class", "plt_formatted").forEach(function(textarea) {
		PaLeg.entangleElement(textarea, PaLeg.FormattedInput);
	});


	// Applica a tutti gli <table class="graphh">.
	document.getDescendants("table", "class", "graphh").forEach(function(table) {
		PaLeg.entangleElement(table, PaLeg.BarGraphH);
	});


	// Applica a tutti gli <table class="graphv">.
	document.getDescendants("table", "class", "graphv").forEach(function(table) {
		PaLeg.entangleElement(table, PaLeg.BarGraphV);
	});


	// Applica a tutti gli <div class="pl_isinput">.
	document.getDescendants("div", "class", "pl_isinput").forEach(function(div) {
		PaLeg.entangleElement(div, PaLeg.IncrSearchInput);
	});


	// Applica a tutti gli <div class="pl_pwsecchk">.
	document.getDescendants("div", "class", "pl_pwsecchk").forEach(function(div) {
		PaLeg.entangleElement(div, PaLeg.PwSecurityChecker);
	});


	// Applica a tutte le <table class="sizedimg"><tr><td><img>.
	document.getDescendants("table", "class", "sizedimg").forEach(function(table) {
		PaLeg.entangleElement(table, PaLeg.SizedImg);
	});


	// Applica a tutte le <dl class="pl_tabsheet">.
	document.getDescendants("dl", "class", "pl_tabsheet").forEach(function(dl) {
		var arrItems = [],
			 i = 0,
			 iSelTab = 0,
			 sSelID = (location.hash != "" ? location.hash.substr(1) : null),
			 o;
		// Appaia linguette e schede, mentre elimina tutto il resto.
		while (o = dl.firstChild) {
			if (o.nodeType == document.ELEMENT_NODE)
				switch ($(o).elemName) {
					case "dt":
						// Se l'ID è quello giusto, la scheda sarà selezionata.
						if (o.hasAttribute("id") && o.getAttribute("id") == sSelID)
							iSelTab = arrItems.length;
						o.addCssClass("pl_tabsheet_oTab");
						o.removeCssClass("pl_tabsheet_tab");
						arrItems.push({
							tab:   o,
							sheet: null
						});
						break;
					case "dd":
						// "Converte" il <dd> in <div>.
						var div = document.createElement("div");
						div.setAttribute("class", o.getAttribute("class") + " pl_tabsheet_oSheet");
						div.removeCssClass("pl_tabsheet_sheet");
						if (o.hasAttribute("id"))
							div.setAttribute("id", o.getAttribute("id"));
						if (o.style.cssText != "")
							div.style.cssText = o.style.cssText;
						while (o.firstChild)
							div.appendChild(o.firstChild);
						arrItems[i++].sheet = div;
						break;
				}
			dl.removeChild(o);
		}
		PaLeg.entangleElement(dl, PaLeg.TabSheet);
		for (i = 0; i < arrItems.length; ++i)
			dl.items.add(arrItems[i]);
		if (arrItems.length)
			dl.select(iSelTab);
	});


	// Applica a tutte le <ul class="treeview">.
	document.getDescendants("ul", "class", "treeview").forEach(function(ul) {
		PaLeg.entangleElement(ul, PaLeg.TreeView);
	});


	// Applica a tutte le <img class="zoomable">.
	document.getDescendants("img", "class", /zoomable(_\d+_\d+)?/).forEach(function(img) {
		img.parentNode.parentNode.replaceChild(img, img.parentNode);
		var cxSrc = img.offsetWidth,
			 cySrc = img.offsetHeight,
			 cxyRealSize = img.getAttribute("class").match(/zoomable_(\d+)_(\d+)/),
			 fScale = 1.0;
		cxyRealSize.shift();
		if (cxyRealSize.length == 2 && (cxSrc != cxyRealSize[0] || cySrc != cxyRealSize[1])) {
			cxyRealSize[0] = parseInt(cxyRealSize[0]);
			cxyRealSize[1] = parseInt(cxyRealSize[1]);
			fScale = ((cxSrc / cxyRealSize[0]) + (cySrc / cxyRealSize[1])) / 2;
			cxSrc  /= fScale;
			cySrc /= fScale;
		}
		PaLeg.entangleElement(img, PaLeg.ZoomableImage);
		img.srcWidth  = cxSrc;
		img.srcHeight = cySrc;
		img._m_fScale = fScale;
	});


if (location.search.indexOf("nojsforms") == -1)
	// Applica a tutti i <form class="... pl_form">.
	document.getDescendants("form", "class", "pl_form").forEach(function(form) {
		if (form.getAttribute("class").match(/pl_form$/))
			PaLeg.entangleElement(form, PaLeg.Form);
	});


if (location.search.indexOf("nojsforms") == -1)
	// Applica a tutti i <form class="... pl_tableform">.
	document.getDescendants("form", "class", "pl_tableform").forEach(function(form) {
		if (form.getAttribute("class").match(/pl_tableform$/))
			PaLeg.entangleElement(form, PaLeg.TableForm);
	});
});

