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