1 /* 2 * ***** BEGIN LICENSE BLOCK ***** 3 * Zimbra Collaboration Suite Web Client 4 * Copyright (C) 2004, 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) 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016 Synacor, Inc. All Rights Reserved. 21 * ***** END LICENSE BLOCK ***** 22 */ 23 24 ZmDoublePaneView = function(params) { 25 26 if (arguments.length == 0) { return; } 27 28 var view = this._view = params.view = params.controller.getCurrentViewId(); 29 params.id = ZmId.getViewId(view); 30 DwtComposite.call(this, params); 31 32 this._controller = params.controller; 33 this._initHeader(); 34 35 params.className = null; 36 params.id = DwtId.getListViewId(view); 37 params.parent = this; 38 params.posStyle = Dwt.ABSOLUTE_STYLE; 39 this._mailListView = this._createMailListView(params); 40 41 // create the item view 42 params.className = null; 43 this._itemView = this._createMailItemView(params); 44 45 var viewType = appCtxt.getViewTypeFromId(view); 46 if (viewType === ZmId.VIEW_TRAD || viewType === ZmId.VIEW_CONVLIST) { 47 this._createSashes(); 48 } 49 this.setReadingPane(); 50 }; 51 52 ZmDoublePaneView.prototype = new DwtComposite; 53 ZmDoublePaneView.prototype.constructor = ZmDoublePaneView; 54 55 ZmDoublePaneView.prototype.isZmDoublePaneView = true; 56 ZmDoublePaneView.prototype.toString = function() { return "ZmDoublePaneView"; }; 57 58 // consts 59 60 ZmDoublePaneView.SASH_THRESHOLD = 5; 61 ZmDoublePaneView.MIN_LISTVIEW_WIDTH = 40; 62 63 ZmDoublePaneView._TAG_IMG = "TI"; 64 65 66 // public methods 67 68 ZmDoublePaneView.prototype.getController = 69 function() { 70 return this._controller; 71 }; 72 73 ZmDoublePaneView.prototype.getTitle = 74 function() { 75 return this._mailListView.getTitle(); 76 }; 77 78 /** 79 * Displays the reading pane, based on the current settings. 80 */ 81 ZmDoublePaneView.prototype.setReadingPane = 82 function(noSet) { 83 84 var mlv = this._mailListView, 85 mv = this._itemView, 86 sashesPresent = this._vertSash && this._horizSash; 87 88 var readingPaneEnabled = this._controller.isReadingPaneOn(); 89 if (!readingPaneEnabled) { 90 mv.setVisible(false); 91 if (sashesPresent) { 92 this._vertSash.setVisible(false); 93 this._horizSash.setVisible(false); 94 } 95 } 96 else { 97 if (!mv.getVisible()) { 98 if (mlv.getSelectionCount() == 1) { 99 this._controller._setSelectedItem(); 100 } else { 101 mv.reset(); 102 } 103 } 104 var readingPaneOnRight = this._controller.isReadingPaneOnRight(); 105 mv.setVisible(true, readingPaneOnRight); 106 if (sashesPresent) { 107 var newSash = readingPaneOnRight ? this._vertSash : this._horizSash; 108 var oldSash = readingPaneOnRight ? this._horizSash : this._vertSash; 109 oldSash.setVisible(false); 110 newSash.setVisible(true); 111 } 112 } 113 114 mlv.reRenderListView(); 115 if (!noSet) { 116 mv.setReadingPane(); 117 } 118 119 mv.noTab = !readingPaneEnabled || AjxEnv.isIE; 120 var sz = this.getSize(); 121 this._resetSize(sz.x, sz.y, true); 122 }; 123 124 ZmDoublePaneView.prototype.getMailListView = 125 function() { 126 return this._mailListView; 127 }; 128 129 ZmDoublePaneView.prototype.getItemView = 130 function() { 131 return this._itemView; 132 }; 133 134 // back-compatibility 135 ZmDoublePaneView.prototype.getMsgView = ZmDoublePaneView.prototype.getItemView; 136 137 ZmDoublePaneView.prototype.getInviteMsgView = 138 function() { 139 return this._itemView.getInviteMsgView(); 140 }; 141 142 143 ZmDoublePaneView.prototype.getSelectionCount = 144 function() { 145 return this._mailListView.getSelectionCount(); 146 }; 147 148 ZmDoublePaneView.prototype.getSelection = 149 function() { 150 return this._mailListView.getSelection(); 151 }; 152 153 ZmDoublePaneView.prototype.reset = 154 function() { 155 this._mailListView.reset(); 156 this._itemView.reset(); 157 }; 158 159 ZmDoublePaneView.prototype.getItem = 160 function() { 161 return this._itemView.getItem(); 162 }; 163 164 ZmDoublePaneView.prototype.setItem = 165 function(item, force, dontFocus) { 166 this._itemView.set(item, force); 167 this._controller._checkKeepReading(); 168 }; 169 170 ZmDoublePaneView.prototype.clearItem = 171 function() { 172 this._itemView.set(); 173 }; 174 175 // TODO: see if we can remove these 176 ZmDoublePaneView.prototype.getMsg = 177 function() { 178 return (this._controller.getCurrentViewType() == ZmId.VIEW_TRAD) ? this._itemView.getMsg() : null; 179 }; 180 181 ZmDoublePaneView.prototype.setMsg = 182 function(msg) { 183 this._itemView.set(msg); 184 this._controller._restoreFocus(); // bug 47700 185 }; 186 187 ZmDoublePaneView.prototype.addInviteReplyListener = 188 function (listener){ 189 this._itemView.addInviteReplyListener(listener); 190 }; 191 192 ZmDoublePaneView.prototype.addShareListener = 193 function (listener){ 194 this._itemView.addShareListener(listener); 195 }; 196 197 ZmDoublePaneView.prototype.addSubscribeListener = 198 function(listener) { 199 this._itemView.addSubscribeListener(listener); 200 }; 201 202 203 ZmDoublePaneView.prototype.resetMsg = 204 function(newMsg) { 205 this._itemView.resetMsg(newMsg); 206 }; 207 208 ZmDoublePaneView.prototype.isReadingPaneVisible = 209 function() { 210 return this._itemView.getVisible(); 211 }; 212 213 ZmDoublePaneView.prototype.setBounds = 214 function(x, y, width, height) { 215 DwtComposite.prototype.setBounds.call(this, x, y, width, height); 216 this._resetSize(width, height); 217 }; 218 219 ZmDoublePaneView.prototype.setList = 220 function(list) { 221 this._mailListView.set(list, ZmItem.F_DATE); 222 this.isStale = false; 223 }; 224 225 // Private / Protected methods 226 227 ZmDoublePaneView.prototype._initHeader = function() {}; 228 ZmDoublePaneView.prototype._createMailListView = function(params) {}; 229 ZmDoublePaneView.prototype._createMailItemView = function(params) {}; 230 231 // create a sash for each of the two reading pane locations 232 ZmDoublePaneView.prototype._createSashes = function() { 233 234 var params = { 235 parent: this, 236 style: DwtSash.HORIZONTAL_STYLE, 237 className: "AppSash-horiz", 238 threshold: ZmDoublePaneView.SASH_THRESHOLD, 239 posStyle: Dwt.ABSOLUTE_STYLE 240 }; 241 242 this._vertSash = new DwtSash(params); 243 this._vertSash.registerCallback(this._sashCallback, this); 244 this._vertSash.addListener(DwtEvent.ONMOUSEUP, this._sashVertRelease.bind(this)); 245 246 params.style = DwtSash.VERTICAL_STYLE; 247 params.className = "AppSash-vert"; 248 this._horizSash = new DwtSash(params); 249 this._horizSash.registerCallback(this._sashCallback, this); 250 this._horizSash.addListener(DwtEvent.ONMOUSEUP, this._sashHorizRelease.bind(this)); 251 this.addListener(DwtEvent.CONTROL, this._controlEventListener.bind(this)); 252 }; 253 254 ZmDoublePaneView.prototype._resetSize = 255 function(newWidth, newHeight, force) { 256 257 if (newWidth <= 0 || newHeight <= 0) { return; } 258 if (!force && newWidth == this._lastResetWidth && newHeight == this._lastResetHeight) { return; } 259 260 var readingPaneOnRight = this._controller.isReadingPaneOnRight(); 261 262 if (this.isReadingPaneVisible()) { 263 var sash = this.getSash(); 264 var sashSize = sash.getSize(); 265 var sashThickness = readingPaneOnRight ? sashSize.x : sashSize.y; 266 var itemViewMargins = this._itemView.getMargins(); 267 if (readingPaneOnRight) { 268 var listViewWidth = this.getReadingSashPosition(true) || (Number(ZmMsg.LISTVIEW_WIDTH)) || Math.floor(newWidth / 2.5); 269 this._mailListView.resetSize(listViewWidth, newHeight); 270 sash.setLocation(listViewWidth, 0); 271 this._itemView.setBounds(listViewWidth + sashThickness, 0, 272 newWidth - (listViewWidth + sashThickness + itemViewMargins.left + itemViewMargins.right), newHeight); 273 } else { 274 var listViewHeight = this.getReadingSashPosition(false) || (Math.floor(newHeight / 2) - DwtListView.HEADERITEM_HEIGHT); 275 this._mailListView.resetSize(newWidth, listViewHeight); 276 sash.setLocation(0, listViewHeight); 277 this._itemView.setBounds(0, listViewHeight + sashThickness, newWidth - itemViewMargins.left - itemViewMargins.right, 278 newHeight - (listViewHeight + sashThickness)); 279 } 280 } else { 281 this._mailListView.resetSize(newWidth, newHeight); 282 } 283 this._mailListView._resetColWidth(); 284 285 this._lastResetWidth = newWidth; 286 this._lastResetHeight = newHeight; 287 }; 288 289 ZmDoublePaneView.prototype._sashCallback = 290 function(delta) { 291 292 var readingPaneOnRight = this._controller.isReadingPaneOnRight(); 293 var listView = this._mailListView; 294 var itemView = this._itemView; 295 //See bug 69593 for the reason for "true" 296 var itemViewSize = itemView.getSize(true); 297 var listViewSize = listView.getSize(true); 298 299 var newListViewSize; 300 var newItemViewBounds; 301 302 var absDelta = Math.abs(delta); 303 304 if (readingPaneOnRight) { 305 var currentListViewWidth = AjxEnv.isIE ? this._vertSash.getLocation().x : listViewSize.x; 306 var currentItemViewWidth = itemViewSize.x; 307 delta = this._getLimitedDelta(delta, currentItemViewWidth, itemView.getMinWidth(), currentListViewWidth, ZmDoublePaneView.MIN_LISTVIEW_WIDTH); 308 if (!delta) { 309 return 0; 310 } 311 newListViewSize = {width: currentListViewWidth + delta, height: Dwt.DEFAULT}; 312 newItemViewBounds = { 313 left: itemView.getLocation().x + delta, 314 top: Dwt.DEFAULT, 315 width: currentItemViewWidth - delta, 316 height: Dwt.DEFAULT 317 }; 318 } 319 else { 320 //reading pane on bottom 321 var currentListViewHeight = AjxEnv.isIE ? this._horizSash.getLocation().y : listViewSize.y; 322 var currentItemViewHeight = itemViewSize.y; 323 delta = this._getLimitedDelta(delta, currentItemViewHeight, itemView.getMinHeight(), currentListViewHeight, this._getMinListViewHeight(listView)); 324 if (!delta) { 325 return 0; 326 } 327 newListViewSize = {width: Dwt.DEFAULT, height: currentListViewHeight + delta}; 328 newItemViewBounds = { 329 left: Dwt.DEFAULT, 330 top: itemView.getLocation().y + delta, 331 width: Dwt.DEFAULT, 332 height: currentItemViewHeight - delta 333 }; 334 } 335 336 listView.resetSize(newListViewSize.width, newListViewSize.height); 337 itemView.setBounds(newItemViewBounds.left, newItemViewBounds.top, newItemViewBounds.width, newItemViewBounds.height); 338 339 listView._resetColWidth(); 340 if (readingPaneOnRight) { 341 this._vertSashX = this._vertSash.getLocation().x + delta; 342 } 343 else { 344 this._horizSashY = this._horizSash.getLocation().y + delta; 345 } 346 347 return delta; 348 }; 349 350 /** 351 * returns the delta after limiting it based on minimum view dimension (which is either width/height - this code doesn't care) 352 * 353 * @param delta 354 * @param currentItemViewDimension 355 * @param minItemViewDimension 356 * @param currentListViewDimension 357 * @param minListViewDimension 358 * @returns {number} 359 * @private 360 */ 361 ZmDoublePaneView.prototype._getLimitedDelta = 362 function(delta, currentItemViewDimension, minItemViewDimension, currentListViewDimension, minListViewDimension) { 363 if (delta > 0) { 364 // moving sash right or down 365 return Math.max(0, Math.min(delta, currentItemViewDimension - minItemViewDimension)); 366 } 367 // moving sash left or up 368 var absDelta = Math.abs(delta); 369 return -Math.max(0, Math.min(absDelta, currentListViewDimension - minListViewDimension)); 370 }; 371 372 ZmDoublePaneView.prototype._getMinListViewHeight = 373 function(listView) { 374 if (this._minListViewHeight) { 375 return this._minListViewHeight; 376 } 377 378 var list = listView.getList(); 379 if (!list || !list.size()) { 380 return DwtListView.HEADERITEM_HEIGHT; 381 } 382 //only cache it if there's a list, to prevent a subtle bug of setting to just the header height if 383 //user first views an empty list. 384 var item = list.get(0); 385 var div = document.getElementById(listView._getItemId(item)); 386 this._minListViewHeight = DwtListView.HEADERITEM_HEIGHT + Dwt.getSize(div).y * 2; 387 return this._minListViewHeight; 388 }; 389 390 ZmDoublePaneView.prototype._selectFirstItem = 391 function() { 392 var list = this._mailListView.getList(); 393 var selectedItem = list ? list.get(0) : null; 394 if (selectedItem) { 395 this._mailListView.setSelection(selectedItem, false); 396 } 397 }; 398 399 ZmDoublePaneView.prototype.getSash = 400 function() { 401 var readingPaneOnRight = this._controller.isReadingPaneOnRight(); 402 return readingPaneOnRight ? this._vertSash : this._horizSash; 403 }; 404 405 ZmDoublePaneView.prototype.getLimit = 406 function(offset) { 407 return this._mailListView.getLimit(offset); 408 }; 409 410 ZmDoublePaneView.prototype._staleHandler = 411 function() { 412 413 var search = this._controller._currentSearch; 414 if (search) { 415 search.lastId = search.lastSortVal = null; 416 search.offset = search.limit = 0; 417 var params = {isRefresh: true}; 418 var mlv = this._mailListView 419 if (mlv.getSelectionCount() == 1) { 420 var sel = mlv.getSelection(); 421 var selItem = sel && sel[0]; 422 var curItem = this.getItem(); 423 if (selItem && curItem && selItem.id == curItem.id) { 424 params.selectedItem = selItem; 425 } 426 } 427 appCtxt.getSearchController().redoSearch(search, false, params); 428 } 429 }; 430 431 ZmDoublePaneView.prototype.handleRemoveAttachment = 432 function(oldMsgId, newMsg) { 433 this._itemView.handleRemoveAttachment(oldMsgId, newMsg); 434 }; 435 436 /** 437 * Returns the sash location (in pixels) based on reading pane preference. 438 * 439 * @param {boolean} readingPaneOnRight true if reading pane is on the right 440 */ 441 ZmDoublePaneView.prototype.getReadingSashPosition = 442 function(readingPaneOnRight) { 443 if (readingPaneOnRight) { 444 if (!this._vertSashX) { 445 var value = this._readingPaneSashVertPos || appCtxt.get(ZmSetting.READING_PANE_SASH_VERTICAL); 446 var percentWidth = value / 100; 447 var screenWidth = this.getSize().x; 448 this._vertSashX = Math.round(percentWidth * screenWidth); 449 } 450 return this._vertSashX; 451 } 452 else { 453 if (!this._horizSashY) { 454 var value = this._readingPaneSashHorizPos || appCtxt.get(ZmSetting.READING_PANE_SASH_HORIZONTAL); 455 var percentHeight = value / 100; 456 var screenHeight = this.getSize().y; 457 this._horizSashY = Math.round(percentHeight * screenHeight); 458 } 459 return this._horizSashY; 460 } 461 }; 462 463 /** 464 * Sets the location of sash (in percentage) depending upon reading pane preference. 465 * 466 * @param {boolean} readingPaneOnRight true if reading pane is on the right 467 * @param {int} value location of sash (in pixels) 468 */ 469 ZmDoublePaneView.prototype.setReadingSashPosition = 470 function(readingPaneOnRight, value) { 471 if (readingPaneOnRight) { 472 var screenWidth = this.getSize().x; 473 var sashWidthPercent = Math.round((value / screenWidth) * 100); 474 if (this._controller.isSearchResults) { 475 this._readingPaneSashVertPos = sashWidthPercent; 476 } 477 else { 478 appCtxt.set(ZmSetting.READING_PANE_SASH_VERTICAL, sashWidthPercent); 479 } 480 } 481 else { 482 var screenHeight = this.getSize().y; 483 var sashHeightPercent = Math.round((value/screenHeight) * 100); 484 if (this._controller.isSearchResults) { 485 this._readingPaneSashHorizPos = sashHeightPercent; 486 } 487 else { 488 appCtxt.set(ZmSetting.READING_PANE_SASH_HORIZONTAL, sashHeightPercent); 489 } 490 } 491 }; 492 493 ZmDoublePaneView.prototype._sashVertRelease = 494 function() { 495 this.setReadingSashPosition(true, this._vertSashX); 496 }; 497 498 ZmDoublePaneView.prototype._sashHorizRelease = 499 function() { 500 this.setReadingSashPosition(false, this._horizSashY); 501 }; 502 503 ZmDoublePaneView.prototype._controlEventListener = 504 function(ev) { 505 //resize can be called multiple times based on the browser so wait till resizing is complete 506 if (ev && (ev.newWidth == ev.requestedWidth) && (ev.newHeight == ev.requestedHeight)) { 507 var readingPaneOnRight = this._controller.isReadingPaneOnRight(); 508 //reset the sash values and resize the pane based on settings 509 if (readingPaneOnRight) { 510 this._readingPaneSashVertPos = appCtxt.get(ZmSetting.READING_PANE_SASH_VERTICAL); 511 delete this._vertSashX; 512 } else { 513 this._readingPaneSashHorizPos = appCtxt.get(ZmSetting.READING_PANE_SASH_HORIZONTAL); 514 delete this._horizSashY; 515 } 516 var sz = this.getSize(); 517 this._resetSize(sz.x, sz.y, true); 518 } 519 }; 520 521