1 /*
  2  * ***** BEGIN LICENSE BLOCK *****
  3  * Zimbra Collaboration Suite Web Client
  4  * Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016 Synacor, Inc.
  5  *
  6  * The contents of this file are subject to the Common Public Attribution License Version 1.0 (the "License");
  7  * you may not use this file except in compliance with the License.
  8  * You may obtain a copy of the License at: https://www.zimbra.com/license
  9  * The License is based on the Mozilla Public License Version 1.1 but Sections 14 and 15
 10  * have been added to cover use of software over a computer network and provide for limited attribution
 11  * for the Original Developer. In addition, Exhibit A has been modified to be consistent with Exhibit B.
 12  *
 13  * Software distributed under the License is distributed on an "AS IS" basis,
 14  * WITHOUT WARRANTY OF ANY KIND, either express or implied.
 15  * See the License for the specific language governing rights and limitations under the License.
 16  * The Original Code is Zimbra Open Source Web Client.
 17  * The Initial Developer of the Original Code is Zimbra, Inc.  All rights to the Original Code were
 18  * transferred by Zimbra, Inc. to Synacor, Inc. on September 14, 2015.
 19  *
 20  * All portions of the code are Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016 Synacor, Inc. All Rights Reserved.
 21  * ***** END LICENSE BLOCK *****
 22  */
 23 
 24 /**
 25  * Creates a list view.
 26  * @constructor
 27  * @class
 28  * A list view presents a list of items as rows with fields (columns).
 29  * 
 30  * @author Parag Shah
 31  * @author Conrad Damon
 32  * 
 33  * @param {hash}	params		a hash of parameters
 34  * @param  {DwtComposite}     parent		the parent widget
 35  * @param {string}	className		the CSS class
 36  * @param {constant}	posStyle		the positioning style (see {@link DwtControl})
 37  * @param  {array}	headerList	a list of IDs for columns
 38  * @param {boolean}	noMaximize	if <code>true</code>, all columns are fixed-width (otherwise, one will
 39  * 											expand to fill available space)
 40  * @param  {constant}     view			the ID of view
 41  * 
 42  * @extends		DwtComposite
 43  */
 44 DwtListView = function(params) {
 45 	if (arguments.length == 0) { return; }
 46 	params = Dwt.getParams(arguments, DwtListView.PARAMS);
 47 	params.className = params.className || "DwtListView";
 48 	DwtComposite.call(this, params);
 49 
 50 	this._view = params.view || Dwt.getNextId();
 51 	if (params.headerList) {
 52 		var htmlElement = this.getHtmlElement();
 53 
 54         var html = new Array(50);
 55         var idx = 0;
 56         var headId = Dwt.getNextId();
 57         var colId = Dwt.getNextId();
 58         html[idx++] = "<table width='100%'><tr><td ";
 59         html[idx++] = "id=" + headId;
 60         html[idx++] = "></td></tr><tr><td ";
 61         html[idx++] = "id=" + colId;
 62         html[idx++] = "></td></tr></table>";
 63         htmlElement.innerHTML = html.join("");
 64 
 65         var headHtml = document.getElementById(headId);
 66         this._listColDiv = document.createElement("div");
 67         this._listColDiv.id = DwtId.getListViewId(this._view, DwtId.LIST_VIEW_HEADERS);
 68         headHtml.appendChild(this._listColDiv);
 69 
 70         var colHtml = document.getElementById(colId);
 71         this._listDiv = this.useListElement() ? document.createElement("ul") : document.createElement("div");
 72         this._listDiv.id = DwtId.getListViewId(this._view, DwtId.LIST_VIEW_ROWS);
 73         this._listDiv.className = "DwtListView-Rows";
 74         colHtml.appendChild(this._listDiv);
 75 
 76 		// setup vars needed for sorting
 77 		this._bSortAsc = false;
 78 		this._currentColId = null;
 79 		this.sortingEnabled = true;
 80 	} else {
 81 		this._listDiv = document.getElementById(params.id);
 82 		this.setScrollStyle(DwtControl.SCROLL); // auto scroll
 83 	}
 84 
 85 	this._setMouseEventHdlrs();
 86 	
 87 	this._listenerMouseOver = this._mouseOverListener.bind(this);
 88 	this._listenerMouseOut = this._mouseOutListener.bind(this);
 89 	this._listenerMouseDown = this._mouseDownListener.bind(this);
 90 	this._listenerMouseUp = this._mouseUpListener.bind(this);
 91 	this._listenerMouseMove = this._mouseMoveListener.bind(this);
 92 	this._listenerDoubleClick = this._doubleClickListener.bind(this);
 93 	this.addListener(DwtEvent.ONMOUSEOVER, this._listenerMouseOver);
 94 	this.addListener(DwtEvent.ONMOUSEOUT, this._listenerMouseOut);
 95 	this.addListener(DwtEvent.ONMOUSEDOWN, this._listenerMouseDown);
 96 	this.addListener(DwtEvent.ONMOUSEUP, this._listenerMouseUp);
 97 	this.addListener(DwtEvent.ONMOUSEMOVE, this._listenerMouseMove);
 98 	this.addListener(DwtEvent.ONDBLCLICK, this._listenerDoubleClick);
 99 
100 	this._evtMgr = new AjxEventMgr();
101 	this._selectedItems = new AjxVector();
102 	this._selAnchor = null; 
103 	this._kbAnchor = null; 
104 	this._selEv = new DwtSelectionEvent(true);
105 	this._actionEv = new DwtListViewActionEvent(true);
106 	this._stateChangeEv = new DwtEvent(true);
107 	this._headerList = params.headerList;
108 	this._noMaximize = params.noMaximize;
109 	if (this._headerList) {
110 		this._parentEl = this._listDiv;
111 	} else {
112 		this._parentEl = this.getHtmlElement();
113 		if (this.useListElement()) {
114 			//insert unordered list element
115 			var ul = document.createElement("ul");
116 			ul.className = "DwtListView-Rows";
117 			this._parentEl.appendChild(ul);
118 			this._parentEl = ul;
119 		}
120 	}
121     this._parentEl.tabIndex = 0;
122 
123     this._list = null;
124 	this.offset = 0;
125 	this.headerColCreated = false;
126 	this.setMultiSelect(true);
127 	this.firstSelIndex = -1;
128 
129 	// the key is the HTML ID of the item's associated DIV; the value is an object
130 	// with information about that row
131 	this._data = {};
132 
133     // item classes
134     this._rowClass = [ this._className, DwtListView.ROW_CLASS ].join("");
135 	var nc = this._normalClass = DwtListView.ROW_CLASS;
136 	this._selectedClass = [nc, DwtCssStyle.SELECTED].join("-");
137 	this._viewedButUnselectedClass = [nc, DwtCssStyle.ALT_SELECTED].join("-");
138 	this._disabledSelectedClass = [this._selectedClass, DwtCssStyle.DISABLED].join("-");
139 	this._kbFocusClass = [nc, DwtCssStyle.FOCUSED].join("-");
140 	this._dndClass = [nc, DwtCssStyle.DRAG_PROXY].join("-");
141 	this._rightClickClass = [this._selectedClass, DwtCssStyle.ACTIONED].join("-");
142 
143     this._styleRe = this._getStyleRegex();
144 };
145 
146 DwtListView.prototype = new DwtComposite;
147 DwtListView.prototype.constructor = DwtListView;
148 
149 DwtListView.prototype.isDwtListView = true;
150 DwtListView.prototype.toString = function() { return "DwtListView"; };
151 
152 DwtListView.prototype.role = 'list';
153 DwtListView.prototype.itemRole = 'listitem';
154 
155 // Consts
156 
157 DwtListView.PARAMS					= ["parent", "className", "posStyle", "headerList", "noMaximize"];
158 DwtListView.ITEM_SELECTED 			= 1;
159 DwtListView.ITEM_DESELECTED 		= 2;
160 DwtListView.ITEM_DBL_CLICKED 		= 3;
161 DwtListView._LAST_REASON 			= 3;
162 DwtListView._TOOLTIP_DELAY 			= 250;
163 DwtListView.HEADERITEM_HEIGHT 		= 24;
164 DwtListView.TYPE_HEADER_ITEM 		= "1";
165 DwtListView.TYPE_LIST_ITEM 			= "2";
166 DwtListView.TYPE_HEADER_SASH 		= "3";
167 DwtListView.DEFAULT_LIMIT			= 25;
168 DwtListView.MAX_REPLENISH_THRESHOLD	= 10;
169 DwtListView.MIN_COLUMN_WIDTH		= 20;
170 DwtListView.COL_MOVE_THRESHOLD		= 3;
171 DwtListView.ROW_CLASS				= "Row";
172 DwtListView.ROW_CLASS_ODD			= "RowEven";
173 DwtListView.ROW_CLASS_EVEN			= "RowOdd";
174 
175 // property names for row DIV to store styles
176 DwtListView._STYLE_CLASS				= "_sc";
177 DwtListView._SELECTED_STYLE_CLASS		= "_ssc";
178 DwtListView._SELECTED_DIS_STYLE_CLASS	= "_sdsc";
179 DwtListView._KBFOCUS_CLASS				= "_kfc";
180 
181 
182 // Public methods
183 
184 
185 DwtListView.prototype.dispose =
186 function() {
187 	this._listColDiv = null;
188 	this._listDiv = null;
189 	this._parentEl = null;
190 	this._clickDiv = null;
191 	this._selectedItems = null;
192 	DwtComposite.prototype.dispose.call(this);
193 };
194 
195 /**
196  * Sets the enabled flag.
197  * 
198  * @param	{boolean}	enabled		if <code>true</code>, enable the list view
199  */
200 DwtListView.prototype.setEnabled =
201 function(enabled) {
202 	DwtComposite.prototype.setEnabled.call(this, enabled);
203 	// always remove listeners to avoid adding listeners multiple times
204 	this.removeListener(DwtEvent.ONMOUSEOVER, this._listenerMouseOver);
205 	this.removeListener(DwtEvent.ONMOUSEOUT, this._listenerMouseOut);
206 	this.removeListener(DwtEvent.ONMOUSEDOWN, this._listenerMouseDown);
207 	this.removeListener(DwtEvent.ONMOUSEUP, this._listenerMouseUp);
208 	this.removeListener(DwtEvent.ONMOUSEMOVE, this._listenerMouseMove);
209 	this.removeListener(DwtEvent.ONDBLCLICK, this._listenerDoubleClick);
210 	// now re-add listeners, if needed
211 	if (enabled) {
212 		this.addListener(DwtEvent.ONMOUSEOVER, this._listenerMouseOver);
213 		this.addListener(DwtEvent.ONMOUSEOUT, this._listenerMouseOut);
214 		this.addListener(DwtEvent.ONMOUSEDOWN, this._listenerMouseDown);
215 		this.addListener(DwtEvent.ONMOUSEUP, this._listenerMouseUp);
216 		this.addListener(DwtEvent.ONMOUSEMOVE, this._listenerMouseMove);
217 		this.addListener(DwtEvent.ONDBLCLICK, this._listenerDoubleClick);
218 	}
219 	// modify selection classes
220 	var selection = this.getSelectedItems();
221 	if (selection) {
222 		var elements = selection.getArray();
223         for (var i = 0; i < elements.length; i++) {
224             Dwt.delClass(elements[i], this._styleRe, enabled ? this._selectedClass : this._disabledSelectedClass);
225 		}
226 	}
227 };
228 
229 DwtListView.prototype.createHeaderHtml =
230 function(defaultColumnSort, isColumnHeaderTableFixed) {
231 	// does this list view have headers or have they already been created?
232 	if (!this._headerList || this.headerColCreated) { return; }
233 
234 	this._headerHash = {};
235 	this._headerIdHash = {};
236 
237 	var idx = 0;
238 	var htmlArr = [];
239 
240 	htmlArr[idx++] = "<table id='";
241 	htmlArr[idx++] = DwtId.getListViewHdrId(DwtId.WIDGET_HDR_TABLE, this._view);
242 	htmlArr[idx++] = "' height=100%";
243 	htmlArr[idx++] = this._noMaximize ? ">" : " width=100%>";
244 	htmlArr[idx++] = "<tr>";
245 
246 	var numCols = this._headerList.length;
247 	for (var i = 0; i < numCols; i++) {
248 		var headerCol = this._headerList[i];
249 		var field = headerCol._field;
250 		headerCol._index = i;
251 		var id = headerCol._id = DwtId.getListViewHdrId(DwtId.WIDGET_HDR, this._view, field);
252 
253 		this._headerHash[field] = headerCol;
254 		this._headerIdHash[id] = headerCol;
255 
256 		if (headerCol._variable) {
257 			this._variableHeaderCol = headerCol;
258 		}
259 
260 		if (headerCol._visible) {
261 			idx = this._createHeader(htmlArr, idx, headerCol, i, numCols, id, defaultColumnSort);
262 		}
263 	}
264 	htmlArr[idx++] = "</tr></table>";
265 
266 	this._listColDiv.innerHTML = htmlArr.join("");
267 	this._listColDiv.className = "DwtListView-ColHeader" + (isColumnHeaderTableFixed ? " FixedColumnHeaderTables" : "");
268 
269 	setTimeout($.proxy(function() {
270 		//run this after the header is fully visible otherwise width will be inaccurate.
271 		//TODO run this as a callback after grid is visible instead of settimeout.
272 		for (var i = 0; i < numCols; i++) {
273 			//find the elements with width auto and create the styles for it.
274 			var headerCol = this._headerList[i];
275 			if (headerCol._cssClass && headerCol._resizeable && headerCol._width === 'auto') {
276 				this._createHeaderCssStyle(headerCol, this._calcRelativeWidth(i));
277 			}
278 		}
279 	}, this), 0);
280 
281 	// for each sortable column, sets its identifier
282 	var numResizeable = 0, resizeableCol;
283 	for (var j = 0; j < this._headerList.length; j++) {
284 		var headerCol = this._headerList[j];
285 		var cell = document.getElementById(headerCol._id);
286 		if (!cell) { continue; }
287 
288 		if (headerCol._sortable && headerCol._field == defaultColumnSort) {
289 			cell.className = "DwtListView-Column DwtListView-ColumnActive";
290 		}
291 
292 		if (headerCol._resizeable) {
293 			// always get the sibling cell to the right
294 			var sashId = DwtId.getListViewHdrId(DwtId.WIDGET_HDR_SASH, this._view, headerCol._field);
295 			var sashCell = document.getElementById(sashId);
296 			if (sashCell) {
297 				this.associateItemWithElement(headerCol, sashCell, DwtListView.TYPE_HEADER_SASH, sashId, {index:j});
298 			}
299 			numResizeable++;
300 			resizeableCol = headerCol;
301 		}
302 		this.associateItemWithElement(headerCol, cell, DwtListView.TYPE_HEADER_ITEM, headerCol._id, {index:j});
303 	}
304 
305 	if (numResizeable == 1) {
306 		resizeableCol._resizeable = false;
307 	}
308 
309 	this.headerColCreated = true;
310 };
311 
312 DwtListView.prototype._createHeader =
313 function(htmlArr, idx, headerCol, i, numCols, id, defaultColumnSort) {
314 
315 	var field = headerCol._field;
316 
317 	htmlArr[idx++] = "<td id='";
318 	htmlArr[idx++] = id;
319 	htmlArr[idx++] = "' class='";
320     var tmpClass = (id == this._currentColId) ? "DwtListView-Column DwtListView-ColumnActive"
321 		: "DwtListView-Column";
322     tmpClass += headerCol._sortable ? "" : " DwtDefaultCursor";
323     htmlArr[idx++] = tmpClass + "'";
324 	if (headerCol._width) {
325 		htmlArr[idx++] = " width=";
326 		htmlArr[idx++] = headerCol._width;
327 		if (headerCol._cssClass && headerCol._resizeable && headerCol._width !== 'auto') {
328 			this._createHeaderCssStyle(headerCol, headerCol._width);
329 		}
330 		if (headerCol._widthUnits) {
331 			htmlArr[idx++] = headerCol._widthUnits;
332 		}
333     }
334 	if (headerCol._tooltip && DwtControl.useBrowserTooltips) {
335 		htmlArr[idx++] = " title='" + headerCol._tooltip + "'";
336 	}
337 	htmlArr[idx++] = ">";
338 	// must add a div to force clipping :(
339 	htmlArr[idx++] = "<div";
340 	var headerColWidth = null;
341 	if (headerCol._width && headerCol._width != "auto") {
342 		//why we need to + 2 here ? It causes the misalign of the list items in IE
343 		if (AjxEnv.isIE) {
344 			headerColWidth = headerCol._width;
345 		} else {
346 			headerColWidth = headerCol._width + 2;
347 		}
348 		if (headerCol._widthUnits) {
349 			headerColWidth += headerCol._widthUnits;
350 		}
351 	}
352 	if (!!headerColWidth) {
353 		htmlArr[idx++] = " style='overflow: hidden; width: ";
354 		htmlArr[idx++] = headerColWidth;
355 		htmlArr[idx++] = "'>";
356 	} else {
357 		htmlArr[idx++] = ">";
358 	}
359 
360 	// add new table for icon/label/sorting arrow
361 	htmlArr[idx++] = "<table width=100%><tr>";
362 	if (headerCol._iconInfo) {
363 		var idText = ["id='", DwtId.getListViewHdrId(DwtId.WIDGET_HDR_ICON, this._view, field), "'"].join("");
364 		htmlArr[idx++] = "<td><center>";
365 		htmlArr[idx++] = AjxImg.getImageHtml(headerCol._iconInfo, null, idText);
366 		htmlArr[idx++] = "</center></td>";
367 	}
368 
369 	if (headerCol._label) {
370 		htmlArr[idx++] = "<td id='";
371 		htmlArr[idx++] = DwtId.getListViewHdrId(DwtId.WIDGET_HDR_LABEL, this._view, field);
372 		htmlArr[idx++] = "' class='DwtListHeaderItem-label' style='padding-right:6px; padding-left:6px'>";
373 		htmlArr[idx++] = headerCol._label;
374 		htmlArr[idx++] = "</td>";
375 	}
376 
377 	if (headerCol._sortable && !headerCol._noSortArrow) {
378 		var arrowIcon = this._bSortAsc ? "ColumnUpArrow" : "ColumnDownArrow";
379 
380 		htmlArr[idx++] = "<td align=right style='padding-right:2px' width='8px' id='";
381 		htmlArr[idx++] = DwtId.getListViewHdrId(DwtId.WIDGET_HDR_ARROW, this._view, field);
382 		htmlArr[idx++] = "'>";
383 		var isDefault = (field == defaultColumnSort);
384 		htmlArr[idx++] = AjxImg.getImageHtml(arrowIcon, isDefault ? null : "visibility:hidden");
385 		htmlArr[idx++] = "</td>";
386 		if (isDefault) {
387 			this._currentColId = id;
388 		}
389 	}
390 
391 	// ALWAYS add "sash" separators
392 	if (i < (numCols - 1)) {
393 		htmlArr[idx++] = "<td width=6>";
394 		htmlArr[idx++] = "<table align=right width=4 height=100% id='";
395 		htmlArr[idx++] = DwtId.getListViewHdrId(DwtId.WIDGET_HDR_SASH, this._view, field);
396 		htmlArr[idx++] = "'><tr>";
397 		htmlArr[idx++] = "<td class='DwtListView-Sash'><div style='width: 1px; height: ";
398 		htmlArr[idx++] = (DwtListView.HEADERITEM_HEIGHT - 2);
399 		htmlArr[idx++] = "px; background-color: #8A8A8A;margin-left:2px'></div></td><td class='DwtListView-Sash'><div style='width: 1px; height: ";
400 		htmlArr[idx++] = (DwtListView.HEADERITEM_HEIGHT - 2);
401 		htmlArr[idx++] = "px;'></div></td></tr></table>";
402 		htmlArr[idx++] = "</td>";
403 	}
404 
405 	htmlArr[idx++] = "</tr></table>";
406 	htmlArr[idx++] = "</div></td>";
407 
408 	return idx;
409 };
410 
411 DwtListView.prototype._createHeaderCssStyle =
412 function(headerCol, width) {
413 	if (!headerCol._cssClass || !headerCol._resizeable) {
414 		return;
415 	}
416 	if (headerCol._cssRuleIndex) {
417 		DwtCssStyle.removeRule(document.styleSheets[0], headerCol._cssRuleIndex);
418 	}
419 	//add a dynamic stylesheet for this header
420 	var selector = "#" + this.parent._htmlElId;
421 	selector += " ." + headerCol._cssClass;
422 	var declaration = "width:" + width + ($.isNumeric(width) ? "px;" : ";");
423 	headerCol._cssRuleIndex = DwtCssStyle.addRule(document.styleSheets[0], selector, declaration, headerCol._cssRuleIndex);
424 };
425 /**
426  * Gets the index of the given item.
427  *
428  * @param	{Object}	item		the item
429  * @return	{number}	the index or <code>null</code> if not found
430  */
431 DwtListView.prototype.getItemIndex =
432 function(item) {
433 	var list = this._list;
434 	if (list) {
435 		var len = list.size();
436 		for (var i = 0; i < len; ++i) {
437 			if (list.get(i).id == item.id) {
438 				return i;
439 			}
440 		}
441 	}
442 	return null;
443 };
444 
445 /**
446  * Sets the size of the view.
447  * 
448  * @param	{number|string}	width		the width (for example: 100, "100px", "75%")
449  * @param	{number|string}	height		the height (for example: 100, "100px", "75%")
450  */
451 DwtListView.prototype.setSize =
452 function(width, height) {
453 	DwtComposite.prototype.setSize.call(this, width, height);
454 	this._sizeChildren(height);
455 };
456 
457 /**
458  * Gets the count of items in the list.
459  * 
460  * @return	{number}	the count of items
461  */
462 DwtListView.prototype.size =
463 function() {
464 	return this._list ? this._list.size() : 0;
465 };
466 
467 /**
468  * Creates a list view out of the given vector of items. The derived class should override _createItemHtml()
469  * in order to display an item.
470  *
471  * @param {AjxVector}	list			a vector of items
472  * @param {number}	[defaultColumnSort]	the default column field to sort
473  * @param {boolean}	noResultsOk		if <code>true</code>, do not show "No Results" for empty list
474  */
475 DwtListView.prototype.set =
476 function(list, defaultColumnSort, noResultsOk) {
477 	if (this._selectedItems) {
478 		this._selectedItems.removeAll();
479 	}
480 	this._curViewedItem = null;
481 	this._rightSelItem = null;
482 	this.sortingEnabled = true;
483 	this._resetList();
484 	this._list = list;
485 	this.setUI(defaultColumnSort, noResultsOk);
486 };
487 
488 /**
489  * Renders the list view using the current list of items.
490  *
491  * @param {string}	defaultColumnSort		the ID of column that represents default sort order
492  * @param {boolean}	noResultsOk			if <code>true</code>, do not show "No Results" for empty list
493  */
494 DwtListView.prototype.setUI =
495 function(defaultColumnSort, noResultsOk) {
496 	this.removeAll();
497 	this.createHeaderHtml(defaultColumnSort);
498 	this._renderList(this._list, noResultsOk);
499 };
500 
501 DwtListView.prototype._renderList =
502 function(list, noResultsOk, doAdd) {
503 	if (list instanceof AjxVector && list.size()) {
504 		var now = new Date();
505 		var size = list.size();
506 		var htmlArr = [];
507 
508 		if (!doAdd) {
509 			this._parentEl.innerHTML = '';
510 		}
511 
512 		Dwt.delClass(this._parentEl, 'DwtListView-Rows-Empty');
513 
514 		for (var i = 0; i < size; i++) {
515 			var item = list.get(i);
516 			var div = this._createItemHtml(item, {now:now}, false, i);
517 			if (div) {
518 				if (div instanceof Array) {
519 					for (var j = 0; j < div.length; j++){
520 						this._addRow(div[j]);
521 					}
522 				} else {
523 					this._addRow(div);
524 				}
525                 this._itemAdded(item);
526 			}
527 		}
528 	} else if (!noResultsOk) {
529 		this._setNoResultsHtml();
530 		Dwt.addClass(this._parentEl, 'DwtListView-Rows-Empty');
531 	}
532 };
533 
534 /**
535  * Adds the items.
536  * 
537  * @param	{array}		itemArray		an array of items
538  */
539 DwtListView.prototype.addItems =
540 function(itemArray) {
541 	if (AjxUtil.isArray(itemArray)) {
542 		if (!this._list) {
543 			this._list = new AjxVector();
544 		}
545 
546 		// clear the "no results" message before adding!
547 		if (this._list.size() == 0) {
548 			this._resetList();
549 		}
550 		this._renderList(AjxVector.fromArray(itemArray), null, true);
551 		this._list.addList(itemArray);
552 	}
553 };
554 
555 /**
556  * Adds a row for the given item to the list view.
557  *
558  * @param {Object}	item			the data item
559  * @param {number}	index			the index at which to add item to list and list view
560  * @param {boolean}	skipNotify	if <code>true</code>, do not notify listeners
561  * @param {number}	itemIndex		index at which to add item to list, if different
562  * 									from the one for the list view
563  */
564 DwtListView.prototype.addItem =
565 function(item, index, skipNotify, itemIndex) {
566 	if (!this._list) {
567 		this._list = new AjxVector();
568 	}
569 
570 	// clear the "no results" message before adding!
571 	if (this._list.size() == 0) {
572 		this._resetList();
573 	}
574 
575 	this._list.add(item, (itemIndex != null) ? itemIndex : index);
576 	var div = this._createItemHtml(item);
577 	if (div) {
578 		if (div instanceof Array) {
579 			for (var j = 0; j < div.length; j++) {
580 				this._addRow(div[j]);
581 			}
582 		} else {
583 			this._addRow(div, index);
584 		}
585 	}
586 
587 	if (!skipNotify && this._evtMgr.isListenerRegistered(DwtEvent.STATE_CHANGE)) {
588 		this._evtMgr.notifyListeners(DwtEvent.STATE_CHANGE, this._stateChangeEv);
589 	}
590 };
591 
592 /**
593  * Removes a row for the given item to the list view.
594  *
595  * @param {Object}	item			the data item
596  * @param {boolean}	skipNotify	if <code>true</code>, do not notify listeners
597  * @param {boolean}	skipAlternation		if <code>true</code>, do not fix alternation
598  */
599 DwtListView.prototype.removeItem =
600 function(item, skipNotify, skipAlternation) {
601 
602 	var itemEl = this._getElFromItem(item);
603 	if (!itemEl) { return; }
604 
605 	var altIndex = this._getRowIndex(item);	// get index before we remove row
606 
607 	this._selectedItems.remove(itemEl);
608 	if (this._rightSelItem === itemEl) {
609 		this._rightSelItem = null;
610 	}
611 	if (this._kbAnchor === itemEl) {
612 		this._kbAnchor = null;
613 	}
614 	if (itemEl.parentNode === this._parentEl) {
615 		this._parentEl.removeChild(itemEl);
616 	}
617 	if (this._list) {
618 		this._list.remove(item);
619 	}
620 	var id = itemEl.id;
621 	if (this._data[id]) {
622 		this._data[id] = null;
623 		delete this._data[id];
624 	}
625 	if (!skipAlternation) {
626 		this._fixAlternation(altIndex);
627 	}
628 
629 	if (!skipNotify && this._evtMgr.isListenerRegistered(DwtEvent.STATE_CHANGE)) {
630 		this._evtMgr.notifyListeners(DwtEvent.STATE_CHANGE, this._stateChangeEv);
631 	}
632 };
633 
634 DwtListView.prototype.redrawItem =
635 function(item) {
636     var odiv = this._getElFromItem(item);
637     if (odiv) {
638 		var className = odiv.className;
639         var ndiv = this._createItemHtml(item);
640 		ndiv.className = className;	// preserve classes
641         odiv.parentNode.replaceChild(ndiv, odiv);
642 		// preserve selection
643 		if (this._selectedItems.contains(odiv)) {
644 			this._selectedItems.remove(odiv);
645 			this.selectItem(item);
646 		}
647 
648         //update the _kbAnchor as the old element has been swapped with the new element
649         if (this._kbAnchor === odiv){
650             this._setKbFocusElement(ndiv);
651         }
652     }
653 };
654 
655 /**
656  * Adds a selection listener.
657  * 
658  * @param	{AjxListener}	listener		the listener
659  */
660 DwtListView.prototype.addSelectionListener =
661 function(listener) {
662 	this._evtMgr.addListener(DwtEvent.SELECTION, listener);
663 };
664 
665 /**
666  * Removes a selection listener.
667  * 
668  * @param	{AjxListener}	listener		the listener
669  */
670 DwtListView.prototype.removeSelectionListener =
671 function(listener) {
672 	this._evtMgr.removeListener(DwtEvent.SELECTION, listener);
673 };
674 
675 /**
676  * Adds an action listener.
677  * 
678  * @param	{AjxListener}	listener		the listener
679  */
680 DwtListView.prototype.addActionListener =
681 function(listener) {
682 	this._evtMgr.addListener(DwtEvent.ACTION, listener);
683 };
684 
685 /**
686  * Removes an action listener.
687  * 
688  * @param	{AjxListener}	listener		the listener
689  */
690 DwtListView.prototype.removeActionListener =
691 function(listener) {
692 	this._evtMgr.removeListener(DwtEvent.ACTION, listener);
693 };
694 
695 /**
696  * Adds a state change listener.
697  * 
698  * @param	{AjxListener}	listener		the listener
699  */
700 DwtListView.prototype.addStateChangeListener =
701 function(listener) {
702 	this._evtMgr.addListener(DwtEvent.STATE_CHANGE, listener);
703 };
704 
705 /**
706  * Adds a state change listener.
707  * 
708  * @param	{AjxListener}	listener		the listener
709  */
710 DwtListView.prototype.removeStateChangeListener =
711 function(listener) {
712 	this._evtMgr.removeListener(DwtEvent.STATE_CHANGE, listener);
713 };
714 
715 /**
716  * Removes all the items from the list.
717  * 
718  * @param {boolean}	skipNotify	if <code>true</code>, do not notify listeners
719  */
720 DwtListView.prototype.removeAll =
721 function(skipNotify) {
722 	if (this._parentEl) {
723 		this._parentEl.innerHTML = "";
724 	}
725 	if (this._selectedItems) {
726 		this._selectedItems.removeAll();
727 	}
728 	this._rightSelItem = this._selAnchor = this._kbAnchor = null;
729 
730 	if (!skipNotify && this._evtMgr.isListenerRegistered(DwtEvent.STATE_CHANGE)) {
731 		this._evtMgr.notifyListeners(DwtEvent.STATE_CHANGE, this._stateChangeEv);
732 	}
733 };
734 
735 /**
736  * Selects all items in the list.
737  * 
738  */
739 DwtListView.prototype.selectAll =
740 function() {
741 	if (this._list && this._list.size()) {
742 		this.setSelectedItems(this._list.getArray());
743 	}
744 };
745 
746 /**
747  * De-selects all items in the list.
748  * 
749  */
750 DwtListView.prototype.deselectAll =
751 function() {
752 	if(this._selectedItems) {
753 		var a = this._selectedItems.getArray();
754 		var sz = this._selectedItems.size();
755 		for (var i = 0; i < sz; i++) {
756 	        Dwt.delClass(a[i], this._styleRe);
757 	        a[i].removeAttribute('aria-selected');
758 	    }
759 	    this._selectedItems.removeAll();
760 		this._rightSelItem = this._selAnchor = null;
761 	
762 		if (this._kbAnchor != null && this.hasFocus()) {
763 			Dwt.addClass(this._kbAnchor, this._kbFocusClass);
764 		}
765 	}
766 };
767 
768 DwtListView.prototype._markUnselectedViewedItem =
769 function(on) {
770 	var viewedItem = this._curViewedItem;
771 	var viewedEl = viewedItem && this._getElFromItem(viewedItem);
772 	if (!viewedEl) {
773 		return;
774 	}
775 	if (on) {
776 		Dwt.delClass(viewedEl, this._styleRe, this._viewedButUnselectedClass);  //ADDING the selectedForViewOnly class
777 	}
778 	else {
779 		//turn off the highlight
780 		Dwt.delClass(viewedEl, this._viewedButUnselectedClass);
781 	}
782 };
783 
784 DwtListView.prototype.getDnDSelection =
785 function() {
786 	if (this._dndSelection instanceof AjxVector) {
787 		return this.getSelection();
788 	} else {
789 		return this.getItemFromElement(this._dndSelection);
790 	}
791 };
792 
793 DwtListView.prototype.getSelection =
794 function() {
795 	var a = [];
796 	if (this._rightSelItem) {
797 		a.push(this.getItemFromElement(this._rightSelItem));
798 	} else if (this._selectedItems) {
799         var sa = this._selectedItems.getArray();
800 		var saLen = this._selectedItems.size();
801 		for (var i = 0; i < saLen; i++) {
802 			a[i] = this.getItemFromElement(sa[i]);
803 		}
804 	}
805 	return a;
806 };
807 
808 /**
809  * Gets the selected items.
810  * 
811  * @return	{Array}	an array of selected items
812  */
813 DwtListView.prototype.getSelectedItems =
814 function() {
815 	return this._selectedItems;
816 };
817 
818 DwtListView.prototype.setSelection =
819 function(item, skipNotify, forceSelection) {
820 
821 	if (!item) {
822 		return;
823 	}
824 
825 	var el = this._getElFromItem(item);
826 	if (el) {
827 		if (this._selectedItems.size() == 1 && this._selectedItems.get(0) == el && !forceSelection) {
828 			return;
829 		}
830 		this.deselectAll();
831 		this._unmarkKbAnchorElement(true);
832 		this._setKbFocusElement(el);
833 		this._selAnchor = el;
834 		this.selectItem(item, this.getEnabled());
835 
836 		// reset the selected index
837 		this.firstSelIndex = (this._list && this._list.size() > 0) ? this._list.indexOf(item) : -1;
838 
839 		this._scrollList(el);
840 
841 		if (!skipNotify && this._evtMgr.isListenerRegistered(DwtEvent.SELECTION)) {
842 			var selEv = new DwtSelectionEvent(true);
843 			selEv.button = DwtMouseEvent.LEFT;
844 			selEv.target = el;
845 			selEv.item = this.getItemFromElement(el);
846 			selEv.detail = DwtListView.ITEM_SELECTED;
847 			selEv.ersatz = true;
848 			this._evtMgr.notifyListeners(DwtEvent.SELECTION, selEv);
849 		}
850 	}
851 };
852 
853 DwtListView.prototype.setMultiSelection =
854 function(clickedEl, bContained, ev) {
855 	if (bContained) {
856 		this._selectedItems.remove(clickedEl);
857 		clickedEl.removeAttribute('aria-selected');
858 		Dwt.delClass(clickedEl, this._styleRe);		// , this._normalClass	MOW
859 		this._selEv.detail = DwtListView.ITEM_DESELECTED;
860 	} else {
861 		this._selectedItems.add(clickedEl, null, true);
862 		clickedEl.setAttribute('aria-selected', true);
863 		Dwt.delClass(clickedEl, this._styleRe, this._selectedClass);
864 		this._selEv.detail = DwtListView.ITEM_SELECTED;
865 	}
866 
867 	// Remove the keyboard hilite from the current anchor
868 	if (this._kbAnchor && this._kbAnchor != clickedEl) {
869 		Dwt.delClass(this._kbAnchor, this._kbFocusClass);
870 	}
871 
872 	// The element that was part of the ctrl action always becomes the anchor
873 	// since it gets focus
874 	this._setKbFocusElement(clickedEl);
875 	this._selAnchor = clickedEl;
876 	Dwt.addClass(this._kbAnchor, this._kbFocusClass);
877 };
878 
879 DwtListView.prototype.setSelectedItems =
880 function(selectedArray) {
881 	this.deselectAll();
882 	var sz = selectedArray.length, doSelect = this.getEnabled();
883 	for (var i = 0; i < sz; ++i) {
884 		this.selectItem(selectedArray[i], doSelect);
885 	}
886 };
887 
888 /**
889  * Selects or deselects a single item.
890  * 
891  * @param	{boolean}	selected		if <code>true</code>, select the item
892  */
893 DwtListView.prototype.selectItem =
894 function(item, selected) {
895 
896 	var el = this._getElFromItem(item);
897 	if (el) {
898 		Dwt.delClass(el, this._styleRe, selected ? this._selectedClass : this._disabledSelectedClass);
899 		if (this._kbAnchor == el && this.hasFocus()) {
900 			Dwt.addClass(el, this._kbFocusClass);
901 			el.focus();
902 		}
903 		this._selectedItems.add(el);
904 		el.setAttribute('aria-selected', true);
905 	}
906 };
907 
908 /**
909  * Gets the selection count.
910  * 
911  * @return	{number}	the selection count
912  */
913 DwtListView.prototype.getSelectionCount =
914 function() {
915 	return this._rightSelItem ? 1 : this._selectedItems.size();
916 };
917 
918 DwtListView.prototype.handleActionPopdown =
919 function() {
920 	this._clearRightSel();
921 };
922 
923 /**
924  * Pairs an item with an element. As a side effect, provides a mechanism for storing
925  * data about a particular element, referenced by its ID.
926  *
927  * @param {Object}	item		an item
928  * @param {Element}	element	an HTML element
929  * @param {constant}	[type=DwtListView.TYPE_LIST_ITEM]		a role that element has
930  * @param {string}	[id]		the ID for element; if not provided, one is generated from the item
931  * @param {hash}	[data]		any additional attributes to store
932  * 
933  * @private
934  */
935 DwtListView.prototype.associateItemWithElement =
936 function(item, element, type, id, data) {
937 	id = id || this._getItemId(item);
938 	if (element) {
939 		element.id = id;
940 	}
941 	type = type || DwtListView.TYPE_LIST_ITEM;
942 	this._data[id] = {item:item, id:id, type:type};
943 	if (data) {
944 		for (var key in data) {
945 			this._data[id][key] = data[key];
946 		}
947 	}
948 	return id;
949 };
950 
951 DwtListView.prototype.getItemFromElement =
952 function(el) {
953 	return this._getItemData(el, "item");
954 };
955 
956 /**
957  * Starts with an element and works its way up the element chain until it finds one
958  * with an ID that maps to an item, then returns the associated item.
959  *
960  * @param {Element}	el	element to start with
961  * @return	{Object}	the item
962  */
963 DwtListView.prototype.findItem =
964 function(el)  {
965 	if (!el) { return; }
966 	var div = this.findItemDiv(el);
967 	return this._getItemData(div, "item");
968 };
969 
970 /**
971  * Starts with an element and works its way up the element chain until it finds one
972  * with an ID that maps to an item.
973  *
974  * @param {Element}	el	the element to start with
975  * @return	{Element}	the element
976  */
977 DwtListView.prototype.findItemDiv =
978 function(el)  {
979 	if (!el) { return; }
980 	while (el && (el.id != this._htmlElId)) {
981 		if (el.id && this._data[el.id]) {
982 			return el;
983 		}
984 		el = el.parentNode;
985 	}
986 	return null;
987 };
988 
989 /**
990  * Gets the item associated with the given event. Starts with the
991  * event target and works its way up the element chain until it finds one
992  * with an ID that maps to an item.
993  *
994  * @param {DwtEvent}	ev				the event
995  * @return	{Object}	the item
996  */
997 DwtListView.prototype.getTargetItem =
998 function(ev)  {
999 	return this.findItem(DwtUiEvent.getTarget(ev));
1000 };
1001 
1002 /**
1003  * Gets the item DIV associated with the given event. Starts with the
1004  * event target and works its way up the element chain until it finds one
1005  * with an ID that maps to an item.
1006  *
1007  * @param {DwtEvent}	ev				the event
1008  * @return	{Object}	the item
1009  */
1010 DwtListView.prototype.getTargetItemDiv =
1011 function(ev)  {
1012 	return this.findItemDiv(DwtUiEvent.getTarget(ev));
1013 };
1014 
1015 DwtListView.prototype.dragSelect =
1016 function(row) {
1017 	// If we have something previously selected, try and remove the selection
1018 	if (this._dragHighlight) {
1019 		var oldRow = document.getElementById(this._dragHighlight);
1020 		// only go forward if the row doesn't exist, or if the new selection
1021 		// is different from the old selection.
1022 		// In the case where a header item is dragged over, the row might be
1023 		// null or void.
1024 		if (!row || (oldRow && (row.id != oldRow.id))) {
1025 			this._updateDragSelection(oldRow, false);
1026 		}
1027 	}
1028 
1029 	if (!row) { return; }
1030 
1031 	// Don't try and select if we are over a header item
1032 	if (this._getItemData(row, "type") != DwtListView.TYPE_LIST_ITEM) { return; }
1033 
1034 	// Try and select only if the new row is different from the currently
1035 	// highlighted row.
1036 	if (row.id != this._dragHighlight) {
1037 		this._dragHighlight = row.id;
1038 		this._updateDragSelection(row, true);
1039 	}
1040 };
1041 
1042 DwtListView.prototype.dragDeselect =
1043 function(row) {
1044 	if (this._dragHighlight) {
1045 		var oldRow = document.getElementById(this._dragHighlight);
1046 		this._updateDragSelection(oldRow, false);
1047 		this._dragHighlight = null;
1048 	}
1049 };
1050 
1051 DwtListView.prototype.getScrollContainer = function() {
1052 
1053     return this._parentEl;
1054 };
1055 
1056 DwtListView.prototype.scrollToItem =
1057 function(item){
1058     var el = this._getElFromItem(item);
1059     if(el){
1060         this._listDiv.scrollTop = el.offsetTop;
1061     }
1062 };
1063 
1064 DwtListView.prototype.scrollToTop =
1065 function() {
1066 	this._listDiv.scrollTop = 0;
1067 };
1068 
1069 /**
1070  * Scrolls the list view up or down one page.
1071  *
1072  * @param {boolean}	up	if true, scroll up
1073  */
1074 DwtListView.prototype.scrollPage =
1075 function(up) {
1076 	var el = this._parentEl;
1077 	if (el.clientHeight >= el.scrollHeight) { return; }
1078 	el.scrollTop = up ? Math.max(el.scrollTop - el.clientHeight, 0) :
1079 				   		Math.min(el.scrollTop + el.clientHeight, el.scrollHeight - el.clientHeight);
1080 };
1081 
1082 DwtListView.prototype.setSortByAsc =
1083 function(column, bSortByAsc) {
1084 	if (!this._headerList) { return; }
1085 
1086 	this._bSortAsc = bSortByAsc;
1087 	var columnId = null;
1088 	for (var i = 0; i < this._headerList.length; i++) {
1089 		if (this._headerList[i]._sortable && this._headerList[i]._field == column) {
1090 			columnId = this._headerList[i]._id;
1091 			break;
1092 		}
1093 	}
1094 	if (columnId) {
1095 		this._setSortedColStyle(columnId);
1096 	}
1097 };
1098 
1099 DwtListView.prototype.getNewOffset =
1100 function(bPageForward) {
1101 	var limit = this.getLimit();
1102 	var offset = bPageForward ? (this.offset + limit) : (this.offset - limit);
1103 	return (offset < 0) ? 0 : offset;
1104 };
1105 
1106 DwtListView.prototype.getLimit =
1107 function() {
1108 	// return the default limit value unless overloaded
1109 	return DwtListView.DEFAULT_LIMIT;
1110 };
1111 
1112 DwtListView.prototype.getReplenishThreshold =
1113 function() {
1114 	// return the default threshold value unless overloaded
1115 	return DwtListView.MAX_REPLENISH_THRESHOLD;
1116 };
1117 
1118 DwtListView.prototype.getList =
1119 function() {
1120 	return this._list;
1121 };
1122 
1123 DwtListView.prototype.getFocusElement = function() {
1124 
1125 	if (!this._kbAnchor) {
1126 		this._setKbFocusElement(null, true);
1127 	}
1128 
1129 	return this._kbAnchor || this._parentEl;
1130 };
1131 
1132 // this method simply appends the given list to this current one
1133 DwtListView.prototype.replenish =
1134 function(list) {
1135 	this._list.addList(list);
1136 
1137 	var size = list.size();
1138 	for (var i = 0; i < size; i++) {
1139 		var item = list.get(i);
1140 		var div = this._createItemHtml(item);
1141 		if (div) {
1142 			this._addRow(div);
1143 		}
1144 	}
1145 };
1146 
1147 DwtListView.prototype.getKeyMapName =
1148 function() {
1149 	return DwtKeyMap.MAP_LIST;
1150 };
1151 
1152 DwtListView.prototype.handleKeyAction = function(actionCode, ev) {
1153 
1154     if (!this.size()) {
1155         return false;
1156     }
1157 
1158 	switch (actionCode) {
1159 		case DwtKeyMap.SELECT:			this._emulateSingleClick({target:this._kbAnchor, button:DwtMouseEvent.LEFT, kbNavEvent:true}); break;
1160 		case DwtKeyMap.SELECT_CURRENT:	this._emulateSingleClick({target:this._kbAnchor, button:DwtMouseEvent.LEFT, ctrlKey:true, kbNavEvent:true}); break;
1161 		case DwtKeyMap.SELECT_NEXT:		this._selectItem(true, false, true); break;
1162 		case DwtKeyMap.SELECT_PREV:		this._selectItem(false, false, true); break;
1163 		case DwtKeyMap.ADD_SELECT_NEXT: this._selectItem(true, true, true); break;
1164 		case DwtKeyMap.ADD_SELECT_PREV: this._selectItem(false, true, true); break;
1165 		case DwtKeyMap.PREV:			this._setKbFocusElement(false); break;
1166 		case DwtKeyMap.NEXT:			this._setKbFocusElement(true); break;
1167 
1168 		case DwtKeyMap.DBLCLICK: {
1169 			if (!this._kbAnchor) {
1170                 break;
1171             }
1172 			var anchorSelected = false;
1173 			var a = this.getSelectedItems().getArray();
1174 			for (var i = 0; i < a.length; i++) {
1175 				if (a[i] == this._kbAnchor) {
1176 					anchorSelected = true;
1177 					break;
1178 				}
1179 			}
1180 			if (anchorSelected) {
1181 				this.emulateDblClick(this.getItemFromElement(this._kbAnchor), true);
1182 			} else {
1183 				this._emulateSingleClick({target:this._kbAnchor, button:DwtMouseEvent.LEFT, kbNavEvent:true});
1184 			}
1185 			break;
1186 		}
1187 
1188 		case DwtKeyMap.SELECT_ALL:
1189 			this.selectAll();
1190 			break;
1191 
1192 		case DwtKeyMap.SELECT_FIRST:
1193 		case DwtKeyMap.SELECT_LAST:
1194 			var item = (actionCode == DwtKeyMap.SELECT_FIRST) ? this._getFirstItem() : this._getLastItem();
1195 			if (item) {
1196 				this.setSelection(item);
1197 				this._scrollList(this._kbAnchor);
1198 			}
1199 			break;
1200 
1201 		case DwtKeyMap.SUBMENU:
1202 			if (this._evtMgr.isListenerRegistered(DwtEvent.ACTION)) {
1203 				var p = Dwt.toWindow(this._kbAnchor, 0, 0);
1204 				var s = Dwt.getSize(this._kbAnchor);
1205 				var docX = p.x + s.x / 4;
1206 				var docY = p.y + s.y / 2;
1207 				this._emulateSingleClick({target:this._kbAnchor, button:DwtMouseEvent.RIGHT, docX:docX, docY:docY, kbNavEvent:true});
1208 			}
1209 			break;
1210 
1211 		case DwtKeyMap.PAGE_UP:
1212 		case DwtKeyMap.PAGE_DOWN:
1213 			this.scrollPage(actionCode == DwtKeyMap.PAGE_UP);
1214 			break;
1215 
1216 		default:
1217 			return false;
1218 	}
1219 
1220 	return true;
1221 };
1222 
1223 DwtListView.prototype.setMultiSelect = function (enabled) {
1224 	this.setAttribute('aria-multiselectable', Boolean(enabled));
1225 };
1226 
1227 DwtListView.prototype.isMultiSelectEnabled = function () {
1228 	return this.getAttribute('aria-multiselectable') === "true";
1229 };
1230 
1231 // DO NOT REMOVE - used by xforms
1232 DwtListView.prototype.setListDivHeight =
1233 function (listViewHeight) {
1234 	if (this._listDiv && this._listColDiv) {
1235 		var headerHeight = Dwt.getSize (this._listColDiv).y ;
1236 		//the 25px allows for the diff between container and list for all browsers and eliminates vertical unnecessary scrolls
1237 		var listDivHeight = listViewHeight - headerHeight - 25;
1238 		Dwt.setSize(this._listDiv, Dwt.DEFAULT, listDivHeight);
1239 	}
1240 };
1241 
1242 
1243 // Private methods
1244 
1245 // returns a regex that matches modified styles such as "Row-selected-actioned"
1246 DwtListView.prototype._getStyleRegex =
1247 function() {
1248 	return new RegExp("\\bRow(-(" + [DwtCssStyle.ALT_SELECTED,
1249 									 DwtCssStyle.SELECTED,
1250 									 DwtCssStyle.ACTIONED,
1251 									 DwtCssStyle.FOCUSED,
1252 									 DwtCssStyle.DISABLED,
1253 									 DwtCssStyle.DRAG_PROXY].join("|") +
1254 					  "))+\\b", "g");
1255 };
1256 
1257 /**
1258  * This is the designated means of adding a row to the table. It does certain
1259  * processing on the row, so don't use any other means.
1260  */
1261 DwtListView.prototype._addRow =
1262 function(row, index) {
1263 
1264 	if (!row || !this._parentEl) { return; }
1265 
1266 	// bug fix #1894 - check for childNodes length otherwise IE barfs
1267 	var len = this._parentEl.childNodes.length;
1268 	if (index != null && len > 0 && index != len) {
1269 		this._parentEl.insertBefore(row, this._parentEl.childNodes[index]);
1270 	} else {
1271 		this._parentEl.appendChild(row);
1272 	}
1273 	this._fixAlternation((index != null) ? index : len);
1274 
1275 	row.setAttribute('role', this.itemRole);
1276 };
1277 
1278 // Placeholder function for any post-add processing
1279 DwtListView.prototype._itemAdded = function(item) {};
1280 
1281 DwtListView.prototype._fixAlternation =
1282 function(index) {
1283 
1284 	var childNodes = this._parentEl.childNodes;
1285 	if (!(childNodes && childNodes.length)) { return; }
1286 	if (!(this._list && this._list.size())) { return; }
1287 
1288 	var row = childNodes[index];
1289 	if (!row) { return; }
1290 	var odd = Boolean(index % 2);
1291 	this._setAlternatingRowClass(row, odd);
1292 	var sibling = row.nextSibling;
1293 	while (sibling) {
1294 		odd = !odd;
1295 		this._setAlternatingRowClass(sibling, odd);
1296 		sibling = sibling.nextSibling;
1297 	}
1298 };
1299 
1300 DwtListView.prototype._setAlternatingRowClass =
1301 function(row, odd) {
1302 	var oclass = odd ? DwtListView.ROW_CLASS_ODD : DwtListView.ROW_CLASS_EVEN;
1303 	var nclass = odd ? DwtListView.ROW_CLASS_EVEN : DwtListView.ROW_CLASS_ODD;
1304 	Dwt.delClass(row, oclass, nclass);
1305 };
1306 
1307 /**
1308  * Renders a single item as a DIV element within a list view. The DIV will
1309  * contain a TABLE with a column for each field. Subclasses will want to
1310  * override supporting classes at their discretion. At the very least, they
1311  * will want to override _getCellContents(). Callers can pass
1312  * in arbitrary info via the params hash, and it will get passed to the
1313  * support functions.
1314  *
1315  * @param {Object}	item			the item to render
1316  * @param {hash}	params		a hash of optional parameters
1317  * @param {Date}      params.now			the current time
1318  * @param {boolean}      params.isDragProxy	if <code>true</code>, we are rendering a the row to be a drag proxy (dragged around the screen)
1319  * @param {Element}      params.div			the <code>div</code> to fill with content
1320  * @param {array}      params.headerList	a list of column headers
1321  * @param	{boolean}	asHtml
1322  * @param	{number}	idx
1323  * 
1324  * @private
1325  */
1326 DwtListView.prototype._createItemHtml =
1327 function(item, params, asHtml, count) {
1328 
1329 	params = params || {};
1330 	this._addParams(item, params, htmlArr, idx);
1331 	var div;
1332 
1333 	var htmlArr = [];
1334 	var idx = 0;
1335 
1336 	if (asHtml) {
1337 		idx = this._getDivHtml(item, params, htmlArr, idx, count);
1338 	} else {
1339 		if (params.div) {
1340 			var classes = [this._getDivClass(params.divClass || this._normalClass, item, params),
1341 				(count % 2) ? DwtListView.ROW_CLASS_EVEN : DwtListView.ROW_CLASS_ODD];
1342 			params.div.className = classes.join(" ");
1343 		}
1344 		div = params.div || this._getDiv(item, params);
1345 	}
1346 
1347 	var useListEl = this.useListElement();
1348 	if (!useListEl) {
1349 		idx = this._getTable(htmlArr, idx, params);
1350 	}
1351 	idx = this._getRow(htmlArr, idx, item, params);
1352 
1353 	// Cells
1354 	var headerList = params.headerList || this._headerList;
1355 	if (headerList && headerList.length) {
1356 		for (var colIdx = 0; colIdx < headerList.length; colIdx++) {
1357 			if (!headerList[colIdx]._visible) { continue; }
1358 
1359 			var field = headerList[colIdx]._field;
1360 			idx = this._getCell(htmlArr, idx, item, field, colIdx, params);
1361 		}
1362 	} else {
1363 		idx = this._getCell(htmlArr, idx, item, null, null, params);
1364 	}
1365 
1366 	htmlArr[idx++] = useListEl ? "</div>" : "</tr></table>";
1367 
1368 	if (asHtml) {
1369 		htmlArr[idx++] = useListEl ? "</li>" : "</div>";
1370 		return htmlArr.join("");
1371 	}
1372 
1373 	div.innerHTML = htmlArr.join("");
1374 	return div;
1375 };
1376 
1377 /**
1378  * Subclasses can override to add params to pass to functions below.
1379  *
1380  * @param {Object}	item			the item to render
1381  * @param {hash}	params		a hash of optional parameters
1382  * 
1383  * @private
1384  */
1385 DwtListView.prototype._addParams = function(item, params) {};
1386 
1387 /**
1388  * Returns the DIV that contains the item HTML, and sets up styles that will
1389  * be used to represent its selection state.
1390  *
1391  * @param {Object}	item			the item to render
1392  * @param {hash}	params		a hash of optional parameters
1393  * 
1394  * @private
1395  */
1396 DwtListView.prototype._getDiv =
1397 function(item, params) {
1398 
1399 	var	div = document.createElement("div");
1400 	var html = [];
1401 	this._getDivHtml(item, params, html, 0, 0);
1402 	div.innerHTML = html.join("");
1403 
1404 	return div.firstChild; //we want the div that includes the style and class in its element, so we wrap it just for fun (actually not for fun - outerHTML doesn't work)
1405 };
1406 
1407 /**
1408  * override if needed. Currently in ZmMailListView for coloring messages.
1409  * @param item
1410  * @return {*}
1411  * @private
1412  */
1413 DwtListView.prototype._getExtraStyle =
1414 function(item) {
1415 	return null;
1416 };
1417 
1418 /**
1419  * This is the "HTML" version of the routine above. Instead of returning a DIV
1420  * element, it returns HTML containing the DIV.
1421  *
1422  * @param {Object}	item		the item to render
1423  * @param {hash}	params	a hash of optional parameters
1424  * @param {array}	html		the array used to contain HTML code
1425  * @param {number}	idx		the index used to contain HTML code
1426  * @param {number}	count		the count of row currently being processed
1427  * @param {array}	classes		the css classes to be assigned to this element
1428  * 
1429  * @private
1430  */
1431 DwtListView.prototype._getDivHtml =
1432 function(item, params, html, idx, count, classes) {
1433 
1434 	html[idx++] = this.useListElement()? "<li ":"<div ";
1435 	classes = classes || [];
1436 	classes.push(this._getDivClass(params.divClass || this._normalClass, item, params));
1437 	classes.push((count % 2) ? DwtListView.ROW_CLASS_EVEN : DwtListView.ROW_CLASS_ODD);
1438 	html[idx++] = AjxUtil.getClassAttr(classes);
1439 
1440 	var style = [];
1441 	if (params.isDragProxy && AjxEnv.isMozilla) {
1442 		style.push("overflow:visible");		// bug fix #3654 - yuck
1443 	}
1444 	if (params.isDragProxy) {
1445 		style.push("position:absolute");
1446 		style.push("width:" + this.getSize().x + "px");
1447 	}
1448 
1449 	var extraStyle = this._getExtraStyle(item);
1450 	if (extraStyle) {
1451 		style.push(extraStyle);
1452 	}
1453 
1454 	if (style.length) {
1455 		html[idx++] = " style='";
1456 		html[idx++] = style.join(";");
1457 		html[idx++] = "'";
1458 	}
1459 
1460 	var id = params.isDragProxy ? this._getItemId(item) + "_dnd" : null;
1461 	html[idx++] = " id='";
1462 	html[idx++] = this.associateItemWithElement(item, null, null, id);
1463 	html[idx++] = "'>";
1464 
1465 	return idx;
1466 };
1467 
1468 /**
1469  * Returns the name of the class to use for the DIV that contains the HTML for this item.
1470  * Typically, a modifier is added to a base class for certain types of rows. For example,
1471  * a row that is created to be dragged will get the class "Row-dnd".
1472  *
1473  * @param {string}	base		the name of base class
1474  * @param {Object}	item		the item to render
1475  * @param {hash}	params	a hash of optional parameters
1476  * 
1477  * @private
1478  */
1479 DwtListView.prototype._getDivClass =
1480 function(base, item, params) {
1481 	return params.isDragProxy ? ([base, " ", base, "-", DwtCssStyle.DRAG_PROXY].join("")) : base;
1482 };
1483 
1484 /**
1485  * Creates the TABLE that holds the items.
1486  *
1487  * @param {array}	htmlArr	the array that holds lines of HTML
1488  * @param {number}	idx		the current line of array
1489  * @param {hash}	params	a hash of optional parameters
1490  * 
1491  * @private
1492  */
1493 DwtListView.prototype._getTable =
1494 function(htmlArr, idx, params) {
1495 	htmlArr[idx++] = "<table width=";
1496 	htmlArr[idx++] = !params.isDragProxy ? "100%>" : (this.getSize().x + ">");
1497 	return idx;
1498 };
1499 
1500 /**
1501  * Creates a TR for the given item.
1502  *
1503  * @param {array}	htmlArr		the array that holds lines of HTML
1504  * @param {number}	idx		the current line of array
1505  * @param {object}	item		the item to render
1506  * @param {hash}	params	a hash of optional parameters
1507  * @param {array}	classes	a list of css classes for this row
1508  * 
1509  * @private
1510  */
1511 DwtListView.prototype._getRow =
1512 function(htmlArr, idx, item, params, classes) {
1513 	var rowId = this._getRowId(item, params) || Dwt.getNextId();
1514 	var className = this._getRowClass(item, params);
1515 	if (this.useListElement()) {
1516 		htmlArr[idx++] =  "<div ";
1517 		classes = classes || [];
1518 		if (className) {
1519 			classes.push(className);
1520 		}
1521 		if (rowId) {
1522 			htmlArr[idx++] = ["id='", rowId, "'"].join("");
1523 		}
1524 		htmlArr[idx++] = AjxUtil.getClassAttr(classes) + ">";
1525 	} else {
1526 		htmlArr[idx++] = rowId ? ["<tr id='", rowId, "'"].join("") : "<tr";
1527 		htmlArr[idx++] = className ? ([" class='", className, "'>"].join("")) : ">";
1528 	}
1529 	return idx;
1530 };
1531 
1532 /**
1533  * Use the list elements <ul> and <li> instead of div and table elements
1534  *
1535  */
1536 DwtListView.prototype.useListElement =
1537 function() {
1538 	return false;
1539 }
1540 
1541 /**
1542  * Returns the class name for this item's TR.
1543  *
1544  * @param {Object}	item		the item to render
1545  * @param {hash}	params		a hash of optional parameters
1546  * 
1547  * @private
1548  */
1549 DwtListView.prototype._getRowClass =
1550 function(item, params) {
1551 	return null;
1552 };
1553 
1554 /**
1555  * Returns the DOM ID to be used for this item's TR.
1556  *
1557  * @param {Object}	item		the item to render
1558  * @param {hash}	params		a hash of optional parameters
1559  * 
1560  * @private
1561  */
1562 DwtListView.prototype._getRowId =
1563 function(item, params) {
1564 	return null;
1565 };
1566 
1567 /**
1568  * Creates a TD and its content for a single field of the given item. Subclasses
1569  * may override several dependent functions to customize the TD and its content.
1570  *
1571  * @param htmlArr	[array]		array that holds lines of HTML
1572  * @param idx		[int]		current line of array
1573  * @param item		[object]	item to render
1574  * @param field		[constant]	column identifier
1575  * @param colIdx	[int]		index of column (starts at 0)
1576  * @param params	[hash]*		hash of optional params
1577  * 
1578  * @private
1579  */
1580 DwtListView.prototype._getCell =
1581 function(htmlArr, idx, item, field, colIdx, params) {
1582 	if (this.useListElement()) {
1583 		var classes = [];
1584 		var className = this._getCellClass(item, field, params);
1585 		if (className) {
1586 			classes.push(className);
1587 		}
1588 		idx = this._getCellContents(htmlArr, idx, item, field, colIdx, params, [className || ""])
1589 	} else {
1590 		var cellId = this._getCellId(item, field, params);
1591 		var idText = cellId ? [" id=", "'", cellId, "'"].join("") : "";
1592 		var width = this._getCellWidth(colIdx, params);
1593 		var widthText = width ? ([" width=", width].join("")) : (" width='100%'");
1594 		var className = this._getCellClass(item, field, params);
1595 		var classText = className ? [" class=", className].join("") : "";
1596 		var alignValue = this._getCellAlign(colIdx, params);
1597 		var alignText = alignValue ? [" align=", alignValue].join("") : "";
1598 		var otherText = (this._getCellAttrText(item, field, params)) || "";
1599 		var attrText = [idText, widthText, classText, alignText, otherText].join(" ");
1600 		htmlArr[idx++] = "<td";
1601 		htmlArr[idx++] = attrText ? (" " + attrText) : "";
1602 		htmlArr[idx++] = ">";
1603 		idx = this._getCellContents(htmlArr, idx, item, field, colIdx, params);
1604 		htmlArr[idx++] = "</td>";
1605 	}
1606 
1607 	return idx;
1608 };
1609 
1610 /**
1611  * Returns the width that should be used for the TD, based on the header setup.
1612  *
1613  * @param colIdx	[int]		index of column (starts at 0)
1614  * @param params	[hash]*		hash of optional params
1615  * 
1616  * @private
1617  */
1618 DwtListView.prototype._getCellWidth =
1619 function(colIdx, params) {
1620 	if (colIdx == null) { return null; }
1621 	// IE/Safari do not obey box model properly so we overcompensate :(
1622 	var headerList = params.headerList || this._headerList;
1623 	var width = headerList[colIdx]._width;
1624 	if (width) {
1625 		if (width != "auto" && width > 0) {
1626 			if (AjxEnv.isIE)		return (width + 2);
1627 			if (AjxEnv.isSafari && !AjxEnv.isSafari6up && !AjxEnv.isChrome19up) {
1628 				return (width + 5);
1629 			}
1630 		}
1631 		return width;
1632 	}
1633 	return null;
1634 };
1635 
1636 DwtListView.prototype._getCellAlign =
1637 function(colIdx, params) {
1638 	if (colIdx == null) { return null; }
1639 	// IE/Safari do not obey box model properly so we overcompensate :(
1640 	var headerList = params.headerList || this._headerList;
1641 	return headerList[colIdx]._align;
1642 };
1643 
1644 /**
1645  * Returns the DOM ID for the TD. The main reasons to provide an ID are to support
1646  * tooltips, and to be able to update cell content dynamically.
1647  *
1648  * @param item		[object]	item to render
1649  * @param field		[constant]	column identifier
1650  * @param params	[hash]*		hash of optional params
1651  * 
1652  * @private
1653  */
1654 DwtListView.prototype._getCellId =
1655 function(item, field, params) {
1656 	return null;
1657 };
1658 
1659 /**
1660  * Returns the class to be used for the TD.
1661  *
1662  * @param item		[object]	item to render
1663  * @param field		[constant]	column identifier
1664  * @param params	[hash]*		hash of optional params
1665  * 
1666  * @private
1667  */
1668 DwtListView.prototype._getCellClass =
1669 function(item, field, params) {
1670 	return null;
1671 };
1672 
1673 /**
1674  * Returns a string of any extra attributes to be used for the TD.
1675  *
1676  * @param item		[object]	item to render
1677  * @param field		[constant]	column identifier
1678  * @param params	[hash]*		hash of optional params
1679  * 
1680  * @private
1681  */
1682 DwtListView.prototype._getCellAttrText =
1683 function(item, field, params) {
1684 	return null;
1685 };
1686 
1687 /**
1688  * Fills the TD with content. The default implementation converts the item
1689  * to a string and uses that.
1690  *
1691  * @param htmlArr	[array]		array that holds lines of HTML
1692  * @param idx		[int]		current line of array
1693  * @param item		[object]	item to render
1694  * @param field		[constant]	column identifier
1695  * @param colIdx	[int]		index of column (starts at 0)
1696  * @param params	[hash]*		hash of optional params
1697  * 
1698  * @private
1699  */
1700 DwtListView.prototype._getCellContents =
1701 function(htmlArr, idx, item, field, colIdx, params) {
1702 	htmlArr[idx++] = item.toString ? item.toString() : item;
1703 	return idx;
1704 };
1705 
1706 /**
1707  * Returns a DOM ID for the given field within the given item.
1708  *
1709  * @param item		[object]	item to render
1710  * @param field		[constant]	column identifier
1711  * 
1712  * @private
1713  */
1714 DwtListView.prototype._getFieldId =
1715 function(item, field) {
1716 	return DwtId.getListViewItemId(DwtId.WIDGET_ITEM_FIELD, this._view, item.id, field);
1717 };
1718 
1719 /**
1720  * Returns the element that represents the given field of the given item.
1721  * Typically returns either a TD or an img DIV.
1722  *
1723  * @param item		[object]	item to render
1724  * @param field		[constant]	column identifier
1725  * 
1726  * @private
1727  */
1728 DwtListView.prototype._getElement =
1729 function(item, field) {
1730 	return document.getElementById(this._getFieldId(item, field));
1731 };
1732 
1733 DwtListView.prototype._getDragProxy =
1734 function(dragOp) {
1735 	var dndSelection = this.getDnDSelection();
1736 	if (!dndSelection) { return null; }
1737 
1738 	var icon;
1739 	var div;
1740 	var roundPlusStyle;
1741 	this._dndImg = null;
1742 
1743 	if (!(dndSelection instanceof Array) || dndSelection.length == 1) {
1744 		var item = (dndSelection instanceof Array) ? dndSelection[0] : dndSelection;
1745 		icon = this._createItemHtml(item, {now:new Date(), isDragProxy:true});
1746 		this._setItemData(icon, "origClassName", icon.className);
1747 		Dwt.setPosition(icon, Dwt.ABSOLUTE_STYLE);
1748 
1749 		roundPlusStyle = "position:absolute;top:18;left:-11;visibility:hidden";
1750 	} else {
1751 		// Create multi one
1752 		icon = document.createElement("div");
1753 		icon.className = "DragProxy";
1754 		Dwt.setPosition(icon, Dwt.ABSOLUTE_STYLE);
1755 
1756 		AjxImg.setImage(icon, "DndMultiYes_48");
1757 		this._dndImg = icon;
1758 
1759 		div = document.createElement("div");
1760 		Dwt.setPosition(div, Dwt.ABSOLUTE_STYLE);
1761 		var text = this.allSelected ? ZmMsg.all : dndSelection.length;
1762 		div.innerHTML = "<table><tr><td class='DragProxyTextLabel'>"
1763 						+ text + "</td></tr></table>";
1764 		icon.appendChild(div);
1765 
1766 		roundPlusStyle = "position:absolute;top:30;left:0;visibility:hidden";
1767 
1768 		// The size of the Icon is envelopeImg.width + sealImg.width - 20, ditto for height
1769 		Dwt.setBounds(icon, Dwt.LOC_NOWHERE, Dwt.LOC_NOWHERE, 43 + 32 - 16, 36 + 32 - 20);
1770 	}
1771 
1772 	var imgHtml = AjxImg.getImageHtml("RoundPlus", roundPlusStyle, "id=" + DwtId.DND_PLUS_ID);
1773 	if (!this._noDndPlusImage) {
1774 		icon.appendChild(Dwt.parseHtmlFragment(imgHtml));
1775 	}
1776 
1777 	this.shell.getHtmlElement().appendChild(icon);
1778 
1779 	// If we have multiple items selected, then we have our cool little dnd icon,
1780 	// so position the text in the middle of the seal
1781 	if (div) {
1782 		var sz = Dwt.getSize(div);
1783 		Dwt.setLocation(div, 16 + (32 - sz.x) / 2, 19 + (32 - sz.y) / 2);
1784 	}
1785 
1786 	Dwt.setZIndex(icon, Dwt.Z_DND);
1787 	return icon;
1788 };
1789 
1790 DwtListView.prototype._setDragProxyState =
1791 function(dropAllowed) {
1792 	// If we are moving multiple items then set borders & icons, else delegate up
1793 	// to DwtControl.prototype._setDragProxyState()
1794 	if (this._dndImg) {
1795 		AjxImg.setImage(this._dndImg, dropAllowed ? "DndMultiYes_48" : "DndMultiNo_48");
1796 	} else if (this._dndProxy) {
1797 		var addClass = dropAllowed ? DwtCssStyle.DROPPABLE : DwtCssStyle.NOT_DROPPABLE;
1798 		var origClass = this._getItemData(this._dndProxy, "origClassName");
1799 		this._dndProxy.className = [origClass, addClass].join(" ");
1800 	}
1801 };
1802 
1803 DwtListView.prototype._setNoResultsHtml =
1804 function() {
1805 	var	div = document.createElement("div");
1806 	var subs = {
1807 		message: this._getNoResultsMessage(),
1808 		type: this.type
1809 	};
1810 	div.innerHTML = AjxTemplate.expand("dwt.Widgets#DwtListView-NoResults", subs);
1811 	this._addRow(div);
1812 };
1813 
1814 DwtListView.prototype._getNoResultsMessage =
1815 function() {
1816 	return AjxMsg.noResults;
1817 };
1818 
1819 DwtListView.prototype._clearRightSel =
1820 function() {
1821 	if (!this._rightSelItem) {
1822 		return;
1823 	}
1824 	Dwt.delClass(this._rightSelItem, this._rightClickClass);
1825 	this._rightSelItem = null;
1826 	if (!this._curViewedItem) {
1827 		return;
1828 	}
1829 	this.deselectAll();
1830 	this.selectItem(this._curViewedItem, true);
1831 };
1832 
1833 DwtListView.prototype._getItemId =
1834 function(item) {
1835 	return DwtId.getListViewItemId(DwtId.WIDGET_ITEM, this._view, (item && item.id) ? item.id : Dwt.getNextId());
1836 };
1837 
1838 DwtListView.prototype._getElFromItem =
1839 function(item) {
1840 	return Dwt.byId(this._getItemId(item));
1841 };
1842 
1843 // returns the index of the given item based on the position of the row
1844 // in this list view that represents it
1845 DwtListView.prototype._getRowIndex =
1846 function(item) {
1847 	var id = this._getItemId(item);
1848 	var childNodes = this._parentEl.childNodes;
1849 	for (var i = 0; i < childNodes.length; i++) {
1850 		if (childNodes[i].id == id) {
1851 			return i;
1852 		}
1853 	}
1854 	return null;
1855 };
1856 
1857 /**
1858  * Returns data associated with the given element.
1859  * 
1860  * @param el		[Element]	an HTML element
1861  * @param field		[string]	key for desired data
1862  * @param id		[string]*	ID that overrides element ID (or if element is not provided)
1863  * 
1864  * @private
1865  */
1866 DwtListView.prototype._getItemData =
1867 function(el, field, id) {
1868 	id = id || (el ? el.id : null);
1869 	var data = this._data[id];
1870 	return data ? data[field] : null;
1871 };
1872 
1873 /**
1874  * Sets data associated with the given element.
1875  * 
1876  * @param el		[Element]	an HTML element
1877  * @param field		[string]	key
1878  * @param value		[object]	value
1879  * @param id		[string]*	ID that overrides element ID (or if element is not provided)
1880  * 
1881  * @private
1882  */
1883 DwtListView.prototype._setItemData =
1884 function(el, field, value, id) {
1885 	id = id || (el ? el.id : null);
1886 	var data = this._data[id];
1887 	if (data) {
1888 		data[field] = value;
1889 	}
1890 };
1891 
1892 // Return true only if the event occurred in one of our Divs. See DwtControl for more info
1893 DwtListView.prototype._isValidDragObject =
1894 function(ev) {
1895 	return (this.getTargetItemDiv(ev) != null);
1896 };
1897 
1898 DwtListView.prototype._updateDragSelection =
1899 function(row, select) {
1900 
1901 	if (!row) { return; }
1902 	
1903     if (!select) {
1904 		row.className = this._getItemData(row, "origClassName");
1905 	} else {
1906 		this._setItemData(row, "origClassName", row.className);
1907 		Dwt.delClass(row, this._styleRe, this._dndClass);
1908 	}
1909 };
1910 
1911 DwtListView.prototype._mouseOverAction =
1912 function(mouseEv, div) {
1913 	var type = this._getItemData(div, "type");
1914 	if (type == DwtListView.TYPE_HEADER_ITEM){
1915 		var hdr = this.getItemFromElement(div);
1916 		if (hdr && this.sortingEnabled && hdr._sortable && !this._headerClone) {
1917 			div.className += " DwtListView-ColumnHover";
1918 		}
1919 	} else if (type == DwtListView.TYPE_HEADER_SASH) {
1920 		div.style.cursor = AjxEnv.isIE ? "col-resize" : "e-resize";
1921 	}
1922 
1923 	return true;
1924 };
1925 
1926 DwtListView.prototype._mouseOutAction =
1927 function(mouseEv, div) {
1928 	var type = this._getItemData(div, "type");
1929 	if (type == DwtListView.TYPE_HEADER_ITEM && !this._headerClone) {
1930 		div.className = (div.id != this._currentColId)
1931 			? "DwtListView-Column"
1932 			: "DwtListView-Column DwtListView-ColumnActive";
1933         var hdr = this.getItemFromElement(div);
1934         if (!hdr._sortable)
1935             div.className += " DwtDefaultCursor";
1936 	} else if (type == DwtListView.TYPE_HEADER_SASH) {
1937 		div.style.cursor = "auto";
1938 	}
1939 
1940 	return true;
1941 };
1942 
1943 DwtListView.prototype._mouseOverListener =
1944 function(ev) {
1945 	var div = this.getTargetItemDiv(ev);
1946 	if (!div) { return; }
1947 
1948 	this._mouseOverAction(ev, div);
1949 };
1950 
1951 DwtListView.prototype._mouseOutListener =
1952 function(ev) {
1953 	var div = this.getTargetItemDiv(ev);
1954 	if (!div) { return; }
1955 
1956 	// NOTE: The DwtListView handles the mouse events on the list items
1957 	//		 that have associated tooltip text. Therefore, we must
1958 	//		 explicitly null out the tooltip content whenever we handle
1959 	//		 a mouse out event. This will prevent the tooltip from
1960 	//		 being displayed when we re-enter the listview even though
1961 	//		 we're not over a list item.
1962 	this.setToolTipContent(null);
1963 	this._mouseOutAction(ev, div);
1964 };
1965 
1966 DwtListView.prototype._mouseMoveListener =
1967 function(ev) {
1968 	if (!this._clickDiv) { return; }
1969 
1970 	var type = this._getItemData(this._clickDiv, "type");
1971 	if (type == DwtListView.TYPE_HEADER_ITEM) {
1972 		this._handleColHeaderMove(ev);
1973 	} else if (type == DwtListView.TYPE_HEADER_SASH) {
1974 		this._handleColHeaderResize(ev);
1975 	}
1976 };
1977 
1978 DwtListView.prototype._mouseDownListener =
1979 function(ev) {
1980 	var div = this.getTargetItemDiv(ev);
1981 
1982 	if (!div) {
1983 		this._dndSelection = null;
1984 	} else {
1985 		this._clickDiv = div;
1986 		if (this._getItemData(div, "type") != DwtListView.TYPE_LIST_ITEM) {
1987 			this._dndSelection = null;
1988 		} else {
1989 			this._dndSelection = (this._selectedItems.contains(div)) ? this._selectedItems : div;
1990 		}
1991 	}
1992 	this._mouseDownAction(ev, div);
1993 };
1994 
1995 DwtListView.prototype._mouseUpListener =
1996 function(ev) {
1997 	var div = this.getTargetItemDiv(ev);
1998 
1999 	var wasDraggingCol = this._handleColHeaderDrop(ev);
2000 	var wasDraggingSash = this._handleColSashDrop(ev);
2001 
2002 	if (!div || div != this._clickDiv || wasDraggingCol || wasDraggingSash) {
2003 		delete this._clickDiv;
2004 		this._mouseUpAction(ev, div);
2005 		return;
2006 	}
2007 	delete this._clickDiv;
2008 
2009 	var type = this._getItemData(div, "type");
2010 	if (this._headerList && type == DwtListView.TYPE_HEADER_ITEM) {
2011 		if (ev.button == DwtMouseEvent.LEFT) {
2012 			this._columnClicked(div, ev);
2013 		} else if (ev.button == DwtMouseEvent.RIGHT) {
2014 			var actionMenu = this._colHeaderActionMenu = this._getActionMenuForColHeader();
2015 			if (actionMenu && actionMenu instanceof DwtMenu) {
2016 				actionMenu.popup(0, ev.docX, ev.docY);
2017 			}
2018 		}
2019 	} else if (type == DwtListView.TYPE_LIST_ITEM) {
2020 		// set item selection, then hand off to derived class for handling
2021 		if (ev.button == DwtMouseEvent.LEFT || ev.button == DwtMouseEvent.RIGHT) {
2022 			this._itemClicked(div, ev);
2023 		}
2024 	}
2025 	this._mouseUpAction(ev, div);
2026 };
2027 
2028 // allow subclasses to set props on mouse event
2029 DwtListView.prototype._mouseDownAction = function(mouseEv, div) {};
2030 DwtListView.prototype._mouseUpAction = function(mouseEv, div) {};
2031 
2032 DwtListView.prototype._doubleClickAction =
2033 function(mouseEv, div) {return true;};
2034 
2035 DwtListView.prototype._doubleClickListener =
2036 function(ev) {
2037 	var div = this.getTargetItemDiv(ev);
2038 	if (!div) { return; }
2039 
2040 	var type = this._getItemData(div, "type");
2041 	if (type == DwtListView.TYPE_LIST_ITEM) {
2042 		if (!this._doubleClickAction(ev, div)) {
2043 			return;
2044 		}
2045 		if (this._evtMgr.isListenerRegistered(DwtEvent.SELECTION)) {
2046 			DwtUiEvent.copy(this._selEv, ev);
2047 			this._selEv.item = this.getItemFromElement(div);
2048 			this._selEv.detail = DwtListView.ITEM_DBL_CLICKED;
2049 			this._evtMgr.notifyListeners(DwtEvent.SELECTION, this._selEv);
2050 		}
2051 	}
2052 };
2053 
2054 DwtListView.prototype.emulateDblClick =
2055 function(item, kbNavEvent) {
2056 	var div = document.getElementById(this._getItemId(item));
2057 	if (div) {
2058 		var mev = new DwtMouseEvent();
2059 		this._setMouseEvent(mev, {target:div, button:DwtMouseEvent.LEFT});
2060 		mev.kbNavEvent = kbNavEvent;
2061 		this._itemClicked(div, mev);
2062 		this._doubleClickListener(mev);
2063 	}
2064 };
2065 
2066 DwtListView.prototype._selectItem =
2067 function(next, addSelect, kbNavEvent) {
2068 	// If there are no elements in the list, then bail
2069 	if (!this.size()) { return; }
2070 
2071 	// if there is currently a selection anchor, then find the next/prev item
2072 	// from the anchor
2073 	var itemDiv = (this._kbAnchor)
2074 		? this._getSiblingElement(this._kbAnchor, next)
2075 		: this._parentEl.firstChild;
2076 
2077 	this._scrollList(itemDiv);
2078 	this._emulateSingleClick({target:itemDiv, button:DwtMouseEvent.LEFT, shiftKey:addSelect, kbNavEvent:kbNavEvent});
2079 };
2080 
2081 DwtListView.prototype._getSiblingElement =
2082 function(element, next) {
2083 	if (!element) { return null; }
2084 
2085 	var el = next ? element.nextSibling : element.previousSibling;
2086 	while (this._hasHiddenRows && el && !Dwt.getVisible(el)) {
2087 		el = next ? el.nextSibling : el.previousSibling;
2088 	}
2089 	return (!el || (this._hasHiddenRows && !Dwt.getVisible(el))) ? element : el;
2090 };
2091 
2092 /**
2093  * This method will scroll the list to ensure that <code>itemDiv</code> is
2094  * scrolled into view.
2095  * 
2096  * @private
2097  */
2098 DwtListView.prototype._scrollList =
2099 function(itemDiv) {
2100 	Dwt.scrollIntoView(itemDiv, itemDiv.parentNode);
2101 };
2102 
2103 DwtListView.prototype._setRowHeight =
2104 function() {
2105 	if (!this._rowHeight) {
2106 		var row = this._parentEl.firstChild;
2107 		this._rowHeight = row && Dwt.getSize(row).y;
2108 	}
2109 };
2110 
2111 DwtListView.prototype._emulateSingleClick =
2112 function(params) {
2113 	this._clickDiv = this.findItemDiv(params.target);
2114 	var mev = new DwtMouseEvent();
2115 	this._setMouseEvent(mev, params);
2116 	mev.kbNavEvent = params.kbNavEvent;
2117 	this.notifyListeners(DwtEvent.ONMOUSEUP, mev);
2118 };
2119 
2120 /**
2121  * Sets the anchor row for selection and keyboard nav.
2122  *
2123  * Please note that merely assigning to this._kbAnchor is insufficient;
2124  * accessibility technologies require that the element receive browser focus as
2125  * well.
2126  *
2127  * @private
2128  *
2129  * @param {boolean|Element}		next	row to make anchor, or if true, move anchor
2130  * 										to next row
2131  */
2132 DwtListView.prototype._setKbFocusElement = function(next, noSetFocus) {
2133 
2134 	// If there are no elements in the list, then bail
2135 	if (!this._list || !this._list.size()) {
2136 		this._kbAnchor = null;
2137 		this.setFocusElement(this.getHtmlElement());
2138         return;
2139     }
2140 
2141 	if (this._kbAnchor) {
2142         this._setEventHdlrs([ DwtEvent.ONFOCUS, DwtEvent.ONBLUR ], true, this._kbAnchor);
2143 		this._unmarkKbAnchorElement();
2144 	}
2145 
2146 	if (next && next !== true) {
2147 		this._kbAnchor = next;
2148 	}
2149     else if (this._kbAnchor) {
2150 		this._kbAnchor = this._getSiblingElement(this._kbAnchor, next);
2151 	}
2152     else {
2153 		this._kbAnchor = this._parentEl.firstChild;
2154 	}
2155 
2156     this.setFocusElement(this._kbAnchor);
2157 
2158     if (this._kbAnchor && !noSetFocus) {
2159 		Dwt.addClass(this._kbAnchor, this._kbFocusClass);
2160 
2161 		if (!this._duringFocusByMouseDown) {
2162 			this._scrollList(this._kbAnchor);
2163 		}
2164 
2165         var kbMgr = this.shell.getKeyboardMgr();
2166 		if (this.hasFocus() || kbMgr.getFocusObj() === this) {
2167             kbMgr.grabFocus(this);
2168 		}
2169 	}
2170 };
2171 
2172 DwtListView.prototype._itemSelected =
2173 function(itemDiv, ev) {
2174 	if (this._allowLeftSelection(itemDiv, ev, ev && ev.button)) {
2175 		/* Unmark the KB focus element. We need to do this because it is
2176 		 * possible for this element to not be the same as the selection
2177 		 * anchor due to NEXT and PREV keyboard actions */
2178 		this._unmarkKbAnchorElement(true);
2179 
2180 		// clear out old left click selection(s)
2181 		this.deselectAll();
2182 
2183 		// save new left click selection
2184 		this._selectedItems.add(itemDiv);
2185 		itemDiv.setAttribute('aria-selected', true);
2186 
2187 		this._setKbFocusElement(itemDiv);
2188 		this._selAnchor = itemDiv;
2189 		Dwt.delClass(itemDiv, this._styleRe, this._selectedClass);
2190 		if (this.hasFocus()) {
2191 			Dwt.addClass(itemDiv, this._kbFocusClass);
2192 		}
2193 
2194 		var item = this.getItemFromElement(itemDiv);
2195 		//since we now select a new item, unmark the list item that was marked as viewed but unselected (if any)
2196 		this._markUnselectedViewedItem(false);
2197 		this._curViewedItem = item;
2198 		this.firstSelIndex = (this._list && item) ? this._list.indexOf(item) : -1;
2199 		//DwtKeyboardMgr.grabFocus(this);
2200 	}
2201 };
2202 
2203 DwtListView.prototype._itemClicked =
2204 function(clickedEl, ev) {
2205 
2206 	// always clear out old right click selection
2207 	if (this._rightSelItem) {
2208 		Dwt.delClass(this._rightSelItem, this._styleRe);	// , this._normalClass	MOW
2209 		this._rightSelItem = null;
2210 	}
2211 
2212 	var numSelectedItems = this._selectedItems.size();
2213 	var bContained = this._selectedItems.contains(clickedEl);
2214 
2215 	if ((!ev.shiftKey && !ev.ctrlKey) || !this.isMultiSelectEnabled()) {
2216 		// always reset detail if left/right click
2217 		if (ev.button == DwtMouseEvent.LEFT || ev.button == DwtMouseEvent.RIGHT) {
2218 			this._selEv.detail = DwtListView.ITEM_SELECTED;
2219 		}
2220 
2221 		if (ev.button == DwtMouseEvent.LEFT) {
2222 			this._itemSelected(clickedEl, ev);
2223 		}
2224 		else if (ev.button == DwtMouseEvent.RIGHT && !bContained && this._evtMgr.isListenerRegistered(DwtEvent.ACTION)) {
2225 			// Right click - OUTSIDE of selection
2226 			// Deselect all - otherwise, we can have a selection that is already showing,
2227 			// but the context menu is not applied to it - very confusing
2228 			this.deselectAll();
2229 			this._markUnselectedViewedItem(true);
2230 
2231 			// save right click selection
2232 			this._rightSelItem = clickedEl;
2233             Dwt.delClass(clickedEl, this._styleRe, this._rightClickClass);
2234 
2235             this._setKbFocusElement(clickedEl, true);
2236 		}
2237 	}
2238 	else if (ev.button == DwtMouseEvent.LEFT) {
2239 		if (ev.ctrlKey) {
2240 			this.setMultiSelection(clickedEl, bContained, ev);
2241 		} else { // SHIFT KEY
2242 			// Adds to the selection to/from the current node to the selection anchor
2243 			if (!this._selAnchor) { return; }
2244 			var els = this._getChildren() || clickedEl.parentNode.childNodes;
2245 			var numEls = els.length;
2246 			var el;
2247 			var state = 0;
2248 			this._rightSelItem = null;
2249 
2250 			this._selectedItems.removeAll();
2251 			for (var i = 0; i < numEls; i++) {
2252 				el = els[i];
2253 				var item = this.getItemFromElement(el);
2254 				if (item === null) {
2255 					continue; //ignore separators
2256 				}
2257 
2258 				var selStyleClass = this._selectedClass;
2259 				var include = (state === 1);
2260 				if (el === clickedEl || el === this._selAnchor || el.id === clickedEl.id || el.id === this._selAnchor.id) {
2261 					/* Increment the state.
2262 					 * 0 - means we havent started
2263 					 * 1 - means we are in selection range
2264 					 * 2 - means we are out of selection range */
2265 					state++;
2266 					include = true; //the borders (clickedEl and _selAnchor) are both included in the selection.
2267 				}
2268 				if (include) {
2269 					this._selectedItems.add(el);
2270 					el.setAttribute('aria-selected', true);
2271 					Dwt.delClass(el, this._styleRe, selStyleClass);
2272 				}
2273 				else if (el.className.indexOf(selStyleClass) !== -1) {
2274 					Dwt.delClass(el, this._styleRe);		// , this._normalClass	MOW
2275 					el.removeAttribute('aria-selected');
2276 				}
2277 			}
2278 
2279 			this._setKbFocusElement(clickedEl);
2280 
2281 			var newSelectedItems = this._selectedItems.size();
2282 			if (numSelectedItems < newSelectedItems) {
2283 				this._selEv.detail = DwtListView.ITEM_SELECTED;
2284 			} else if (numSelectedItems > newSelectedItems) {
2285 				this._selEv.detail = DwtListView.ITEM_DESELECTED;
2286 			} else {
2287 				return;
2288 			}
2289 		}
2290 	}
2291 
2292 	if (ev.button == DwtMouseEvent.LEFT && this._evtMgr.isListenerRegistered(DwtEvent.SELECTION)) {
2293 		if (this._setListEvent(ev, this._selEv, clickedEl)) {
2294 			this._evtMgr.notifyListeners(DwtEvent.SELECTION, this._selEv);
2295 		}
2296 
2297 		if (!this.hasFocus()) {
2298 			this.focus();
2299 		}
2300 	} else if (ev.button == DwtMouseEvent.RIGHT && !ev.shiftKey && !ev.ctrlKey && this._evtMgr.isListenerRegistered(DwtEvent.ACTION)) {
2301 		if (this._setListEvent(ev, this._actionEv, clickedEl)) {
2302 			this._evtMgr.notifyListeners(DwtEvent.ACTION, this._actionEv);
2303 		}
2304 	}
2305 };
2306 
2307 DwtListView.prototype._focusByMouseDownEvent =
2308 function() {
2309 	// Do nothing, we'll focus manually later. If we focus now, the list will
2310 	// jump to the top before an item is selected
2311 };
2312 
2313 /**
2314  * Creates a list event from a mouse event. Returns true if it is okay to notify listeners.
2315  * Subclasses may override to add more properties to the list event.
2316  *
2317  * @param	[DwtEvent]		mouse event
2318  * @param	[DwtEvent]		list event (selection or action)
2319  * @param	[element]		HTML element that received mouse click
2320  * 
2321  * @private
2322  */
2323 DwtListView.prototype._setListEvent =
2324 function(ev, listEv, clickedEl) {
2325 	DwtUiEvent.copy(listEv, ev);
2326 	listEv.kbNavEvent = ev.kbNavEvent;
2327 	listEv.item = this.findItem(clickedEl);
2328 	return true;
2329 };
2330 
2331 DwtListView.prototype._columnClicked =
2332 function(clickedCol, ev) {
2333 	var hdr = this.getItemFromElement(clickedCol);
2334 	if (!(hdr._sortable && this.sortingEnabled)) { return; }
2335 
2336 	var list = this.getList();
2337 	var size = list ? list.size() : null;
2338 	var customQuery = this._columnHasCustomQuery(hdr);
2339 	if (!size && !customQuery) { return; }
2340 
2341 	// reset order by sorting preference
2342 	this._bSortAsc = (hdr._id === this._currentColId) ? !this._bSortAsc : this._isDefaultSortAscending(hdr);
2343 
2344 	// reset arrows as necessary
2345 	this._setSortedColStyle(hdr._id);
2346 
2347 	// call sorting callback if more than one item to sort
2348 	if (size >= 1 || customQuery) {
2349 		this._sortColumn(hdr, this._bSortAsc);
2350 	}
2351 };
2352 
2353 DwtListView.prototype._columnHasCustomQuery =
2354 function(columnItem) {
2355 	// overload me
2356 	return false;
2357 };
2358 
2359 DwtListView.prototype._sortColumn =
2360 function(columnItem, bSortAsc) {
2361 	// overload me
2362 };
2363 
2364 DwtListView.prototype._getActionMenuForColHeader =
2365 function() {
2366 	// overload me if you want action menu for column headers
2367 	return null;
2368 };
2369 
2370 DwtListView.prototype._isDefaultSortAscending =
2371 function(colHeader) {
2372 	// by default, always return ascending
2373 	return true;
2374 };
2375 
2376 DwtListView.prototype._allowLeftSelection =
2377 function(clickedEl, ev, button) {
2378 	// overload me (and return false) if you dont want to actually select clickedEl
2379 	return true;
2380 };
2381 
2382 DwtListView.prototype._setSortedColStyle = 
2383 function(columnId) {
2384 	
2385 	if (this._currentColId && (columnId != this._currentColId)) {
2386 		// unset current column arrow
2387 		var headerCol = this._headerIdHash[this._currentColId];
2388 		if (headerCol && !headerCol._noSortArrow) {
2389 			var field = headerCol._field;
2390 			var oldArrowId = DwtId.getListViewHdrId(DwtId.WIDGET_HDR_ARROW, this._view, field);
2391 			var oldArrowCell = document.getElementById(oldArrowId);
2392 			if (oldArrowCell && oldArrowCell.firstChild) {
2393 				var imgEl = (AjxImg._mode == AjxImg.SINGLE_IMG) ? oldArrowCell.firstChild : oldArrowCell.firstChild.firstChild;
2394 				if (imgEl) {
2395 					imgEl.style.visibility = "hidden";
2396 				}
2397 			}
2398 		}
2399 
2400 		// reset style for old sorted column
2401 		var oldSortedCol = document.getElementById(this._currentColId);
2402 		if (oldSortedCol) {
2403 			oldSortedCol.className = "DwtListView-Column";
2404 		}
2405 	}
2406 	this._currentColId = columnId;
2407 	var headerCol = this._headerIdHash[this._currentColId];
2408 
2409 	// set new column arrow
2410 	if (!headerCol._noSortArrow) {
2411 		var field = headerCol._field;
2412 		var newArrowId = DwtId.getListViewHdrId(DwtId.WIDGET_HDR_ARROW, this._view, field);
2413 		var newArrowCell = document.getElementById(newArrowId);
2414 		if (newArrowCell) {
2415 			AjxImg.setImage(newArrowCell, this._bSortAsc ? "ColumnUpArrow" : "ColumnDownArrow");
2416 			var imgEl = (AjxImg._mode == AjxImg.SINGLE_IMG) ? newArrowCell.firstChild : newArrowCell.firstChild.firstChild;
2417 			if (imgEl) {
2418 				imgEl.style.visibility = "visible";
2419 			}
2420 		}
2421 	}
2422 	
2423 	// set new column style
2424 	var newSortedCol = document.getElementById(columnId);
2425 	if (newSortedCol) {
2426 		newSortedCol.className = "DwtListView-Column DwtListView-ColumnActive";
2427 	}
2428 };
2429 
2430 DwtListView.prototype._resetList =
2431 function() {
2432 	// clear out old list to force GC
2433 	if (this._list && this._list.size()) {
2434 		this._list.removeAll();
2435 	}
2436 	this._resetListView();
2437 };
2438 
2439 DwtListView.prototype._resetListView =
2440 function() {
2441 	// explicitly remove each child (setting innerHTML causes mem leak)
2442 	var cDiv;
2443 	while (this._parentEl && this._parentEl.hasChildNodes()) {
2444 		var cDiv = this._parentEl.removeChild(this._parentEl.firstChild);
2445 		this._data[cDiv.id] = null;
2446 	}
2447 	if (this._selectedItems) {
2448 		this._selectedItems.removeAll();
2449 	}
2450 	this._rightSelItem = null;
2451 };
2452 
2453 DwtListView.prototype._destroyDragProxy =
2454 function(icon) {
2455 	this._data[icon.id] = null;
2456 	DwtControl.prototype._destroyDragProxy.call(this, icon);
2457 };
2458 
2459 DwtListView.prototype._handleColHeaderMove = 
2460 function(ev) {
2461 	if (!this._headerClone) {
2462 		if (!this._headerColX) {
2463 			this._headerColX = ev.docX;
2464 			return;
2465 		} else {
2466 			var threshold = Math.abs(this._headerColX - ev.docX);
2467 			if (threshold < DwtListView.COL_MOVE_THRESHOLD) { return; }
2468 		}
2469 		
2470 		// create a clone of the selected column to move
2471 		this._headerClone = document.createElement("div");
2472 		var size = Dwt.getSize(this._clickDiv);
2473 		var width = AjxEnv.isIE ? size.x : size.x - 3;	// browser quirks
2474 		var height = AjxEnv.isIE ? size.y : size.y - 5;
2475 		Dwt.setSize(this._headerClone, width, height);
2476 		Dwt.setPosition(this._headerClone, Dwt.ABSOLUTE_STYLE); 
2477 		Dwt.setZIndex(this._headerClone, Dwt.Z_DND);
2478 		Dwt.setLocation(this._headerClone, Dwt.DEFAULT, ev.docY);
2479 		
2480 		this._headerClone.className = this._clickDiv.className + " DragProxy";
2481 		this._headerClone.innerHTML = this._clickDiv.innerHTML;
2482 		this._clickDiv.className = "DwtListView-Column DwtListView-ColumnEmpty";
2483 		
2484 		// XXX: style hacks - improve this later
2485 		this._headerClone.style.borderTop = "1px solid #777777";
2486 
2487 		var headerCol = this._headerIdHash[this._clickDiv.id];
2488 		var field = headerCol._field;
2489 		var hdrLabelId = DwtId.getListViewHdrId(DwtId.WIDGET_HDR_LABEL, this._view, field);
2490 		var labelCell = document.getElementById(hdrLabelId);
2491 		if (labelCell) {
2492 			labelCell.style.color = "#FFFFFF";
2493 		}
2494 		this.shell.getHtmlElement().appendChild(this._headerClone);
2495 	} else {
2496 		var div = this.getTargetItemDiv(ev);
2497 		var type = this._getItemData(div, "type");
2498 		if (type == DwtListView.TYPE_HEADER_ITEM) {
2499 			if (this._headerCloneTarget && (this._headerCloneTarget == this._clickDiv)) {
2500 				this._headerCloneTarget = null;
2501 			} else if (this._headerCloneTarget != div) { 
2502 				this._headerCloneTarget = div;
2503 			}
2504 		} else {
2505 			this._headerCloneTarget = null;
2506 		}
2507 	}
2508 
2509 	if (this._headerClone) {
2510 		Dwt.setLocation(this._headerClone, ev.docX + 2);
2511 	}
2512 };
2513 
2514 DwtListView.prototype._handleColHeaderResize = 
2515 function(ev) {
2516 	if (!this._headerSash) {
2517 		this._headerSash = document.createElement("div");
2518 
2519 		Dwt.setSize(this._headerSash, Dwt.DEFAULT, this.getSize().y);
2520 		Dwt.setPosition(this._headerSash, Dwt.ABSOLUTE_STYLE); 
2521 		Dwt.setZIndex(this._headerSash, Dwt.Z_DND);
2522 		var sashLoc = this._getHeaderSashLocation();
2523 		this._headerSashFudgeX = sashLoc.x;
2524 		Dwt.setLocation(this._headerSash, Dwt.DEFAULT, sashLoc.y);
2525 
2526 		this._headerSash.className = "DwtListView-ColumnSash";
2527 		this.getHtmlElement().appendChild(this._headerSash);
2528 		
2529 		// remember the initial x-position
2530 		this._headerSashX = ev.docX;
2531 	}
2532 	
2533 	// always update the sash's position
2534 	var parent = this._getParentForColResize();
2535 	var loc = Dwt.toWindow(parent.getHtmlElement(), 0 ,0);
2536 	Dwt.setLocation(this._headerSash, (ev.docX - loc.x) + this._headerSashFudgeX);
2537 };
2538 
2539 DwtListView.prototype._getHeaderSashLocation =
2540 function() {
2541 	if (!this._tmpPoint) {
2542 		this._tmpPoint = new DwtPoint();
2543 	}
2544 	this._tmpPoint.x = 0;
2545 	this._tmpPoint.y = 0;
2546 	return this._tmpPoint;
2547 };
2548 
2549 DwtListView.prototype._handleColHeaderDrop = 
2550 function(ev) {
2551 	this._headerColX = null;
2552 
2553 	if (this._headerClone == null || ev.button == DwtMouseEvent.RIGHT) { return false; }
2554 	
2555 	// did the user drop the column on a valid target?
2556 	if (this._headerCloneTarget) {
2557 		var divItemIdx = this._getItemData(this._clickDiv, "index");
2558 		var tgtItemIdx = this._getItemData(this._headerCloneTarget, "index");
2559 		this._reIndexColumn(divItemIdx, tgtItemIdx);
2560 	}
2561 
2562 	this._clickDiv.className = (this._clickDiv.id != this._currentColId)
2563 		? "DwtListView-Column" : "DwtListView-Column DwtListView-ColumnActive";
2564 
2565 	// clean up
2566 	var parent = this._headerClone.parentNode;
2567 	if (parent) {
2568 		parent.removeChild(this._headerClone);
2569 	}
2570 	delete this._headerClone;
2571 
2572 	var data = this._data[this._clickDiv.id];
2573 	if (data.type != DwtListView.TYPE_HEADER_ITEM) {
2574 		// something is messed up! redraw the header
2575 		var headerCol = this._headerIdHash[this._currentColId];
2576 		var sortField = headerCol._sortable ? headerCol._field : null;
2577 		this.headerColCreated = false;
2578 		this.createHeaderHtml(sortField);
2579 	} else {
2580 		// reset styles as necessary
2581 		var headerCol = this._headerIdHash[this._clickDiv.id];
2582 		var hdrLabelId = DwtId.getListViewHdrId(DwtId.WIDGET_HDR_LABEL, this._view, headerCol._field);
2583 		var labelCell = document.getElementById(hdrLabelId);
2584 		if (labelCell) {
2585 			labelCell.style.color = "#000000";
2586 		}
2587 	}
2588 
2589 	// force all relative widths to be static
2590 	for (var i = 0; i < this._headerList.length; i++) {
2591 		this._headerList[i]._width = this._calcRelativeWidth(i);
2592 	}
2593 
2594 	this._resetColWidth();
2595 
2596 	return true;
2597 };
2598 
2599 DwtListView.prototype._reIndexColumn =
2600 function(columnIdx, newIdx) {
2601 	// do some sanity checks before continuing
2602 	if (!this._headerList) { return; }
2603 	var len = this._headerList.length;
2604 	if (columnIdx < 0 || newIdx < 0 || columnIdx >= len || newIdx >= len || columnIdx == newIdx) { return; }
2605 
2606 	// reindex the header list
2607 	var temp = this._headerList.splice(columnIdx, 1);
2608 	this._headerList.splice(newIdx, 0, temp[0]);
2609 
2610 	// finally, relayout the list view (incl. header columns)
2611 	this._relayout();
2612 };
2613 
2614 /**
2615  * Per bug #15853, the change in column width will remove width from the last
2616  * column unless the change makes the width of the last column less than
2617  * MIN_COLUMN_WIDTH.
2618  *
2619  * @param ev
2620  * 
2621  * @private
2622  */
2623 DwtListView.prototype._handleColSashDrop =
2624 function(ev) {
2625 	if (this._headerSash == null || ev.button == DwtMouseEvent.RIGHT) {	return false; }
2626 		
2627 	// destroy the sash
2628 	var parent = this._headerSash.parentNode;
2629 	if (parent) {
2630 		parent.removeChild(this._headerSash);
2631 	}
2632 	delete this._headerSash;
2633 
2634 	// force all relative widths to be static
2635 	for (var i = 0; i < this._headerList.length; i++) {
2636 		this._headerList[i]._width = this._calcRelativeWidth(i);
2637 	}
2638 
2639 	// find out where the user dropped the sash and update column width
2640 	var headerIdx = this._getItemData(this._clickDiv, "index");
2641 	if (headerIdx == null) { return false; }
2642 
2643 	var delta = ev.docX - this._headerSashX;
2644 
2645 	var fcol = this._headerList[headerIdx];
2646 
2647 	var col1 = fcol;
2648 	var col2;// = this._variableHeaderCol;
2649 	var resized = [];
2650 
2651 	if (delta < 0) {
2652 		if ((col1 == col2) || !col2) {
2653 			col2 = this._getNextResizeableColumnHeader(col1);
2654 		}
2655 		if (!col2) return false;
2656 		//delta =    - Math.min(fcol._width - DwtListView.MIN_COLUMN_WIDTH, -delta);
2657 		delta = Math.max(DwtListView.MIN_COLUMN_WIDTH - fcol._width, delta);
2658 		fcol._width = Math.max(fcol._width + delta, DwtListView.MIN_COLUMN_WIDTH);
2659 		col2._width = Math.max(this._calcRelativeWidth(col2._index) - delta, DwtListView.MIN_COLUMN_WIDTH);
2660 		resized.push(fcol._index, col2._index);
2661 		
2662 	} else if (delta > 0) {
2663 
2664 		var remain = delta;
2665 		while (remain > 0) {
2666 			if ((col1 == col2) || !col2) {
2667 				col2 = this._getNextResizeableColumnHeader(col1, [], false);
2668 			}
2669 			//if (!col2) return false;
2670 			if (!col2) {
2671 				delta -= remain;
2672 				break;
2673 			}
2674 			var col2width = this._calcRelativeWidth(col2._index);
2675 			var room = col2width - DwtListView.MIN_COLUMN_WIDTH;
2676 			
2677 			if (remain > room) { // There column is too small to be fully resized
2678 				remain -= room;
2679 				col2width = DwtListView.MIN_COLUMN_WIDTH;
2680 			} else { // The column is not too small; all the requested delta may be taken from this column
2681 				col2width -= remain;
2682 				remain = 0;
2683 			}
2684 			col2._width = col2width;
2685 			resized.push(col2._index);
2686 			col1 = col2;
2687 		}
2688 	
2689 		fcol._width = Math.max(fcol._width + delta, DwtListView.MIN_COLUMN_WIDTH);
2690 		resized.push(fcol._index);
2691 
2692 	}
2693 
2694 	var col = this._getNextResizeableColumnHeader(fcol, resized, true);
2695 	if (col) {
2696 		col._width = "auto";
2697 	}
2698 
2699 	this._relayout();
2700 	//recalculate the css styles after the width changes (_restColWidth calls recalculateCssStyle)
2701 	this._resetColWidth();
2702 
2703 	return true;
2704 };
2705 
2706 DwtListView.prototype.recalculateCssStyle =
2707 function() {
2708 	for (var i = 0; i < this._headerList.length; i++) {
2709 		var headerCol = this._headerList[i];
2710 		this._createHeaderCssStyle(headerCol, this._calcRelativeWidth(i));
2711 	}
2712 };
2713 
2714 DwtListView.prototype._calcRelativeWidth =
2715 function(headerIdx) {
2716 	var column = this._headerList[headerIdx];
2717 	if (!column._width || (column._width && column._width == "auto")) {
2718 		var cell = document.getElementById(column._id);
2719 		// UGH: clientWidth is 5px more than HTML-width (4px for IE)
2720 		return (cell) ? (cell.clientWidth - (AjxEnv.isIE ? 4 : 5)) : null;
2721 	}
2722 	return column._width;
2723 };
2724 
2725 // This method will add padding to the *last* column depending on whether
2726 // scrollbars are shown or not.
2727 DwtListView.prototype._resetColWidth =
2728 function() {
2729 
2730 	if (!this.headerColCreated) { return; }
2731 
2732 	var lastColIdx = this._getLastColumnIndex();
2733     if (lastColIdx) {
2734         var lastCol = this._headerList[lastColIdx];
2735         var lastCell = document.getElementById(lastCol._id);
2736 		if (lastCell) {
2737 			var div = lastCell.firstChild;
2738 			var scrollbarPad = 16;
2739 
2740 			var headerWidth = this._listColDiv.clientWidth;
2741 			var rowWidth = this._listDiv.clientWidth;
2742 
2743 			if (headerWidth != rowWidth) {
2744 				lastCell.style.width = div.style.width = (lastCol._width != null && lastCol._width != "auto")
2745 					? (lastCol._width + scrollbarPad  + "px")
2746 					: (lastCell.clientWidth + scrollbarPad + "px");
2747 			}
2748 			else {
2749 				Dwt.setSize(lastCell, lastCol._width, Dwt.DEFAULT);
2750 				Dwt.setSize(div, lastCol._width, Dwt.DEFAULT);
2751 			}
2752 			this.recalculateCssStyle(); //make sure to call this AFTER modifying the last col width.
2753 		}
2754     }
2755 };
2756 
2757 /**
2758  * Dynamically get column index for last column b/c columns may or may not be
2759  * visible.
2760  */
2761 DwtListView.prototype._getLastColumnIndex =
2762 function() {
2763 	var lastColIdx = null;
2764 	if (this._headerList) {
2765 		var count = this._headerList.length - 1;
2766 		while (lastColIdx == null && count >= 0) {
2767 			if (this._headerList[count]._visible) {
2768 				lastColIdx = count;
2769 			}
2770 			count--;
2771 		}
2772 	}
2773 	return lastColIdx;
2774 };
2775 
2776 /**
2777  * Returns the index of the next resizeable (and visible) column after the one
2778  * with the given index. If it doesn't find one to the right, starts over at the
2779  * first column.
2780  *
2781  * @param start		[int]		index of reference column
2782  * @param exclude	[array]		list of indices to exclude
2783  * 
2784  * @private
2785  */
2786 DwtListView.prototype._getNextResizeableColumnIndex =
2787 function(start, exclude, wrap) {
2788 
2789 	exclude = exclude ? AjxUtil.arrayAsHash(exclude) : {};
2790 	exclude[start] = true;
2791 	if (this._headerList) {
2792 		for (var i = start + 1; i < this._headerList.length; i++) {
2793 			var col = this._headerList[i];
2794 			if (exclude[i]) { continue; }
2795 			if (col._visible && col._resizeable) {
2796 				return i;
2797 			}
2798 		}
2799 		if (wrap) {
2800 			for (var i = 0; i < start; i++) {
2801 				if (exclude[i]) { continue; }
2802 				var col = this._headerList[i];
2803 				if (col._visible && col._resizeable) {
2804 					return i;
2805 				}
2806 			}
2807 		}
2808 	}
2809 	return null;
2810 };
2811 
2812 DwtListView.prototype._getNextResizeableColumnHeader =
2813 function(start, exclude, wrap) {
2814 	var index = this._getNextResizeableColumnIndex(start._index, exclude, wrap);
2815 	return (index !== null) ? this._headerList[index] : false;
2816 }
2817 
2818 DwtListView.prototype._relayout =
2819 function() {
2820 	// force relayout of header column
2821 	this.headerColCreated = false;
2822 	var headerCol = this._headerIdHash[this._currentColId];
2823 	var sortField = (headerCol && headerCol._sortable) ? headerCol._field : null;
2824 	var sel = this.getSelection()[0];
2825 	this.setUI(sortField);
2826 	this.setSelection(sel, true);
2827 };
2828 
2829 DwtListView.prototype._getParentForColResize = 
2830 function() {
2831 	// overload me to return a higher inheritance chain parent
2832 	return this;
2833 };
2834 
2835 DwtListView.prototype._sizeChildren =
2836 function(height) {
2837 	if (this.headerColCreated && this._listDiv && (height != Dwt.DEFAULT)) {
2838 		Dwt.setSize(this._listDiv, Dwt.DEFAULT, height - DwtListView.HEADERITEM_HEIGHT);
2839 		return true;
2840 	} else {
2841 		return false;
2842 	}
2843 };
2844 
2845 // overload if parent element's children are not DIV's (i.e. div's w/in a table)
2846 DwtListView.prototype._getChildren = 
2847 function() {
2848 	return null;
2849 };
2850 
2851 DwtListView.prototype._focus =
2852 function() {
2853 	if (this.size() == 0) { return; }
2854 
2855 	if (this._kbAnchor) {
2856 		Dwt.addClass(this._kbAnchor, this._kbFocusClass);
2857 	} else {
2858 		this._setKbFocusElement(null, true);
2859 	}
2860 };
2861 
2862 DwtListView.prototype._blur =
2863 function() {
2864 	this._unmarkKbAnchorElement();
2865 };
2866 
2867 /**
2868  * Removes the "focus style" from the current KB anchor.
2869  * 
2870  * @param clear		[boolean]*		if true, clear KB anchor
2871  */
2872 DwtListView.prototype._unmarkKbAnchorElement =
2873 function(clear) {
2874 	if (this._kbAnchor) {
2875 		Dwt.delClass(this._kbAnchor, this._kbFocusClass);
2876 	}
2877 	if (clear) {
2878 		this._kbAnchor = null;
2879 	}
2880 };
2881 
2882 DwtListView.prototype._getFirstItem =
2883 function() {
2884 	var a = this._list.getArray();
2885 	if (a && a.length > 1) {
2886 		return a[0];
2887 	}
2888 	return null;
2889 };
2890 
2891 DwtListView.prototype._getLastItem =
2892 function() {
2893 	var a = this._list.getArray();
2894 	if (a && a.length > 1) {
2895 		return a[a.length - 1];
2896 	}
2897 	return null;
2898 };
2899 
2900 /**
2901  * DwtListHeaderItem
2902  * This is a (optional) "container" class for DwtListView objects which want a
2903  * column header to appear. Create a new DwtListViewItem for each column header
2904  * you want to appear. Be sure to specify width values (otherwise, undefined is
2905  * default)
2906  *
2907  * @param params		[hash]		hash of params:
2908  *        field			[int]		identifier for this column
2909  *        text	 		[string]*	text shown for the column
2910  *        icon	 		[string]*	icon shown for the column
2911  *        width 		[int]*		width of the column
2912  *        sortable 		[boolean]*	flag indicating whether column is sortable
2913  *        resizeable 	[boolean]*	flag indicating whether column can be resized
2914  *        visible 		[boolean]*	flag indicating whether column is initially visible
2915  *        name 			[string]*	description of column used if column headers have action menu
2916  * 									- if not supplied, uses label value. This param is
2917  *									primarily used for columns w/ only an icon (no label).
2918  *        align			[int]		alignment style of label
2919  *        noRemove		[boolean]*	flag indicating whether this column can be removed (overrides visible flag)
2920  *        view			[constant]	ID of owning view
2921  *        noSortArrow	[boolean]*	if true, do not show up/down sort arrow in column
2922  *        tooltip		[string]*	tooltip
2923  *        
2924  * @private
2925  */
2926 DwtListHeaderItem = function(params) {
2927 
2928 	if (arguments.length == 0) { return; }
2929 	params = Dwt.getParams(arguments, DwtListView.PARAMS);
2930 
2931 	this._field = params.field;
2932 	this._label = params.text;
2933 	this._iconInfo = params.icon;
2934 	this._sortable = params.sortable;
2935 	this._noSortArrow = params.noSortArrow;
2936 	this._resizeable = params.resizeable;
2937 	this._visible = (params.visible !== false); // default to visible
2938 	this._name = params.name || params.text;
2939 	this._align = params.align;
2940 	this._noRemove = params.noRemove;
2941 	this._tooltip = params.tooltip;
2942 	this._cssClass = params.cssClass;
2943 	
2944 	// width:
2945 	var w = parseInt(params.width);
2946 	if (isNaN(w) || !w) {
2947 		this._width = "auto";
2948 		this._variable = true;
2949 		this._resizeable = true;
2950 	} else if (String(w) == String(params.width)) {
2951 		this._width = w;
2952 	} else {
2953 		this._width = parseInt(String(params.width).substr(0, String(w).length));
2954 		this._widthUnits = AjxStringUtil.getUnitsFromSizeString(params.width);
2955 	}
2956 };
2957 
2958 DwtListHeaderItem.prototype.isDwtListHeaderItem = true;
2959 DwtListHeaderItem.prototype.toString = function() { return "DwtListHeaderItem"; };
2960 
2961 DwtListHeaderItem.PARAMS = ["id", "text", "icon", "width", "sortable", "resizeable", "visible", "name", "align", "noRemove", "view"];
2962 
2963 DwtListHeaderItem.sortCompare =
2964 function(a, b) {
2965 	return a._index < b._index ? -1 : (a._index > b._index ? 1 : 0);
2966 };
2967