/*
* ***** BEGIN LICENSE BLOCK *****
* Version: ZAPL 1.1
*
* The contents of this file are subject to the Zimbra AJAX Public
* License Version 1.1 ("License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
* http://www.zimbra.com/license
*
* Software distributed under the License is distributed on an "AS IS"
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
* the License for the specific language governing rights and limitations
* under the License.
*
* The Original Code is: Zimbra AJAX Toolkit.
*
* The Initial Developer of the Original Code is Zimbra, Inc.
* Portions created by Zimbra are Copyright (C) 2005 Zimbra, Inc.
* All Rights Reserved.
*
* Contributor(s):
*
* ***** END LICENSE BLOCK *****
*/
function DwtListView(parent, className, posStyle, headerList, noMaximize) {
if (arguments.length == 0) return;
className = className || "DwtListView";
DwtComposite.call(this, parent, className, posStyle);
if (headerList) {
var doc = this.getDocument();
var htmlElement = this.getHtmlElement();
this._listColDiv = doc.createElement("div");
this._listColDiv.id = Dwt.getNextId();
this._listColDiv.className = "DwtListView-ColHeader";
htmlElement.appendChild(this._listColDiv);
this._listDiv = doc.createElement("div");
this._listDiv.id = Dwt.getNextId();
this._listDiv.className = "DwtListView-Rows";
htmlElement.appendChild(this._listDiv);
// setup vars needed for sorting
this._bSortAsc = false;
this._currentColId = null;
} else {
this.setScrollStyle(DwtControl.SCROLL); // auto scroll
}
this._setMouseEventHdlrs();
this._setKeyEventHdlrs();
this.setCursor("default");
this._listenerMouseOver = new AjxListener(this, DwtListView.prototype._mouseOverListener);
this._listenerMouseOut = new AjxListener(this, DwtListView.prototype._mouseOutListener);
this._listenerMouseDown = new AjxListener(this, DwtListView.prototype._mouseDownListener);
this._listenerMouseUp = new AjxListener(this, DwtListView.prototype._mouseUpListener);
this._listenerMouseMove = new AjxListener(this, DwtListView.prototype._mouseMoveListener);
this._listenerDoubleClick = new AjxListener(this, DwtListView.prototype._doubleClickListener);
this.addListener(DwtEvent.ONMOUSEOVER, this._listenerMouseOver);
this.addListener(DwtEvent.ONMOUSEOUT, this._listenerMouseOut);
this.addListener(DwtEvent.ONMOUSEDOWN, this._listenerMouseDown);
this.addListener(DwtEvent.ONMOUSEUP, this._listenerMouseUp);
this.addListener(DwtEvent.ONMOUSEMOVE, this._listenerMouseMove);
this.addListener(DwtEvent.ONDBLCLICK, this._listenerDoubleClick);
this._evtMgr = new AjxEventMgr();
this._selectedItems = new AjxVector();
this._selAnchor = null;
this._selEv = new DwtSelectionEvent(true);
this._actionEv = new DwtListViewActionEvent(true);
this._stateChangeEv = new DwtEvent(true);
this._headerList = headerList;
this._noMaximize = noMaximize;
this._parentEl = this._headerList ? this._listDiv : this.getHtmlElement();
this._list = null;
this._offset = 0;
this._headerColCreated = false;
this._firstSelIndex = -1;
this.setMultiSelect(true);
}
DwtListView.ITEM_SELECTED = 1;
DwtListView.ITEM_DESELECTED = 2;
DwtListView.ITEM_DBL_CLICKED = 3;
DwtListView._LAST_REASON = 3;
DwtListView._TOOLTIP_DELAY = 250;
DwtListView.HEADERITEM_HEIGHT = 24;
DwtListView.HEADERITEM_ARROW = "arr--";
DwtListView.HEADER_ID = "crr--";
DwtListView.HEADERITEM_LABEL = "drr--";
DwtListView.TYPE_HEADER_ITEM = 1;
DwtListView.TYPE_LIST_ITEM = 2;
DwtListView.TYPE_HEADER_SASH = 3;
DwtListView.DEFAULT_LIMIT = 25;
DwtListView.MAX_REPLENISH_THRESHOLD = 10;
DwtListView.MIN_COLUMN_WIDTH = 10;
DwtListView.COL_MOVE_THRESHOLD = 3;
DwtListView.prototype = new DwtComposite;
DwtListView.prototype.constructor = DwtListView;
DwtListView.prototype.toString =
function() {
return "DwtListView";
}
DwtListView.prototype.setEnabled =
function(enabled) {
DwtComposite.prototype.setEnabled.call(this, enabled);
// always remove listeners to avoid adding listeners multiple times
this.removeListener(DwtEvent.ONMOUSEOVER, this._listenerMouseOver);
this.removeListener(DwtEvent.ONMOUSEOUT, this._listenerMouseOut);
this.removeListener(DwtEvent.ONMOUSEDOWN, this._listenerMouseDown);
this.removeListener(DwtEvent.ONMOUSEUP, this._listenerMouseUp);
this.removeListener(DwtEvent.ONMOUSEMOVE, this._listenerMouseMove);
this.removeListener(DwtEvent.ONDBLCLICK, this._listenerDoubleClick);
// now re-add listeners, if needed
if (enabled) {
this.addListener(DwtEvent.ONMOUSEOVER, this._listenerMouseOver);
this.addListener(DwtEvent.ONMOUSEOUT, this._listenerMouseOut);
this.addListener(DwtEvent.ONMOUSEDOWN, this._listenerMouseDown);
this.addListener(DwtEvent.ONMOUSEUP, this._listenerMouseUp);
this.addListener(DwtEvent.ONMOUSEMOVE, this._listenerMouseMove);
this.addListener(DwtEvent.ONDBLCLICK, this._listenerDoubleClick);
}
// modify selection classes
var selection = this.getSelectedItems();
if (selection) {
var elements = selection.getArray();
for (var i = 0; i < elements.length; i++) {
var element = elements[i];
element.className = enabled
? element._selectedStyleClass : element._selectedDisabledStyleClass;
}
}
}
DwtListView.prototype.createHeaderHtml =
function(defaultColumnSort) {
// does this list view have headers or have they already been created?
if (!this._headerList || this._headerColCreated)
return;
var idx = 0;
var htmlArr = new Array();
this._headerTableId = DwtListView.HEADER_ID + Dwt.getNextId();
htmlArr[idx++] = "
";
this._listColDiv.innerHTML = htmlArr.join("");
// for each sortable column, sets its identifier
for (var j = 0; j < this._headerList.length; j++) {
var cell = Dwt.getDomObj(this.getDocument(), this._headerList[j]._id);
if (cell == null) continue;
var sortable = this._headerList[j]._sortable;
if (sortable && sortable == defaultColumnSort)
cell.className = "DwtListView-Column DwtListView-ColumnActive";
var isResizeable = this._headerList[j]._resizeable;
if (isResizeable) {
// always get the sibling cell to the right
var sashCell = cell.firstChild.firstChild.rows[0].lastChild;
if (sashCell) {
sashCell._type = DwtListView.TYPE_HEADER_SASH;
sashCell._itemIndex = j + "--sash";
}
}
cell._isSortable = sortable != null;
cell._isResizeable = isResizeable;
cell._type = DwtListView.TYPE_HEADER_ITEM;
cell._itemIndex = j;
}
this._headerColCreated = true;
}
// this returns the index into the header list array for the given Id
DwtListView.prototype.getColIndexForId =
function(headerId) {
if (this._headerList) {
for (var i = 0; i < this._headerList.length; i++) {
if (this._headerList[i]._id.indexOf(headerId) != -1)
return i;
}
}
return -1;
};
/**
* Creates a list view out of the given vector of items. The derived class should override _createItemHtml()
* in order to display an item.
*
* @param list a vector of items (AjxVector)
* @param defaultColumnSort default column field to sort (optional)
*/
DwtListView.prototype.set =
function(list, defaultColumnSort) {
this._selectedItems.removeAll();
this._resetList();
this._list = list;
this._now = new Date();
this.setUI(defaultColumnSort);
}
DwtListView.prototype.setUI =
function(defaultColumnSort) {
this.removeAll();
this.createHeaderHtml(defaultColumnSort);
if (this._list instanceof AjxVector && this._list.size()) {
var size = this._list.size();
for (var i = 0; i < size; i++) {
var item = this._list.get(i);
var div = this._createItemHtml(item, this._now);
if (div) {
if (div instanceof Array) {
for (var j = 0; j < div.length; j++)
this._addRow(div[j]);
} else {
this._addRow(div);
}
}
}
} else {
this._setNoResultsHtml();
}
}
DwtListView.prototype.addItem =
function(item, index, skipNotify) {
if (!this._list)
this._list = new AjxVector();
// clear the "no results" message before adding!
if (this._list.size() == 0)
this._resetList();
this._list.add(item, index);
var div = this._createItemHtml(item, this._now);
if (div)
this._addRow(div, index);
if (!skipNotify && this._evtMgr.isListenerRegistered(DwtEvent.STATE_CHANGE)) {
this._evtMgr.notifyListeners(DwtEvent.STATE_CHANGE, this._stateChangeEv);
}
}
DwtListView.prototype.removeItem =
function(item, skipNotify) {
var itemEl = this._getElFromItem(item);
this._selectedItems.remove(itemEl);
this._parentEl.removeChild(itemEl);
this._list.remove(item);
if (!skipNotify && this._evtMgr.isListenerRegistered(DwtEvent.STATE_CHANGE)) {
this._evtMgr.notifyListeners(DwtEvent.STATE_CHANGE, this._stateChangeEv);
}
}
DwtListView.prototype.removeLastItem =
function(skipNotify) {
var last = this._list.get(this._list.size() - 1);
this._list.remove(last);
this._parentEl.removeChild(this._getElFromItem(last));
if (!skipNotify && this._evtMgr.isListenerRegistered(DwtEvent.STATE_CHANGE)) {
this._evtMgr.notifyListeners(DwtEvent.STATE_CHANGE, this._stateChangeEv);
}
}
DwtListView.prototype.reIndexColumn =
function(columnIdx, newIdx) {
// do some sanity checks before continuing
if (this._headerList == null)
return;
var len = this._headerList.length;
if (columnIdx < 0 || newIdx < 0 || columnIdx >= len || newIdx >= len || columnIdx == newIdx)
return;
// reindex the header list
var temp = this._headerList.splice(columnIdx, 1);
this._headerList.splice(newIdx, 0, temp[0]);
// finally, relayout the list view (incl. header columns)
this._relayout();
}
DwtListView.prototype.reSizeColumn =
function(headerIdx, newWidth) {
// TODO: do some (more?) sanity checks before changing the header width
if (newWidth == this._headerList._width || newWidth < DwtListView.MIN_COLUMN_WIDTH)
return;
this._headerList[headerIdx]._width = newWidth;
this._relayout();
}
// determine if col header needs padding to accomodate for scrollbars
DwtListView.prototype._resetColWidth =
function() {
if (this._headerList == null)
return;
// dynamically get col idx for last column (b/c col may or may not be turned on)
var count = this._headerList.length-1;
var lastColIdx = null;
while (lastColIdx == null && count >= 0) {
if (this._headerList[count]._visible)
lastColIdx = count;
count--;
}
var lastCell = Dwt.getDomObj(this.getDocument(), this._headerList[lastColIdx]._id);
var div = lastCell.firstChild;
var scrollbarPad = 16;
var headerWidth = this._listColDiv.clientWidth;
var rowWidth = this._listDiv.clientWidth;
if (headerWidth != rowWidth) {
lastCell.style.width = div.style.width = this._headerList[lastColIdx]._width
? (this._headerList[lastColIdx]._width + scrollbarPad)
: (lastCell.clientWidth + scrollbarPad);
} else {
lastCell.style.width = div.style.width = (this._headerList[lastColIdx]._width || "");
}
}
DwtListView.prototype.size =
function() {
return this._list.size();
}
DwtListView.prototype.setMultiSelect =
function (enabled) {
this._multiSelectEnabled = enabled;
};
DwtListView.prototype.isMultiSelectEnabled =
function () {
return this._multiSelectEnabled;
};
DwtListView.prototype._addRow =
function(row, index) {
// bug fix #1894 - check for childNodes length otherwise IE barfs
if (index != null && this._parentEl.childNodes.length > 0)
this._parentEl.insertBefore(row, this._parentEl.childNodes[index]);
else
this._parentEl.appendChild(row);
}
/**
* Renders a single item as a DIV element.
*
* @abstract
*/
DwtListView.prototype._createItemHtml = function(item, now, isDnDIcon) {}
DwtListView.prototype._setNoResultsHtml =
function() {
var htmlArr = new Array(5);
var div = this.getDocument().createElement("div");
var idx = 0;
htmlArr[idx++] = "";
htmlArr[idx++] = " ";
htmlArr[idx++] = DwtMsg.noResults;
htmlArr[idx++] = " |
";
div.innerHTML = htmlArr.join("");
this._addRow(div);
}
DwtListView.prototype.addSelectionListener =
function(listener) {
this._evtMgr.addListener(DwtEvent.SELECTION, listener);
}
DwtListView.prototype.removeSelectionListener =
function(listener) {
this._evtMgr.removeListener(DwtEvent.SELECTION, listener);
}
DwtListView.prototype.addActionListener =
function(listener) {
this._evtMgr.addListener(DwtEvent.ACTION, listener);
}
DwtListView.prototype.removeActionListener =
function(listener) {
this._evtMgr.removeListener(DwtEvent.ACTION, listener);
}
DwtListView.prototype.addStateChangeListener = function(listener) {
this._evtMgr.addListener(DwtEvent.STATE_CHANGE, listener);
}
DwtListView.prototype.removeStateChangeListener = function(listener) {
this._evtMgr.removeListener(DwtEvent.STATE_CHANGE, listener);
}
DwtListView.prototype.removeAll =
function(skipNotify) {
this._parentEl.innerHTML = "";
this._selectedItems.removeAll();
this._selAnchor = null;
if (!skipNotify && this._evtMgr.isListenerRegistered(DwtEvent.STATE_CHANGE)) {
this._evtMgr.notifyListeners(DwtEvent.STATE_CHANGE, this._stateChangeEv);
}
}
DwtListView.prototype.deselectAll =
function() {
var a = this._selectedItems.getArray();
var sz = this._selectedItems.size();
for (var i = 0; i < sz; i++)
a[i].className = a[i]._styleClass;
this._selectedItems.removeAll();
this._selAnchor = null;
}
DwtListView.prototype.getDnDSelection =
function() {
if (this._dndSelection instanceof AjxVector) {
return this.getSelection();
} else {
return AjxCore.objectWithId(this._dndSelection);
}
}
DwtListView.prototype.getSelection =
function() {
var a = new Array();
if (this._rightSelItems) {
a.push(AjxCore.objectWithId(this._rightSelItems._itemIndex));
} else {
var sa = this._selectedItems.getArray();
var saLen = this._selectedItems.size();
for (var i = 0; i < saLen; i++)
a[i] = AjxCore.objectWithId(sa[i]._itemIndex);
}
return a;
}
DwtListView.prototype.getSelectedItems =
function() {
return this._selectedItems;
}
DwtListView.prototype.setSelection =
function(item, skipNotify) {
var el = this._getElFromItem(item);
if (el) {
var i;
var a = this._selectedItems.getArray();
var sz = this._selectedItems.size();
for (i = 0; i < sz; i++)
a[i].className = a[i]._styleClass;
this._selectedItems.removeAll();
this._selectedItems.add(el);
this._selAnchor = el;
el.className = this.getEnabled() ? el._selectedStyleClass : el._selectedDisabledStyleClass;
if (!skipNotify && this._evtMgr.isListenerRegistered(DwtEvent.SELECTION)) {
var selEv = new DwtSelectionEvent(true);
selEv.button = DwtMouseEvent.LEFT;
selEv.target = el;
selEv.item = AjxCore.objectWithId(el._itemIndex);
selEv.detail = DwtListView.ITEM_SELECTED;
this._evtMgr.notifyListeners(DwtEvent.SELECTION, selEv);
}
}
}
DwtListView.prototype.getSelectionCount =
function() {
return this._rightSelItems ? 1 : this._selectedItems.size();
}
DwtListView.prototype.handleActionPopdown =
function(ev) {
// clear out old right click selection
if (this._rightSelItems) {
this._rightSelItems.className = this._rightSelItems._styleClass;
this._rightSelItems = null;
}
}
DwtListView.prototype._getItemId =
function(item) {
return item ? (this._getViewPrefix() + item.id) : null;
}
DwtListView.prototype._getHeaderTableId =
function() {
return this._headerList ? this._headerTableId : null;
}
DwtListView.prototype._getElFromItem =
function(item) {
var childNodes = this._parentEl.childNodes;
var len = childNodes.length;
var comparisonId = this._getItemId(item);
for (var i = 0; i < len; i++) {
if (childNodes[i].id == comparisonId)
return childNodes[i];
}
return null;
}
DwtListView.prototype._getItemIndex =
function(item) {
var list = this._list;
var len = list.size();
for (var i = 0; i < len; ++i){
if (list.get(i).id == item.id){
return i;
}
}
}
DwtListView.prototype.getItemFromElement =
function (element){
if (element._itemIndex !== void 0){
switch (element._type){
case DwtListView.TYPE_LIST_ITEM:
return AjxCore.objectWithId(element._itemIndex);
case DwtListView.TYPE_HEADER_ITEM:
// most users don't want to get the header items this way
//return this._headerList[element._itemIndex];
default:
return null;
}
} else {
return null;
}
}
DwtListView.prototype._getViewPrefix =
function() {
return "";
}
DwtListView.prototype.associateItemWithElement =
function (item, element, type, optionalId) {
element.id = optionalId ? optionalId : this._getItemId(item);
element._itemIndex = AjxCore.assignId(item);
element._type = type;
}
/* Return true only if the event occurred in one of our Divs
* See DwtControl for more info */
DwtListView.prototype._isValidDragObject =
function(ev) {
var div = ev.target;
div = this._findAncestor(div, "_itemIndex");
return (div != null);
}
DwtListView.prototype.dragSelect =
function(row) {
// If we have something previously selected, try and remove the selection
if (this._dragHighlight != null) {
var oldRow = document.getElementById(this._dragHighlight);
// only go forward if the row doesn't exist, or if the new selection
// is different from the old selection.
// In the case where a header item is dragged over, the row might be
// null or void.
if (!row || row.id != oldRow.id){
this._updateDragSelection(oldRow, false);
}
}
// Don't try and select if we are over a header item
if (!row || row._type != DwtListView.TYPE_LIST_ITEM) return;
// Try and select only if the new row is different from the currently
// highlighted row.
if (row.id != this._dragHighlight){
this._dragHighlight = row.id;
this._updateDragSelection(row, true);
}
}
DwtListView.prototype.dragDeselect =
function(row) {
if (this._dragHighlight) {
var oldRow = document.getElementById(this._dragHighlight);
this._updateDragSelection(oldRow, false);
this._dragHighlight = null;
}
}
DwtListView.prototype._updateDragSelection =
function(row, select) {
if (!select){
row.className = row._dwtListViewOldClassName;
} else {
row._dwtListViewOldClassName = row.className;
row.className = row.className + "-drag";
}
}
DwtListView.prototype._mouseOverAction =
function(mouseEv, div) {
if (div._type == DwtListView.TYPE_HEADER_ITEM && div._isSortable && this._headerClone == null) {
div.className = "DwtListView-Column DwtListView-ColumnHover";
} else if (div._type == DwtListView.TYPE_HEADER_SASH) {
div.style.cursor = AjxEnv.isIE ? "col-resize" : "e-resize";
} else if (div._type == DwtListView.TYPE_LIST_ITEM) {
if (div._hoverStyleClass == null || div == this._rightSelItems) {
div.hoverSet = false;
} else {
var selItems = this._selectedItems.getArray();
div.hoverSet = true;
for (var i = 0; i < selItems.length; i++) {
if (div == selItems[i]) {
div.hoverSet = false;
break;
}
}
}
if (div.hoverSet)
div.className += " " + div._hoverStyleClass;
}
return true;
}
DwtListView.prototype._mouseOutAction =
function(mouseEv, div) {
if (div._type == DwtListView.TYPE_HEADER_ITEM && this._headerClone == null) {
div.className = div.id != this._currentColId
? "DwtListView-Column"
: "DwtListView-Column DwtListView-ColumnActive";
} else if (div._type == DwtListView.TYPE_HEADER_SASH) {
div.style.cursor = "auto";
} else if (div._type == DwtListView.TYPE_LIST_ITEM) {
if (div._hoverStyleClass && div.hoverSet)
div.className = div._styleClass;
}
return true;
}
DwtListView.prototype._mouseOverListener =
function(ev) {
var div = ev.target;
div = this._findAncestor(div, "_itemIndex");
if (!div)
return;
this._mouseOverAction(ev, div);
}
DwtListView.prototype._mouseOutListener =
function(ev) {
var div = ev.target;
div = this._findAncestor(div, "_itemIndex");
if (!div)
return;
// NOTE: The DwtListView handles the mouse events on the list items
// that have associated tooltip text. Therefore, we must
// explicitly null out the tooltip content whenever we handle
// a mouse out event. This will prevent the tooltip from
// being displayed when we re-enter the listview even though
// we're not over a list item.
this._toolTipContent = null;
this._mouseOutAction(ev, div);
}
DwtListView.prototype._mouseMoveListener =
function(ev) {
if (this._clickDiv == null)
return;
if (this._clickDiv._type == DwtListView.TYPE_HEADER_ITEM) {
this._handleColHeaderMove(ev);
} else if (this._clickDiv._type == DwtListView.TYPE_HEADER_SASH) {
this._handleColHeaderResize(ev);
}
}
DwtListView.prototype._mouseUpAction =
function(mouseEv, div) {return true;}
DwtListView.prototype._findAncestor =
function(elem, attr) {
while (elem && (elem[attr] == void 0)){
elem = elem.parentNode;
}
return elem;
}
DwtListView.prototype._mouseDownListener =
function(ev) {
var div = ev.target;
div = this._findAncestor(div, "_itemIndex");
if (div == null){
this._dndSelection = null;
} else {
this._clickDiv = div;
if (div._type != DwtListView.TYPE_LIST_ITEM)
this._dndSelection = null;
else
this._dndSelection = (this._selectedItems.contains(div)) ? this._selectedItems : div._itemIndex;
}
}
DwtListView.prototype._mouseUpListener =
function(ev) {
var div = ev.target;
div = this._findAncestor(div, "_itemIndex");
var wasDraggingCol = this._handleColHeaderDrop(ev);
var wasDraggingSash = this._handleColSashDrop(ev);
if (!div || div != this._clickDiv ||
wasDraggingCol || wasDraggingSash)
{
delete this._clickDiv;
return;
}
delete this._clickDiv;
if (this._headerList && div._type == DwtListView.TYPE_HEADER_ITEM) {
if (div._isSortable && ev.button == DwtMouseEvent.LEFT) {
this._columnClicked(div, ev);
} else if (ev.button == DwtMouseEvent.RIGHT) {
var actionMenu = this._getActionMenuForColHeader();
if (actionMenu && actionMenu instanceof DwtMenu)
actionMenu.popup(0, ev.docX, ev.docY);
}
} else if (div._type == DwtListView.TYPE_LIST_ITEM) {
// set item selection, then hand off to derived class for handling
if (ev.button == DwtMouseEvent.LEFT || ev.button == DwtMouseEvent.RIGHT)
this._itemClicked(div, ev);
if (!this._mouseUpAction(ev, div))
return;
}
}
DwtListView.prototype._doubleClickAction =
function(mouseEv, div) {return true;}
DwtListView.prototype._doubleClickListener =
function(ev) {
var div = ev.target;
div = this._findAncestor(div, "_itemIndex");
if (!div) return;
if (div._type == DwtListView.TYPE_LIST_ITEM) {
if (!this._doubleClickAction(ev, div))
return;
if (this._evtMgr.isListenerRegistered(DwtEvent.SELECTION)) {
DwtUiEvent.copy(this._selEv, ev);
this._selEv.item = this.getItemFromElement(div);
this._selEv.detail = DwtListView.ITEM_DBL_CLICKED;
this._evtMgr.notifyListeners(DwtEvent.SELECTION, this._selEv);
}
}
}
DwtListView.prototype.emulateDblClick =
function(item) {
var div = Dwt.getDomObj(this.getDocument(), this._getItemId(item));
if (div) {
var ev = new Object();
ev.target = div;
ev.button = DwtMouseEvent.LEFT;
this._itemClicked(div, ev);
this._doubleClickListener(ev);
}
}
DwtListView.prototype._itemClicked =
function(clickedEl, ev) {
var i;
var a = this._selectedItems.getArray();
var numSelectedItems = this._selectedItems.size();
// always clear out old right click selection
if (this._rightSelItems) {
this._rightSelItems.className = this._rightSelItems._styleClass;
this._rightSelItems = null;
}
if ((!ev.shiftKey && !ev.ctrlKey) || !this.isMultiSelectEnabled()) {
// always reset detail if left/right click
if (ev.button == DwtMouseEvent.LEFT || ev.button == DwtMouseEvent.RIGHT)
this._selEv.detail = DwtListView.ITEM_SELECTED;
// is this element currently in the selected items list?
var bContained = this._selectedItems.contains(clickedEl);
if (ev.button == DwtMouseEvent.LEFT) {
// clear out old left click selection(s)
for (i = 0; i < numSelectedItems; i++)
a[i].className = a[i]._styleClass;
this._selectedItems.removeAll();
// save new left click selection
this._selectedItems.add(clickedEl);
this._selAnchor = clickedEl;
clickedEl.className = clickedEl._selectedStyleClass;
this._firstSelIndex = this._list
? this._list.indexOf(AjxCore.objectWithId(clickedEl._itemIndex)) : -1;
} else if (ev.button == DwtMouseEvent.RIGHT && !bContained) {
// save right click selection
this._rightSelItems = clickedEl;
clickedEl.className = clickedEl._selectedStyleClass + "-right";
}
clickedEl.hoverSet = false;
} else {
if (ev.ctrlKey) {
if (this._selectedItems.contains(clickedEl)) {
this._selectedItems.remove(clickedEl);
clickedEl.className = clickedEl._styleClass;
this._selEv.detail = DwtListView.ITEM_DESELECTED;
this._firstSelIndex = this._selectedItems.size() > 0
? this._selectedItems.get(0)._itemIndex : -1;
} else {
this._selectedItems.add(clickedEl);
clickedEl.className = clickedEl._selectedStyleClass;
clickedEl.hoverSet = false;
this._selEv.detail = DwtListView.ITEM_SELECTED;
}
// The element that was part of the ctrl action always becomes
// the anchor since it gets focus
this._selAnchor = clickedEl;
} else { // SHIFT KEY
// Adds to the selection to/from the current node to the selection anchor
if (this._selAnchor == null)
return;
var convEls = this._getChildren() || clickedEl.parentNode.childNodes;
var numConvEls = convEls.length;
var convEl;
var state = 0;
for (var i = 0; i < numConvEls; i++) {
convEl = convEls[i];
if (convEl == this._rightSelItems)
this._rightSelItems = null;
if (convEl == clickedEl) {
/* Increment the state.
* 0 - means we havent started
* 1 - means we are in selection range
* 2 - means we are out of selection range */
state++;
}
if (convEl == this._selAnchor) {
state++;
if (convEl.className != convEl._selectedStyleClass) {
convEl.className = convEl._selectedStyleClass;
this._selectedItems.add(convEl);
}
continue;
}
// If state == 0 or 2 (i.e. we are out of the selection range,
// we have to deselect the node. Else we select it
if (state != 1 && convEl.className == convEl._selectedStyleClass && convEl != clickedEl) {
convEl.className = convEl._styleClass;
this._selectedItems.remove(convEl);
} else if (state == 1 || convEl == clickedEl) {
if (convEl.className != convEl._selectedStyleClass) {
convEl.className = convEl._selectedStyleClass;
convEl.hoverSet = false;
this._selectedItems.add(convEl);
}
}
}
var newSelectedItems = this._selectedItems.size();
if (numSelectedItems < newSelectedItems)
this._selEv.detail = DwtListView.ITEM_SELECTED;
else if (numSelectedItems > newSelectedItems)
this._selEv.detail = DwtListView.ITEM_DESELECTED;
else
return;
}
}
// let derived class call notifyListeners(), since it may want to add to event
if (ev.button == DwtMouseEvent.LEFT) {
DwtUiEvent.copy(this._selEv, ev);
this._selEv.item = AjxCore.objectWithId(clickedEl._itemIndex);
if (this.constructor == DwtListView)
this._evtMgr.notifyListeners(DwtEvent.SELECTION, this._selEv);
} else if (ev.button == DwtMouseEvent.RIGHT) {
DwtUiEvent.copy(this._actionEv, ev);
this._actionEv.item = AjxCore.objectWithId(clickedEl._itemIndex);
if (this.constructor == DwtListView)
this._evtMgr.notifyListeners(DwtEvent.ACTION, this._actionEv);
}
}
DwtListView.prototype._columnClicked =
function(clickedCol, ev) {
var list = this.getList();
if (!list) return;
var size = list.size();
if (!size) return;
var item = this._headerList[clickedCol._itemIndex];
// reset order by sorting preference
this._bSortAsc = item._id == this._currentColId ? !this._bSortAsc : this._getDefaultSortbyForCol(item);
// reset arrows as necessary
this._setSortedColStyle(item._id);
// call sorting callback if more than one item to sort
if (size >= 1)
this._sortColumn(item, this._bSortAsc);
}
DwtListView.prototype._sortColumn =
function(columnItem, bSortAsc) {
// overload me
}
DwtListView.prototype._getActionMenuForColHeader =
function() {
// overload me if you want action menu for column headers
return null;
}
DwtListView.prototype._getDefaultSortbyForCol =
function(colHeader) {
// by default, always return ascending
return true;
}
DwtListView.prototype._setSortedColStyle =
function(columnId) {
var doc = this.getDocument();
if (this._currentColId != null && columnId != this._currentColId) {
// unset current column arrow
oldArrowId = DwtListView.HEADERITEM_ARROW + this._currentColId;
oldArrowCell = Dwt.getDomObj(doc, oldArrowId);
if (oldArrowCell && oldArrowCell.firstChild) {
var imgEl = (AjxImg._mode == AjxImg.SINGLE_IMG) ? oldArrowCell.firstChild : oldArrowCell.firstChild.firstChild;
if (imgEl)
imgEl.style.visibility = "hidden";
}
// reset style for old sorted column
var oldSortedCol = Dwt.getDomObj(doc, this._currentColId);
if (oldSortedCol)
oldSortedCol.className = "DwtListView-Column";
}
this._currentColId = columnId;
// set new column arrow
var newArrowId = DwtListView.HEADERITEM_ARROW + columnId;
var newArrowCell = Dwt.getDomObj(doc, newArrowId);
if (newArrowCell) {
AjxImg.setImage(newArrowCell, this._bSortAsc ? DwtImg.COLUMN_UP : DwtImg.COLUMN_DOWN);
var imgEl = (AjxImg._mode == AjxImg.SINGLE_IMG) ? newArrowCell.firstChild : newArrowCell.firstChild.firstChild;
if (imgEl)
imgEl.style.visibility = "visible";
}
// set new column style
var newSortedCol = Dwt.getDomObj(doc, columnId);
if (newSortedCol)
newSortedCol.className = "DwtListView-Column DwtListView-ColumnActive";
}
DwtListView.prototype._resetList =
function() {
// clear out old list to force GC
if (this._list && this._list.size())
this._list.removeAll();
// explicitly remove each child (setting innerHTML causes mem leak)
while (this._parentEl.hasChildNodes()) {
cDiv = this._parentEl.removeChild(this._parentEl.firstChild);
AjxCore.unassignId(cDiv._itemIndex);
}
}
DwtListView.prototype._destroyDnDIcon =
function(icon) {
if (icon._itemIndex){
AjxCore.unassignId(icon._itemIndex);
}
DwtControl.prototype._destroyDnDIcon.call(this,icon);
}
DwtListView.prototype._handleColHeaderMove =
function(ev) {
if (this._headerClone == null) {
if (this._headerColX == null) {
this._headerColX = ev.docX;
return;
} else {
var threshold = Math.abs(this._headerColX - ev.docX);
if (threshold < DwtListView.COL_MOVE_THRESHOLD)
return;
}
// create a clone of the selected column to move
var doc = this.getDocument();
this._headerClone = doc.createElement("div");
var size = Dwt.getSize(this._clickDiv);
var width = AjxEnv.isIE ? size.x : size.x - 3; // browser quirks
var height = AjxEnv.isIE ? size.y : size.y - 5;
Dwt.setSize(this._headerClone, width, height);
Dwt.setPosition(this._headerClone, Dwt.ABSOLUTE_STYLE);
Dwt.setZIndex(this._headerClone, Dwt.Z_DND);
Dwt.setLocation(this._headerClone, Dwt.DEFAULT, ev.docY);
this._headerClone.className = this._clickDiv.className + " DndIcon";
this._headerClone.innerHTML = this._clickDiv.innerHTML;
this._clickDiv.className = "DwtListView-Column DwtListView-ColumnEmpty";
// XXX: style hacks - improve this later
this._headerClone.style.borderTop = "1px solid #777777";
var labelCell = Dwt.getDomObj(doc, DwtListView.HEADERITEM_LABEL + this._clickDiv.id);
if (labelCell)
labelCell.style.color = "white";
//this._listColDiv.appendChild(this._headerClone);
this.shell.getHtmlElement().appendChild(this._headerClone);
} else {
var target = this._findAncestor(ev.target, "_itemIndex");
if (target && target._type == DwtListView.TYPE_HEADER_ITEM) {
if (this._headerCloneTarget && this._headerCloneTarget == this._clickDiv)
this._headerCloneTarget = null;
else if (this._headerCloneTarget != target) {
this._headerCloneTarget = target;
}
} else {
this._headerCloneTarget = null;
}
}
Dwt.setLocation(this._headerClone, ev.docX + 2);
}
DwtListView.prototype._handleColHeaderResize =
function(ev) {
if (this._headerSash == null) {
this._headerSash = this.getDocument().createElement("div");
Dwt.setSize(this._headerSash, Dwt.DEFAULT, this.getSize().y);
Dwt.setPosition(this._headerSash, Dwt.ABSOLUTE_STYLE);
Dwt.setZIndex(this._headerSash, Dwt.Z_DND);
Dwt.setLocation(this._headerSash, Dwt.DEFAULT, 0);
this._headerSash.className = "DwtListView-ColumnSash";
this.getHtmlElement().appendChild(this._headerSash);
// remember the initial x-position
this._headerSashX = ev.docX;
}
// always update the sash's position
var parent = this._getParentForColResize();
var loc = parent.getLocation();
Dwt.setLocation(this._headerSash, ev.docX-loc.x);
}
DwtListView.prototype._handleColHeaderDrop =
function(ev) {
this._headerColX = null;
if (this._headerClone == null || ev.button == DwtMouseEvent.RIGHT)
return false;
// did the user drop the column on a valid target?
if (this._headerCloneTarget) {
this.reIndexColumn(this._clickDiv._itemIndex, this._headerCloneTarget._itemIndex);
}
this._clickDiv.className = this._clickDiv.id != this._currentColId
? "DwtListView-Column"
: "DwtListView-Column DwtListView-ColumnActive";
wasDraggingCol = true;
var parent = this._headerClone.parentNode;
if (parent) {
parent.removeChild(this._headerClone);
} else {
DBG.println(AjxDebug.DBG1, "XXX: column header has no parent!");
}
delete this._headerClone;
if (this._clickDiv._type != DwtListView.TYPE_HEADER_ITEM) {
// something is messed up! redraw the header
var sortable = this._getSortableFromColId(this._currentColId);
this._headerColCreated = false;
this.createHeaderHtml(sortable);
} else {
// reset styles as necessary
var labelCell = Dwt.getDomObj(this.getDocument(), DwtListView.HEADERITEM_LABEL + this._clickDiv.id);
if (labelCell)
labelCell.style.color = "black";
}
this._resetColWidth();
// TODO: generate notification for column reorder
return true;
}
DwtListView.prototype._handleColSashDrop =
function(ev) {
if (this._headerSash == null || ev.button == DwtMouseEvent.RIGHT)
return false;
// find out where the user dropped the sash and update column width
var delta = ev.docX - this._headerSashX;
var suffixIdx = this._clickDiv._itemIndex.indexOf("--sash");
var headerIdx = parseInt(this._clickDiv._itemIndex.substring(0, suffixIdx));
if (headerIdx >= 0 && headerIdx < this._headerList.length) {
var newWidth = null;
if (this._headerList[headerIdx]._width)
newWidth = this._headerList[headerIdx]._width + delta;
else {
// lets actually adjust the next column since this one has a relative width
var nextCol = this._headerList[headerIdx+1];
if (nextCol && nextCol._width && nextCol._resizeable) {
var cell = Dwt.getDomObj(this.getDocument(), nextCol._id);
newWidth = cell ? Dwt.getSize(cell).x + delta : null;
}
}
this.reSizeColumn(headerIdx, newWidth);
} else {
DBG.println("XXX: Bad header ID.");
}
var parent = this._headerSash.parentNode;
if (parent)
parent.removeChild(this._headerSash);
delete this._headerSash;
this._resetColWidth();
return true;
}
DwtListView.prototype._relayout =
function() {
// force relayout of header column
this._headerColCreated = false;
var sortable = this._getSortableFromColId(this._currentColId);
var sel = this.getSelection()[0];
this.setUI(sortable);
this.setSelection(sel, true);
}
// XXX: this could be optimized by saving the sortable everytime the sort column changes
DwtListView.prototype._getSortableFromColId =
function(colId) {
// helper function to find column that was last sorted
var sortable = null;
for (var i = 0; i < this._headerList.length; i++) {
if (this._headerList[i]._id == colId) {
sortable = this._headerList[i]._sortable;
break;
}
}
return sortable;
}
DwtListView.prototype._getParentForColResize =
function() {
// overload me to return a higher inheritance chain parent
return this;
}
DwtListView.prototype.setSize =
function(width, height) {
DwtComposite.prototype.setSize.call(this, width, height);
this._sizeChildren(height);
}
DwtListView.prototype.setBounds =
function(x, y, width, height) {
DwtComposite.prototype.setBounds.call(this, x, y, width, height);
this._sizeChildren(height);
}
DwtListView.prototype._sizeChildren =
function(height) {
if (this._listDiv && (height != Dwt.DEFAULT))
Dwt.setSize(this._listDiv, Dwt.DEFAULT, height - DwtListView.HEADERITEM_HEIGHT);
}
// overload if parent element's children are not DIV's (i.e. div's w/in a table)
DwtListView.prototype._getChildren =
function() {
return null;
}
DwtListView.prototype.setSortByAsc =
function(column, bSortByAsc) {
if (!this._headerList)
return;
this._bSortAsc = bSortByAsc;
var columnId = null;
for (var i = 0; i < this._headerList.length; i++) {
if (this._headerList[i]._sortable && this._headerList[i]._sortable == column) {
columnId = this._headerList[i]._id;
break;
}
}
if (columnId)
this._setSortedColStyle(columnId);
}
DwtListView.prototype.getOffset =
function() {
return this._offset;
}
DwtListView.prototype.setOffset =
function(newOffset) {
this._offset = newOffset;
}
DwtListView.prototype.getNewOffset =
function(bPageForward) {
var limit = this.getLimit();
var offset = bPageForward ? this._offset + limit : this._offset - limit;
// normalize..
if (offset < 0)
offset = 0;
return offset;
}
DwtListView.prototype.getLimit =
function() {
// return the default limit value unless overloaded
return DwtListView.DEFAULT_LIMIT;
}
DwtListView.prototype.getReplenishThreshold =
function() {
// return the default threshold value unless overloaded
return DwtListView.MAX_REPLENISH_THRESHOLD;
}
DwtListView.prototype.getList =
function() {
return this._list;
}
// this method simply appends the given list to this current one
DwtListView.prototype.replenish =
function(list) {
this._list.addList(list);
var size = list.size();
for (var i = 0; i < size; i++) {
var item = list.get(i);
var div = this._createItemHtml(item, this._now);
if (div)
this._addRow(div);
}
}
//////////////////////////////////////////////////////////////////////////////
// DwtListHeaderItem
// - This is a (optional) "container" class for DwtListView objects which
// want a column header to appear. Create a new DwtListViewItem for each
// column header you want to appear. Be sure to specify width values
// (otherwise, undefined is default)
//
// @id Some ID used internally (a GUID gets appended to ensure uniqueness)
// @label The text shown for the column
// @iconInfo The icon shown for the column
// @width The width of the column
// @sortable Flag indicating whether column is sortable
// @resizeable Flag indicating whether column can be resized
// @visible Flag indicating whether column is initially visible
// @name Description of column used if column headers have action menu
// - If not supplied, uses label value.
// This param is primarily used for columns w/ only an icon (no label)
//
// TODO - kill this class and make a static array in derived class describing
// column info (i.e. derived classes will be required to supply this!)
//////////////////////////////////////////////////////////////////////////////
function DwtListHeaderItem(id, label, iconInfo, width, sortable, resizeable, visible, name) {
this._id = id + Dwt.getNextId();
this._label = label;
this._iconInfo = iconInfo;
this._width = width;
this._sortable = sortable;
this._resizeable = resizeable;
// only set visible if explicitly set to false
this._visible = visible == null || visible === true;
this._name = name || label;
}