1 /* 2 * ***** BEGIN LICENSE BLOCK ***** 3 * Zimbra Collaboration Suite Web Client 4 * Copyright (C) 2006, 2007, 2008, 2009, 2010, 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) 2006, 2007, 2008, 2009, 2010, 2013, 2014, 2015, 2016 Synacor, Inc. All Rights Reserved. 21 * ***** END LICENSE BLOCK ***** 22 */ 23 24 25 /** 26 * Creates an empty tab group. 27 * @constructor 28 * @class 29 * A tab group is used to manage keyboard focus among a group of related visual 30 * elements. It is a tree structure consisting of elements and other tab groups. 31 * <p> 32 * The root tab group is the only one without a parent tab group, and is the one 33 * that the application interacts with. Focus listeners register with the root 34 * tab group. The root tab group tracks where focus is. 35 * 36 * @param {string} name the name of this tab group 37 * 38 * @author Ross Dargahi 39 */ 40 DwtTabGroup = function(name) { 41 42 this.__members = new AjxVector(); 43 this.__parent = null; 44 this.__name = name; 45 this.__currFocusMember = null; 46 this.__evtMgr = new AjxEventMgr(); 47 48 DwtTabGroup.BY_NAME[name] = this; 49 }; 50 51 DwtTabGroup.prototype.isDwtTabGroup = true; 52 DwtTabGroup.prototype.toString = function() { return "DwtTabGroup"; }; 53 54 55 56 /** 57 * Exception string that is thrown when an operation is attempted 58 * on a non-root tab group. 59 */ 60 DwtTabGroup.NOT_ROOT_TABGROUP = "NOT ROOT TAB GROUP"; 61 62 DwtTabGroup.__changeEvt = new DwtTabGroupEvent(); 63 64 // Allow static access to any tab group by its name 65 DwtTabGroup.getByName = function(name) { 66 return DwtTabGroup.BY_NAME[name]; 67 }; 68 DwtTabGroup.BY_NAME = {}; 69 70 /** 71 * Gets the name of this tab group. 72 * 73 * @return {string} the tab group name 74 */ 75 DwtTabGroup.prototype.getName = function() { 76 return this.__name; 77 }; 78 79 /** 80 * Adds a focus change listener to the root tab group. The listener is called 81 * when the focus member changes. Note that the focus member hasn't actually 82 * been focused yet - only its status within the tab group has changed. It is 83 * up to the listener to implement the appropriate focus action. 84 * 85 * @param {AjxListener} listener a listener 86 * 87 * @throws DwtTabGroup.NOT_ROOT_TABGROUP 88 */ 89 DwtTabGroup.prototype.addFocusChangeListener = function(listener) { 90 91 this.__checkRoot(); 92 this.__evtMgr.addListener(DwtEvent.STATE_CHANGE, listener); 93 }; 94 95 /** 96 * Removes a focus change listener from the root tab group. 97 * 98 * @param {AjxListener} listener a listener 99 * 100 * @throws DwtTabGroup.NOT_ROOT_TABGROUP 101 */ 102 DwtTabGroup.prototype.removeFocusChangeListener = function(listener) { 103 104 this.__checkRoot(); 105 this.__evtMgr.removeListener(DwtEvent.STATE_CHANGE, listener); 106 }; 107 108 /** 109 * Adds a member to the tab group. 110 * 111 * @param {Array|DwtControl|DwtTabGroup|HTMLElement} member the member(s) to be added 112 * @param {number} [index] the index at which to add the member. If omitted, the member 113 * will be added to the end of the tab group 114 */ 115 DwtTabGroup.prototype.addMember = function(member, index) { 116 117 index = (index != null) ? index : this.__members.size(); 118 var members = AjxUtil.collapseList(AjxUtil.toArray(member)); 119 120 for (var i = 0, len = members.length; i < len; i++) { 121 var member = members[i]; 122 this.__members.add(member, index + i); 123 // If adding a tab group, register me as its parent 124 if (member.isDwtTabGroup) { 125 member.newParent(this); 126 } 127 } 128 }; 129 130 /** 131 * Resets all members of the tab group to the given arguments. 132 * 133 * @param {Array|DwtControl|DwtTabGroup|HTMLElement} members the member(s) for the tab group 134 */ 135 DwtTabGroup.prototype.setMembers = function(members) { 136 this.removeAllMembers(); 137 this.addMember(members); 138 }; 139 140 /** 141 * Adds a member to the tab group, positioned after another member. 142 * 143 * @param {DwtControl|DwtTabGroup|HTMLElement} member the member to be added 144 * @param {DwtControl|DwtTabGroup|HTMLElement} afterMember the member after which to add <code>member</code> 145 */ 146 DwtTabGroup.prototype.addMemberAfter = function(newMember, afterMember) { 147 148 this.addMember(newMember, this.__indexOfMember(afterMember) + 1); 149 }; 150 151 /** 152 * Adds a member to the tab group, positioned before another member. 153 * 154 * @param {DwtControl|DwtTabGroup|HTMLElement} member the member to be added 155 * @param {DwtControl|DwtTabGroup|HTMLElement} beforeMember the member before which to add <code>member</code> 156 */ 157 DwtTabGroup.prototype.addMemberBefore = function(newMember, beforeMember) { 158 159 this.addMember(newMember, this.__indexOfMember(beforeMember)); 160 }; 161 162 /** 163 * This method removes a member from the tab group. If the member being removed 164 * is currently the focus member, then we will try to set focus to the 165 * previous member. If that fails, we will try the next member. 166 * 167 * @param {DwtControl|DwtTabGroup|HTMLElement} member the member to be removed 168 * @param {boolean} [checkEnabled] if <code>true</code>, then make sure that if we have a newly focused member it is enabled 169 * @param {boolean} [skipNotify] if <code>true</code>, notification is not fired. This flag typically set by Dwt tab management framework when it is calling into this method 170 * @return {DwtControl|DwtTabGroup|HTMLElement} the removed member or <code>null</code> if <code>oldMember</code> is not in the tab groups hierarchy 171 */ 172 DwtTabGroup.prototype.removeMember = function(member, checkEnabled, skipNotify) { 173 174 return this.replaceMember(member, null, checkEnabled, skipNotify); 175 }; 176 177 /** 178 * Removes all members. 179 * 180 */ 181 DwtTabGroup.prototype.removeAllMembers = function() { 182 183 this.__members.removeAll(); 184 }; 185 186 /** 187 * This method replaces a member in the tab group with a new member. If the member being 188 * replaced is currently the focus member, then we will try to set focus to the 189 * previous member. If that fails, we will try the next member. 190 * 191 * @param {DwtControl|DwtTabGroup|HTMLElement} oldMember the member to be replaced 192 * @param {DwtControl|DwtTabGroup|HTMLElement} newMember the replacing member 193 * If this parameter is <code>null</code>, then this method effectively removes <code>oldMember</code> 194 * @param {boolean} [checkEnabled] if <code>true</code>, then make sure that if we have a newly focused 195 * member it is enabled 196 * @param {boolean} [skipNotify] if <code>true</code>, notification is not fired. This flag is 197 * typically set by the tab management framework when it is calling into this method 198 * @return {DwtControl|DwtTabGroup|HTMLElement} replaced member or <code>null></code> if <code>oldMember</code> is not in the tab group 199 */ 200 DwtTabGroup.prototype.replaceMember = function(oldMember, newMember, checkEnabled, skipNotify, focusItem, noFocus) { 201 202 var tg = this.__getTabGroupForMember(oldMember); 203 if (!tg) { 204 this.addMember(newMember); 205 return null; 206 } 207 208 /* If we are removing the current focus member, then we need to adjust the focus 209 * member index. If the tab group is empty as a result of the removal 210 */ 211 var root = this.__getRootTabGroup(); 212 var newFocusMember; 213 if (focusItem) { 214 newFocusMember = focusItem; 215 } 216 else if (root.__currFocusMember === oldMember || (oldMember && oldMember.isDwtTabGroup && oldMember.contains(root.__currFocusMember))) { 217 if (newMember) { 218 newFocusMember = (newMember.isDwtTabGroup) ? newMember.getFirstMember() : newMember; 219 } 220 else { 221 newFocusMember = this.__getPrevMember(oldMember, checkEnabled); 222 if (!newFocusMember) { 223 newFocusMember = this.__getNextMember(oldMember, checkEnabled); 224 } 225 } 226 } 227 228 if (newFocusMember && !noFocus) { 229 root.__currFocusMember = newFocusMember; 230 this.__showFocusedItem(this.__currFocusMember, "replaceMember"); 231 if (!skipNotify) { 232 this.__notifyListeners(newFocusMember); 233 } 234 } 235 236 if (newMember && newMember.isDwtTabGroup) { 237 newMember.newParent(this); 238 } 239 240 return newMember ? this.__members.replaceObject(oldMember, newMember) : this.__members.remove(oldMember); 241 }; 242 243 /** 244 * Returns true if this tab group contains <code>member</code>. 245 * 246 * @param {DwtControl|DwtTabGroup|HTMLElement} member the member for which to search 247 * 248 * @return {boolean} <code>true</code> if the tab group contains member 249 */ 250 DwtTabGroup.prototype.contains = function(member) { 251 252 return !!this.__getTabGroupForMember(member); 253 }; 254 255 /** 256 * Sets a new parent for this tab group. 257 * 258 * @param {DwtTabGroup} newParent the new parent. If the parent is <code>null</code>, then this tabGroup is the root tab group. 259 */ 260 DwtTabGroup.prototype.newParent = function(newParent) { 261 262 this.__parent = newParent; 263 }; 264 265 /** 266 * Gets the first member of the tab group. 267 * 268 * @param {boolean} [checkEnabled] if <code>true</code>, then return first enabled member 269 * 270 * @return {DwtControl|HTMLElement} the first member of the tab group 271 */ 272 DwtTabGroup.prototype.getFirstMember = function(checkEnabled) { 273 274 return this.__getLeftMostMember(checkEnabled); 275 }; 276 277 /** 278 * Gets the last member of the tab group. 279 * 280 * @param {boolean} [checkEnabled] if <code>true</code>, then return last enabled member 281 * 282 * @return {DwtControl|HTMLElement} the last member of the tab group 283 */ 284 DwtTabGroup.prototype.getLastMember = function(checkEnabled) { 285 286 return this.__getRightMostMember(checkEnabled); 287 }; 288 289 /** 290 * Returns the current focus member. 291 * 292 * @return {DwtControl|HTMLElement} current focus member 293 * 294 * @throws DwtTabGroup.NOT_ROOT_TABGROUP 295 */ 296 DwtTabGroup.prototype.getFocusMember = function(){ 297 298 this.__checkRoot(); 299 return this.__currFocusMember; 300 }; 301 302 /** 303 * Sets the current focus member. 304 * 305 * @param {DwtControl|HTMLElement} member the member to which to set focus 306 * @param {boolean} [checkEnabled] if <code>true</code>, then make sure the member is enabled 307 * @param {boolean} [skipNotify] if <code>true</code>, notification is not fired. This flag 308 * typically set by Dwt tab management framework when it is calling into this method 309 * 310 * @return {boolean} <code>true</code> if member was part of the tab group hierarchy, else false 311 * 312 * @throws DwtTabGroup.NOT_ROOT_TABGROUP 313 */ 314 DwtTabGroup.prototype.setFocusMember = function(member, checkEnabled, skipNotify) { 315 316 if (!member) { 317 return false; 318 } 319 320 if (member.isDwtTabGroup) { 321 DBG.println(AjxDebug.FOCUS, "DwtTabGroup SETFOCUSMEMBER to a DwtTabGroup: " + member + " / " + member.getName()); 322 member = member.getFocusMember() || member.getFirstMember(); 323 } 324 this.__checkRoot(); 325 if (!this.__checkEnabled(member, checkEnabled)) { 326 return false; 327 } 328 329 if (this.contains(member)) { 330 this.__currFocusMember = member; 331 this.__showFocusedItem(this.__currFocusMember, "setFocusMember"); 332 if (!skipNotify) { 333 this.__notifyListeners(this.__currFocusMember); 334 } 335 return true; 336 } 337 338 return false; 339 }; 340 341 /** 342 * This method sets and returns the next focus member in this tab group. If there is no next 343 * member, sets and returns the first member in the tab group. 344 * 345 * @param {boolean} [checkEnabled] if <code>true</code>, get the next enabled member 346 * @param {boolean} [skipNotify] if <code>true</code>, notification is not fired. This flag 347 * typically set by {@link Dwt} tab management framework when it is calling into this method 348 * 349 * @return {DwtControl|HTMLElement} new focus member or <code>null</code> if there is no focus member or if the focus 350 * member has not changed (i.e. only one member in the tabgroup) 351 * 352 * @throws DwtTabGroup.NOT_ROOT_TABGROUP 353 */ 354 DwtTabGroup.prototype.getNextFocusMember = function(checkEnabled, skipNotify) { 355 356 this.__checkRoot(); 357 return this.__setFocusMember(true, checkEnabled, skipNotify); 358 }; 359 360 /** 361 * This method sets and returns the previous focus member in this tab group. If there is no 362 * previous member, sets and returns the last member in the tab group. 363 * 364 * @param {boolean} [checkEnabled] if <code>true</code>, get the previously enabled member 365 * @param {boolean} [skipNotify] if <code>true</code>, notification is not fired. This flag 366 * typically set by Dwt tab management framework when it is calling into this method 367 * 368 * @return {DwtControl|HTMLElement} new focus member or <code>null</code> if there is no focus member or if the focus 369 * member has not changed (i.e. only one member in the tabgroup) 370 * 371 * @throws DwtTabGroup.NOT_ROOT_TABGROUP 372 */ 373 DwtTabGroup.prototype.getPrevFocusMember = function(checkEnabled, skipNotify) { 374 375 this.__checkRoot(); 376 return this.__setFocusMember(false, checkEnabled, skipNotify); 377 }; 378 379 /** 380 * Resets the the focus member to the first element in the tab group. 381 * 382 * @param {boolean} [checkEnabled] if <code>true</code>, then pick a enabled member to which to set focus 383 * @param {boolean} [skipNotify] if <code>true</code>, notification is not fired. This flag 384 * typically set by Dwt tab management framework when it is calling into this method 385 * 386 * @return {DwtControl|HTMLElement} the new focus member 387 * 388 * @throws DwtTabGroup.NOT_ROOT_TABGROUP 389 */ 390 DwtTabGroup.prototype.resetFocusMember = function(checkEnabled, skipNotify) { 391 392 this.__checkRoot(); 393 var focusMember = this.__getLeftMostMember(checkEnabled); 394 if ((focusMember != this.__currFocusMember) && !skipNotify) { 395 this.__notifyListeners(this.__currFocusMember); 396 } 397 this.__showFocusedItem(this.__currFocusMember, "resetFocusMember"); 398 DBG.println(AjxDebug.FOCUS, "DwtTabGroup RESETFOCUSMEMBER: " + focusMember); 399 this.__currFocusMember = focusMember; 400 401 return this.__currFocusMember; 402 }; 403 404 /** 405 * Pretty-prints the contents of the tab group to the browser console or the 406 * debug window. 407 * 408 * @param {number} [debugLevel] if specified, dump to the debug window 409 * at the given level. 410 */ 411 DwtTabGroup.prototype.dump = function(debugLevel) { 412 413 if (debugLevel) { 414 if (!window.AjxDebug || !window.DBG) { 415 return; 416 } 417 418 var logger = function(s) { 419 var s = AjxStringUtil.convertToHtml(s); 420 DBG.println(debugLevel, s); 421 } 422 423 DwtTabGroup.__dump(this, logger, 0); 424 } else if (window.console && window.console.log) { 425 var r = []; 426 DwtTabGroup.__dump(this, r.push.bind(r), 0); 427 console.log(r.join('\n')); 428 } 429 }; 430 431 /** 432 * Gets the size of the group. 433 * 434 * @return {number} the size 435 */ 436 DwtTabGroup.prototype.size = function() { 437 438 return this.__members.size(); 439 }; 440 441 /** 442 * Returns the previous member in the tag group. 443 * 444 * @private 445 */ 446 DwtTabGroup.prototype.__getPrevMember = function(member, checkEnabled) { 447 448 var a = this.__members.getArray(); 449 450 // Start working from the member to the immediate left, then keep going left 451 for (var i = this.__lastIndexOfMember(member) - 1; i > -1; i--) { 452 var prevMember = a[i]; 453 /* if sibling is not a tab group, then it is the previous child. If the 454 * sibling is a tab group, get its rightmost member.*/ 455 if (!prevMember.isDwtTabGroup) { 456 if (this.__checkEnabled(prevMember, checkEnabled)) { 457 return prevMember; 458 } 459 } else { 460 prevMember = prevMember.__getRightMostMember(checkEnabled); 461 if (this.__checkEnabled(prevMember, checkEnabled)) { 462 return prevMember; 463 } 464 } 465 } 466 467 /* If we have fallen through to here it is because the tab group only has 468 * one member. So we roll up to the parent, unless we are at the root in 469 * which case we return null. */ 470 return this.__parent ? this.__parent.__getPrevMember(this, checkEnabled) : null; 471 }; 472 473 /** 474 * Returns true if the given member can accept focus, or if there is no need to check. 475 * If we are checking, the member must be enabled and visible if it is a control, and 476 * enabled otherwise. A member may also set the "noTab" flag to take itself out of the 477 * tab hierarchy. 478 * 479 * @private 480 */ 481 DwtTabGroup.prototype.__checkEnabled = function(member, checkEnabled) { 482 483 if (!checkEnabled) { 484 return true; 485 } 486 487 if (!member || member.noTab) { 488 return false; 489 } 490 491 if (member.isDwtControl ? !member.getEnabled() : member.disabled) { 492 return false; 493 } 494 495 if (member.isDwtControl) { 496 member = member.getHtmlElement(); 497 } 498 499 var loc = Dwt.getLocation(member); 500 if (loc.x === null || loc.y === null || loc.x === Dwt.LOC_NOWHERE || loc.y === Dwt.LOC_NOWHERE) { 501 return false; 502 } 503 504 var size = Dwt.getSize(member); 505 if (!size || size.x === 0 || size.y === 0) { 506 return false; 507 } 508 509 if (member.nodeName && member.nodeName.toLowerCase() === "body") { 510 return true; 511 } 512 return (Dwt.getZIndex(member, true) > Dwt.Z_HIDDEN && 513 Dwt.getVisible(member) && Dwt.getVisibility(member)); 514 }; 515 516 DwtTabGroup.prototype.__indexOfMember = function(member) { 517 518 return this.__members.indexOf(member); 519 }; 520 521 DwtTabGroup.prototype.__lastIndexOfMember = function(member) { 522 523 return this.__members.lastIndexOf(member); 524 }; 525 526 /** 527 * Sets and returns the next member in the tag group. 528 * 529 * @private 530 */ 531 DwtTabGroup.prototype.__getNextMember = function(member, checkEnabled) { 532 533 var a = this.__members.getArray(); 534 var sz = this.__members.size(); 535 536 // Start working from the member rightwards 537 for (var i = this.__indexOfMember(member) + 1; i < sz; i++) { 538 var nextMember = a[i]; 539 /* if sibling is not a tab group, then it is the next child. If the 540 * sibling is a tab group, get its leftmost member.*/ 541 if (!nextMember.isDwtTabGroup) { 542 if (this.__checkEnabled(nextMember, checkEnabled)) { 543 return nextMember; 544 } 545 } 546 else { 547 nextMember = nextMember.__getLeftMostMember(checkEnabled); 548 if (this.__checkEnabled(nextMember, checkEnabled)) { 549 return nextMember; 550 } 551 } 552 } 553 554 /* If we have fallen through to here it is because the tab group only has 555 * one member or we are at the end of the list. So we roll up to the parent, 556 * unless we are at the root in which case we return null. */ 557 return this.__parent ? this.__parent.__getNextMember(this, checkEnabled) : null; 558 }; 559 560 /** 561 * Finds the rightmost member of the tab group. Will recurse down 562 * into contained tab groups if necessary. 563 * @private 564 */ 565 DwtTabGroup.prototype.__getRightMostMember = function(checkEnabled) { 566 567 var a = this.__members.getArray(); 568 var member = null; 569 570 /* Work backwards from the rightmost member. If the member is a tab group, then 571 * recurse into it. If member is not a tab group, return it as it is the 572 * rightmost element. */ 573 for (var i = this.__members.size() - 1; i >= 0; i--) { 574 member = a[i] 575 if (!member.isDwtTabGroup) { 576 if (this.__checkEnabled(member, checkEnabled)) { 577 break; 578 } 579 } 580 else { 581 member = member.__getRightMostMember(checkEnabled); 582 if (this.__checkEnabled(member, checkEnabled)) { 583 break; 584 } 585 } 586 } 587 588 return this.__checkEnabled(member, checkEnabled) ? member : null; 589 }; 590 591 /** 592 * Finds the leftmost member of the tab group. Will recurse down 593 * into contained tab groups if necessary. 594 * @private 595 */ 596 DwtTabGroup.prototype.__getLeftMostMember = function(checkEnabled) { 597 598 var sz = this.__members.size(); 599 var a = this.__members.getArray(); 600 var member = null; 601 602 /* Work forwards from the leftmost member. If the member is a tabgroup, then 603 * recurse into it. If member is not a tabgroup, return it as it is the 604 * rightmost element */ 605 for (var i = 0; i < sz; i++) { 606 member = a[i] 607 if (!member.isDwtTabGroup) { 608 if (this.__checkEnabled(member, checkEnabled)) { 609 break; 610 } 611 } 612 else { 613 member = member.__getLeftMostMember(checkEnabled); 614 if (this.__checkEnabled(member, checkEnabled)) { 615 break; 616 } 617 } 618 } 619 620 return this.__checkEnabled(member, checkEnabled) ? member : null; 621 }; 622 623 /** 624 * Notifies focus change listeners. 625 * @private 626 */ 627 DwtTabGroup.prototype.__notifyListeners = function(newFocusMember) { 628 629 // Only the root tab group will issue notifications 630 var rootTg = this.__getRootTabGroup(); 631 if (rootTg.__evtMgr) { 632 var evt = DwtTabGroup.__changeEvt; 633 evt.reset(); 634 evt.tabGroup = this; 635 evt.newFocusMember = newFocusMember; 636 rootTg.__evtMgr.notifyListeners(DwtEvent.STATE_CHANGE, evt); 637 } 638 }; 639 640 /** 641 * @private 642 */ 643 DwtTabGroup.prototype.__getRootTabGroup = function() { 644 645 var root = this; 646 while (root.__parent) { 647 root = root.__parent; 648 } 649 650 return root; 651 } 652 653 DwtTabGroup.DUMP_INDENT = '|\t'; 654 655 /** 656 * @private 657 */ 658 DwtTabGroup.__dump = function(tg, logger, level) { 659 660 var myIndent = AjxStringUtil.repeat(DwtTabGroup.DUMP_INDENT, level); 661 662 logger(myIndent + "TABGROUP: " + tg.__name); 663 664 myIndent += DwtTabGroup.DUMP_INDENT; 665 666 var sz = tg.__members.size(); 667 var a = tg.__members.getArray(); 668 for (var i = 0; i < sz; i++) { 669 var m = a[i]; 670 if (m.isDwtTabGroup) { 671 DwtTabGroup.__dump(m, logger, level + 1); 672 } 673 else { 674 var desc = m.nodeName ? [ m.nodeName, m.id, m.className ].join(' ') : [ String(m), m._htmlElId ].join(' '); 675 if (m.noTab) { 676 desc += ' - no tab!'; 677 } 678 logger(myIndent + desc); 679 } 680 } 681 }; 682 683 /** 684 * Sets the next or previous focus member. 685 * @private 686 */ 687 DwtTabGroup.prototype.__setFocusMember = function(next, checkEnabled, skipNotify) { 688 689 // If there is currently no focus member, then reset to the first member and return 690 if (!this.__currFocusMember) { 691 return this.resetFocusMember(checkEnabled, skipNotify); 692 } 693 694 var tabGroup = this.__getTabGroupForMember(this.__currFocusMember); 695 if (!tabGroup) { 696 DBG.println(AjxDebug.DBG1, "tab group not found for focus member: " + this.__currFocusMember); 697 return null; 698 } 699 var m = next ? tabGroup.__getNextMember(this.__currFocusMember, checkEnabled) 700 : tabGroup.__getPrevMember(this.__currFocusMember, checkEnabled); 701 702 if (!m) { 703 // wrap around 704 m = next ? this.__getLeftMostMember(checkEnabled) 705 : this.__getRightMostMember(checkEnabled); 706 707 // Test for the case where there is only one member in the tabgroup 708 if (m == this.__currFocusMember) { 709 return null; 710 } 711 } 712 713 this.__currFocusMember = m; 714 715 this.__showFocusedItem(this.__currFocusMember, "__setFocusMember"); 716 if (!skipNotify) { 717 this.__notifyListeners(this.__currFocusMember); 718 } 719 720 return this.__currFocusMember; 721 }; 722 723 /** 724 * Returns the tab group from within this tab group's hierarchy that contains the given member. Traverses the tree top-down. 725 * 726 * @private 727 */ 728 DwtTabGroup.prototype.__getTabGroupForMember = function(member) { 729 730 if (!member) { 731 return null; 732 } 733 734 var a = this.__members.getArray(), 735 ln = a.length, i, m; 736 737 for (i = 0; i < ln; i++) { 738 m = a[i]; 739 if (m === member) { 740 return this; 741 } 742 else if (m.isDwtTabGroup && (m = m.__getTabGroupForMember(member))) { 743 return m; 744 } 745 } 746 return null; 747 }; 748 749 /** 750 * Throws an exception if this is not the root tab group. 751 * 752 * @private 753 */ 754 DwtTabGroup.prototype.__checkRoot = function() { 755 756 if (this.__parent) { 757 DBG.println(AjxDebug.DBG1, "DwtTabGroup NOT_ROOT_TABGROUP: " + this.getName()); 758 // throw DwtTabGroup.NOT_ROOT_TABGROUP; 759 } 760 }; 761 762 // Prints out a debug line describing the currently focused member 763 DwtTabGroup.prototype.__showFocusedItem = function(item, caller) { 764 765 if (item && window.AjxDebug && window.DBG) { 766 var callerText = caller ? "DwtTabGroup." + caller + ": " : "", 767 idText = " [" + (item.isDwtControl ? item._htmlElId : item.id) + "] ", 768 itemText = (item.nodeName || item) + " " + idText, 769 otherText = (item.getTitle && item.getTitle()) || (item.getText && item.getText()) || "", 770 fullText = itemText + otherText; 771 772 DBG.println(AjxDebug.FOCUS, callerText + "current focus member is now " + itemText); 773 DBG.println(AjxDebug.FOCUS1, "Focus: " + fullText); 774 } 775 }; 776