/* Copyright (c) 2005, 2021, Oracle and/or its affiliates.*/
/* All rights reserved.*/

// AdfDhtmlPivotTablePeer
//
AdfRichUIPeer.createPeerClass(AdfRichUIPeer, "AdfDhtmlPivotTablePeer", false);
AdfPage.PAGE.getLookAndFeel().registerPeerConstructor(
  "oracle.dss.adf.pivotTable.PivotTable",
  "AdfDhtmlPivotTablePeer");
    
//Start Peer class
/**
 * @export
 * @this {AdfDhtmlPivotTablePeer}
 */
AdfDhtmlPivotTablePeer.InitSubclass = function()
{
  // listen for change events for ADA nav controls!
  AdfRichUIPeer.addDomEventHandlers(this, "change");
                
  // Don't register for MouseUp, because it will only get called if the MouseUp occurs inside our component, so that if MouseUp occurs elsewhere, we fail to (say) exit the drag-in-progress state.  Use page.startDrag() instead, like we do for resize drags and selection drags, which gets called no matter where the mouseUp occurs.
  AdfRichUIPeer.addComponentEventHandlers(this, AdfUIInputEvent.CLICK_EVENT_TYPE,
                                                AdfUIInputEvent.MOUSE_DOWN_EVENT_TYPE,
                                                AdfUIInputEvent.KEY_DOWN_EVENT_TYPE,
                                                AdfUIInputEvent.KEY_UP_EVENT_TYPE,
                                                AdfComponentEvent.FOCUS_EVENT_TYPE,
                                                AdfComponentEvent.BLUR_EVENT_TYPE,
                                                AdfUIInputEvent.DOUBLE_CLICK_EVENT_TYPE,                                          
                                                AdfUIInputEvent.MOUSE_OUT_EVENT_TYPE,
                                                AdfUIInputEvent.MOUSE_IN_EVENT_TYPE,
                                                AdfComponentEvent.CONTEXT_MENU_EVENT_TYPE, 
                                                AdfActionEvent.ACTION_EVENT_TYPE, 
                                                AdfDialogEvent.EVENT_TYPE, 
                                                AdfPopupOpeningEvent.POPUP_OPENING_EVENT_TYPE, 
                                                AdfPopupClosedEvent.POPUP_CLOSED_EVENT_TYPE);

  AdfObject.ensureClassInitialization(AdfDhtmlPivotTablePeer); 

  // Avoid PPR when setting these properties.  Instead, they go up the next time there would have been a PPR anyway.  
  AdfRichUIPeer.addSuppressedPPRAttributes(this, "splitRow");
  AdfRichUIPeer.addSuppressedPPRAttributes(this, "splitColumn");
  AdfRichUIPeer.addSuppressedPPRAttributes(this, "startRow");
  AdfRichUIPeer.addSuppressedPPRAttributes(this, "startColumn");
  AdfRichUIPeer.addSuppressedPPRAttributes(this, "secondStartRow");
  AdfRichUIPeer.addSuppressedPPRAttributes(this, "secondStartColumn");
  AdfRichUIPeer.addSuppressedPPRAttributes(this, "startRowPixel");
  AdfRichUIPeer.addSuppressedPPRAttributes(this, "startColumnPixel");
  AdfRichUIPeer.addSuppressedPPRAttributes(this, "secondStartRowPixel");
  AdfRichUIPeer.addSuppressedPPRAttributes(this, "secondStartColumnPixel");
  AdfRichUIPeer.addSuppressedPPRAttributes(this, "rowHeaderStartLayer");
  AdfRichUIPeer.addSuppressedPPRAttributes(this, "columnHeaderStartLayer");
  AdfRichUIPeer.addSuppressedPPRAttributes(this, "rowHeaderStartPixel");
  AdfRichUIPeer.addSuppressedPPRAttributes(this, "columnHeaderStartPixel");
  AdfRichUIPeer.addSuppressedPPRAttributes(this, "_sizeOverrides");
  AdfRichUIPeer.addSuppressedPPRAttributes(this, "selection");
  AdfRichUIPeer.addSuppressedPPRAttributes(this, "activeCellKey");
  
  // "this" refers to AdfDhtmlPivotTablePeer (the class, not an instance), so these are class constants:
  
  this.ROW_HEADER_COMPONENT = "0";
  this.COLUMN_HEADER_COMPONENT = "1";
  this.DATABODY_COMPONENT = "2";
  
  this.PARTIAL = "partial";
  
  // Mutually exclusive states that the PT can be in.  For many of these states, we want to avoid handling keystrokes, etc.
  this.STATE_NORMAL = 0; // The PT is in this state when it is not in any of the other states.
  this.STATE_CONTEXT_MENU = 1; // this state begins when a context menu is requested (before fetching it from the server and displaying it), and ends when the CM is dismissed (by cancelling or selecting a menu item)
  this.STATE_DIALOG = 2; // this state begins when a dialog is requested (before fetching it from the server and displaying it), and ends when the dialog is dismissed (by cancelling or accepting it)
  this.STATE_STAMP_POPUP = 3; // this state begins when a popup from stamp component is requested.

  this.PIVOT_BEFOREAFTER_MARGIN=5;
  this.COLUMN_RESIZE_MARGIN = 3;
  this.ROW_RESIZE_MARGIN = 1;
  // Note: these 4 width/height constants are 1 less than the real value--i.e. a value of 10 here corresponds to a value of 11 in the sizing manager and resize dialog.
  // TODO: refactor the logic that uses these constants, and adjust the constants by 1, to be consistent with other usages.  
  // TODO: Only the DnD resize logic uses these.  Resize dialog logic uses hard-coded numbers.  Make dialog use these too.
  // TODO: Currently these constants are higher than necessary, which customers hate.  To help a customer while avoiding disruption, we allowed apps to at least set these lower programmatically (by adjusting drag logic to work in that case).  Consider reducing these so user can set lower too (and make resize dialog logic use these constants so it picks up the fix too).
  if (AdfPivotTableUtils.isMobileTouchDevice())
  {
    this.MIN_COLUMN_WIDTH = 22;
    this.MIN_ROW_HEIGHT = 22;
    this.MIN_ROW_HEADER_WIDTH = 22;
    this.MIN_COL_HEADER_HEIGHT = 22;
  }
  else
  {
    this.MIN_COLUMN_WIDTH = 10;
    this.MIN_ROW_HEIGHT = 10;
    this.MIN_ROW_HEADER_WIDTH = 10;
    this.MIN_COL_HEADER_HEIGHT = 10;
  }
  
  // put all regexps here so they are only compiled once 
  this._INPUT_REGEXP = /^INPUT|SELECT|OPTION|BUTTON|^A\b|TEXTAREA/;
  this._DIGIT_REGEXP      = /\d+/g;
  this._WHITESPACE_REGEXP = /\s+/g;
  
  this.ORIGINAL_COLOR = "_oc"; // the name of the expando in which we place the original background color of a cell, from the CellFormat, so that the client knows what color to restore when the cell is later deselected
  this.BACKGROUND_COLOR_NEEDS_RESTORING = "_bc"; // the name of the expando that is set whenever we're setting a cell's background color to "", to indicate that the restoration logic shouldn't bail out even though it's "".
  this._NAMED_COLOR_MAP = {ALICEBLUE:'#F0F8FF',ANTIQUEWHITE:'#FAEBD7',AQUA:'#00FFFF',AQUAMARINE:'#7FFFD4',AZURE:'#F0FFFF',BEIGE:'#F5F5DC',BISQUE:'#FFE4C4',BLACK:'#000000',BLANCHEDALMOND:'#FFEBCD',BLUE:'#0000FF',BLUEVIOLET:'#8A2BE2',BROWN:'#A52A2A',BURLYWOOD:'#DEB887',CADETBLUE:'#5F9EA0',CHARTREUSE:'#7FFF00',CHOCOLATE:'#D2691E',CORAL:'#FF7F50',CORNFLOWERBLUE:'#6495ED',CORNSILK:'#FFF8DC',CRIMSON:'#DC143C',CYAN:'#00FFFF',DARKBLUE:'#00008B',DARKCYAN:'#008B8B',DARKGOLDENROD:'#B8860B',DARKGRAY:'#A9A9A9',DARKGREEN:'#006400',DARKKHAKI:'#BDB76B',DARKMAGENTA:'#8B008B',DARKOLIVEGREEN:'#556B2F',DARKORANGE:'#FF8C00',DARKORCHID:'#9932CC',DARKRED:'#8B0000',DARKSALMON:'#E9967A',DARKSEAGREEN:'#8FBC8F',DARKSLATEBLUE:'#483D8B',DARKSLATEGRAY:'#2F4F4F',DARKTURQUOISE:'#00CED1',DARKVIOLET:'#9400D3',DEEPPINK:'#FF1493',DEEPSKYBLUE:'#00BFFF',DIMGRAY:'#696969',DODGERBLUE:'#1E90FF',FIREBRICK:'#B22222',FLORALWHITE:'#FFFAF0',FORESTGREEN:'#228B22',FUCHSIA:'#FF00FF',GAINSBORO:'#DCDCDC',GHOSTWHITE:'#F8F8FF',GOLD:'#FFD700',GOLDENROD:'#DAA520',GRAY:'#808080',GREEN:'#008000',GREENYELLOW:'#ADFF2F',HONEYDEW:'#F0FFF0',HOTPINK:'#FF69B4',INDIANRED:'#CD5C5C',INDIGO:'#4B0082',IVORY:'#FFFFF0',KHAKI:'#F0E68C',LAVENDER:'#E6E6FA',LAVENDERBLUSH:'#FFF0F5',LAWNGREEN:'#7CFC00',LEMONCHIFFON:'#FFFACD',LIGHTBLUE:'#ADD8E6',LIGHTCORAL:'#F08080',LIGHTCYAN:'#E0FFFF',LIGHTGOLDENRODYELLOW:'#FAFAD2',LIGHTGREY:'#D3D3D3',LIGHTGREEN:'#90EE90',LIGHTPINK:'#FFB6C1',LIGHTSALMON:'#FFA07A',LIGHTSEAGREEN:'#20B2AA',LIGHTSKYBLUE:'#87CEFA',LIGHTSLATEGRAY:'#778899',LIGHTSTEELBLUE:'#B0C4DE',LIGHTYELLOW:'#FFFFE0',LIME:'#00FF00',LIMEGREEN:'#32CD32',LINEN:'#FAF0E6',MAGENTA:'#FF00FF',MAROON:'#800000',MEDIUMAQUAMARINE:'#66CDAA',MEDIUMBLUE:'#0000CD',MEDIUMORCHID:'#BA55D3',MEDIUMPURPLE:'#9370D8',MEDIUMSEAGREEN:'#3CB371',MEDIUMSLATEBLUE:'#7B68EE',MEDIUMSPRINGGREEN:'#00FA9A',MEDIUMTURQUOISE:'#48D1CC',MEDIUMVIOLETRED:'#C71585',MIDNIGHTBLUE:'#191970',MINTCREAM:'#F5FFFA',MISTYROSE:'#FFE4E1',MOCCASIN:'#FFE4B5',NAVAJOWHITE:'#FFDEAD',NAVY:'#000080',OLDLACE:'#FDF5E6',OLIVE:'#808000',OLIVEDRAB:'#6B8E23',ORANGE:'#FFA500',ORANGERED:'#FF4500',ORCHID:'#DA70D6',PALEGOLDENROD:'#EEE8AA',PALEGREEN:'#98FB98',PALETURQUOISE:'#AFEEEE',PALEVIOLETRED:'#D87093',PAPAYAWHIP:'#FFEFD5',PEACHPUFF:'#FFDAB9',PERU:'#CD853F',PINK:'#FFC0CB',PLUM:'#DDA0DD',POWDERBLUE:'#B0E0E6',PURPLE:'#800080',RED:'#FF0000',ROSYBROWN:'#BC8F8F',ROYALBLUE:'#4169E1',SADDLEBROWN:'#8B4513',SALMON:'#FA8072',SANDYBROWN:'#F4A460',SEAGREEN:'#2E8B57',SEASHELL:'#FFF5EE',SIENNA:'#A0522D',SILVER:'#C0C0C0',SKYBLUE:'#87CEEB',SLATEBLUE:'#6A5ACD',SLATEGRAY:'#708090',SNOW:'#FFFAFA',SPRINGGREEN:'#00FF7F',STEELBLUE:'#4682B4',TAN:'#D2B48C',TEAL:'#008080',THISTLE:'#D8BFD8',TOMATO:'#FF6347',TURQUOISE:'#40E0D0',VIOLET:'#EE82EE',WHEAT:'#F5DEB3',WHITE:'#FFFFFF',WHITESMOKE:'#F5F5F5',YELLOW:'#FFFF00',YELLOWGREEN:'#9ACD32'};

  // ADA navigation control constants
  this._SELECT_RANGE = [["colNav0", "colNav1"], ["rowNav0", "rowNav1"]];
  this._CHOICE_ID_SUFFIX = "c";
  this._PREV_LINK_ID_SUFFIX = "p";
  this._NEXT_LINK_ID_SUFFIX = "n";
  
  this._ADA_CLEAR_SELECTION_LINK_ID = "ClearSelectionLink";
  this._ADA_TOGGLE_SELECTION_CHECKBOXES_LINK_ID = "ToggleCheckboxesLink"
  this._ADA_SELECTION_CHECKBOX_ID = "SelectionCheckbox";

  this.EMPTY_TEXT_ID = "et";

  this.NAV_LAYER_DECREASE = 0;
  this.NAV_LAYER_INCREASE = 1;
  this.NAV_SLICE_DECREASE = 2;
  this.NAV_SLICE_INCREASE = 3;

  this._SELECTION_DIV_ID = "SelectionDiv";

  this.HIGHLIGHTING_NORMAL = 0;
  this.HIGHLIGHTING_SELECTED = 1;
  this.HIGHLIGHTING_FOCUSED = 2;
  this.HIGHLIGHTING_FOCUSED_AND_SELECTED = 3;
  
  this.CONTEXT_MENU_POPUP_ID = "MainContextMenuPopup"; // prepend with "main" so that operations like id.contains(CONTEXT_MENU_POPUP_ID) don't return true when id==BODY_CONTEXT_MENU_POPUP_ID
  this.BODY_CONTEXT_MENU_POPUP_ID = "BodyContextMenuPopup";
  this.EMPTY_MENU_ID = "EmptyMenu";
  this.HEADER_SORT_ASCENDING_MENU_ITEM_ID = "HeaderSortAscendingMenuItem";
  this.HEADER_SORT_DESCENDING_MENU_ITEM_ID = "HeaderSortDescendingMenuItem";
  this.DATA_SORT_ASCENDING_MENU_ITEM_ID = "DataSortAscendingMenuItem";
  this.DATA_SORT_DESCENDING_MENU_ITEM_ID = "DataSortDescendingMenuItem";
  this.RESIZE_HEIGHT_MENU_ITEM_ID = "ResizeHeightMenuItem";
  this.RESIZE_WIDTH_MENU_ITEM_ID = "ResizeWidthMenuItem";
  this.SPLIT_PANES_MENU_ITEM_ID = "SplitPanesMenuItem";
  
  this.RESIZE_DIALOG_POPUP_ID   = "ResizeDialogPopup";
  this.RESIZE_DIALOG_ID         = "ResizeDialog";
  this.RESIZE_DIALOG_PLM_ID     = "ResizeDialogPlm";
  this.RESIZE_DIALOG_SPINNER_ID = "ResizeDialogSpin";

  this._SORT_INDICATOR = "si";
  // Expandos on the sort elements
  this._SORT_ASCENDING = "_asc";
  this._SORT_DESCENDING = "_desc";
  // indicates that the column is already sorted
  this._SORTED_TYPE = "_sorted"; 
  // Expando values
  this._SORTED_ASCENDING = "1";
  this._SORTED_DESCENDING = "2";

  this._NAMING_SEP = ":";

  // track whether we are in navigation mode (esc key)
  this._isNavigationMode = false;
  
  // navigation bar constants
  this._NAVBAR_PREV = [[], []];
  this._NAVBAR_PREV[0][0] = "nb_pv_00";
  this._NAVBAR_PREV[0][1] = "nb_pv_01";
  this._NAVBAR_PREV[1][0] = "nb_pv_10";
  this._NAVBAR_PREV[1][1] = "nb_pv_11";
  
  this._NAVBAR_FIRST = [[], []];
  this._NAVBAR_FIRST[0][0] = "nb_fr_00";
  this._NAVBAR_FIRST[0][1] = "nb_fr_01";
  this._NAVBAR_FIRST[1][0] = "nb_fr_10";
  this._NAVBAR_FIRST[1][1] = "nb_fr_11";
  
  this._NAVBAR_NEXT = [[], []];
  this._NAVBAR_NEXT[0][0] = "nb_nx_00";
  this._NAVBAR_NEXT[0][1] = "nb_nx_01";
  this._NAVBAR_NEXT[1][0] = "nb_nx_10";
  this._NAVBAR_NEXT[1][1] = "nb_nx_11";
  
  this._NAVBAR_LAST = [[], []];
  this._NAVBAR_LAST[0][0] = "nb_ls_00";
  this._NAVBAR_LAST[0][1] = "nb_ls_01";
  this._NAVBAR_LAST[1][0] = "nb_ls_10";
  this._NAVBAR_LAST[1][1] = "nb_ls_11";
}

// for behavior backward compatibility
AdfDhtmlPivotTablePeer.prototype.preserveEditingBehavior = function () {
    var flag = this.getDomElement().getAttribute("_old_edit");
    if (flag == "true")
        return true;
    else
        return false;
}

// whether there is no editable stamps in the pivot table
AdfDhtmlPivotTablePeer.prototype._isReadOnly = function () 
{
    var flag = this.getDomElement().getAttribute("_readonly");
    if (flag == "true")
        return true;
    else
        return false;    
}

AdfDhtmlPivotTablePeer.prototype._updateInitialACK = function(block)
{
    var component = this.getComponent();
    var activeCellKey = block.getAttribute("_initACK");
    if (activeCellKey)
    {
        // tell UIComponent to treat this setting as an internal set without sending property
        // change to the server or firing client-side events
        component.setProperty(AdfRichPivotTable.__ACTIVE_CELL_KEY, activeCellKey, false, AdfUIComponent.PROPAGATE_NEVER);
        component.setProperty("activeCellKey", activeCellKey, false, AdfUIComponent.PROPAGATE_NEVER);        
        
        this._activeCellKeySetFromServer = true;
    }
    else if (block.getAttribute("_afrACKChange"))
        this._activeCellKeySetFromServer = true;
}

/**
 * Checks whether click to edit all mode is enabled.
 * @return {boolean} true if click to edit all mode, false otherwise.
 */
AdfDhtmlPivotTablePeer.prototype._isClickToEditAll = function()
{
    var flag = this.getDomElement().getAttribute("_cteAll");
    if (flag == "true")
        return true;
    else
        return false;
}

///////////////////////////////
// Pivot Table Event Handlers
AdfDhtmlPivotTablePeer.prototype.HandleComponentFocus = function(componentEvent) {
    // in ADA mode we don't have a focus cell, which is the only thing this method currently deals with.
    if (this._isAda) 
        return;

    // if we're displaying the "no data" message, then there are no cells to focus, and attempting to set the focus cell would cause an error
    if (this._ptUtils._rowExtent==0 || this._ptUtils._colExtent==0) 
        return;

    // on IE, the focus to the input element where user navigates to next was delayed, as a result the 
    // focus is incorrectly shifted from the blocking UI, as a workaround, check if it's in busy state,
    // and shift the focus back to the glass pane so that user input is prevented (if user input is not 
    // prevented, shifting focus to blocking UI will do nothing)
    if (this._serverBusy && AdfAgent.AGENT.getPlatform()==AdfAgent.IE_PLATFORM)
    {
        AdfPage.PAGE.getGlassPane().focus();
        return;
    }

    if (this._activeCellKeySetFromServer)
    { 
        // if the active cell key is set from server, when pt has focus the cell should be focus and selected
        var cellKey = this._getActiveCellKey();
        if (cellKey != null && this._focusCellIndex == null)
        {
            var cellIndex = this._getDataCellIndexFromKey(cellKey);
            this.selectAndFocusDataCell(cellIndex);
            this._focusCellNeedsInit = false;      
        }
    }
    else if (this._focusCellNeedsInit) {
        this.initializeFocusCell(); // includes highlighting
    } else { 
        this.highlightFocusCell();
    }

    // check if we support esc key to revert edit
    if (!this.preserveEditingBehavior())
    {
        // capture current cell value for revert edit
        var domEvent = componentEvent.getNativeEvent();
        var eventTarget = AdfAgent.AGENT.getEventTarget(domEvent);
        var attrs = this._getDataCellKeyAndCell(eventTarget);
        if(attrs != null && attrs[0] != null && attrs[1] != null && attrs[1].id)
        {
            if (eventTarget._afrOldValue == undefined)
                AdfDhtmlPivotTablePeer._saveFocusCellValue(eventTarget);
        }
    }
}

AdfDhtmlPivotTablePeer.prototype.HandleComponentBlur = function(componentEvent) {
    // in ADA mode we don't have a focus cell, and we don't do Shift-Alt-Arrow pivoting, so bail out.
    
    // this._codeExecutionCounter is set to 0 in InitDomElement, incremented at top of DomReplaceNotify, and decremented in that method's finally clause.  We bail out of HandleComponentBlur if non-0.  Note that the mechanism is reentrant since we're using a counter rather than a boolean.
    // Reason: We have had multiple problems where HandleComponentBlur was being called in the middle of code execution, when we are in an inconsistent state, leading to various problems.  In all cases so far, this happened in DRN() or its callees, so for now only that method modifies the ivar, but the same try++/finally-- code can be added to other methods as needed.
    // - Older problem: see comment in DRN > RequestDone block > !vError block.  (Happened in IE)
    // - 12/2011 bug 13459119: in Chrome, with cell in rightmost column open for editing, press Tab to navigate to first cell of next row, triggering long-scroll.  When block returns, in DRN > longScroll case > removeAllBlocks > removeExistingBlocksFromDOM > removeChildren(databodyArray[i][j]) > node.removeChild(), a blur event happens, HandleComponentBlur is called in an inconsistent state (col header buffer is empty), and an assertion failure happens when we pass the first block (null) to AdfAgent.getIntAttribute().
    if (!this._isAda && this._codeExecutionCounter==0) {
        // On mobile touch devices, pivot handles are not hidden on mouseOut, so we hide when the header cell loses focus
        if (AdfPivotTableUtils.isMobileTouchDevice())
            this.hidePivotHandle();
        
        this.highlightFocusCell(true);
        
        // don't check for kbPivotMode before calling this, because callee needs to hide the pivot handle that is shown upon pressing Shift-Alt, even if kbPivotMode was never entered.
        this.endKeyboardPivoting(true); // TODO: UPDATE COMMENT: Ctrl-Alt-Esc cancels KB pivoting, but in Windows, that key combo is an Alt-Tab variant that switches to another application.  To make sure pivoting is canceled in this case, we cancel it onBlur.  This is the right thing if the PT loses focus for other reasons too.  
    }
}

AdfDhtmlPivotTablePeer.prototype._displayContextMenuAfterEdit = function()
{
  this._displayContextMenu(this.domEventForContextMenuAfterEdit, this.domEventForContextMenuAfterEdit['flag']);
  this.domEventForContextMenuAfterEdit = null;
}

// called by HandleComponentContextMenu.  This method determines if there is an active cell when user right clicks to display context menu
// editing should stop before showing the context menu.
AdfDhtmlPivotTablePeer.prototype._dismissEditAndDisplayContextMenu = function(componentEvent, isBodyContextMenu) 
{
    var domEvent = componentEvent.getNativeEvent();

    // if there is an active cell, then don't display context menu until value is submitted and editing is done
    // since we don't want to show context menu if there is a validation error
    if (this._activeCell != null)
    {
      // submit the value first
      this._processDismissCTE(componentEvent);
      
      // prevent default browser menu
      var agent = AdfAgent.AGENT;
      agent.preventDefault(domEvent);

      // display context menu after click to edit comes back
      // inject property to indicate whether to show context menu or body context menu
      domEvent['flag'] = isBodyContextMenu;
      this.domEventForContextMenuAfterEdit = domEvent;
    }
    else
      this._displayContextMenu(domEvent, isBodyContextMenu);     
}

AdfDhtmlPivotTablePeer.prototype.HandleComponentContextMenu = function(componentEvent)
{
//  if (this.loggingOn) AdfLogger.LOGGER.severe("HCCM called");
  if (this.isAttachmentMode())
    return;

  // If currentRequest is PARTIAL (which is what isSelectionUiEnabled() checks) or if we're showing a dialog, then don't display the context menu
  if(!this.isSelectionUiEnabled() || this._ptState == AdfDhtmlPivotTablePeer.STATE_DIALOG || this._ptState == AdfDhtmlPivotTablePeer.STATE_STAMP_POPUP)
    return;

  // Mouse gestures like right-click are not supposed to work in ADA mode.  The context menu is launched via Ctrl-Alt-m instead.
  if (this._isAda) 
    return;

  var agent = AdfAgent.AGENT;
  var domEvent = componentEvent.getNativeEvent();

  // allow the browser context menu to work when the event target is an input control. 
  var eventTarget = agent.getEventTarget(domEvent);
  if(this._isInputElement(eventTarget))// if the target is an input control in a cell,and is edited at the moment
    return;
    
  // checks if there is a client side validation error, if so, bail out
  if (this.isClientValidationError(eventTarget))
  {
     // prevent browser context menu to appear so focus on error cell will stick
     agent.preventDefault(domEvent);
     return;
  }

  this.getDomElement().focus(); 
  var context = this._getMouseContext(domEvent);

  // if edge is empty, then clicking on the dummy header cell should display the body context menu, not the menu for a header cell.  Also, attempting to do the latter causes an exception
  if (context.headerContext && context.headerContext['cell'] && !context.headerContext.isEmptyEdge) 
  { 
    var headerCellIndex = this.newHeaderCellIndexWithEdgeFromCell(context.headerContext['cell']);
    // if the cell is not selected, then select the cell. 
    if(!this.isSelected(headerCellIndex)) 
      this.selectAndFocusHeaderCell(headerCellIndex);

    this._dismissEditAndDisplayContextMenu(componentEvent, false);
  } 
  else if (context.databodyContext) 
  { // databodyContext['cell'] exists iff databodyContext does, so no need to check for that too.
    var cell = context.databodyContext['cell'];
    var dataCellIndex = this.newDataCellIndexFromCell(cell);
    var selectedStyleClass = this._getStyleClass(AdfPivotTableUtils.DATABODY_CELL_SELECTED_STYLE);   

    if (this.preserveEditingBehavior())
    {      
      // if the cell is selected or if the header selection has highlighted the data cell, then maintain the current selection, 
      // regardless of whether it is a header or data selection
      // no need to check for "focused" style, since if datacell can't be focused if header is selected
      if(! (this.isSelected(dataCellIndex) || AdfDhtmlCommonUtils.containsCSSClassName(cell, selectedStyleClass)) )
        this.selectAndFocusDataCell(dataCellIndex);

      this._dismissEditAndDisplayContextMenu(componentEvent, false);
    }
    else
    {
      // if the cell is in header cell selection OR if the cell is selected and there are more than one selected cell in databody, then keep the selection and don't go into edit mode      
      if(this.isInHeaderSelection(dataCellIndex,null,true) || (this.isMultipleDatabodyCellsSelected() && this.isSelected(dataCellIndex)))
      {
          this._dismissEditAndDisplayContextMenu(componentEvent, false);
          return;
      }

      // if we are right clicking on the data cell that is currently active (and does not contain any input elements), dismiss edit and show context menu
      if (this._activeCell == cell)
      {
          this._dismissEditAndDisplayContextMenu(componentEvent, false);
          return;          
      }

      // selects the cell
      if(! (this.isSelected(dataCellIndex) || AdfDhtmlCommonUtils.containsCSSClassName(cell, selectedStyleClass)) )
        this.selectAndFocusDataCell(dataCellIndex);

      // first prevent default browser menu to appear
      agent.preventDefault(domEvent);

      // immediately show the context menu if it is read only
      if (this._isReadOnly())
          this._dismissEditAndDisplayContextMenu(componentEvent, false);
      else
      {
          if (this._isClickToEditAll())
          {
              // dismiss any editing
              this._dismissPreviousActiveCell(cell, null, null);

              this._cancelDismiss = false;
              // this will reset current active cell
              this._dismissClickToEdit(true);

              // show context menu
              this._displayContextMenu(domEvent, false);
          }
          else
          {
              // display context menu after click to edit comes back
              this.domEventForContextMenuAfterEdit = agent.copyEvent(domEvent);
        
              // make the cell editable
              this.doHandleClickToEdit(componentEvent);      
          }
      }
    }
  } 
  else 
  {
    this._dismissEditAndDisplayContextMenu(componentEvent, true);
  }
}

AdfDhtmlPivotTablePeer.prototype._getBodyContextMenuPopupId = function()
{
    var popupId = this.getDomElement().getAttribute("_cbcm");    
    if (popupId == null)
        return AdfDhtmlPivotTablePeer.BODY_CONTEXT_MENU_POPUP_ID;
    else
        return popupId;
}

AdfDhtmlPivotTablePeer.prototype._getContextMenuPopupId = function()
{
    var popupId = this.getDomElement().getAttribute("_ccm");    
    if (popupId == null)
        return AdfDhtmlPivotTablePeer.CONTEXT_MENU_POPUP_ID;
    else
        return popupId;
}

/**
 * This method should not be called when the PT is in certain states.  These checks are done in the two 
 * entry points to this method, HandleComponentKeyDown and HandleComponentContextMenu.
 */
AdfDhtmlPivotTablePeer.prototype._displayContextMenu = function(domEvent, isBodyContextMenu, element) {

//    if (this.loggingOn) AdfLogger.LOGGER.severe("_displayContextMenu setting ptState to CM");
    this._ptState = AdfDhtmlPivotTablePeer.STATE_CONTEXT_MENU; // we avoid handling keystrokes when in this state.
    this.selectionChangedSinceLastCmRequest = false; // see HandleComponentPopupOpening for explanation
    
    var isLegacy = this._displayLegacyContextMenu(domEvent, isBodyContextMenu, element);
    if (isLegacy) // then we've already displayed the legacy context menu, so...
        return;
    
    var menuId = (isBodyContextMenu) 
                 ? this._getBodyContextMenuPopupId() 
                 : this._getContextMenuPopupId();
    this.showContextMenu(domEvent, menuId, this.getComponent(), element);
    
    // prevent multiple calls to the context menu
    var agent = AdfAgent.AGENT;
    agent.stopPropagation(domEvent);
    agent.preventDefault(domEvent);
}

/*
 * Displays app context menus specified in the legacy way, using ID's instead of facets.  
 * 
 * If the app specifies a contextMenuId/bodyContextMenuId, which is the deprecated way
 * to specify a context menu, then our encodeAll is never called since their popup isn't 
 * part of the PT.  Thus the menu contains only the app's menu items, not any of the PT's 
 * built-in menu items.  Before these ID's were deprecated, there were no built-in menu 
 * items, so no functionality is being taken away from users of the legacy functionality.  
 */
AdfDhtmlPivotTablePeer.prototype._displayLegacyContextMenu = function(domEvent, isBodyContextMenu, element)
{
  var component = this.getComponent();
  var isLegacy = false;
  var menuId = null;
  if(isBodyContextMenu)
    menuId = component.getBodyContextMenuId ? component.getBodyContextMenuId() : null;
  else
    menuId = component.getContextMenuId ? component.getContextMenuId() : null;
  
  if (menuId)
  {
    isLegacy = true;
    
    // resolve menu using pivot table
    var popup = component.findComponent(menuId);
    var menuFindContainer = component;
    if (popup == null)
    {
      // look the menu up in the root
      popup = AdfPage.PAGE.findComponent(menuId);
      menuFindContainer = null;
    }
    
    // fix for bug 9056621
    if (popup != null) {
        // remove old listener first
        popup.removeEventListener("popupClosed", this.handleLegacyContextMenuClosing, null);
        popup.addEventListener("popupClosed", this.handleLegacyContextMenuClosing, null);    
    }

    this.showContextMenu(domEvent, menuId, menuFindContainer,element);
    
    // prevent multiple calls to the context menu
    var agent = AdfAgent.AGENT;
    agent.stopPropagation(domEvent);
    agent.preventDefault(domEvent);
  }
  
  return isLegacy;
}

AdfDhtmlPivotTablePeer.prototype.handleLegacyContextMenuClosing = function() {
    this.HandleComponentPopupClosed(null);
}

/**
 * Called when context menu items are clicked.  
 */
AdfDhtmlPivotTablePeer.prototype.HandleComponentAction = function(componentEvent) {
//    AdfLogger.LOGGER.severe("HandleComponentAction");
    var source = componentEvent.getSource();
    // if it is disabled, do nothing
    if (source.getDisabled())
        return;
    var clientId = source.getClientId();
    
    // TODO: replace this growing if/else block with a clean actionMap impl, as in AdfDhtmlPanelCollectionPeer.prototype.HandleComponentAction

    // handle built-in context menu items
    if (clientId.indexOf(AdfDhtmlPivotTablePeer.DATA_SORT_ASCENDING_MENU_ITEM_ID) != -1) {
        this.handleSortContextMenuAction(true, false);
        componentEvent.cancel();
    } else if (clientId.indexOf(AdfDhtmlPivotTablePeer.DATA_SORT_DESCENDING_MENU_ITEM_ID) != -1) {
        this.handleSortContextMenuAction(false, false);
        componentEvent.cancel();
    } else if (clientId.indexOf(AdfDhtmlPivotTablePeer.HEADER_SORT_ASCENDING_MENU_ITEM_ID) != -1) {
        this.handleSortContextMenuAction(true, true);
        componentEvent.cancel();
    } else if (clientId.indexOf(AdfDhtmlPivotTablePeer.HEADER_SORT_DESCENDING_MENU_ITEM_ID) != -1) {
        this.handleSortContextMenuAction(false, true);
        componentEvent.cancel();
    } else if (clientId.indexOf(AdfDhtmlPivotTablePeer.RESIZE_HEIGHT_MENU_ITEM_ID) != -1) {
        this.showResizeDialog(true);
        componentEvent.cancel();
    } else if (clientId.indexOf(AdfDhtmlPivotTablePeer.RESIZE_WIDTH_MENU_ITEM_ID) != -1) {
        this.showResizeDialog(false);
        componentEvent.cancel();
    } else if (clientId.indexOf(AdfDhtmlPivotTablePeer.SPLIT_PANES_MENU_ITEM_ID) != -1) {
        this.queueSplitRequest();
        componentEvent.cancel();
    } // else it must have been a menu item provided by the application, in which case their handler, not ours, does the work
}

/**
 * @param isHeader boolean indicates whether is header sort vs. data sort
 */
AdfDhtmlPivotTablePeer.prototype.handleSortContextMenuAction = function(isAscending, isHeader) {
    var selectedEdge = this.getSelectedEdge();
    var startIndex = this.getSelectionForEdge(selectedEdge)[0]['startIndex'];
    var reorderedEdge;
    var sliceOrLayer;
    if (isHeader) {
        reorderedEdge = selectedEdge;
        sliceOrLayer = startIndex['layer'];
    } else { // is data sort
        reorderedEdge = 1 - selectedEdge; // the opposite edge
        sliceOrLayer = startIndex.slice;
    }
    this.queueSortRequest(reorderedEdge, sliceOrLayer, isAscending, isHeader);
}

/**
 * Note that when the dialog is closed by clicking the red X in the titlebar, this method 
 * is not called, but HandleComponentPopupClosed is.  All other ways of closing the dialog 
 * do call this method.
 */
AdfDhtmlPivotTablePeer.prototype.HandleComponentDialog = function(componentEvent) {
//    AdfLogger.LOGGER.severe("HandleComponentDialog.  Selected edge is " + this.getSelectedEdge());
    // TBD:  commented lines are how PanelCollection's peer handles multiple dialogs.  We only have the resize dialog for now, so it's commented out.
    
//    if(null == this._openedDialogType)
//        return; // Not a standard dialog
//    var dialogShouldClose = true;

    // make sure the dialog is the pivot table resize dialog
    if (componentEvent.getSource().getClientId() != this.getComponent().getClientId() + AdfDhtmlPivotTablePeer._NAMING_SEP + AdfDhtmlPivotTablePeer.RESIZE_DIALOG_ID)
        return;

    if(componentEvent.getOutcome() == AdfDialogEvent.OUTCOME_OK) {
//        var openedDialogType = this._openedDialogType;
//        
//        if(openedDialogType == AdfDhtmlPivotTablePeer._DIALOG_TYPE_RESIZE) {
            this.handleResizeDialog(componentEvent);
//        } else if(openedDialogType == AdfDhtmlPivotTablePeer._DIALOG_TYPE_SOMETHING_ELSE) {
//            this.handleSomethingElse(componentEvent);
//        }
        
        componentEvent.cancel(); // don't send to server
        
        var clientId = this.getComponent().getClientId();
        var popup = AdfPage.PAGE.findComponent(clientId + 
                                AdfDhtmlPivotTablePeer._NAMING_SEP + 
                                AdfDhtmlPivotTablePeer.RESIZE_DIALOG_POPUP_ID);

        popup.hide();
    }
}

AdfDhtmlPivotTablePeer.prototype.HandleComponentPopupOpening = function(componentEvent) {
    // if there is a pivot/drill/etc. pending, stop our context menu/resize dialog or stamp component activities (fixing bug 8871654)
    if (this._currentRequest == AdfDhtmlPivotTablePeer.PARTIAL)
    {
        componentEvent.cancel();
        return;
    }
    
    var clientId = componentEvent.getSource().getClientId();
    var index = clientId.indexOf(':',this.getComponent().getClientId().length+1);
    var cellIndex = clientId.substring(this.getComponent().getClientId().length+1, index);

    // if this is a popup that is coming from stamp component, just return from here
    if (!isNaN(cellIndex))
    {
        this._ptState = AdfDhtmlPivotTablePeer.STATE_STAMP_POPUP;
        return;
    }


    // When this point in the code is reached, the ptState is Dialog iff it's the Resize Dialog that opened, and its CM or Normal iff it's the CM that opened.
    // When it's the CM that opened, the state is typically CM since _displayCM set it to CM when it requested the cM from the server.  But if an 
    // older CM was already open when the new one was requested, and it closed in the meantime, then HCPopupClosed set the state to Normal, 
    // because it had no way of knowing that another CM was on its way.  In this case we set the state back to CM so that KB handling is disabled 
    // while the new CM is displayed. 
    switch (this._ptState) {
        case AdfDhtmlPivotTablePeer.STATE_NORMAL:
        case AdfDhtmlPivotTablePeer.STATE_CONTEXT_MENU:
            // If the selection has changed since the CM was requested, then the arriving CM is relevant to an old selection, not the current one, 
            // so we must cancel it to avoid showing irrelevant menuItems, and possibly having the user click them with unpredictable results.  
            var cancelCM = this.selectionChangedSinceLastCmRequest;
            
            if (!cancelCM) { // if not already canceling CM due to selection change, see if we need to do so because it's empty.
                // The popup framework will display a context menu even if it is empty, so we must suppress it ourselves.  We can detect emptiness either on the server or client.  
                //
                // (1) If the server, then we have to send the popup anyway and set a sentinel telling the client to bail out.  Reason: if don't send the popup, then clientside popup framework will error out the next time a popup is requested because previous one never arrived.  Options for sentinel:
                //
                // - Can't put expando on popup or its descendants b/c we don't control its rendering.  
                // - Nothing else is being rendered (e.g. no data blocks) so can't put expando elsewhere.  
                // - Setting a PT property would be overkill.  
                // - Shouldn't set ID of anything potentially app-provided, including popup, menu, or menuItem.
                // - Also, can't change the popup ID since client is waiting for popup with a specific ID.  
                // - So, only good server-side option seems to be adding dummy menuItem (or submenu or whatever) with sentinel ID (and remove it on next render if menu non-empty then).  Small risk that an app's menuItem would have the same ID, so choose obscure ID.  To ensure uniqueness, must prepend sentinel ID with popup ID, since we have multiple CM popups, and popups are not naming containers.
                //
                // Also, can't just check (menu.getChildCount==0) on the server, since it might have children with rendered==false.  So we'd actually have to walk the subtree to see if anything is rendered, and be careful what we check for.  (E.g. if a group is rendered but none of its children are, then the menu is still empty.)
                //
                // (2) If detect emptiness on the client:  
                //
                // - Unlike the server, non-rendered components aren't present, so can just check the number of children/descendants.  
                // - Ideally, we'd check (popup.firstChildComponent.childCount==0), but there only seems to be API to search all descendants, not just immediate children, making it less efficient than desired.  (But typically the menu shouldn't be huge.)  The options seem to be popup.findComponent() and popup.getDescendantComponents().
                //
                // (3) Conclusion: 
                //
                // - If menu empty, finding a sentinel and finding all descendants both involve walking a very small subtree.
                // - If menu non-empty, finding a non-existent sentinel and finding all descendants both involve walking the entire subtree (which typically is reasonable size).
                // - menu=popup.findComponent(menuID);menu.getDescendantComponents();
                //   is probably slower than
                //   popup.getDescendantComponents()
                // - So no benefit to more complicated server-side detection or 2-step client detection.  So do this:

                var popup = componentEvent.getSource();
                var desc = popup.getDescendantComponents();
                cancelCM = (desc==null || desc.length<=1); // if empty menu, then popup has 1 descendant: the menu itself.  The null/length-0 cases shouldn't happen.
                
//                if (this.loggingOn) AdfLogger.LOGGER.severe((cancelCM) 
//                                        ? "HCPOpening: Old state was " + this._ptState + ".  Cancelling CM b/c menu empty.  Setting ptState to Normal."
//                                        : "HCPOpening: Old state was " + this._ptState + ".  Showing CM.");
//            } else {
//                if (this.loggingOn) AdfLogger.LOGGER.severe("HCPOpening: Old state was " + this._ptState + ".  Cancelling CM b/c selection changed.  Setting ptState to Normal.");
            }

            if (cancelCM) {
                componentEvent.cancel(); // don't show the context menu
                // Exit CM mode so that keystroke handling is no longer prevented
                this._ptState = AdfDhtmlPivotTablePeer.STATE_NORMAL;
            } else {
                // Stay in (or go to) CM state so that keyboard handling is disabled when CM is displayed.  
                // (We will return to Normal state later when the popup closes.)
                this._ptState = AdfDhtmlPivotTablePeer.STATE_CONTEXT_MENU;
            }
            break;
        case AdfDhtmlPivotTablePeer.STATE_DIALOG:
//            if (this.loggingOn) AdfLogger.LOGGER.severe("HCPOpening: ptState already Dialog.");
            this.resizeDialogOpening();
            break;
    }
}

/**
 * Sets the ptState back to Normal, from ContextMenu or Dialog, when the ContextMenu or 
 * Dialog closes, so that handling of keystrokes, etc., is no longer blocked.
 * 
 * This method is called when the CM or dialog is closed for any reason, keyboard or mouse.
 * For the CM, it's called whether a menuItem was chosen or the menu was canceled.  
 * For the dialog, it's called whether the dialog was accepted via Enter/OK, or canceled via 
 * Cancel/Esc/clicking the X in the titlebar.  (By contrast, HandleComponentDialog is not called 
 * when the dialog is closed by clicking the X in the titlebar.)
 * 
 * When the context menu closes, it might be because a menuItem that launches the dialog 
 * was selected.  In this case, this method is called before HandleComponentAction, 
 * so HCA's callee sets ptState to Dialog after this method sets it to Normal, as desired.  
 */
AdfDhtmlPivotTablePeer.prototype.HandleComponentPopupClosed = function(componentEvent) {
//    if (this.loggingOn) AdfLogger.LOGGER.severe("HandleComponentPopupClosed setting ptState to Normal");
    this._ptState = AdfDhtmlPivotTablePeer.STATE_NORMAL; // no longer displaying context menu/dialog, so no longer need to prevent handling keystrokes, etc.
}

// if a user clicks or right click on the background of a cell, the input component inside the cell loses focus and cause
// client side valiation to occur.  If validation failed, since we are clicking on the same cell, there is no submit.  As a result
// , no error message is displayed since the input component do not have focus.  We'll need to check for error messages and force focus 
// back to the cell so that the error message is display correctly.
AdfDhtmlPivotTablePeer.prototype.isClientValidationError = function(target)
{
  var agent = AdfAgent.AGENT;
  var component = this.getComponent();
  
  // detect clicking on the background of a cell
  if (target == this._activeCell)
  {
      // it's not going to submit, so check for any client validation error messages
      // for stamped component
      var clientIds = AdfPage.PAGE.getClientIdsWithMessages(component.getClientId());
      if (clientIds != null && clientIds.length > 0)
      {
          for (var i=0; i<clientIds.length; i++)
          {
              if (clientIds[i].indexOf(target.id) == 0)
              {
                  var elem = agent.getElementById(clientIds[i]);
                  var inputElems = this._getInputElementsInNode(elem);
                  if (inputElems != null && inputElems.length > 0)
                      inputElems[0].focus();

                  return true;
              }
          }
      }
  }    
  
  return false;
}

AdfDhtmlPivotTablePeer.prototype.HandleComponentClick = function(componentEvent)
{  
  // AdfLogger.LOGGER.severe('Handle Component Click');  
  // Bail if we're in resize-dialog-displayed state.  Once the dialog is actually displayed, the glass pane prevents mouse interaction for us, but we want to prevent mouse interaction in the time after a user requests the dialog (from a cell's right-click menu) and before the dialog actually displays, to avoid weird state changes during that time--e.g. sorting.  We want the displaying of the dialog to be an atomic operation.
  if (this._currentRequest == AdfDhtmlPivotTablePeer.PARTIAL || this._ptState == AdfDhtmlPivotTablePeer.STATE_DIALOG || this._ptState == AdfDhtmlPivotTablePeer.STATE_STAMP_POPUP) 
    return;
  
  var component = this.getComponent();
  var agent = AdfAgent.AGENT;
  var domEvent = componentEvent.getNativeEvent();
  var target = agent.getEventTarget(domEvent); 
  
  // checks if there is a client side validation error, if so, bail out
  if (this.isClientValidationError(target))
    return;
  
  if (this._isPaging)
  {
    if (this._handleClickOnPagingControl(domEvent, target))
      return;
  }

  if(componentEvent.isLeftButtonPressed())
  {
    this.handleImageAction(component, target);            
    if(this._isSortEnabled) // is *data* sorting enabled
      this.handleSortAction(component, domEvent,target);         
  }    

  if (this._isClickToEditAll())
  {
    var valid = true;
    if (this._activeCell != null)
    {
        // check if the current active component is valid
        var result = this._findInputComponentInCell(this._activeCell);
        var comp = result.component;
        if (comp != null && comp instanceof AdfUIEditableValue)
            valid = comp.getValid();
    }

    if (valid)  
    {
        // make the cell active
        this._handleClickToEditAll(domEvent, target);
    }
    else
    {
        // shift focus back to invalid (active) cell
        var dataCellIndex = this.newDataCellIndexFromCell(this._activeCell);
        this.selectAndFocusDataCell(dataCellIndex);
        this._processClickToEditRequest();
    }

    // we are done with click to edit
    return;      
  }

  // --- BEGIN Dismiss-click-to-edit logic  ---
  
  // - NOTE: dismissClickToEdit needs to be executed after all the code for sort/drill/pivot are executed,
  //   to make sure that the timer won't be started when there is a pending sort/drill/pivot action  
  // - Dismiss click to edit on any click ( left / right ) that is in another cell
  // - We call _dismissClickToEditIfNeeded() here (and in handleHeaderSelectionDrag and handleDatabodySelectionDrag) rather than on mouse down so that user can resize rows/cols without exiting edit mode.
  //   - TBD: Might be better to do it on mouseDown, in all cases except when the mousedown starts a resize.  
  // - The _resizeDropMousePos stuff is to detect whether the click event is the result of a mouse resize that was small enough to register as a click event, and avoid dismissing CTE in that case.
  // - TODO - determine if we should dismiss on right click of editable cell.   
  //    - RESPONSE: Yes!  We should dismiss upon right-clicking elsewhere in PT, whether is editable cell, non-editable cell, header cell, etc.  Otherwise we get in a state where the selection is different than the cell being edited.
  var mousePos = AdfAgent.AGENT.getMousePosition(domEvent);
  if(this._resizeDropMousePos==undefined || 
     (this._resizeDropMousePos.x!=mousePos.x || this._resizeDropMousePos.x!=mousePos.x)  ) {

    // ###  AdfLogger.LOGGER.severe('in HandleComponentClick, going to _dismissClickToEditIfNeeded with actionPending :' + (this._currentRequest == AdfDhtmlPivotTablePeer.PARTIAL) + ', cancelDismiss = ' + this._cancelDismiss + ', alreadyRequestedDismiss =' + this.alreadyRequestedDismissEdit);
    // CCCHOW - do NOT dismiss edit if CTRL or SHIFT key is pressed
    if (!domEvent.shiftKey && !this.ctrlEquivalent(domEvent)) // TODO: Mac should use Command instead of Ctrl for mouse (e.g. Ctrl-click), but should use Ctrl for keystrokes like Ctrl-arrow and Ctrl-space.  ctrlEquivalent does the right thing for mouse, but unclear whether we're doing right thing for KB.  What keystrokes call this click handler, and are they ones for which Mac should use Ctrl not Command?
        this._dismissClickToEditIfNeeded(target);
  }
  this._resizeDropMousePos = undefined;

  // --- END Dismiss-click-to-edit logic  ---
  if (!this.preserveEditingBehavior())
    this.doHandleClickToEdit(domEvent, target);
}

AdfDhtmlPivotTablePeer.prototype._handleClickToEditAll = function(domEvent, target)
{
    // find closiest component
    var attrs = this._getDataCellKeyAndCell(target);
    if(attrs != null)
    {
        var cellKey = attrs[0];
        var cell = attrs[1];
        // checks whether target is within cell
        if (cellKey == null || cell == null)
            return;

        var success = this._updateInputReadOnlyState(target, cell, false);
        if (success)
        {
            // dismiss any currently click to edit cell
            if (this._submittedCellKey != null)
            {
                this._cancelDismiss = false;
                this._dismissClickToEdit(false);
                // todo: should we return immediately? and do the rest in DomReplaceNotify?
            }

            var oldRowHeight = cell.parentNode.offsetHeight; //cache, to go back to original size
            if (this._activeCell != this._prevActiveCell)
                this._dismissPreviousActiveCell(cell, cell, oldRowHeight);

            this._cellRowHeight = cell.parentNode.offsetHeight; //cache, to go back to original size
            var rowHeight = cell.parentNode.offsetHeight;
            var delta = rowHeight-oldRowHeight;
            // adjust row height as neccessary
            if(delta != 0)
            {
                this._clickToEditResize(cell, delta);
            }

            this._lastUpdatedRow = this.getCoordinates(cell)['row'];
            this._oldRowHeight = oldRowHeight;
            this._activeCell = cell;
            this._prevActiveCell = this._activeCell;

            this._setActiveCellKey(cellKey, true);                
        }
        else
        {
            // normal click to edit
            if (!this.preserveEditingBehavior())
              this.doHandleClickToEdit(domEvent, target);
        }
    }    
}

AdfDhtmlPivotTablePeer.prototype.doHandleClickToEdit = function(domEvent, target)
{
  // CCCHOW - single click to edit logic
  // only when shift and ctrl key not pressed, otherwise it is a contiguous selection
  if(this._isClickToEdit() && !this._isReadOnly())
  {
      if(!domEvent.shiftKey && !this.ctrlEquivalent(domEvent)) // TODO: same Mac ctrlEquivalent() todo as in HandleComponentClick.
      {
        var attrs = this._getDataCellKeyAndCell(target);
        if(attrs != null)
        {
          var cellKey = attrs[0], cell = attrs[1];
          if(cellKey != null && cell.id)
          {
            // clicking on the same cell?
            if (cellKey != this._getActiveCellKey())
            {
              this._activeCell = cell;
              this._cellRowHeight = cell.parentNode.offsetHeight; //cache, to go back to original size
              this._cancelDismiss = true; // cancel the dismiss from a single click, if a double click occurs!
              this._setActiveCellKey(cellKey);
            }
            else if (this._activeCellKeySetFromServer)
            {
              // highlight the whole text
              var eventTarget = AdfAgent.AGENT.getEventTarget(domEvent);
              if (eventTarget.tagName=="INPUT" && eventTarget.type=="text") 
                  eventTarget.select();
            }
          }
        }
      }
      else if (this._getActiveCellKey() != null)
      {
          // force focus back to active cell if shift or ctrl key is pressed
          this._processClickToEditRequest();
      }

      // clear the flag otherwise for shift/ctrl click HandleComponentFocus will unselect other cells
      // we'll also need to clear the flag for other case so that HandleComponentFocus will correctly
      // highlight the cell (as focused and selected)
      this._activeCellKeySetFromServer = false;
   }
}

// place the cursor to the end of the text field, scroll to it if neccessary
AdfDhtmlPivotTablePeer.prototype.setCursorToEndOfText = function(componentEvent) 
{
  var agent = AdfAgent.AGENT;
  var domEvent = componentEvent.getNativeEvent();
  var eventTarget = agent.getEventTarget(domEvent);
  if (eventTarget.tagName=="INPUT" && eventTarget.type=="text") 
  {
    if (agent.getPlatform()==AdfAgent.IE_PLATFORM)
    {
      var range = eventTarget.createTextRange();
      range.moveStart("character", eventTarget.value.length);
      range.moveEnd("character", eventTarget.value.length);
      range.select();
    }
    else
    {
      eventTarget.setSelectionRange(eventTarget.value.length, eventTarget.value.length);
            
      // needed this for Safari and Chrome
      eventTarget.blur();
      eventTarget.focus();
            
      // simulate keypress so that text field scroll to the end
      var evt = document.createEvent("KeyboardEvent");
      evt.initKeyEvent("keypress", true, true, null, false, false, false, false, 27, 0);
      eventTarget.dispatchEvent(evt);
    }
  }    
}

AdfDhtmlPivotTablePeer.prototype.HandleComponentDblClick = function(componentEvent)
{
  // AdfLogger.LOGGER.severe("dbl-click");  
  // old behavior - double click to edit
  if (this.preserveEditingBehavior())
  {
    this.doHandleClickToEdit(componentEvent);
  }
  else
  {
    this.setCursorToEndOfText(componentEvent);    
  }

  // reset
  this._clickTimeStamp = null; // keep last click timetamp and last clicked cell (for 2-clicks-edit feature) 
  this._clickCellKey = null;
}

AdfDhtmlPivotTablePeer.prototype.HandleComponentKeyDown = function(componentEvent)
{
  // for debugging IE focus bug 
  //if(window._foo)
  //{
  //   this._focusElement(this.getDomElement());
  // }
  // If the resize dialog or context menu or popup from stamp component is displayed, then don't handle any keystrokes, e.g. Tab, arrow-key navigation, Ctrl-Alt-m, KB pivoting, etc.
  if (this._ptState == AdfDhtmlPivotTablePeer.STATE_DIALOG || this._ptState == AdfDhtmlPivotTablePeer.STATE_CONTEXT_MENU || this._ptState == AdfDhtmlPivotTablePeer.STATE_STAMP_POPUP) 
    return;
  
  var domEvent = componentEvent.getNativeEvent();
  var agent = AdfAgent.AGENT;
  var eventTarget = agent.getEventTarget(domEvent);
  var keyCode = domEvent.keyCode;
  
  // If the paging control has focus, then only handle the enter key stroke.
  if (this._isPaging)
  {
      var elem = eventTarget;
      var domElem = this.getDomElement();
      while (elem != domElem)
      {
          if (elem == this._ptUtils._pagingControl[0][0] || elem == this._ptUtils._pagingControl[0][1] ||
              elem == this._ptUtils._pagingControl[1][1] || elem == this._ptUtils._pagingControl[1][0])
          {
              if (keyCode==AdfKeyStroke.ENTER_KEY)
                  this._updatedPagingInputValue = true;
              return;
          }
          elem = elem.parentNode;
      }
  }
  
  var altKey = domEvent.altKey;
  var ctrlKey = domEvent.ctrlKey; // use CtrlKey, not this.ctrlEquivalent(domEvent), since unlike mouse handling, Ctrl (not Command) should be used on the Mac.     
  var shiftKey = domEvent.shiftKey;
  var component = this.getComponent();  
  
  if (eventTarget.tagName == "A" && keyCode==AdfKeyStroke.ENTER_KEY) // we don't want to do HandleComponentKeyDown if target is 'A' because it will be handled by HandleComponentClick
  {
      // test to ensure we are really pressing enter key on the active cell
      // there are cases where the link temporary has the focus and the enter key is consumed incorrectly
      var attrs = this._getDataCellKeyAndCell(eventTarget);
      if(attrs != null)
      {
          var cellKey = attrs[0], cell = attrs[1];
          if(cellKey != null && cell.id && cell == this._activeCell)
              return;
      }
  }

  // separate ADA and non-ADA handling
  if( !this._isAda && this._currentRequest != AdfDhtmlPivotTablePeer.PARTIAL)
  {
    var focusCellIndex = this._focusCellIndex;
    var isMultipleCellsSelected = this.isMultipleDatabodyCellsSelected();
    // we are focusing on the databody, have an active cell, has either multiple cells selected or the active cell matches _focusCellIndex
    if(focusCellIndex != null && this.getEdge(focusCellIndex) == AdfPivotTableUtils.DATABODY && this._activeCell != null && (isMultipleCellsSelected || this._getDataCellFromCellIndex(focusCellIndex) == this._activeCell)) 
    {
        switch(keyCode)
        {
            case AdfKeyStroke.ESC_KEY:
                // cancel bubbling the ESC event, since we got strange behavior here, where the dismiss didn't work, except in the debugger.
                componentEvent.cancel();
                
                // revert cell edit
                if(!this.preserveEditingBehavior() && undefined != eventTarget._afrOldValue)
                {
                    // revert value if it has changed, otherwise dismiss editing
                    if (AdfDhtmlPivotTablePeer._hasFocusCellValueChanged(eventTarget))
                    {
                        // use a timout to restore the value (required for FF)
                        AdfPage.PAGE.scheduleTimer(null, AdfDhtmlPivotTablePeer._restoreFocusCellValue, eventTarget, 0);

                        if (this._isClickToEditAll())
                        {
                            // make it valid again
                            var comp = this._findInputComponent(eventTarget, this._activeCell);
                            if (comp != null)
                            {
                                comp.resetValue();
                                
                                // remove dirty flag
                                var domElement = AdfRichUIPeer.getDomElementForComponent(comp);
                                if (domElement != null)
                                    domElement.removeAttribute("_afrCellDirty");
                            }
                        }
                    }
                    else
                    {
                        // don't revalidate if client click to edit
                        if (!this._isClickToEditAll())
                            this._clearMessageAndRevalidate(componentEvent); 
                        this._processDismissCTE(componentEvent);                  
                        this._isNavigationMode = true;
                    }
                }
                else if (this.preserveEditingBehavior())
                {
                    // clear error message, since input control do not validate themselves until they lose focus, 
                    // but in our scenario ESC can be pressed before the input control loses focus.
                    this._clearMessageAndRevalidate(componentEvent); 
                    
                    this._processDismissCTE(componentEvent);
                }
                break;            
            case AdfKeyStroke.TAB_KEY:
            case AdfKeyStroke.ENTER_KEY:
                // cancel bubbling the ESC event, since we got strange behavior here, where the dismiss didn't work, except in the debugger.
                var isValid = true;
                // don't revalidate if value has not changed
                if (eventTarget._afrOldValue != undefined && eventTarget._afrOldValue != eventTarget.value)
                    isValid = this._clearMessageAndRevalidate(componentEvent);                    
                componentEvent.cancel();

                if (isValid)
                {
                    if (keyCode == AdfKeyStroke.TAB_KEY)
                        this.handleTabLeftRightKey_EditMode(componentEvent, keyCode);
                    else
                        // enable ENTER-key handling when 1) event target is itself (hit a readonly cell) 2) arrow keys 3) input controls and combo box controls
                        this.handleEnterUpDownKey_EditMode(componentEvent, keyCode);            
                }
                break;
            case AdfKeyStroke.ARROWLEFT_KEY:
            case AdfKeyStroke.ARROWRIGHT_KEY:
            case AdfKeyStroke.ARROWUP_KEY:
            case AdfKeyStroke.ARROWDOWN_KEY:
                if(!this.preserveEditingBehavior() && ctrlKey && !shiftKey)
                {
                    isValid = true;
                    // don't revalidate if value has not changed
                    if (eventTarget._afrOldValue != undefined && eventTarget._afrOldValue != eventTarget.value)
                        isValid = this._clearMessageAndRevalidate(componentEvent);                    
                    componentEvent.cancel();

                    if (isValid)
                    {
                        if (keyCode == AdfKeyStroke.ARROWLEFT_KEY || keyCode == AdfKeyStroke.ARROWRIGHT_KEY)
                        {
                            // navigate left or right
                            this.handleTabLeftRightKey_EditMode(componentEvent, keyCode);
                        }
                        else 
                        {
                            // navigate up or down
                            this.handleEnterUpDownKey_EditMode(componentEvent, keyCode);                                    
                        }
                    }
                }
                break;
            default:
                break;
        }
        
        return; // does not want to do the keystrokes handling below
    } // end if focusCellIndex
    else // target is NOT something in a datacell. (We're still in the "not ADA" block.) 
    {
      // In this case, if the Enter key was pressed, and the focus cell is a drillable 
      // header cell, then drill. (Given currently available metadata, we can know that 
      // the cell is drillable only if at least one of the cell's TH's is present on the 
      // client and it is the one that contains the drill icon.  Otherwise, the Enter key 
      // will be a no-op.)  
      
      var focusCellIndex = this._focusCellIndex;
      if (focusCellIndex != null && this.getEdge(focusCellIndex) != AdfPivotTableUtils.DATABODY && keyCode==AdfKeyStroke.ENTER_KEY)
      {
        // get the cell to focus into (current focus is in PT)
        range = this.newHeaderCellRange(focusCellIndex);
        nodes = this.getNodesInHeaderCellRange(range, focusCellIndex['edge']);          
    
        if(nodes.length == 0) // there are no cell nodes--i.e. focusCell is not present on the client
          return;
        var th = nodes[0]; // the first TH we find is the one that would contain the drill icon if present
        
        //before we do drilling for this header cell, 
        //need to make sure that we don't have focus on any elements inside this header cell that has it's own action when Enter is used  
        var elems = this._getInputElementsInNode(th);
        var index = this._getCurrentInputElementIndex(elems,eventTarget);
        if(elems != null && index == -1) // 
        {
            var div = th.firstChild;
            if(div == null) 
              return;
            var span = div.firstChild;
            if (span==null || span.nodeName!="SPAN")
              return;
            if(agent.getAttribute(span,"_dr")) 
              this.handleDrillAction(component,th);
            return;
        }
      }
    } // end of "target is NOT something in a datacell" case
  }
  
////////////////////////////////////////////////////////////////////////////////
    switch(keyCode)
  {
    case 16: // Shift
//    case 17: // Ctrl
    case 18: // Alt
      if(this._isAda) 
        break; // no KB pivoting in ADA
      
      // If Shift-Alt is pressed, then show the pivotHandle (if callee's conditions are met) to indicate that additionally pressing an arrow key will do KB pivoting.  
      if (altKey && shiftKey) {
          this.showPivotHandlesForKeyboardPivoting();
      }
      break;

    case AdfKeyStroke.ARROWRIGHT_KEY:
    case AdfKeyStroke.ARROWDOWN_KEY:
    case AdfKeyStroke.ARROWUP_KEY:
    case AdfKeyStroke.ARROWLEFT_KEY:
        if(this._isAda) 
            break; // no cell navigation or KB pivoting in ADA
            
        if (altKey) {
            if (!ctrlKey) {
                // (1) Alt-Shift-Arrow and (2) Alt-Arrow. (1) has been supported for years, chosen b/c neither Alt-Shift-Arrow nor Alt-Shift-Esc (to cancel pivot)
                // have existing functionality in the PT, browser, or system-wide (e.g. Windows modifier+Esc shortcuts and Intel's Ctrl-Alt-Arrow rotate-screen 
                // shortcuts).  Adding support for (2) for JAWS users, since in JAWS Alt-Shift-Arrow moves the "mouse", and Alt-Ctrl-Shift-Arrow does table nav.
                // Alt-Esc has Alt-Tab-like functionality in Windows, but JAWS (and other) users can still cancel by pivoting back to where they started before 
                // releasing Alt.
                if (!this.isAttachmentMode())
                    this.handleKeyboardPivoting(keyCode, shiftKey);
            } // else it's Alt-Ctrl-Arrow or Alt-Ctrl-Shift-Arrow, for which we do nothing
        } else if ( !ctrlKey || shiftKey) { // Includes Arrow, Shift-Arrow, and Ctrl-Shift-Arrow, all without Alt.  Why include Ctrl-Shift-Arrow:  different apps have different rules regarding how to extend the current range of a discontigous selection.  Excel uses Shift-arrow (which we emulate), but other apps (e.g. Thunderbird) require Ctrl-Shift-Arrow.  We're not using Ctrl-Shift-Arrow for anything else anyway, so we should make it so that users who press Ctrl-Shift-Arrow get the behavior they were expecting.  If we invent another use for Ctrl-Shift-Arrow, we can start using that keystroke for that purpose at that time.
            this.handleKeyboardNavigation(shiftKey, keyCode, false);
            // TBD: it's suspicious that the following fix is needed for arrow nav but not Ctrl-arrow nav.  Is it needed for both cases, or neither?  
            this._focusElement(this.getDomElement());// if there is an element that's being focused in the header cell, we need to clear the browser focus back to PT
        } else { // Ctrl-Arrow, no Alt or Shift.
            this.handleCtrlArrowNavigation(keyCode);
        }
      
        componentEvent.cancel(); // prevent entire page from scrolling, prevent Ctrl-Shift-Arrow from doing browser text selection (as opposed to PT selection), etc.
        break;
    
    case AdfKeyStroke.ESC_KEY:
      if(this._isAda) 
        break; // no KB pivoting in ADA 
      
      // Shift-Alt-Esc cancels KB pivoting.  TODO: IS THIS COMMENT STILL TRUE? In Windows, that key combo is an Alt-Tab variant that switches to another application.  (To make sure pivoting is canceled in this case, we cancel it onBlur.)  
      if (altKey && shiftKey)
        this.endKeyboardPivoting(true); 
      break;
////////////////////////////////////////////////////////////////////////////////
    
    case AdfKeyStroke.SPACE_KEY:
      if(this._isAda) 
        break; // ADA has a different selection mechanism
        
      var focusCellIndex = this._focusCellIndex;
      if (focusCellIndex != null && this.getEdge(focusCellIndex) != AdfPivotTableUtils.DATABODY) { // Space key used only in headers
        this.handleHeaderSelection(focusCellIndex, domEvent);
      }
      componentEvent.cancel();
      break;
    
    case AdfKeyStroke.F2_KEY:
      if(this._isAda) 
        break; // ADA cell editing is edit-all, not click-to-edit, and there is no focus cell in ADA anyway
        
      this.editCellWithFocus();
      break;
    
    case AdfKeyStroke.TAB_KEY:
      if(this._isAda) 
        break; 

      // fixing bug 9937758
      // we are in editing, but ended up here because
      // 1) action pending because of block fetch
      // 2) edit cell has not return yet in DomReplaceNotify, so focusCellIndex is ahead of active cell
      if (this._focusCellIndex != null && this.getEdge(this._focusCellIndex) == AdfPivotTableUtils.DATABODY)
      {
          if (this._activeCell != null)
            // don't allow it to go outside of Pivot Table
            componentEvent.cancel();
          else if (this._isReadOnly() && !this.preserveEditingBehavior())
            this.handleTabKey(componentEvent, shiftKey);
          else
            this.handleTABKey_NonEditMode(componentEvent);
      }
      else // header cell case
        this.handleTABKey_NonEditMode(componentEvent);

      break;
    
    case AdfKeyStroke.M_KEY:
      if(ctrlKey && altKey)// show context menu
        this.handleKeyboardContextMenu(domEvent);
      break;

    case AdfKeyStroke.ENTER_KEY:
      if(this._isAda) 
        break; 

      if (!this.preserveEditingBehavior())
      {
          // read only stamp case, we still want to support the ENTER key
          if (this._isReadOnly())
            this.handleEnterKey(domEvent);
          else
          {
              // starts editing like pressing F2 key
              // set a flag so that DomReplaceNotify will check if returned content is read only
              // if it is read only then Enter key should go to the cell below instead (see bug 11888208)
              this.domEventForReadOnlyContentCheck = AdfAgent.AGENT.copyEvent(domEvent);        
              this.editCellWithFocus();
          }
      }

      break;
  }  
  //AdfLogger.LOGGER.severe('handle component keydown end - '+ AdfPage.PAGE.getActiveDomElement().id + ' has focus');
}

AdfDhtmlPivotTablePeer.prototype.HandleComponentKeyUp = function(componentEvent) {
    if (!this._isAda) {
        var domEvent = componentEvent.getNativeEvent();
    
        // We use "OR" to end KB pivoting if at least one of Shift and Alt have been released, rather than requiring both to be released, since (in FF, and perhaps other browsers) we don't always get the keyup event for Alt.  (When we used Ctrl-Alt rather than Shift-Alt, we found that behavior in FF is consistent, but arbitrary:  if keys are pressed/released in this order -- Alt-Ctrl-Arrow-Alt-Ctrl -- you get the Alt keyup event, but for the other 3 orders you don't.)
        if (!domEvent.altKey || (!domEvent.shiftKey && this._keyboardPivotingShiftKey)) { 
            // don't check for kbPivotMode before calling this, because callee needs to hide the pivot handle that is shown upon pressing Shift-Alt, even if kbPivotMode was never entered.
            this.endKeyboardPivoting(false);
        }
    }
}

AdfDhtmlPivotTablePeer._hasFocusCellValueChanged = function(eventTarget)
{
  if (eventTarget.tagName == "INPUT")
  {
    if (eventTarget.type == "text") 
        return (eventTarget._afrOldValue != eventTarget.value);    
    else if (eventTarget.type == "checkbox" || eventTarget.type == "radio")
        return (eventTarget._afrOldValue != eventTarget.checked);
  }
  else if (eventTarget.tagName == "SELECT")
  {
    for (var i=0; i<eventTarget.options.length; i++)
    {
      if (eventTarget.options[i].selected)
        return (eventTarget._afrOldValue != eventTarget.options[i].value);
    }
  }  
  else if (eventTarget.tagName == "TEXTAREA")
    return (eventTarget._afrOldValue != eventTarget.value);    
  
  return false;
}

AdfDhtmlPivotTablePeer._saveFocusCellValue = function(eventTarget)
{
  if (eventTarget.tagName == "INPUT")
  {
    if (eventTarget.type == "text") 
      eventTarget._afrOldValue = eventTarget.value;    
    else if (eventTarget.type == "checkbox" || eventTarget.type == "radio")
      eventTarget._afrOldValue = eventTarget.checked;
  }
  else if (eventTarget.tagName == "SELECT")
  {
    for (var i=0; i<eventTarget.options.length; i++)
    {
      if (eventTarget.options[i].selected)
        eventTarget._afrOldValue = eventTarget.options[i].value;
    }
  }  
  else if (eventTarget.tagName == "TEXTAREA")
    eventTarget._afrOldValue = eventTarget.value;    
}

AdfDhtmlPivotTablePeer._restoreFocusCellValue = function(eventTarget)
{
  if (eventTarget.tagName == "INPUT")
  {
    if (eventTarget.type == "text") 
    {
      eventTarget.value = eventTarget._afrOldValue;    
      eventTarget.select();
    }
    else if (eventTarget.type == "checkbox" || eventTarget.type == "radio")
      eventTarget.checked = eventTarget._afrOldValue;
  }
  else if (eventTarget.tagName == "SELECT")
  {
    for (var i=0; i<eventTarget.options.length; i++)
    {
      eventTarget.options[i].selected = false;
      if (eventTarget._afrOldValue == eventTarget.options[i].value)
        eventTarget.options[i].selected = true;
    }
  }    
  else if (eventTarget.tagName == "TEXTAREA")
  {
    eventTarget.value = eventTarget._afrOldValue;    
    eventTarget.select();
  }
}

AdfDhtmlPivotTablePeer.prototype.isDataCellInBuffer = function(dataCellIndex) {
    for (var edge=0; edge<2; ++edge) {
        var sliceParam = this._ptUtils.sliceAttr[edge]; // "column" or "row"
        var slice = dataCellIndex[sliceParam];
        var pane = (slice < this._ptUtils._splitSlice[edge]) ? 0 : 1; // this works when splitSlice == -1, since we use pane 1 in that case
        var bufferStartSlice = this.getBufferStartSlice(edge, pane);
        if (slice < bufferStartSlice) 
            return false;
        var bufferEndSlice = this.getBufferEndSlice(edge, pane);
        if (slice >= bufferEndSlice) 
            return false;
    }
        
    return true;
}

// handles enter key for pivot table with data cell stamps.   Takes navigation index into consideration
AdfDhtmlPivotTablePeer.prototype.handleEnterKey = function (domEvent)
{
  // in reality this should never be null
  if (this._focusCellIndex != null) 
  {
    // determine the new editable cell index location
    var newCellIndex;
    if (domEvent.shiftKey)
    {
      // if on first row of bottom pane and press Shift-Enter, jump to last *visible* row of top pane.
      if (this._focusCellIndex['row'] == this._ptUtils._splitSlice[1]) // this works when unsplit, since splitSlice == -1 in that case
        newCellIndex = this.closestVisibleDataCellIndex(1, 0, false, this._focusCellIndex['column']); 
      else if (this._focusCellIndex['row'] > 0)
        newCellIndex = this.newDataCellIndex(this._focusCellIndex['row']-1, this._focusCellIndex['column']); 
    }
    else
    {
      // if on last row of top pane and press Enter, jump to first *visible* row of bottom pane.
      if (this._focusCellIndex['row']+1 == this._ptUtils._splitSlice[1]) // this works when unsplit, since splitSlice == -1 in that case
        newCellIndex = this.closestVisibleDataCellIndex(1, 1, true, this._focusCellIndex['column']); 
      else if (this._focusCellIndex['row']+1 < this._ptUtils._rowExtent)
        newCellIndex = this.newDataCellIndex(this._focusCellIndex['row']+1, this._focusCellIndex['column']); 
    }
      
    // hit the first/last row
    if (newCellIndex == null)
    {
      if (!this._isReadOnly() && !this._isClickToEditAll())
      {
        // we still want to submit the value
        var activeCellKey = this.getComponent().getProperty(AdfRichPivotTable.__ACTIVE_CELL_KEY);
        var eventObj = this._queueClickToEditEvent(activeCellKey,null,this._cellRowHeight, null);
        // update old active cell key
        if (eventObj)
          this._oldActiveCellKey = activeCellKey;
      }
      return;
    }

    // determine if new cell index is in the buffer
    if (this.isDataCellInBuffer(newCellIndex)) 
    {
      // select and scroll to it if necessary
      this.selectAndFocusDataCell(newCellIndex); 
      this.scrollToDataCell(newCellIndex);           

      if (!this._isReadOnly())
        // now make it editable
        this.editCellWithFocus();         
    }
    else // not in buffer.  If we jumped from top to bottom pane or vice versa, the new row is definitely in buffer, but column might not be.  
    {
      // It's always safe to do a long scroll, but as optimization, we do a vertical-only short scroll if we can.  Current logic says "short scroll safe if newColumn==oldColumn."  That's valid if we assume that 
      // old focus cell is definitely in the buffer, since then:
      // - In common case where we did not jump from top to bottom pane or vice versa, if column is same, then new focus cell is nearest cell in block that's vertically adjacent to block we have, so short scroll works.
      // - In jump-to-top/bottom-pane case, the new row is definitely in buffer, and per the assumption the column (and row) of the old focus cell is too, so if newColumn==oldColumn, then new focus cell is already in 
      //   buffer, so it cannot enter this new-FC-not-in-buffer case.  
      // TBD: Is that assumption always valid, or can old focus cell ever not be on client when this method is called?  E.g. if PT editable but current focus cell isn't, and user scrolls that cell offscreen and 
      //      then presses Enter, would code reach this point?  Should we check for "old focus cell in buffer" just in case, and do long scroll if not?  
      var isLongScroll = (this._focusCellIndex['column'] != newCellIndex['column']); // TODO: ideally if the column did not shift then we should do a short scroll also

      // make the cell editable after scroll and block fetch
      this.selectAndFocusDataCell(newCellIndex); 
      this._isEditingCellWithFocus=true; // see DomReplaceNotify

      if (isLongScroll)
      {
        // long scroll first then scroll to it in DomReplaceNotify
        this.longJumpToCell(newCellIndex['row'], newCellIndex['column']);
      }
      else
      {
        // programmatically cause a short scroll to happen
        var rowPane = (newCellIndex['row'] < this._ptUtils._splitSlice[1]) ? 0 : 1; // this works when splitSlice == -1, since we use pane 1 in that case
        var scroller = this._ptUtils._databodyScrollers[rowPane][1];//AdfAgent.AGENT.getEventTarget(domEvent);
        var cellHeight = this._activeCell.parentNode.offsetHeight;
        if (domEvent.shiftKey)
            scroller.scrollTop = scroller.scrollTop - cellHeight;
        else
            scroller.scrollTop = scroller.scrollTop + cellHeight;
        this.HandleDomScroll(this.getComponent(), null);
      }
    }
  }
}

AdfDhtmlPivotTablePeer.prototype.handleEnterUpDownKey_EditMode = function(componentEvent, keyCode)
{  
  var domEvent = componentEvent.getNativeEvent();
  var varKeyCode = keyCode;

  if (keyCode == AdfKeyStroke.ENTER_KEY)
  {
    this.handleEnterKey(domEvent);
    return;
  }

  // first row case
  if (varKeyCode == AdfKeyStroke.ARROWUP_KEY && this._focusCellIndex['row'] == 0) 
  {
    // set a flag so that when the editing comes back (with no error) to move to the header
    this._navigateToHeaderKeyCode = AdfKeyStroke.ARROWUP_KEY;

    // cancel current edit
    this._processDismissCTE(componentEvent);
    return;    
  }

  // handle arrow up and down keys
  // move up only if we're not in first row 
  if(varKeyCode == AdfKeyStroke.ARROWDOWN_KEY || varKeyCode == AdfKeyStroke.ARROWUP_KEY) 
  {
    // a tag to remember focusing on the last element, checked in _processClickToEditequest()
    this._setFocusOnLastElement=(varKeyCode == AdfKeyStroke.ARROWUP_KEY)?true:false; 
    this.handleKeyboardNavigation(false, varKeyCode, true); 
    
    var shortScroll = this._isShortScrollActive();
    if(shortScroll)                  
    {
      this._isEditingCellWithFocus=true; //remember showing input text control after block fetch
      return;
    }
    
    var activeCellKey = this.getCoordinates(this._activeCell);

    // if ENTER keydown (no shift), check current focus, no short scroll, 
    // if it's the same after the navigation, means end of data rows
    if(varKeyCode == AdfKeyStroke.ARROWDOWN_KEY && this._focusCellIndex != null && !shortScroll && activeCellKey['col'] == this._focusCellIndex['column'] &&
       activeCellKey['row'] == this._focusCellIndex['row'])
    {   
// CCCHOW - don't cancel edit if last row
// do nothing
    }
    else
      this.editCellWithFocus();     
  }
}

AdfDhtmlPivotTablePeer.prototype.scrollToCellIndexCTE = function(newCellIndex)
{
  // select and focus cell now, even if fetch is needed, because there's no reason to wait, and so that cell will be editable after scroll and block fetch
  this.selectAndFocusDataCell(newCellIndex); 
  
  // determine if new cell index is in the buffer
  if (this.isDataCellInBuffer(newCellIndex)) 
  {
    // scroll to it if necessary
    this.scrollToDataCell(newCellIndex);           

    // now make it editable
    if (!this._isReadOnly())
      this.editCellWithFocus();
  }
  else 
  {
    this._isEditingCellWithFocus=true; // see DomReplaceNotify

    // long scroll first then scroll to it in DomReplaceNotify
    this.longJumpToCell(newCellIndex['row'], newCellIndex['column']);
  }                         
    
}

// handles TAB key for read only stamp case
// TODO: merge this code with handleEditLeftCell and handleEditRightCell
AdfDhtmlPivotTablePeer.prototype.handleTabKey = function (componentEvent, shiftKey) 
{
    if (this._currentRequest == AdfDhtmlPivotTablePeer.PARTIAL)
        return;

    var keyCode = -1;
    if (shiftKey) 
    {
        // Shift+TAB and first column
        if (this._focusCellIndex['column'] == 0) 
        {
            // if we hit the last row then return, otherwise go to the first column of the previous row
            if (this._focusCellIndex['row'] > 0) 
            {
                var newCellIndex = this.newDataCellIndex(this._focusCellIndex['row']-1,  this._ptUtils._colExtent-1); 
                this.scrollToCellIndexCTE(newCellIndex);
            }                
            else
                return;
        }
        else
        {
            if (this._ptUtils._rightToLeft) 
                keyCode = AdfKeyStroke.ARROWRIGHT_KEY;                                
            else
                keyCode = AdfKeyStroke.ARROWLEFT_KEY;                
        }
    }
    else 
    {        
        // TAB and last column       
        if (this._focusCellIndex['column'] == this._ptUtils._colExtent-1) 
        {    
            // if we hit the last row then return otherwise go to the first column of the next row
            if (this._focusCellIndex['row'] < this._ptUtils._rowExtent-1) 
            {
                // not the first row, go to the last column of the next row
                var newCellIndex = this.newDataCellIndex(this._focusCellIndex['row']+1, 0); 
                this.scrollToCellIndexCTE(newCellIndex);
            }
            else
                return;
        }
        else
        {
            if (this._ptUtils._rightToLeft) 
                keyCode = AdfKeyStroke.ARROWLEFT_KEY;                
            else
                keyCode = AdfKeyStroke.ARROWRIGHT_KEY;                
        }
    }    

    if (keyCode != -1)
        this.handleKeyboardNavigation(false, keyCode, false);         

    // don't allow it to go outside of Pivot Table
    componentEvent.cancel();
}

/*
 * This method is called if we're in editing mode, and hitting a Shift-TAB key in LTR or TAB in RTL results in editing the next cell on the left
 */
AdfDhtmlPivotTablePeer.prototype.handleEditLeftCell = function(componentEvent, isTabKey)
{
    var activeCellKey = this.getCoordinates(this._activeCell);

    // if this is not the first column and LTR or not the last column and RTL
    if(activeCellKey['col'] != 0 && !this._ptUtils._rightToLeft || activeCellKey['col'] < this._ptUtils._colExtent-1 && this._ptUtils._rightToLeft) 
    {
        // normal, non-boundary case
        // go to the left cell
        // a tag to remember focusing on the last element, checked in _processClickToEditRequest()
        this._setFocusOnLastElement=true; 
        this.handleKeyboardNavigation(false, AdfKeyStroke.ARROWLEFT_KEY, true); 
        
        var shortScroll = this._isShortScrollActive(); // did we send a block fetch?
        if(shortScroll)
        {
            this._isEditingCellWithFocus=true; //remember showing input text control after block fetch

            // if client click to edit, change current cell to read only to prevent further editing
            if (this._isClickToEditAll())
                this._dismissPreviousActiveCell(this._activeCell, this._activeCell, this._oldRowHeight);
        } 
        
        this.editCellWithFocus();         
    }
    else 
    {
        if (isTabKey)
        {
            // if first column and LTR
            if (!this._ptUtils._rightToLeft && activeCellKey['col'] == 0) 
            {
                // if old behavior or if we hit the first row
                if (this.preserveEditingBehavior() || this._focusCellIndex['row'] == 0)
                {
                    // cancel current edit
                    this._processDismissCTE(componentEvent);
                }
                else
                {
                    // go to the last column of the previous row
                    var newCellIndex = this.newDataCellIndex(this._focusCellIndex['row']-1, this._ptUtils._colExtent-1); 
                    this.scrollToCellIndexCTE(newCellIndex);
                }            
            }
            // if last column and RTL
            else if (this._ptUtils._rightToLeft && activeCellKey['col'] == this._ptUtils._colExtent-1) 
            {
                // if old behavior or if we hit the last row
                if (this.preserveEditingBehavior() || activeCellKey['row'] == this._ptUtils._rowExtent-1) 
                {
                   // cancel current edit
                   this._processDismissCTE(componentEvent);
                   // if last row then should shift to next focusable component                
                }
                else
                {
                   // go to the last column of the previous row
                   var newCellIndex = this.newDataCellIndex(this._focusCellIndex['row']+1, 0); 
                   this.scrollToCellIndexCTE(newCellIndex);
                }            
            }
        }
        else 
        {
            // first column and LTR 
            if (!this._ptUtils._rightToLeft && activeCellKey['col'] == 0) 
            {
                // set a flag so that when the editing comes back (with no error) to move to the header
                this._navigateToHeaderKeyCode = AdfKeyStroke.ARROWLEFT_KEY;

                // cancel current edit
                this._processDismissCTE(componentEvent);
            }

            // do nothing otherwise
        }
    }
}

/*
 * This method is called if we're in editing mode, and hitting a TAB key in LTR or SHIFT+TAB in RTL results in editing the next cell on the right
 */
AdfDhtmlPivotTablePeer.prototype.handleEditRightCell = function(componentEvent, isTabKey)
{
    var activeCellKey = this.getCoordinates(this._activeCell);

    // if this is not the last column and LTR or not the first column and RTL
    if(activeCellKey['col'] < this._ptUtils._colExtent-1 && !this._ptUtils._rightToLeft || activeCellKey['col'] != 0 && this._ptUtils._rightToLeft) 
    {
        // normal, non-boundary case
        this.handleKeyboardNavigation(false, AdfKeyStroke.ARROWRIGHT_KEY, true); 
        var shortScroll = this._isShortScrollActive();
        if(shortScroll)
        {
            this._isEditingCellWithFocus=true; //remember showing input text control after block fetch

            // if client click to edit, change current cell to read only to prevent further editing
            // value should have already submitted at this point
            if (this._isClickToEditAll())
                this._dismissPreviousActiveCell(this._activeCell, this._activeCell, this._oldRowHeight);
        }

        this.editCellWithFocus();      
    }
    else 
    {
        // if last column and LTR
        if (!this._ptUtils._rightToLeft && activeCellKey['col'] == this._ptUtils._colExtent-1) 
        {
            // if old behavior or if we are hitting the last column of the last row, tab out of pivot table, 
            // otherwise, go to the first cell of the next row
            if (isTabKey) 
            {
                if (this.preserveEditingBehavior() || activeCellKey['row'] == this._ptUtils._rowExtent-1) 
                {
                    // stop editing
                    this._processDismissCTE(componentEvent); // cancel the edit
                }
                else
                {
                    // go to the first column of the next row
                    var newCellIndex = this.newDataCellIndex(this._focusCellIndex['row']+1, 0); 
                    this.scrollToCellIndexCTE(newCellIndex);
                }                
            }
            else 
            {
                // ctrl+arrow key and last column, do nothing
            }
        }        
        // if first column and RTL
        else if (this._ptUtils._rightToLeft && activeCellKey['col'] == 0) 
        {
            // first row? 
            if (isTabKey) 
            {
                if (this._focusCellIndex['row'] == 0)
                {
                    // stop editing
                    this._processDismissCTE(componentEvent); // cancel the edit
                    // focus should shift to the previous focusable component
                }
                else 
                {
                    // not the first row, go to the last column of the previous row
                    var newCellIndex = this.newDataCellIndex(this._focusCellIndex['row']-1, this._ptUtils._colExtent-1); 
                    this.scrollToCellIndexCTE(newCellIndex);
                }
            }
            else 
            {
                // ctrl+arrow key and first column (rtl)
                // set a flag so that when the editing comes back (with no error) to move to the header
                this._navigateToHeaderKeyCode = AdfKeyStroke.ARROWRIGHT_KEY;

                // cancel current edit
                this._processDismissCTE(componentEvent);
            }
        }
    }
}

AdfDhtmlPivotTablePeer.prototype.handleTabLeftRightKey_EditMode = function(componentEvent, keyCode)
{
  var domEvent = componentEvent.getNativeEvent();
  var agent = AdfAgent.AGENT;
  var eventTarget = agent.getEventTarget(domEvent);
  var dataCell= this._getDataCell(eventTarget);
  var rightToLeft = this._ptUtils._rightToLeft;
                    
  if(dataCell) // we have a data cell as event Target; this means that we have input element in the data cell
  {
    if (keyCode == AdfKeyStroke.TAB_KEY)
    {
      var elems = this._getInputElementsInNode(dataCell);
      var index = this._getCurrentInputElementIndex(elems,eventTarget);
      
      if((domEvent.shiftKey && !rightToLeft) || (!domEvent.shiftKey && rightToLeft)) //shift-TAB or TAB and BIDI
      {
        // if current focus is first input element, go to the left cell, unless we're in the first col
        if(this._isAncestorOrSelf(elems[0], eventTarget))
        {
           this.handleEditLeftCell(componentEvent, true);          
        }
        else
          this._focusElement((index==-1)?elems[0]:elems[index-1]); //set tab stop to the previous input element
      }
      else // TAB-key, no shift 
      {
        // TAB, if current focus is last input element, go to the right cell                
        if(this._isAncestorOrSelf(elems[elems.length-1], eventTarget))
        {                  
          this.handleEditRightCell(componentEvent, true);          
        }
        else // not the last element in the cell
          this._focusElement((index==-1)?elems[0]:elems[index+1]); // set the tab stop to the next input element
      } 
    }
    else if (keyCode == AdfKeyStroke.ARROWLEFT_KEY)
    {
      this.handleEditLeftCell(componentEvent, false);  
    }
    else
    {
      this.handleEditRightCell(componentEvent, false);  
    }
  }
  else //dataCell is null. means we're in a non-editable cell, but in editing mode
  {
    if (keyCode == AdfKeyStroke.TAB_KEY)
    {
        if ((domEvent.shiftKey && !rightToLeft) || (!domEvent.shiftKey && rightToLeft))
            this.handleEditLeftCell(componentEvent, true);  
        else
            this.handleEditRightCell(componentEvent, true);          
    }
    else if(keyCode == AdfKeyStroke.ARROWLEFT_KEY) 
    {
        this.handleEditLeftCell(componentEvent, false);  
    }
    else 
    {
        this.handleEditRightCell(componentEvent, false);          
    }
  }
}  

AdfDhtmlPivotTablePeer.prototype.handleTABKey_NonEditMode = function(componentEvent)
{
  var domEvent = componentEvent.getNativeEvent();
  
  if(!this._isAda)
  {
    if (this._getActiveCellKey() == null) 
    {
      // if no cell is opened for editing
      
      // we're not in edit mode
      // if we're in a header cell, try to establish focus the action source components in the header cell
      if(this._focusElementInHeaderCell(componentEvent))
        return; // focus established, don't do the rest of method
      
      if(domEvent.shiftKey)
        this._focusElement(this.getDomElement());
      else // no shift
      {                  
        // tab key will go out of the PT when 
        // we're not in editing mode and does not need to focus on any header cell element. only applies to non-ADA mode
        var domElement = this.getDomElement();
        // focuses the ScrollbarTesterParent, which is currently the last tab stop in PT
        // (The STP is an offscreen div used by sizePT() for determining the size of a scrollbar in the current browser)
        // TBD: this will break if the STP stops being the very last tab stop in the PT.  Is there a more robust way to do this?
        var lastTabStop=AdfDhtmlPivotTablePeer._getChildById(domElement.childNodes[0], domElement.id+"::ScrollbarTesterParent");
        if (lastTabStop != null)
          this._focusElement(lastTabStop);   
      }
    }
    else // dismiss if there is an active cell
    {    
      // clear error message, since input control do not validate themselves until they lose focus, 
      // but in our scenario ESC/F2 can be pressed before the input control loses focus. 
      this._clearMessageAndRevalidate(componentEvent); 
      // cancel bubbling the event, since we got strange behavior here, where the dismiss didn't work, except in the debugger.
      componentEvent.cancel();
      this._processDismissCTE(componentEvent); // cancel the edit 
    }
  }// end if !isAda
}

/**
 * This helper method is used to check if the currently focused cell :
 * 1. is a header cell
 * 2. contain at least one input element
 * 3. manage to establish focus on one of the input elements
 */
AdfDhtmlPivotTablePeer.prototype._focusElementInHeaderCell = function(componentEvent)
{
    var domEvent = componentEvent.getNativeEvent();
    var agent = AdfAgent.AGENT;
    var eventTarget = agent.getEventTarget(domEvent);
    
    var focusCellIndex = this._focusCellIndex;
    if (focusCellIndex != null && this.isHeaderCellIndex(focusCellIndex)) 
    { 
      // we're in header cell
      var range = this.newHeaderCellRange(focusCellIndex); 
      var nodes = this.getNodesInHeaderCellRange(range, focusCellIndex['edge']); 

      if(nodes.length>0) 
      {
        var cell = nodes[0]; // the cell is the first item in the nodes array
        var elems = this._getInputElementsInNode(cell);
        var index = this._getCurrentInputElementIndex(elems,eventTarget);
            
        if(elems.length>0 )  // if there are input elems in the cell
        {          
          if(!domEvent.shiftKey && index!=(elems.length-1)) //no shift and we're not currently focusing on the last one
          {
            // cancel bubbling the Tab event, since we got strange behavior here, where the focus didn't work, except in the debugger.
            componentEvent.cancel();
            this._focusElement((index==-1)?elems[0]:elems[index+1]); // set the tab stop to the next input element                 
            return true; // we found an element to focus to
          }
          else if(domEvent.shiftKey && index!=0) 
          {
            // cancel bubbling the Tab event, since we got strange behavior here, where the focus didn't work, except in the debugger.
            componentEvent.cancel();
            this._focusElement((index==-1)?elems[elems.length-1]:elems[index-1]); // set the tab stop to the prev input element                 
            return true; // we found an element to focus to
          }
        }   
      }
    }
    return false; // didn't find any element to focus   
}

/**
 * This method should not be called when the PT is in certain states.  The check is done in the 
 * only entry point to this method, HandleComponentKeyDown.
 */
AdfDhtmlPivotTablePeer.prototype.handleKeyboardContextMenu = function(domEvent)
{
    if (this.isAttachmentMode())
        return;

    if(!this.isSelectionUiEnabled())
        return;

    this.getDomElement().focus(); 

    if (this._isAda) {
        var nothingSelected = (this.getSelectedEdge() == null);
        this._displayContextMenu(domEvent, nothingSelected); // display bodyCM (as opposed to regular CM) iff nothing is selected
    } else {
        var focusCellIndex = this._focusCellIndex;
  
        if(focusCellIndex == null) // the only known way for this to happen is if the PT is displaying the "no data" message
        {
            this._displayContextMenu(domEvent, true); // display the "body" context menu
        } else if (this.isHeaderCellIndex(focusCellIndex)) // focus cell is in header
        {
            var range = this.newHeaderCellRange(focusCellIndex); 
            var nodes = this.getNodesInHeaderCellRange(range, focusCellIndex['edge']); 
            if (nodes.length>0) // the cell object exists
            {
                if(!this.isSelected(focusCellIndex))
                    this.selectAndFocusHeaderCell(focusCellIndex);
      
                this._displayContextMenu(domEvent,false,nodes[0]);
            }
        }
        else  // focus cell is in databody 
        {
            range = this.newDataCellRange(focusCellIndex); 
            nodes = this.getNodesInDataCellRange(range); 
            if (nodes.length>0) // the cell object exists
                this._displayContextMenu(domEvent, false, nodes[0]);     
        }
    }
}

// Called whenever a dom element is *entered* with the mouse (e.g. move mouse across cell boundaries from one TD to another, or mouse into sort icons)
AdfDhtmlPivotTablePeer.prototype.HandleComponentMouseOver = function(componentEvent)
{
  // Mouse is not used in screenReader mode or touchscreen
  if (this._isAda || AdfPivotTableUtils.isMobileTouchDevice() || this.isAttachmentMode()) 
    return;

  // As with handleDomMouseMove, if we're in the middle of a drag, bail out.  Per bug 12840714 and base bug 12840707, framework is inconsistent across browsers as to 
  // whether handleDomMouseMove is called during a drag, and same may be true of this method, so to ensure consistent behavior we should bail out of this method 
  // during drags, and do all work in the drag callback methods (which is the preferred/standard approach anyway).  Also, the only thing this method does is to 
  // show the popup sort icons, which we don't want to happen during a drag anyway.  
  if (this._resizeDragState || this._headerSelectionDragState || this._databodySelectionDragState)
    return;
  
  if(this._isSortEnabled) // is *data* sorting enabled
  {
      // display column header sort icons if necessary
      var target = componentEvent.getNativeEventTarget();
      var th = this._getParentElement(target,"TH");
      if (th) 
        this._setSortIndicatorEnabled(th,true);
  }
}

// Called whenever a dom element is *exited* with the mouse (e.g. move mouse across cell boundaries from one TD to another).
// Despite method name, is *not* accurate to say that this is called only when entire component is exited with mouse.  
AdfDhtmlPivotTablePeer.prototype.HandleComponentMouseOut = function(componentEvent)
{
  // mouse not used in screenReader mode or for touchscreen
  if(this._isAda || AdfPivotTableUtils.isMobileTouchDevice())
    return;

  this.hidePivotHandle();

  if(this._isSortEnabled) // is *data* sorting enabled
  {
      var target = componentEvent.getNativeEventTarget();

      // if this is a header cell ( not one of its contents ) - prevents flickering. 
//      if(target.getAttribute("_coords"))
      //{
          // display column header sort icons if necessary
          var th = this._getParentElement(target,"TH");
          if (th)
            this._setSortIndicatorEnabled(th,false);
      //}
  }
}

// Unlike HandleComponentMouseOver, this is called on every mouse move, not just when a dom element is *entered* with the mouse
/**
 * @export
 */
AdfDhtmlPivotTablePeer.prototype.HandleDomMouseMove = function(component, domEvent) {
    // mouse not used in screenReader mode or for touchscreen
    if(this._isAda || AdfPivotTableUtils.isMobileTouchDevice())
        return;
    
    // do nothing if already waiting for a response to a drill/pivot/sort
    if (this._currentRequest == AdfDhtmlPivotTablePeer.PARTIAL) 
        return; 
    
    // If we're in the middle of a drag, bail out.  Per bug 12840714 and base bug 12840707, framework is inconsistent across browsers as to whether handleDomMouseMove 
    // is called during a drag, so for consistent behavior it's important to bail out of this method during drags, and do all work in the drag callback methods (which 
    // is the preferred/standard approach anyway).
    if (this._resizeDragState || this._headerSelectionDragState || this._databodySelectionDragState)
        return;
    
    var agent = AdfAgent.AGENT;
    var platform = AdfAgent.AGENT.getPlatform();
    var isIE8Plus= platform==AdfAgent.IE_PLATFORM && agent.getVersion() >= 8;
    
    this._oldMousePos = agent.getMousePosition(domEvent);
    var context = this._getMouseContext(domEvent);
    var action = this._getHeaderResizeMouseAction(agent, context);
    var headerCell;
    if (action)
        headerCell = action['cell']; // equals (headerContext && headerContext['cell']) ? headerContext['cell'] : null.  So var is either a header cell or null, never data cell.
    var bSetCursor = false;
  
    // set resize cursor if appropriate:
    if (action && headerCell && "resize" == action.type && !this._resizedCell) { // this._resizedCell should always be null unless we're in the middle of a selection drag, in which case we already bailed out above, but this check has long been here and can't hurt.
        bSetCursor = true;
        if(action.data=="startRow" || action.data=="endRow")
            AdfDhtmlPivotTablePeer._setCursor(headerCell, agent.getCursor("row-resize"));
        else
            AdfDhtmlPivotTablePeer._setCursor(headerCell, agent.getCursor("col-resize"));   
    }      
    
    // if appropriate, set header selection cursor, show/hide pivot handles, and set pivot handle drag cursor:
    if( (context.headerContext&&context.headerContext['cell']) || context.pivotHandleContext)
    {
      var edge,layer;
      var bPivotHeader = false;
      var isEmptyEdge;
      
      if (context.headerContext)
      {
        edge = context.headerContext['edge'];
        layer = context.headerContext['layer'];          
        isEmptyEdge = context.headerContext.isEmptyEdge;
    
        // if in header cell, and resize cursor is not set, set the cursor to row/column selection cursor accordingly
        if(!isEmptyEdge && !bSetCursor)
        {          
          AdfDhtmlPivotTablePeer._setCursor(headerCell, this._headerSelectionCursors[edge]);
          bSetCursor = true;
        }
      } else
      {
        bPivotHeader = true;
        edge = context.pivotHandleContext['edge']; 
        layer = context.pivotHandleContext['layer'];          
        isEmptyEdge = context.pivotHandleContext.isEmptyEdge;
      }

      // pivot is not supported in attachment mode      
      if (!this.isAttachmentMode())
      {
        if(!isEmptyEdge)
        {
          var pivotHandle = this.getPivotHandle(edge, layer);     
          if(bPivotHeader && this.isPivotEnabled() && this.isPivotable(edge, layer, pivotHandle))
          {
            bSetCursor = true;
            AdfDhtmlPivotTablePeer._setCursor(pivotHandle, agent.getCursor("move"));
          }
          if( (this._currentPivotHandle==null) || ((pivotHandle!=null)&&(pivotHandle.id!=this._currentPivotHandle.id)) )
          {
            this.hidePivotHandle();
            if(pivotHandle!=null)
            {
              this.showPivotHandle(edge, layer, pivotHandle);
            }
          }
        } else {
          this.hidePivotHandle();          
        }
      }
    } else
    {
      this.hidePivotHandle();
    }

    this.handleCellImageMouseMove(component, domEvent, agent);
  
    // Clear the resize cursor in the dummy header cell of an empty edge.  
    // That's the only case where headerCell is non-null and bSetCursor is false, without which this block a no-op.
    // In IE8 this line triggers bug 8841577 (sizing glitch), so we skip it in IE8+, which means that once the dummy header cell of an empty edge has acquired a 
    // resize cursor, that cell keeps the cursor until the next PPR.
    // TBD: 
    // - May no longer need to skip the line in IE8.  The bug occurred only during a col-header-col-resize-drag, and this line is no longer reached during drags.
    // - Test whether we need to skip it in IE9, or just in IE8.  
    if(!bSetCursor && !isIE8Plus)
      AdfDhtmlPivotTablePeer._setCursor(headerCell,"");    
}

// shows/hides the cell image (deprecated functionality)
AdfDhtmlPivotTablePeer.prototype.handleCellImageMouseMove = function(component, domEvent, agent) {
    var target = agent.getEventTarget(domEvent);
    var imageContainer = target.parentNode.parentNode;
    var imageName = agent.getAttribute(imageContainer,"_cellImgName"); // exists iff target is a cell image
    if (imageName) {
        if (this._imagePopupDisplayed) {
            // mouseMove events are fired slowly enough (especially in IE) that it's 
            // possible to move from one cell image to another without an event 
            // firing in the meantime, so that the old image's popup is still displayed 
            // when the mouse arrives at the new image.  So if a popup is 
            // already displayed, we must check whether it is the current image's 
            // popup, and if not, hide the old one and display the new one.
            var isCurrentImagesPopup = (target==this._lastCellImage);
            if (!isCurrentImagesPopup) {
                this.hideImagePopup(component);
                this.showImagePopup(component, target);
            }
        } else {
            this.showImagePopup(component, target);
        }
    } else {
        if (this._imagePopupDisplayed) {
            this.hideImagePopup(component); 
        }
    }
}

AdfDhtmlPivotTablePeer.prototype.HandleComponentMouseDown = function(componentEvent)
{
  // AdfLogger.LOGGER.severe("mouseDown");
  if(this._isAda)
    return;

  // Bail if we're in resize-dialog-displayed state.  Once the dialog is actually displayed, the glass pane prevents mouse interaction for us, but we want to prevent mouse interaction in the time after a user requests the dialog (from a cell's right-click menu) and before the dialog actually displays, to avoid weird state changes during that time--e.g. the selection should not change since the dialog is supposed to act on the selection that was in place when the dialog was launched.  We want the displaying of the dialog to be an atomic operation.
  if (this._ptState == AdfDhtmlPivotTablePeer.STATE_DIALOG || this._ptState == AdfDhtmlPivotTablePeer.STATE_STAMP_POPUP) 
    return;
  
  var domEvent = componentEvent.getNativeEvent();
  var agent = AdfAgent.AGENT;  
  if (!agent.isLeftButton(domEvent))
    return;

  // If the target is an input element in a cell, don't do selection and the like:
  var eventTarget = agent.getEventTarget(domEvent);
  if( this._isInputElement(eventTarget) && !this._isClickToEditAll()) 
  {
    this.enableUserSelect();      
    return;
  }
  this.disableUserSelect();      

  var context = this._getMouseContext(domEvent, true); // TODO: REMOVE THE 2ND TEST PARAM
  var action = this._getHeaderResizeMouseAction(agent, context);
  if ("resize" == action.type && !AdfPivotTableUtils.isMobileTouchDevice())
  {
    this.hidePivotHandle();
    var bRowResize = (action.data=="startRow") || (action.data=="endRow");
    if(bRowResize)
      this.showRowResizeIndicator(action);      
    else
      this.showColumnResizeIndicator(action);
    this._resizeDelta = 0;
    this._resizeDeltaRaw = 0;
    this._resizeDragState = true;
    if(bRowResize)
      AdfPage.PAGE.startDrag(domEvent, this._rowResizeDragCallback, this._rowResizeDropCallback);    
    else
      AdfPage.PAGE.startDrag(domEvent, this._colResizeDragCallback, this._colResizeDropCallback);
  } else if (context.headerContext && context.headerContext['cell'] && !context.headerContext.isEmptyEdge) { 
    // User is clicking on header cell to select it
    this.getDomElement().focus(); // TODO: should probably do this for all cases of HandleComponentMouseDown
    // todo: if we do add support for editable header cell we'll need to add the a similar check we did for data cell
    this.handleMouseHeaderSelection(context, domEvent);
  } else if (context.databodyContext) { // databodyContext['cell'] exists iff databodyContext does, so no need to check for that too.
    // User is clicking on data cell to select it
    if (AdfPivotTableUtils.isMobileTouchDevice()) {
      // Reverse order for mobile safari to avoid first selection resulting in first cell being selected along with new selection
      this.handleMouseDatabodySelection(context, domEvent);
      this.getDomElement().focus(); // TODO: should probably do this for all cases of HandleComponentMouseDown
      // On mobile touch devices, pivot handles are not hidden on mouseOut, so we hide when the header cell loses focus
      this.hidePivotHandle();
    }
    else {
      this.getDomElement().focus(); // TODO: should probably do this for all cases of HandleComponentMouseDown

      // checks if we are clicking/dragging on the current active cell
      var skipSelection = false;
      if (eventTarget != null && this._activeCell != null && this._getDataCell(eventTarget) == this._activeCell)
          skipSelection = true;

      if (!skipSelection)
          this.handleMouseDatabodySelection(context, domEvent);
    }
  } else if (AdfPivotTableUtils.isMobileTouchDevice()) {
      // On mobile touch devices, pivot handles are not hidden on mouseOut, so we hide when the header cell loses focus
      this.hidePivotHandle();
  }
  
  // this block will check if it's a second click on the same cell, then start to edit that cell
  // NOTE:  since we are relying on this._focusCellIndex, we cannot put this block in HandleComponentClick
  // it is put here to solve inconsistent focusCellIndex value issues
  // check if the clicked target is a databody cell            
  var attrs = this._getDataCellKeyAndCell(componentEvent.getNativeEventTarget());
  if (attrs != null && attrs[0] != null)
  {
    // cell is in databody
    var currCellKey = attrs[0];
    var currTimeStamp = new Date().getTime();
    var timeDiff = currTimeStamp - this._clickTimeStamp;
    //AdfLogger.LOGGER.severe('current timestamp:' + currTimeStamp + ' cell clicked:' + attrs[0]);

    // check if the timestamp between clicks in databody cell is more than 3 sec (1000*3)
    if(this._clickTimeStamp == null || timeDiff > 3000 ||
       this._clickCellKey != currCellKey)
    {
      this._clickTimeStamp = currTimeStamp; // keep click timetamp and clicked cell
      this._clickCellKey = currCellKey;
    }
    else if(this._clickTimeStamp != null && timeDiff > 0 && timeDiff < 3000 && this._clickCellKey == currCellKey)
    {
      // timestamp is less than 60s, check if same cell is clicked
      var currCellIndex = this._getDataCellIndexFromKey(currCellKey);
      if( (this._clickCellKey !=null) && (this._focusCellIndex != null) && (currCellIndex != null) &&               
          (currCellIndex['row']==this._focusCellIndex['row']) && (currCellIndex['column'] == this._focusCellIndex['column']))
      {
        this.HandleComponentDblClick(componentEvent);     // open cell for editing                 
      }
    }                    
  }           
}

///////////////////////////////////
// Mouse Position Code
AdfDhtmlPivotTablePeer.prototype._getMouseContext = function(domEvent, isTest) // TODO: REMOVE THE 2ND TEST PARAM
{
  var context = new Object();
  
  var agent = AdfAgent.AGENT;
  var mousePos = agent.getMousePosition(domEvent);
  
  // normalizedMouseX is always measured from the beginning side of the browser window, which is the right side in RTL:
  if (this._ptUtils._rightToLeft) {
      var windowWidth = agent.getWindowWidth();
//      var windowClientWidth = agent.getWindowClientWidth(window); 
//      var windowScrollWidth = agent.getWindowScrollWidth(window); 
      normalizedMouseX = windowWidth - mousePos.x;
  } else {
      normalizedMouseX = mousePos.x;
  }
  
  context.component = this._getVisitedComponent(agent, mousePos); // requires unnormalized mousePos
  context.target = agent.getEventTarget(domEvent);

  if( context.component==AdfPivotTableUtils.COLUMN_EDGE || context.component==AdfPivotTableUtils.ROW_EDGE )
  {
    var headerContext = new Object();
    context.headerContext = headerContext;    
    context.headerContext.isEmptyEdge = ((context.component==AdfPivotTableUtils.COLUMN_EDGE)&&(this._ptUtils._colLayerCount==0)) ||
                                        ((context.component==AdfPivotTableUtils.ROW_EDGE)&&(this._ptUtils._rowLayerCount==0));
    var cell = this._getParentElement(context.target,"TH");
    if(cell)
    {
      headerContext['cell'] = cell;
      var cellPos = agent.getElementPosition(cell);
      headerContext['cellWidth'] = cell['offsetWidth'];
      headerContext['cellHeight'] = cell['offsetHeight'];
      
      // ensure cellPos is always measured from the beginning side of the cell, which is the right side in RTL:
      if (this._ptUtils._rightToLeft) {
        // transform cellPos.x from distanceFromLeftSideOfBrowserToLeftSideOfCell to distanceFromRightSideOfBrowserToRightSideOfCell
        cellPos.x = windowWidth - cellPos.x - headerContext['cellWidth'];
      }
      
      // cellMouseX always indicates the number of pixels after the beginning of the cell
      headerContext['cellMouseX'] = normalizedMouseX - cellPos.x;
      headerContext['cellMouseY'] = mousePos.y - cellPos.y;
      
      headerContext['edge'] = context.component;
      // not clear that the layer as computed here is necessarily the memberStartLayer in all cases, particularly outline mode for the row edge.  For memberStartLayer, get it from the _coords expando.
      if(context.component==AdfPivotTableUtils.ROW_EDGE)
        headerContext['layer'] = this.getAbsoluteColumnIndex(agent, cell)-1; // is always the layer arg of renderHeaderCell()
      else
        headerContext['layer'] = this._getParentElement(cell,"TR")['rowIndex']-1; 
    }
    // add the pivot context, so that we can tell if we have a proper pivot drop target
    if(context.component==AdfPivotTableUtils.ROW_EDGE)
      context.pivotHandleContext = this._getRowHeaderPivotContext(agent, mousePos.x, windowWidth);
    else
      context.pivotHandleContext = this._getColHeaderPivotContext(agent, mousePos.y);
  } else if(context.component==AdfPivotTableUtils.ROW_PIVOT_HANDLES)
  {
    context.pivotHandleContext = this._getRowHeaderPivotContext(agent, mousePos.x, windowWidth);
  } else if(context.component==AdfPivotTableUtils.COL_PIVOT_HANDLES)
  {
    context.pivotHandleContext = this._getColHeaderPivotContext(agent, mousePos.y);
  } else { 
    cell = this._getDataCell(context.target);
    if(cell) {
      context.databodyContext = new Object();    
      context.databodyContext['cell'] = cell;
    }
  }
  return context;
}



/**
 * Generic routine to determine what pivot table component the mouse is over, using 
 * mousePos from a mouse event.
 * 
 * @return AdfPivotTableUtils.COLUMN_EDGE, AdfPivotTableUtils.ROW_EDGE, 
 *         AdfPivotTableUtils.COL_PIVOT_HANDLES, AdfPivotTableUtils.ROW_PIVOT_HANDLES, 
 *         or null if it's none of the above.
 */
AdfDhtmlPivotTablePeer.prototype._getVisitedComponent = function(agent, mousePos)
{
  var rightToLeft = this._ptUtils._rightToLeft;

  // Guard code for timing issue with moving mouse while over PT when refreshing
  if (this._ptUtils._splitSlice == undefined)
  {
        return;
  }
    
  var splitCol = this._ptUtils._splitSlice[0];
  var colHeader = this._ptUtils._colHeaders[(splitCol>0) ? 0 : 1]; // the column header div for the first in-use pane (leftmost in LTR, rightmost in RTL)
  var colPos = agent.getElementPosition(colHeader);
  
  // If mouse is above and to the right of the lower left corner of the column 
  // header, it's the column header.  (In right-to-left mode, reverse right and 
  // left in that sentence.)
  var horizTest = (rightToLeft) 
                  ? (mousePos.x - colPos.x <= colHeader['_width'])
                  : (mousePos.x - colPos.x > 0);
  if( horizTest && (mousePos.y - colPos.y <= colHeader['_height']) )
  {
    this._lastVisitedComponent = AdfPivotTableUtils.COLUMN_EDGE;
    return AdfPivotTableUtils.COLUMN_EDGE;
  }
  
  var splitRow = this._ptUtils._splitSlice[1];
  var rowHeader = this._ptUtils._rowHeaders[(splitRow>0) ? 0 : 1]; // the row header div for the first in-use pane
  var rowPos = agent.getElementPosition(rowHeader);  
  // If mouse is below and to the left of the top right corner of the row 
  // header, it's the row header.  (In right-to-left mode, reverse right and 
  // left in that sentence.)
  horizTest = (rightToLeft) 
              ? (mousePos.x - rowPos.x > 0)
              : (mousePos.x - rowPos.x <= rowHeader['_width']);
  if( horizTest && (mousePos.y - rowPos.y > 0) )
  {
    this._lastVisitedComponent = AdfPivotTableUtils.ROW_EDGE;
    return AdfPivotTableUtils.ROW_EDGE;
  }
  
  // Now we know it's not the rowHeader or colHeader, so it must be either the rowHeaderPivotHandles, 
  // colHeaderPivotHandles, or none of the above.
  
  var lastPH; // the last pivot handles to be displayed
  switch (this._lastVisitedComponent) {
    case AdfPivotTableUtils.ROW_EDGE:
    case AdfPivotTableUtils.ROW_PIVOT_HANDLES:
      lastPH = AdfPivotTableUtils.ROW_PIVOT_HANDLES;
      break;
    case AdfPivotTableUtils.COLUMN_EDGE:
    case AdfPivotTableUtils.COL_PIVOT_HANDLES:
      lastPH = AdfPivotTableUtils.COL_PIVOT_HANDLES;
      break;
    default: 
      lastPH = null;
  }  
  
  // The mouse is in a pivot handle region if it is within n px of the header, where n is normally 40, but is bumped up to the size of the currently displayed pivot handle if any.  
  var phSize = (this._phSize) ? this._phSize : 0;
  var normalSize = 40;
  var colPivotHandleWidth  = (lastPH == AdfPivotTableUtils.COL_PIVOT_HANDLES && phSize>normalSize) ? phSize : normalSize;
  var rowPivotHandleHeight = (lastPH == AdfPivotTableUtils.ROW_PIVOT_HANDLES && phSize>normalSize) ? phSize : normalSize;
  
  var isRowPivotHandle = false;
  var isColPivotHandle = false;
  // If mouse is above the bottom edge of the column header, and between 0 and 40px 
  // to the left of the left edge of the column header, then it might be the column 
  // edge pivot handles.  (In right-to-left mode, reverse right and left in that 
  // sentence.)  (The 0 portion of the horiz test is implicit since it follows the 
  // previous tests.)
  horizTest = (rightToLeft) 
              ? (mousePos.x - colPos.x <= colHeader['_width'] + colPivotHandleWidth)
              : (mousePos.x - colPos.x > -colPivotHandleWidth);
  if( horizTest && (mousePos.y - colPos.y <= colHeader['_height']) )
    isColPivotHandle = true;
  // If mouse is to the left of the right edge of the row header, and between 0 
  // and 40px above the top edge of the row header, then it might be the row 
  // edge pivot handles.  (In right-to-left mode, reverse right and left in that 
  // sentence.)  (The 0 portion of the vert test is implicit since it follows the 
  // previous tests.)
  horizTest = (rightToLeft) 
              ? (mousePos.x - rowPos.x > 0)
              : (mousePos.x - rowPos.x <= rowHeader['_width']);
  if( horizTest && (mousePos.y - rowPos.y > -rowPivotHandleHeight) )
    isRowPivotHandle = true;

  // the row/col pivot handles overlap in the corner, so use the last visited edge to determine which one wins. 
  if(isColPivotHandle&&isRowPivotHandle)
  {
    if( (this._lastVisitedComponent==AdfPivotTableUtils.ROW_EDGE) || (this._lastVisitedComponent==AdfPivotTableUtils.ROW_PIVOT_HANDLES) )
    {
      this._lastVisitedComponent = AdfPivotTableUtils.ROW_PIVOT_HANDLES;
      return AdfPivotTableUtils.ROW_PIVOT_HANDLES;
    } else
    {
      this._lastVisitedComponent = AdfPivotTableUtils.COL_PIVOT_HANDLES;      
      return AdfPivotTableUtils.COL_PIVOT_HANDLES;        
    }
  } else if(isColPivotHandle)
  {
    this._lastVisitedComponent = AdfPivotTableUtils.COL_PIVOT_HANDLES;      
    return AdfPivotTableUtils.COL_PIVOT_HANDLES;        
  } else if(isRowPivotHandle)
  {
    this._lastVisitedComponent = AdfPivotTableUtils.ROW_PIVOT_HANDLES;      
    return AdfPivotTableUtils.ROW_PIVOT_HANDLES;
  }
  return null;
}

//////////////////////////////
// Action Handling Methods
AdfDhtmlPivotTablePeer.prototype.hideImagePopup = function(component) {
    this._imagePopupDisplayed = false;
    this.hidePopup(component);
}

AdfDhtmlPivotTablePeer.prototype.showImagePopup = function(component, target) {
    this._imagePopupDisplayed = true;
    this._lastCellImage = target;
    
    // This code is a bit brittle, since if the framework (or we) changed any structure (e.g. 
    // added a comment as the firstChild of a node), this would break.
    //                   img   .<a>       
    var imageContainer = target.parentNode.parentNode; // the element containing the image and the hidden popup text
    //                               .hiddenDiv .content   .make a deep copy
    var popupContent = imageContainer.firstChild.firstChild.cloneNode(true);
    // OPTZN: instead of creating a new node each time, maybe reclaim the content from the popup
    // upon dismissing it, if possible.
    
    var hints = {};  
    hints[AdfDhtmlCommonUtils.TYPE] = AdfDhtmlCommonUtils.TYPE_NOTEWINDOW;
    // position above and to right of image: 
    hints[AdfDhtmlCommonUtils.ALIGN_ELEMENT] = target; 
    hints[AdfRichPopup.HINT_ALIGN] = AdfRichPopup.ALIGN_BEFORE_START;
    // prevent clicks from being consumed by the autodismiss functionality, so that 
    // user doesn't need to click twice to send a click event to the annotation icon:
    // (If they implement my suggestion not to consume the click event, we can remove this line.)
    hints[AdfDhtmlCommonUtils.AUTODISMISS] = AdfDhtmlCommonUtils.AUTODISMISS_NEVER;
    // Note: showPopup and hidePopup now take an optional "popupId" arg, which could come in handy 
    // for making sure that the correct popup is being dismissed.
    this.showPopup(component, popupContent, hints);
}

AdfDhtmlPivotTablePeer.prototype.handleImageAction = function(component, target)
{
  var agent = AdfAgent.AGENT;
  var ret = false;
  if(target)
  {     
      var imageContainer;     
      // if called from HandleComponentKeyDown, target is the anchor (image container).
      // if called from HandleComponentClick, target is the image, whose parent is the anchor.
      // Either way, we set imageContainer to be the anchor.  
      if(target.tagName == "A")
        imageContainer = target;
      else
        imageContainer = target.parentNode;
            
      // In all cases, it is the imageContainer's parentNode that has the expando that indicates 
      // that it is a drill target, adaPivot icon, or cell image.  

      var imageName = agent.getAttribute(imageContainer.parentNode,"_cellImgName"); // exists iff target is a cell image; refering to the span node above <a>
      if(imageName) 
      {
        if (this._currentRequest == AdfDhtmlPivotTablePeer.PARTIAL) 
          return; // do nothing if already waiting for a drill/pivot response
        
        // Unlike drills, which are handled by the component, the application handles 
        // image-click events.  Thus, we don't lock the UI (set this._currentRequest to AdfDhtmlPivotTablePeer.PARTIAL), 
        // since we don't know whether the app would want that, and we would have to require the app to 
        // later unlock the UI.
        
        var iRow;
        var iCol;
        if(this._isAda)
        {
          iRow = agent.getIntAttribute(imageContainer.parentNode,"_row"); // span node
          iCol = agent.getIntAttribute(imageContainer.parentNode,"_col"); // span node           
        } else
        {  
          var td = imageContainer.parentNode.parentNode; // the td that is the databody cell
          var coords = this.getCoordinates(td);
          iRow = coords['row'];
          iCol = coords['col'];
        } 
        var event = new AdfPivotTableCellImageClickEvent(component, iRow, iCol, imageName);
        event.setPartial(true);
        component.queueEvent(event);
        ret = true;
      } else if(agent.getAttribute(imageContainer.parentNode,"_dr"))
      {
        var th = this._getParentElement(imageContainer.parentNode,"TH");
        this.handleDrillAction(component,th);
        ret = true;
      } else if(agent.getAttribute(imageContainer.parentNode,"_adaPivot"))
      {
        var fromEdge = agent.getIntAttribute(imageContainer.parentNode,"_fromEdge");
        var fromLayer = agent.getIntAttribute(imageContainer.parentNode,"_fromLayer");
        var toEdge = agent.getIntAttribute(imageContainer.parentNode,"_toEdge");
        var toLayer = agent.getIntAttribute(imageContainer.parentNode,"_toLayer");
        var flags = agent.getAttribute(imageContainer.parentNode,"_flags");

        this._pivot(fromEdge, 
                    fromLayer, 
                    toEdge, 
                    toLayer, 
                    flags);
        ret = true;
      } else if(agent.getAttribute(imageContainer.parentNode, "_adaExtPivot"))
      {
        var pivotPeer = this.getKeyboardPivotPeer();
        if (pivotPeer != null)
        {
          var dropData = new Object();
          dropData['fromLayerName'] = AdfAgent.AGENT.getAttribute(imageContainer.parentNode, "_fromLayerName");
          pivotPeer.getPeer().PerformADADrop(component, dropData);
          
          ret = true;
        }
      }
  }
  return ret;
}

AdfDhtmlPivotTablePeer.prototype.handleDrillAction = function(component, imageContainer)
{
  if (this._currentRequest == AdfDhtmlPivotTablePeer.PARTIAL) 
    return; // do nothing if already waiting for a drill/pivot/sort/split response

  var cellCoords = this.getHeaderCellCoordinates(imageContainer);
  var edge = cellCoords['edge']; 
  var startLayer = cellCoords['startLayer'];
  var startSlice = cellCoords['startSlice'];
  var flag = "0";
  
  var event = new AdfPivotTableDrillEvent(component, edge, startLayer, startSlice, flag);
  event.setPartial(true);
  this._focusElement(this.getDomElement()); // to prevent PT focus lost after sort and drill actions

  component.queueEvent(event);
  var canceled = event.isCanceled();  
   // AdfLogger.LOGGER.severe('drill action canceled =' + canceled);
  if (!canceled)
  {
    this.setCurrentRequest(AdfDhtmlPivotTablePeer.PARTIAL); // indicate we're waiting for a drill/pivot/sort/split, and not a scroll fetch
    this.setServerRequest(null); // clear serverRequest per comments in callee
    if(!this._isAda)
      this._displayStatus(this._fetchText, true);        
  }
  else
  {
    // drill event is canceled    
    // handle client side validation here        
    this._clientValidationErrorRecovery(this._getActiveCellKey());      
  }

}

AdfDhtmlPivotTablePeer.prototype.handleSortAction = function(component, domEvent, target)
{
  if (this._currentRequest == AdfDhtmlPivotTablePeer.PARTIAL)
    return;

  //if using default sort button <A>, parent <TD> contains the attribute
  var sortAscending = target.parentNode.getAttribute(AdfDhtmlPivotTablePeer._SORT_ASCENDING);
  //if using override sort button image <IMG>, then the grandparent <TD> contains the attribute
  if(!sortAscending)
    sortAscending = target.parentNode.parentNode.getAttribute(AdfDhtmlPivotTablePeer._SORT_ASCENDING);
    
  //if using default sort button <A>, parent <TD> contains the attribute
  var sortDescending = target.parentNode.getAttribute(AdfDhtmlPivotTablePeer._SORT_DESCENDING);
  //if using override sort button image <IMG>, then the grandparent <TD> contains the attribute
  if(!sortDescending)
    sortDescending = target.parentNode.parentNode.getAttribute(AdfDhtmlPivotTablePeer._SORT_DESCENDING);
  
  if(sortAscending || sortDescending)
  {
    var th = this._getParentElement(target,"TH");
    var cellCoords = this.getHeaderCellCoordinates(th);
    
    var siId = AdfRichUIPeer.CreateSubId(this.getComponent().getClientId(), cellCoords['startSlice'] + "::" + AdfDhtmlPivotTablePeer._SORT_INDICATOR);
    var siElem = null;
    var verticalText = th.getAttribute("_vt");
    if(verticalText==undefined)
    {
        if (AdfPivotTableUtils.isMobileTouchDevice())
            siElem = AdfDhtmlPivotTablePeer._getChildById(th.childNodes[0],siId);
        else
            siElem = AdfDhtmlPivotTablePeer._getChildById(th,siId);
    } else 
    {
        // the sort indicator is in cell.table.tr.td
        siElem = AdfDhtmlPivotTablePeer._getChildById(th['childNodes'][0]['rows'][0]['cells'][0],siId);
    }

    var sortedType = siElem.getAttribute(AdfDhtmlPivotTablePeer._SORTED_TYPE);
    
    var sortOrder = undefined;                 
    // if already sorted propertly clicking on the icon should do nothing
    if(sortAscending && (sortedType != AdfDhtmlPivotTablePeer._SORTED_ASCENDING))
        sortOrder = true;
    else if(sortDescending && (sortedType != AdfDhtmlPivotTablePeer._SORTED_DESCENDING))
        sortOrder = false;
   
    if(sortOrder!=undefined)
    {
        this.queueSortRequest(AdfPivotTableUtils.ROW_EDGE, cellCoords['startSlice'], sortOrder, false);
        AdfAgent.AGENT.eatEvent(domEvent);
    }
  }
}

/**
 * @export
 */
AdfDhtmlPivotTablePeer.prototype.queueSortRequest = function(edge, sliceOrLayer, isAscending, isHeader) {
    sliceOrLayer = "" + sliceOrLayer; // convert to string // TBD: is string conversion really necessary?  Edge works without this...
    var component = this.getComponent()
    var event = new AdfPivotTableSortEvent(component, edge, sliceOrLayer, isAscending, isHeader);
    event.setPartial(true);
    this._focusElement(this.getDomElement()); // to prevent PT focus lost after sort and drill actions
    
    component.queueEvent(event);
    var canceled = event.isCanceled();  
    // AdfLogger.LOGGER.severe('sort action canceled =' + canceled);
    
    if (!canceled) {
        this.setCurrentRequest(AdfDhtmlPivotTablePeer.PARTIAL); // indicate we're waiting for a drill/pivot/sort/split, and not a scroll fetch
        this.setServerRequest(null); // clear serverRequest per comments in callee
        if (!this._isAda)
            this._displayStatus(this._fetchText, true);        
    } else {
        // sort event is canceled
        // handle client side validation here        
        this._clientValidationErrorRecovery(this._getActiveCellKey());        
    }
}

AdfDhtmlPivotTablePeer.prototype._isSortIndicator = function(target) 
{
  //if using default sort button <A>, parent <TD> contains the attribute
  var sortAscending = target.parentNode.getAttribute(AdfDhtmlPivotTablePeer._SORT_ASCENDING);
  //if using override sort button image <IMG>, then the grandparent <TD> contains the attribute
  if(!sortAscending)
    sortAscending = target.parentNode.parentNode.getAttribute(AdfDhtmlPivotTablePeer._SORT_ASCENDING);
    
  //if using default sort button <A>, parent <TD> contains the attribute
  var sortDescending = target.parentNode.getAttribute(AdfDhtmlPivotTablePeer._SORT_DESCENDING);
  //if using override sort button image <IMG>, then the grandparent <TD> contains the attribute
  if(!sortDescending)
    sortDescending = target.parentNode.parentNode.getAttribute(AdfDhtmlPivotTablePeer._SORT_DESCENDING);

  return sortDescending!=undefined || sortAscending!=undefined;
}

// TBD: Drop handler (for resize and selection drags) should check whether to display sort icons (using same logic as MouseOver), since currently if a drag ends 
// in a header cell, the sort icons don't show until you mouse to another cell and back. This is because they are shown in MouseOver (i.e. mouse-in) rather than 
// MouseMove, and we skipped displaying them upon mousing into the cell since we were in a drag.  In FF at least, is an issue for selection drags, but not for 
// resize drags since then the disappearance of the resize indicator seems to invoke MouseOver of the header cell. This is a minor issue.
// 
// TBD: Consider dismissing sort icons at drag start time.  Current behavior: If sort icons are already popped up when resize- or selection-drag starts, they will 
// stay until you mouse-out of cell or into the icons, since it's the MouseOut handler that dismisses them.  Not terrible, but ideally they'd dismiss immediately.  
// Can do it at mouseDown/dragStart time if drop handler checks whether to redisplay them (see preceding TBD).  Without that fix, this would trigger that bug every
// time you click-select a single header cell, so without that fix would be better to dismiss them on first call to drag handlers instead of on mouseDown. 
AdfDhtmlPivotTablePeer.prototype._setSortIndicatorEnabled = function(headerCell, enabled) 
{
  // find out if this is a pivot table header cell
  var cellCoords = this.getHeaderCellCoordinates(headerCell);
  if (cellCoords) {
    var siId = AdfRichUIPeer.CreateSubId(this.getComponent().getClientId(), cellCoords['startSlice'] + "::" + AdfDhtmlPivotTablePeer._SORT_INDICATOR);
    
    var siElem = null;
    var verticalText = headerCell.getAttribute("_vt");
    if(verticalText==undefined)
    {
        if (AdfPivotTableUtils.isMobileTouchDevice())
            siElem = AdfDhtmlPivotTablePeer._getChildById(headerCell.childNodes[0],siId);
        else
            siElem = AdfDhtmlPivotTablePeer._getChildById(headerCell,siId);
    } else 
    {
        // the sort indicator is in cell.table.tr.td
        siElem = AdfDhtmlPivotTablePeer._getChildById(headerCell['childNodes'][0]['rows'][0]['cells'][0],siId);
    }
    if(siElem == null)
      return;
    var sortedType = siElem.getAttribute(AdfDhtmlPivotTablePeer._SORTED_TYPE);
    
    // If it is already sorted it is always displayed 
    if(sortedType == null)
    {
      if(enabled)
        siElem.childNodes[0].style.display = "";
      else
        siElem.childNodes[0].style.display = "none";
    }
  }    
}

/**
 * Returns the AdfUIComponent for which this is the peer.
 * 
 * For some reason, the component is not yet bound to the Peer when initDomElement is called, 
 * so that the normal version of this method returns null when called at that time.  
 * This means that every single method that directly or indirectly accesses this.getComponent()
 * cannot be called at that time.  This is a huge limitation.
 * 
 * To unbreak all our methods, we set an ivar containing the component at the top of 
 * InitDomElement, and clear it at the bottom of that method in a finally clause.  
 * We override getComponent to return that ivar if it is set, and otherwise to do 
 * the usual thing.  
 */
AdfDhtmlPivotTablePeer.prototype.getComponent = function() {
    if (this._tempComponent) {
        return this._tempComponent;
    } else {
        return AdfDhtmlPivotTablePeer.superclass.getComponent.call(this); // super.getComponent()
    }
}

/**
 * @export
 * Initialize the associated domElement when the peer is created.  Stateless peers
 * are not bound at the time that this method is called.
 * @param {AdfUIComponent} component Component that this peer will be associated with
 * @param {Object} domElement Root DOM element of this peer
 */
AdfDhtmlPivotTablePeer.prototype.InitDomElement = function(component, domElement)
{
    try {
//        this.loggingOn = true;
        this._tempComponent = component; // for explanation, see javadoc of our override of getComponent() 
        this._codeExecutionCounter = 0; // See HandleComponentBlur()
        
        // apply superclass binding
        AdfDhtmlPivotTablePeer.superclass.InitDomElement.call(this, component, domElement);
        
        this.__logEnabled = false; // set to false unless tracing fetch events
        if (this.__logEnabled) 
          this.initTestLog();
        
        var agent = AdfAgent.AGENT;
        var ada            = agent.getAttribute(domElement, "_ada");
        var immediate      = agent.getAttribute(domElement, "_immediate");
        this._adsEnabled   = agent.getAttribute(domElement, "_adsEnabled") != undefined; // this ivar requires that any change in ADS on/off status be followed by a re-render, so that the ivar gets reset.  (Similarly, our JS partitioning also requires a re-render, so that the JS gets sent.)  Max confirms that's fine and Table has the same requirement: they use different row key mgr depending on ADS on/off.
        this._fetchText    = agent.getAttribute(domElement, "_fetchText"); 
        this._cellSelected = agent.getAttribute(domElement, "_cellSelected"); 
        this._rowSelected  = agent.getAttribute(domElement, "_rowSelected"); 
        this._colSelected  = agent.getAttribute(domElement, "_colSelected"); 
        this._multiple     = agent.getAttribute(domElement, "_multiple"); 
        this._rowInfo      = agent.getAttribute(domElement, "_rowInfo"); 
        this._colInfo      = agent.getAttribute(domElement, "_colInfo"); 
        this._heightText   = agent.getAttribute(domElement, "_heightText"); 
        this._widthText    = agent.getAttribute(domElement, "_widthText"); 
        this._moveBefore   = agent.getAttribute(domElement, "_moveBefore");
        this._swapWith     = agent.getAttribute(domElement, "_swapWith");
        this._moveAfter    = agent.getAttribute(domElement, "_moveAfter");
        this._moveToRows   = agent.getAttribute(domElement, "_moveToRows");
        this._moveToCols   = agent.getAttribute(domElement, "_moveToCols");
       
        this._ptState = AdfDhtmlPivotTablePeer.STATE_NORMAL;  
        this._isAda = (ada!=undefined);  
        this._isSortEnabled = false; // is *data* sorting enabled.  Real value set in DRN.
        this._isImmediate = (immediate!=undefined);    
        this._streamingDisabled = false;
        // This check should be eventually removed when streaming is enabled permanently
        if(AdfPage.PAGE.isStreamingDisabled)
          this._streamingDisabled = AdfPage.PAGE.isStreamingDisabled();
        this._currentRequest = AdfPivotTableDataFetchEvent.FETCH_TYPE_INITIAL; // indicates we're waiting on the initial block fetch.  Whether immediate or lazy, this will be cleared in DomReplaceNotify as with any block fetch.
        this._pivotDragEnabled = false;
        this._resizeDragState = false;
        this._currentPivotHandle = null;  
        this._selectedEdge = null;
        this._selectedLayer = null;
        this.clearSizeOverrides(component);
        this.alreadyRequestedDismissEdit = false;
        this._cellDirty = false;
        this._serverBusy = false;
        
        // always true:  this._blockADSCounter <= this._bufferADSCounter.  All events with ID's in (blockADSCounter, bufferADSCounter] that were actually sent to the client are in the buffer.
        // (Reasons not to send to client include "nothing actually changed" and "cell not on client").  Can't assume that blockADSCounter + buffer.length == bufferADSCounter due to 
        // non-atomicity of adding/removing from buffer and updating the ivars, and since buffer length is smaller if some events in that range were never sent.
        this._blockADSCounter = -1; // the latest ADS event that's already been applied to client blocks
        this._bufferADSCounter = -1;  // the latest ADS event that's been received, but might be buffered rather than already applied to blocks, if it arrived when fetch was outstanding
        
        // write modelName info to the page so PivotTable can find it during keyboard pivot
        var modelName = component.getProperty('modelName');
        if (modelName != null)
            AdfPage.PAGE.setPageProperty('ROW_AND_COLUMN_EDGE:'+modelName, component.getClientId());
        
        this._attachmentMode = (domElement.getAttribute("_attachment") == "true");
        this._isPaging = AdfDhtmlPivotTablePeer.isPaging(domElement);
        
        if(!this._isAda)
        {
          this._ptUtils = new AdfPivotTableUtils(component,this,domElement);
          
          for (var i=0; i<2; ++i) { // note that most loops over panes must start at startPane[edge], not 0.  Don't copy this elsewhere.
            for (var j=0; j<2; ++j) { // ditto
              if (i+j>0) // top left databody pane has no scroller
                AdfRichUIPeer.addEventHandlerToDomInstance(AdfDhtmlPivotTablePeer,
                                                           this._ptUtils._databodyScrollers[i][j], // i,j are rowPane, colPane
                                                           "scroll");
              this._ptUtils._headers   [i][j].onscroll = this.createCallback2(this.handleHeaderScroll,   [i,j]); // i,j are edge, pane
              this._ptUtils._databodies[i][j].onscroll = this.createCallback2(this.handleDatabodyScroll, [i,j]); // i,j are rowPane, colPane
            }
          }
                                                      
          AdfRichUIPeer.addEventHandlerToDomInstance(AdfDhtmlPivotTablePeer, domElement, "mousemove");
          
          if (!AdfPivotTableUtils.isMobileTouchDevice()) 
            this.setupMouseWheel();
          
          this._colResizeDragCallback = this.createCallback(this._handleColResizeDrag);
          this._colResizeDropCallback = this.createCallback(this._handleColResizeDrop);
        
          this._rowResizeDragCallback = this.createCallback(this._handleRowResizeDrag);
          this._rowResizeDropCallback = this.createCallback(this._handleRowResizeDrop);
        
          this._selectionDragCallback = this.createCallback(this.handleSelectionDrag);
          this._selectionDropCallback = this.createCallback(this.handleSelectionDrop);
          
          if (agent.getAttribute(domElement, "_cteAll") == "true")
              AdfPage.PAGE.addBusyStateListener(this, this._handleBusyState);

          // no pivoting support in attachment mode          
          if (!this.isAttachmentMode())
          {
              // override any other drag source that is specified, since a bogus, component drag source
              // is needed to drag to an external drop target / process the drop event on the server
              // TODO - get rid of this as soon as the pivot table has the ability to add its own drag source 
              // on the server!
              component.setDragSource(new AdfPivotTableDragSource());
              // don't allow other drop targets, since we want to accept drags ourselves.  
              // Allow other drop targets on the pivot table as a whole, if they are registered
              // TODO - Can we allow multiple drop targets at the same time?
              if(component.getDropTarget()==null)
                component.setDropTarget(new AdfPivotTableDropTarget());
          }

          this._headerSelectionCursors = [this.getHeaderSelectionCursor(domElement, 0), this.getHeaderSelectionCursor(domElement, 1)];
        } else {
          // TODO: decomp this entire block to own method in ADA.js, so that JS Partitioning can send it down only in screenReader mode.
          
          var nodes = domElement.childNodes[0].childNodes

          for (var i=0; i<nodes.length; ++i) { 
            var node = nodes[i];
            if (node.id && (node.id.indexOf("Inner")!=-1 || node.id.lastIndexOf("::et")==node.id.length-4)) { // The "Inner" table node is replaced with an "et" (empty text) div saying "no data to display" when rowExtent or colExtent is 0.  Either way, this node has the expandos that the rest of this method looks for.
              var adaTable = nodes[i]; // the HTML table that contains the column header as its first few rows and the row header and databody as its remaining rows
              break;
            }
          }
          
          // The Utils class is an empty shell in ADA mode, so ivars like edgeExtent that are normally stored on the Utils class are stored on the Peer.  To easily distinguish from the regular ivars, we prefix these versions with "ada".
          // TODO: move these constants to the following ADA-only Utils object that has just enough contents to work.
          this._ptUtils = {};

          // fetch row extent and col extent from ADA Table element!
          var colExtent = agent.getIntAttribute(adaTable, "_colExtent");
          var rowExtent = agent.getIntAttribute(adaTable, "_rowExtent");
          this._adaEdgeExtent = [colExtent, rowExtent];
          
          var colBlockSize = agent.getIntAttribute(adaTable, "_colBlockSize");
          var rowBlockSize = agent.getIntAttribute(adaTable, "_rowBlockSize");
          this._adaBlockSize = [colBlockSize, rowBlockSize];

          var startRow = agent.getIntAttribute(adaTable, "_startRow");
          var startCol = agent.getIntAttribute(adaTable, "_startCol");
          component.setProperty(AdfRichPivotTable.__START_ROW_KEY, startRow);
          component.setProperty(AdfRichPivotTable.__START_COLUMN_KEY, startCol);
          
          // we want to sync the client blocks in ADA mode, 
          // the start slices must be saved now because by the time the request goes to the server and getClientBlocks() is called, their values are updated
          this._adaStartSlice = []; // indexed by edge then pane
          
          this._adaStartPane = []; // indexed by edge
          this._adaCanvasStartSlice = []; // indexed by edge then pane
          this._adaCanvasEndSlice = []; // indexed by edge then pane
          
          var splitCol = agent.getIntAttribute(adaTable,"_splitCol");
          var splitRow = agent.getIntAttribute(adaTable,"_splitRow");
          this._ptUtils._splitSlice = [splitCol, splitRow];
          component.setProperty(AdfRichPivotTable.__SPLIT_COLUMN_KEY, splitCol);
          component.setProperty(AdfRichPivotTable.__SPLIT_ROW_KEY,    splitRow);
          
          if (splitCol != -1) {
              var secondStartCol = agent.getIntAttribute(adaTable, "_secondStartCol");
              component.setProperty(AdfRichPivotTable.__SECOND_START_COLUMN_KEY, secondStartCol);
              
              this._adaStartPane[0] = 0;
              this._adaStartSlice[0] = [startCol, secondStartCol];
              this._adaCanvasStartSlice[0] = [0, secondStartCol];
              this._adaCanvasEndSlice[0] = [secondStartCol, colExtent];
          } else {
              this._adaStartPane[0] = 1;
              this._adaStartSlice[0] = [undefined, startCol];
              this._adaCanvasStartSlice[0] = [undefined, 0];
              this._adaCanvasEndSlice[0] = [undefined, colExtent];
          }
          
          if (splitRow != -1) {
              var secondStartRow = agent.getIntAttribute(adaTable, "_secondStartRow");
              component.setProperty(AdfRichPivotTable.__SECOND_START_ROW_KEY, secondStartRow);
              
              this._adaStartPane[1] = 0;
              this._adaStartSlice[1] = [startRow, secondStartRow];
              this._adaCanvasStartSlice[1] = [0, secondStartRow];
              this._adaCanvasEndSlice[1] = [secondStartRow, rowExtent];
          } else {
              this._adaStartPane[1] = 1;
              this._adaStartSlice[1] = [undefined, startRow];
              this._adaCanvasStartSlice[1] = [undefined, 0];
              this._adaCanvasEndSlice[1] = [undefined, rowExtent];
          }
          
          // reset this value so that the server always picks up the client blocks in ADA mode
          var clientBlocks = component.getProperty(AdfRichPivotTable.__CLIENT_BLOCKS_KEY);
          if(clientBlocks==undefined || clientBlocks=="foo")
              component.setProperty(AdfRichPivotTable.__CLIENT_BLOCKS_KEY, "bar", false, AdfUIComponent.PROPAGATE_ALWAYS); 
          else
              component.setProperty(AdfRichPivotTable.__CLIENT_BLOCKS_KEY, "foo", false, AdfUIComponent.PROPAGATE_ALWAYS); 
        
          // register event listeners for ADA navigation controls!
          for (var edge=0; edge<2; ++edge) {
              for (var pane=this._adaStartPane[edge]; pane<2; ++pane) {
                  var choiceElement = this._getChoiceElement(component, edge, pane);
                  // The choiceElement is not there for unknown row count. (I think this comment was pasted from Table, as PT currently does not support unknown row count, but the if-clause does no harm)
                  if (choiceElement)
                      AdfRichUIPeer.addEventHandlerToDomInstance(AdfDhtmlPivotTablePeer, choiceElement, "change");
                  
                  for (var isNext=0; isNext<2; ++isNext) {
                      var linkElement = this._getPrevNextLinkElement(component, edge, pane, isNext);
                      // The link may not be there if there are no previous/next blocks
                      if (linkElement) {
                          var handleClick = this.createCallback2(this._handlePrevNextSliceClick, [edge, pane, isNext]);
                          agent.addBubbleEventListener(linkElement, "click", handleClick);  
                      }
                  }
              }
          }
        
          // Hack.  We need these constants in ADA mode, but they don't exist because AdfPivotTableUtils is an empty shell in ADA mode.  So we add them back in.  
          // TODO: Move these and all other constants that live in Utils to Peer, and remove this hack.
          AdfPivotTableUtils.DATABODY = -1;
          AdfPivotTableUtils.COLUMN_EDGE = 0;
          AdfPivotTableUtils.ROW_EDGE = 1;
          
          this._graphScreenReaderMode = agent.getBooleanAttribute(adaTable, "_graphSR");  
          this.initializeSelectionAda(component, adaTable, nodes);
        }        
    } finally {
        this._tempComponent = null;
    }
}

/**
 * @export
 */
AdfDhtmlPivotTablePeer.prototype.needsResizeNotify = function(component)
{
  if(this._isAda)
    return false
  else
    return true;
}

/**
 * @export
 * There are 3 cases when ResizeNotify is called (per email thread with Matt Cooper) :
 * 1. when the component (PT) is first drawn or replaced (PPR'd)
 * 2. if a parent component that stretches its children is resizing the child (the PT)
 * 3. if the browser window queues a resize event (e.g. user manually resizes the window or I presume if the size changes due to scroll bars appearing or disappearing) 
 * 
 * The PT has 3 geometry cases:
 * (i) Parent stretches us (thus we don't shrinkToFit even if app sets sizing="auto").  Parent dictates our size.
 * (ii) Parent doesn't stretch us, and sizing != "auto".  Our size is the "skin size", i.e. the size computed from skin/styleClass/inlineStyle.  
 * (iii) Parent doesn't stretch us, and sizing="auto" (i.e. shrinkToFit mode).  Our width = min(width of PT contents, "skin width" as defined above), and likewise for height.
 *
 * In bug 13073440, we saw the following:  In case (iii), for some non-stretching parents but not others, we got an extra call to ResizeNotify *after* 
 * we shrank to fit small contents, with newWidth/Height being the after-shrink values, causing _maxWidth/_Height to be set to the after-shrink values, which prevented us 
 * from ever growing larger than that size, e.g. after a drill-expand.  Presumably this call is part of Case 3 above.  
 * 
 * Pre-bug-fix, we updated _maxWidth/_Height on every call to ResizeNotify.
 * Post-bug-fix, for Case (iii) (shrinkToFit mode) only, we update them only for Case 1 (the initial call) to avoid the bug.  
 * For Case (i), we continue to update on every call so sizePivotTable() will use our latest stretched size.  
 * For Case (ii), presumably only Case 1 (initial call) matters, since subsequent calls would either not occur or would pass in the same size, so for this case it shouldn't 
 * matter whether we set the ivars in subsequent calls.  For minimal disruption, we continue to set the ivars on subsequent calls in this case, just in case they occur.
 * Matt agreed that this handling of the 3 cases is probably the correct fix.  
 */

/////////////////////////////////////////////////////////////// TODO: TODOS BELOW.  O/W DONE.
/**
 * @export
 */
AdfDhtmlPivotTablePeer.prototype.ResizeNotify = function(
  oldWidth,
  oldHeight,
  newWidth,
  newHeight
  )
{
  if (!this._fetchInitialBlock) // if this is the very first call to ResizeNotify, which occurs when PT is first drawn or PPR'd (Case 1 above)
  {
    // TBD: sizePivotTable uses CONTAINER_EXTRA_WIDTH/HEIGHT, both of which = 2, as the difference between the inner container and outer container sizes.  Here we use a hardcoded 1 for both.  Why?  Are these 2 lines even necessary given that sizePT will set these sizes in a moment?  
    this._ptUtils._innerContainer.style.width = (this.getDomElement().offsetWidth-1) + "px";
    this._ptUtils._innerContainer.style.height = (this.getDomElement().offsetHeight-1) + "px"; // -1 for border!    
    
    // always set the ivars on the initial call to ResizeNotify (Case 1)
    this._maxHeight = newHeight;
    this._maxWidth = newWidth;
    
    this._fetchInitialBlock=true;
    
    if (this._isImmediate) {
        var pprTarget = AdfDhtmlPivotTablePeer._getChildBySubId(this.getDomElement(), "PPRTarget");
        this.DomReplaceNotify(pprTarget); // include content in intial render
    } else {
        // Here we are checking is there is a pending streaming request. There won't be one when the contents are made visible,
        // so we will fire a fetch event
        if(this._streamingDisabled || !AdfPage.PAGE.isStreamingRequestPending(this.getComponent().getClientId())) {
            //request first block from the server
            var size = [];
            size[0] = AdfPivotTableDataFetchEvent.createSizeParameter(newWidth, newHeight);
            // TODO: we should check whether just the one block is enough.  This implies 
            // that we should pass a list of blocks to the event c'r like any other data fetch.
            
///////////// TODO: DOES THIS CALL (AND ITS PARAMS CREATED ABOVE) NEED UPDATING?
///////////// OTHERWISE, RN() DONE.
            this.fetchBlocks(AdfPivotTableDataFetchEvent.FETCH_TYPE_INITIAL, AdfPivotTableUtils.DELAY_INITIAL, [], size, undefined,undefined,undefined);
        }
        this._displayStatus(this._fetchText, true);
    }
  } else { // Case 2 or 3 above
      // Per method JSDoc above, don't update ivars for Case (iii) (shrinkToFit) unless it's Case 1 (handled above).
      if (!this._ptUtils.isShrinkToFit()) { // isShrinkToFit() returns true iff it is Case (iii) above.
          this._maxHeight = newHeight;
          this._maxWidth = newWidth;
      }
      
      this.HandleDomScroll(this.getComponent(), null);
      this._ptUtils.sizePivotTable(newWidth, newHeight);  
      this._ptUtils.scrollHTMLBlocks(); // we call scrollHTMLBlocks to restore the invariant that can be destroyed by sizePivotTable, as explained in the sizePivotTable doc.  
      this._ptUtils.fetchNearbyBlocks(true);
      
      if (this._isPaging)
          this.updatePagingControls();
  }
}

/**
 * @export
 * Notifies the peer that some DOM corresponding to a child (or descendent) has been replaced.
 */
AdfDhtmlPivotTablePeer.prototype.DomReplaceNotify = function(domNode)
{
  
  try {
this._ptUtils.startTimer(1,2);    
this._ptUtils.checkpointTimer(1,"wrote to logger");

    this._codeExecutionCounter++; // See HandleComponentBlur()
    
    // In accessiblity mode, we replace the entire element, so that initDomElement is called instead of this method.  Just in case this method is ever called, we bail out here.
    if(this._isAda) { 
      return;
    }

    // reset cell dirty flag
    this._cellDirty = false;
    
    var component = this.getComponent();
    
    // AdfLogger.LOGGER.severe('DRN : ack: ' + component.getProperty(AdfRichPivotTable.__ACTIVE_CELL_KEY));  
    // reset this value so that the server always picks up the client blocks
    var clientBlocks = component.getProperty(AdfRichPivotTable.__CLIENT_BLOCKS_KEY);
    if(clientBlocks==undefined || clientBlocks=="foo")
      component.setProperty(AdfRichPivotTable.__CLIENT_BLOCKS_KEY, "bar", false, AdfUIComponent.PROPAGATE_ALWAYS); 
    else
      component.setProperty(AdfRichPivotTable.__CLIENT_BLOCKS_KEY, "foo", false, AdfUIComponent.PROPAGATE_ALWAYS); 

    // check if this is a click to edit cell and process it if it is. 
    var clickToEdit = AdfAgent.AGENT.getAttribute(domNode, "_cte");
this._ptUtils.checkpointTimer(1,"before CTE block");
    if(clickToEdit) 
    {
      var rowHeight;
      var delta=undefined;
      var oldRowHeight = AdfAgent.AGENT.getIntAttribute(domNode, "_orh");
      if(clickToEdit=="true"){
        rowHeight = domNode.parentNode.offsetHeight;
        delta = rowHeight-oldRowHeight;

        // check if it is the same cell (in the case of pressing enter key on the last cell), we don't need to compute delta
        if (this._oldActiveCellKey != null)
        {
            var oldActiveCellIndex = this._getDataCellIndexFromKey(this._oldActiveCellKey);
            var newActiveCellKeyInfo = this.getCoordinates(domNode);
            if (oldActiveCellIndex['column'] == newActiveCellKeyInfo['col'] && oldActiveCellIndex['row'] == newActiveCellKeyInfo['row'])
            {
                delta = 0;
            }
        }
        
        // save the new height as the old height!
        this._oldCellRowHeight = oldRowHeight;  
        this._lastUpdatedRow = this.getCoordinates(domNode)['row'];
        this._activeCell = domNode;
        this._lastEditCellRowHeight = rowHeight;
        //AdfLogger.LOGGER.severe('focus click to edit cell');  
        //this._focusClickToEditCell(); // bypass the focus restoration logic in PPR at Request done - ###  old implementation
      }else if(clickToEdit=="false") {
        rowHeight = domNode.parentNode.offsetHeight;
        // we may not know the row height, if the active cell was rendered after a full block refresh. 
        // TODO - Determine if we should prevent this from occuring, by always rendering read only in those cases. 
        if(oldRowHeight) 
          delta = oldRowHeight-rowHeight;        

        // don't update the row height, if its the same row that is being editted. 
        if(this.getCoordinates(domNode)['row']==this._lastUpdatedRow) {
            delta=0;
            // maintain the initial row height, since the same row is being editted!
            this._oldCellRowHeight = oldRowHeight;
        }
        //AdfLogger.LOGGER.severe('got non-editable content');
      }
      // adjust the row size
      if(delta)
      {
        // no need to call updateTextAreaCellHeights here since it will be handled in _clickToEditResize
        this._clickToEditResize(domNode, delta);
      }
      else 
      {
        // if there's no delta, we'll still need to adjust the height of TD that contains a textarea
        if (AdfAgent.AGENT.getPlatform() == AdfAgent.IE_PLATFORM)
          this._ptUtils.updateTextAreaCellHeights(domNode.parentNode, rowHeight);
      }
      return;
    }
this._ptUtils.checkpointTimer(1,"CTE block");

    // right now this is only used by Click to Edit to indicate that all cells have been updated
    if(domNode.id ==  this.getDomElement().id + "::RequestDone")
    {
        this.highlightFocusCell(); // returns focus after quitting click-to-edit mode
        
        //show error handled by input text component
        var vError = AdfAgent.AGENT.getBooleanAttribute(domNode, "_vError",false);
        if (vError)
        {
          var actionCTE = AdfAgent.AGENT.getBooleanAttribute(domNode, "_CTEAction",false);          
          var recoverACK = this._recoverACK(actionCTE);//after recover, must call this._syncAckWithAC (it is called below)
          this._recover(recoverACK);
          
          // clean up variables once since there is a validation error
          this._cleanupStateVariable();               

          // don't show delayed context menu if server validation occurs
          this.domEventForContextMenuAfterEdit = null;
        }                 
         
        this._syncAckWithAC();
        
        // if currentRequest indicates a "partial" request is outstanding, clear it, but don't clobber any scroll/fillViewport requests. 
        // TBD: this is wrapped in "if partial" to achieve the "don't clobber" part of that statement, in order to make the logic equivalent to the old "actionPending=false" logic.  Is the scroll/fillViewport case even possible?  Do we really want to avoid clearing it if it happens?
        if (this._currentRequest == AdfDhtmlPivotTablePeer.PARTIAL)
            this.setCurrentRequest(null); // ADS not supported with cell editing, so no need to pass in an adsCounter. (If we did, should we pass -1?)
        
        this.alreadyRequestedDismissEdit = false; // we don't want to cancel any delayedDismissCTE timer anymore

        this._hideStatus();               
        this._focusClickToEditCell(); // bypass the focus restoration logic in PPR at Request done
        // ### AdfLogger.LOGGER.severe('after requestdone, activecellkey =' + this._getActiveCellKey()+ ' activeCell=' + this._activeCell);  

        if(this._needFetchNearbyBlock && this._startDelayedDismissCTE) // dropped the fill viewport response while waiting for delayed dismiss CTE
             this._ptUtils.fetchNearbyBlocks(true);//////////////////////////////////////// DONE
        this._needFetchNearbyBlock = null;
        this._startDelayedDismissCTE = null;
             
        // handling of navigate to header cell from data body 
        if (this._navigateToHeaderKeyCode != null) 
        {
            if (!vError) 
            {
                this.getDomElement().focus();
                this.handleCtrlArrowNavigation(this._navigateToHeaderKeyCode);
                
                // in IE the above action triggers unwanted blur event for some reason, which unhighlights the header.  
                // [Jim] Update 12/2011: see update above
            }            
            this._navigateToHeaderKeyCode = null;              
        }

        // handling of displaying context menu
        if (this.domEventForContextMenuAfterEdit != null)
        {
            this._displayContextMenuCB = this.createCallback(this._displayContextMenuAfterEdit);
            // delay showing the context menu
            AdfAgent.AGENT.getDomWindow().setTimeout(this._displayContextMenuCB, 300);
        }

        // checks if there's no editable content, if so then enter key should go to the cell below
        if(this.domEventForReadOnlyContentCheck != null) 
        {
            if (this._activeCell != null && this._getInputElementsInNode(this._activeCell).length == 0)
                this.handleEnterKey(this.domEventForReadOnlyContentCheck);
            this.domEventForReadOnlyContentCheck = null;
        }

        // TBD: Currently we don't return here so that the focus/selection code at the end of this 
        // method runs.  This relies on the main part of the block-fetch section to be a no-op for non-block-
        // fetch responses.  May be better to put any needed focus cell code here and return.
    }     
this._ptUtils.checkpointTimer(1,"RequestDone block");
  
    var pprTarget = AdfDhtmlPivotTablePeer._getChildBySubId(this.getDomElement(), "PPRTarget");
    
    // Handle block fetches:
    if(domNode==pprTarget)
    {
        var renderType = AdfAgent.AGENT.getAttribute(pprTarget, "_renType");        
        var databodyBlocks = this._ptUtils.getArrivingBlocks(pprTarget, this._ptUtils._id, AdfPivotTableUtils.DATABODY);
        
        if (this.__logEnabled) {
            var item = {};
            this.putInTestLog(item);
            item.type = "DRN";
            item.time = new Date().getTime() - this.__baseTime;
            item.scrollLeft = this._ptUtils.getScroller(1,1).scrollLeft; // TODO
            item.scrollTop = this._ptUtils.getScroller(1,1).scrollTop; // TODO
            item.renderType = renderType;
            item.databodyBlocks = databodyBlocks;
            item.rowHeaderBlocks = this._ptUtils.getArrivingBlocks(pprTarget, this._ptUtils._id, AdfPivotTableUtils.ROW_EDGE);
            item.colHeaderBlocks = this._ptUtils.getArrivingBlocks(pprTarget, this._ptUtils._id, AdfPivotTableUtils.COLUMN_EDGE);
        }

        // When the serverRequest ivar is non-null, it indicates that the blocks in the ivar are already on their way, so that it's OK to bail out of any new fetch of those blocks.  
        // So if the response matches serverRequest, clear it to indicate that the request is no longer on its way.  Do this before bailing out due to currentRequest 
        // not matching, since we want to make sure to clear *server*Request if the arriving blocks match it, even if they don't match the currentRequest (e.g. if 
        // currentRequest was nulled out because no longer needed).
        if (this.requestMatchesResponse(this._serverRequest, databodyBlocks, renderType))
            this.setServerRequest(null);
        
        // If we're waiting for a drill/pivot/sort/CTE/etc. response and a scroll/fillVP response arrives, discard it:
        if (this._currentRequest == AdfDhtmlPivotTablePeer.PARTIAL && renderType != AdfDhtmlPivotTablePeer.PARTIAL) {
            if (this.__logEnabled) 
                item.bailingDueToAp = true;

            if(renderType == AdfPivotTableDataFetchEvent.FETCH_TYPE_INITIAL_FILL_VIEWPORT && this._startDelayedDismissCTE) 
            {
                // we drop the initial fill viewport response while waiting for the response from delayed dismiss CTE
                // there is a race condition when sort with small block size triggers initial fill viewport
                // and then timer from dismissCTE takes place
                this._needFetchNearbyBlock = true;               
            }
            // ###  AdfLogger.LOGGER.severe('currentRequest is PARTIAL and rendertype is not partial :' + renderType);
            return;
        }
        
        // Rarely, DomReplaceNotify will get called after the user has scrolled but before the resulting 
        // call to HandleDomScroll occurs, which causes DRN to find an inconsistent state, which causes problems.
        // Thus we call HDS at the beginning of all block-fetch cases other than init/partial.  In the 99% case where no 
        // unhandled scroll has occurred, this call is a no-op.  Otherwise, the result is as if HDS had been called 
        // just before DRN was called, as desired.  If this causes a new request to be sent that is different than 
        // the response just received, the current response will be discarded as desired, avoiding an inconsistent state.  
        if (renderType == AdfPivotTableDataFetchEvent.FETCH_TYPE_LONG_SCROLL || renderType == AdfPivotTableDataFetchEvent.FETCH_TYPE_SHORT_SCROLL || renderType == AdfPivotTableDataFetchEvent.FETCH_TYPE_INITIAL_FILL_VIEWPORT) {
            if (this.__logEnabled) 
                item.callingHdsJustInCase = true;
            this.HandleDomScroll(this._component, null, true); // 3rd arg for testing only
        }
        
        // Discard response if not waiting for a response, or if waiting for scroll/fillVP response and response doesn't match it.
        // If waiting for initial blocks, callee indicates a match so we keep it.
        // If waiting for a PARTIAL response (pivot/etc.), do nothing (don't call method) since this case is handled above.
        if (this._currentRequest != AdfDhtmlPivotTablePeer.PARTIAL && 
            !this.requestMatchesResponse(this._currentRequest, databodyBlocks, renderType))
        {
            if (this.__logEnabled) 
                item.bailingBcDoesntMatchAtTime = new Date().getTime() - this.__baseTime;

            // ###   AdfLogger.LOGGER.severe('the request does not match with the response');
            return;
        }
        
        // Now we know that these are definitely the blocks we're waiting for, since we've passed all the "are these the blocks we're waiting for?" checks.
        
        if (this._adsEnabled) { // references to methods/etc. in ADS JS partition must be wrapped in "if ADS" since otherwise the partition might not be present
            // Note that all incoming blocks have the same adsCounter, since we pause events during rendering.
            var adsCounter = AdfAgent.AGENT.getIntAttribute(databodyBlocks[0], "_adsCounter");
            
            // If the new blocks' adsCounter is either too old or too new, relative to existing blocks and events received so far, to be usable, we must PPR.
            // To avoid unnecessary PPR's, this check must happen after determining that these are definitely the blocks we're waiting for.
            if (!this.validateBlockForADS(adsCounter, renderType)) {
                AdfPage.PAGE.addPartialTargets(this.getComponent());
                return;
            }
        }
        
        // Since these are definitely the blocks we're waiting for, it's time to:
        // 1 clear the currentRequest ivar (to indicate that we're no longer waiting on any block fetch, whether an initial block fetch, 
        //   a scroll/fillViewport request, or a "partial" request like pivot).  We do this farther down after the blocks are positioned, 
        //   since it causes the buffered ADS events to be applied, which the user should see.
        // 2 hide the "fetching" tooltip, 
        // 3 and clear the scroll lockout ivar used for touch scrolling.
        this._hideStatus();
        if (AdfPivotTableUtils.isMobileTouchDevice() && !this._isPaging)
            this._ptUtils._scrollWaitForFetch = false;
        
        // always clear size overrides when we receive content from the server, since the size overrides are synchronized every time a request goes to the server. 
        // TBD: this doesn't seem right.  We already clear them in GetChanges, immediately after sending them, which is the right place.  Only a subset of server trips cause our DRN() to be called and to make it this far without bailing, so it seems like doing this here would lead to inconsistent behavior.  Specifically, in the subset of server trips that cause this line to be executed, this line would cause us to lose DnD user resizes that were done after kicking off a fetch and before the request came back.
        this.clearSizeOverrides(component);

        // ### AdfLogger.LOGGER.severe('renderType :' + renderType);
        
        var selectionString = this.getArrivingSelectionString(pprTarget);
        
this._ptUtils.checkpointTimer(1,"in block-fetch block, just before switch");
        switch (renderType)
        {
          case AdfPivotTableDataFetchEvent.FETCH_TYPE_INITIAL: // first render, either lazy or immediate 
            this._initSummary(pprTarget);
            // fall through!!

          case AdfDhtmlPivotTablePeer.PARTIAL: // pivot, drill, sort, split
            this._ptUtils.initInfo(pprTarget, component);
this._ptUtils.checkpointTimer(1,"Init case: just called initInfo");
            
            this._isSortEnabled = AdfAgent.AGENT.getBooleanAttribute(pprTarget, "_sortEnabled"); // is *data* sorting enabled
            this._currentPivotHandle=null; // display the pivot handle again
            
            var emptyTextDivArray = this._ptUtils.getArrivingElements(pprTarget, this._ptUtils._id + "::" + AdfDhtmlPivotTablePeer.EMPTY_TEXT_ID);
            var emptyTextDiv = emptyTextDivArray[0]; // non-null iff we need to display the "no data" message rather than actual data
            this._ptUtils._emptyTextDiv = emptyTextDiv; // by null-checking this ivar, we can tell at any time whether we're displaying the no-data message
            
            // we format the row and column info for the scroll tooltip with rowExtent and column extent first
            this._colInfo = TrFastMessageFormatUtils.format(this._colInfo,"{0}", this._ptUtils._colExtent );
            this._rowInfo = TrFastMessageFormatUtils.format(this._rowInfo,"{0}", this._ptUtils._rowExtent );
            
this._ptUtils.checkpointTimer(1,"Init case: just did some misc init");

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

            this._ptUtils.setVisibilityOfPanes();
this._ptUtils.checkpointTimer(1,"Init case: setVisibilityOfPanes");
            this._ptUtils.removeAllBlocks();//////////////////////////////////////// DONE
this._ptUtils.checkpointTimer(1,"Init case: removeAllBlocks");
            this._ptUtils.removePivotHandlesAndLayerLabelBlock();
            this._ptUtils.addPivotHandlesAndLayerLabelBlock(pprTarget);
this._ptUtils.checkpointTimer(1,"Init case: pivothandles");
            if (emptyTextDiv)
                this._ptUtils._databodies[1][1].appendChild(emptyTextDiv);
            this._ptUtils.addBlocksToBuffer();//////////////////////////////////////// DONE
this._ptUtils.checkpointTimer(1,"Init case: addBlocksToBuffer");
            
//            console.log("just before calling sizeHtmlBlocks");

            var sizeBeforeViewport = this._ptUtils.sizeInitialHTMLBlocks(true);/////////////////// DONE
this._ptUtils.checkpointTimer(1,"Init case: sizeInitialHTMLBlocks");
            if (this._isRowHeaderTextWrap() && this._ptUtils._isRowHeaderWidthOverrideSmaller)
                this._ptUtils.coordinateRowBlockSizes();
            this._ptUtils.sizePivotTable(this._maxWidth, this._maxHeight); /////////////////// DONE           // The call to positionInitialBlocks establishes the invariant that can be destroyed by sizePivotTable, as explained in the sizePivotTable doc.  
this._ptUtils.checkpointTimer(1,"Init case: sizePivotTable");
            this._ptUtils.positionInitialBlocks(sizeBeforeViewport);////////////////// DONE
this._ptUtils.checkpointTimer(1,"Init case: positionInitialBlocks");
            
            this.setCurrentRequest(null, adsCounter); // indicate that we're no longer waiting for a fetch.  Do this here, after positioning the new blocks but before markBlocksNotNew/fetchNearbyBlocks, since it causes buffered ADS events to be processed, which should happen after the blocks are onscreen.
            this._ptUtils.markBlocksNotNew();//////////////////////////////////////// DONE
            this._ptUtils.fetchNearbyBlocks(true); //////////////////////////////////////// DONE
this._ptUtils.checkpointTimer(1,"Init case: fetchNearbyBlocks");
             this.initializeSelection(selectionString);
this._ptUtils.checkpointTimer(1,"Init case: initializeSelection");
          
            // sync the ACK with the one from server. This happens when the ACK has value, but _activeCell == something else
            // right now it's in FETCH_TYPE_INITIAL case
            // ?? after this, we have to call _syncAckWithAC (_syncAckWithAC is in request done block and the bottom of domNode == pprTarget)
            var varActiveCellKey = AdfAgent.AGENT.getAttribute(pprTarget,"_ack");
            if (varActiveCellKey != "null")             
            {
              component.setProperty(AdfRichPivotTable.__ACTIVE_CELL_KEY, varActiveCellKey, false, AdfUIComponent.PROPAGATE_ALWAYS);
              component.setProperty("activeCellKey", varActiveCellKey, false, AdfUIComponent.PROPAGATE_ALWAYS);        
            }
            else
            {
              component.setProperty(AdfRichPivotTable.__ACTIVE_CELL_KEY, null, false, AdfUIComponent.PROPAGATE_ALWAYS);
              component.setProperty("activeCellKey", null, false, AdfUIComponent.PROPAGATE_ALWAYS);        
            }
this._ptUtils.checkpointTimer(1,"Init case: ACK");
 
            // ###    AdfLogger.LOGGER.severe('renderType :' + renderType + '; actionPending: ' + (this._currentRequest == AdfDhtmlPivotTablePeer.PARTIAL));

            // sync with active cell key set on server initially
            this._updateInitialACK(pprTarget);///////////////////////// DONE: callee unaffected

          break;
          
          ///////////////////////////////// LS DONE EXCEPT AS NOTED BELOW
          case AdfPivotTableDataFetchEvent.FETCH_TYPE_LONG_SCROLL: 
            // We keep the existing client-side values for the startRow/Col properties, which reflect any minor scrolling done 
            // since the request was sent, rather than replacing them with the server's values, which don't, to avoid jumping 
            // back to the older scrollPos.  It appears that the existing logic makes it impossible for the new block to be 
            // completely off the viewport as a result of this, since the existing long scroll logic keeps the block iff it is relevant.  
            // Note that there is no scroll event, so the app is not allowed to set these props on the server for a scroll, so 
            // ignoring the server value is OK.
            // 
            // For the headerStartLayer props, we keep the existing values.  Since these props are set lazily, and were 
            // most recently set in GetChanges() when sending up the request, this is the same as using the server values.
            // To use the latest client values, we'd have to recompute them now, which can be expensive, for a tiny benefit.  
            // It's not worth it.
            // 
            // TBD: If/when we make the startRow/Col props lazy too, the same logic will apply to them: it's not worth 
            // recomputing them here.  (Until then, there's no performance difference, so we keep the current policy, and 
            // either way, the result is not to run any code here.)

            if (this.getDomElement().getAttribute("_rngChgLstnr") == "true") 
                var clone = this._ptUtils.cloneArray(this._ptUtils._databodyBuffers);            
            this._ptUtils.removeAllBlocks();
            this._ptUtils.addBlocksToBuffer();            
            if (clone != null)
            {
                var event = this._getRangeChangeEvent(clone, this._ptUtils._databodyBuffers);
                event.setBlockRemoved(false);
            }

            var sizeBeforeViewport = this._ptUtils.sizeInitialHTMLBlocks(false);
            if (this._isRowHeaderTextWrap() && this._ptUtils._isRowHeaderWidthOverrideSmaller)
                this._ptUtils.coordinateRowBlockSizes();
            this._ptUtils.sizePivotTable(this._maxWidth, this._maxHeight); // The call to positionInitialBlocks establishes the invariant that can be destroyed by sizePivotTable, as explained in the sizePivotTable doc.  
            this._ptUtils.positionInitialBlocks(sizeBeforeViewport);
            
            this.setUnchangedSelection(selectionString);

            this.setCurrentRequest(null, adsCounter); // indicate that we're no longer waiting for a fetch.  Do this here, after positioning the new blocks but before markBlocksNotNew/fetchNearbyBlocks, since it causes buffered ADS events to be processed, which should happen after the blocks are onscreen.
            this._ptUtils.markBlocksNotNew();            

            if (event != null)
                component.queueEvent(event);

            // handle any post fetch editing/focusing.  Note that the call to fetchNearbyBlocks(), which is important with or without cell editing, seems to have been moved to this callee.
            this._handleEditingAndFocusAfterFetch();

            break;
            
          case AdfPivotTableDataFetchEvent.FETCH_TYPE_SHORT_SCROLL:
          case AdfPivotTableDataFetchEvent.FETCH_TYPE_INITIAL_FILL_VIEWPORT:            
            // Do all calcs that force relayout up front, and THEN position the blocks, 
            // so that everything appears to change at once, rather than being jerky.
            
            var deltas = [[],[]];
            for (var edge=1; edge>=0; --edge) {
                for (var pane=this._ptUtils._startPane[edge]; pane<2; ++pane) {
                    // the distance the scrollbar has moved since the blocks were last allowed to catch up to the scrollbar (the user might have scrolled since the request was sent):
                    deltas[edge][pane] = this._ptUtils.getScrollPos(edge, pane) - this._ptUtils.getOldScrollPos(edge, pane);
                }
            }
            
            // if there is a range change listener registered
            if (this.getDomElement().getAttribute("_rngChgLstnr") == "true") 
                clone = this._ptUtils.cloneArray(this._ptUtils._databodyBuffers);            
            this._ptUtils.trimBuffer(deltas); ///// DONE
            if (clone != null)
                event = this._getRangeChangeEvent(clone, this._ptUtils._databodyBuffers);
            this._ptUtils.addBlocksToBuffer(); ////// DONE
            
            // Call this after trimBuffer (to avoid applying events to blocks we'll discard), after addBlocksToBuffer (so buffer is in consistent state since we access 
            // buffer to apply the events to cells), but before positioning new blocks onscreen (as explained in callee JSdoc).
            // Only relevant for SS/FV, since those are the only fetch types that keep old blocks around.
            if (this._adsEnabled) // references to methods/etc. in ADS JS partition must be wrapped in "if ADS on" since the partition might not be present when ADS is off
                this.applyOldBufferedADSEvents(adsCounter);
            
            this._ptUtils.sizeNewHTMLBlocks(); ///////// DONE
            //bug-32892714 : Aligns the height of the data body row and the row header when the text wrap is enabled in row header.
            if (this._isRowHeaderTextWrap())
                this._ptUtils.coordinateRowBlockSizes();
            this._ptUtils.sizePivotTable(this._maxWidth, this._maxHeight); // the subsequent call to scrollHTMLBlocks restores the invariant that can be destroyed by sizePivotTable, as explained in the sizePivotTable doc.  //////////////// DONE
            this._ptUtils.positionNewHTMLBlocks(deltas); ///////////// DONE
            this._ptUtils.scrollHTMLBlocks(true); ////////////// DONE
            
            // Now that we've positioned the new blocks next to the existing blocks, we 
            // identify the viewport start row and start column, and the start pixel within
            // that row and column, and set those properties.  (This is the opposite of 
            // long scrolls, in which the blocks are positioned to honor a particular 
            // start row/col.)
            this._ptUtils.setViewportStartSliceFromPosition(); ///////////////// DONE
            
            this.mergeSelection(selectionString);
            this.highlightSelection(false, true); // we must highlight the new blocks on the client, since highlighting on server leads to a highlighting race condition when the user keeps navigating after the fetch.  TBD: If we decide to lock the Selection UI when a fetch is pending, or if/when we impl a separate fetch type for navigation-driven fetches, revisit the need to highlight on client.
            
            this.setCurrentRequest(null, adsCounter); // indicate that we're no longer waiting for a fetch.  Do this here, after positioning the new blocks but before markBlocksNotNew/fetchNearbyBlocks, since it causes buffered ADS events to be processed, which should happen after the blocks are onscreen.
            this._ptUtils.markBlocksNotNew(); ///////////////////////////////////////////////////// DONE
            
            if (event != null)
                component.queueEvent(event);

            // handle any post fetch editing/focusing.  Note that the call to fetchNearbyBlocks(), which is important with or without cell editing, seems to have been moved to this callee.
            this._handleEditingAndFocusAfterFetch();
            
            //this._ptUtils.checkpointTimer(1, renderType);
           
            break;
          default: 
            AdfAssert.assert(AdfPivotTableUtils.assertsOff, "unexpected renderType in DomReplaceNotify"); 
        }
this._ptUtils.checkpointTimer(1,"after switch");

        // Focus cell code:

        // TODO: should delay this until the viewport is filled.  To enable this, probably have fetchNearbyBlocks return a bool indicating whether it needed to fetch blocks to fill the VP.
        if (this._unfinishedHeaderKeyboardNavigation) {
            this.restartHandlingHeaderKeyboardNavigation();
        }

        // need to sync ACK with _activeCell in case the ACK value was changed in the rendering process
        // it could not be executed before this because the active cell is not in the client yet and therefore will always return null
        // we have to wait until the activeCell is in the client
        // this function is also called from requestDone block of DRN
        this._syncAckWithAC();

        // This is needed in case the focus cell is in one of the new blocks.  
        // TBD: 
        // - For the init case of DomReplaceNotify, this call is redundant because that case already calls initializeSelection.  Consider moving this into each of the other cases.  
        // - Once all cases all cases of DRN are calling initializeSelection, this will be redundant for those cases too.  
        // - This is also redundant with the restart...() call above
        // - What about the new uses of DomReplaceNotify, for ClickToEdit?  If the entire td comes down, we presumably need to call this for those cases too, but if only the contents of the cell come down, presumably we don't.  
        this.highlightFocusCell();

        // ###  AdfLogger.LOGGER.severe('renderType :' + renderType + '; actionPending: ' + (this._currentRequest == AdfDhtmlPivotTablePeer.PARTIAL));
        if (this._isPaging)
            this.updatePagingControls();
    }       
    else if (domNode.nodeName == 'TD') {
        // cell ppr case, we'll need to update the TD for text area stamps
        if (AdfAgent.AGENT.getPlatform() == AdfAgent.IE_PLATFORM)
        {
          var parent = domNode.parentNode;
          if (parent != null)
              this._ptUtils.updateTextAreaCellHeights(parent);        
        }
    }

    // fix for bug 12317927, the case where autoSubmit is set to true so a ppr caused by autosubmit might stay in the same cell
    // if the active cell gets replace because of PPR of the cell, we'll need to update the reference    
    if (this._activeCell != null && domNode != null && domNode.id == this._activeCell.id)
    {
        this._activeCell = domNode;
    }
  } catch(e) {
      AdfLogger.LOGGER.severe(e.message);
  } finally {
      this._codeExecutionCounter--; // See HandleComponentBlur()
  }
  this._ptUtils.stopTimer(1, "end", "DomReplaceNotify");  
  
//    for (jim=0; jim<25; ++jim) {
//      AdfLogger.LOGGER.severe(  "FINAL: headerRow.cells[0].offsetHeight:" +   jimHeaderCell[jim].offsetHeight + ", headerRow.offsetHeight:"   +   jimHeaderRow[jim].offsetHeight);
//      AdfLogger.LOGGER.severe("FINAL: databodyRow.cells[0].offsetHeight:" + jimDatabodyCell[jim].offsetHeight + ", databodyRow.offsetHeight:" + jimDatabodyRow[jim].offsetHeight);
//    }
  
  return domNode;
} // end DRN

AdfDhtmlPivotTablePeer.prototype._initSummary = function(pprTarget) {
    var summary = AdfAgent.AGENT.getAttribute(pprTarget, "_summary");
    var summaryDiv = this._ptUtils._summaryDiv;
    var summaryDivId = AdfAgent.AGENT.getAttribute(summaryDiv, "id");
    AdfAgent.AGENT.setTextContent(summaryDiv, summary);
    AdfAgent.AGENT.setAttribute(this._ptUtils._container, "aria-describedby", summaryDivId);
}

// called by DomReplaceNotify after block fetch from scroll
// after block fetch we might need to 1) make the focus cell editable 2) scroll to the focus cell or scroll to it if not in viewport
AdfDhtmlPivotTablePeer.prototype._handleEditingAndFocusAfterFetch = function()
{
  // fixing bug 9314333, do not call editCellWithFocus until all nearby blocks has been fetched
  var blocksToFetch = this._ptUtils.fetchNearbyBlocks(true);//////////////////////////////////////// DONE
  if (!blocksToFetch)
  {
    // this is needed in case the new block is fetched while the user is trying to edit it
    // in the keydown handler, the node cell will be null because it has not been fetched yet 
    // after the new block has focus, there will be a node and make that editable
    // flag is set to 'true' in HandleComponentKeyDown if event is sent
    // flag is set to 'false' in _dismissClickToEdit()
    if(this._isEditingCellWithFocus)
    {
      if(this._focusCellIndex != null)
      {
        // adjust the scroll again now that the block is available
        this.scrollToDataCell(this._focusCellIndex);
        if (!this._isReadOnly())
          this.editCellWithFocus();
        else
          this.selectAndFocusDataCell(this._focusCellIndex); // TBD: Now that request-time code (scrollToCellIndexCTE) is correctly calling selectAndFocusDataCell() rather than just setting the focus cell ivar, the selection and focus should be in sync now (unless there is some other bug too), so hopefully this method call is no longer needed.  If so, then we need to call highlightFC() instead (the callee of this methods that *is* needed), unless some other response-time code is already calling it, since that can only be done at response time.
      }
      this._isEditingCellWithFocus = false;
    }
    else
    {
      // fix for bug 11780707
      // scrolling (keyboard) out of block that might require adjustments to ensure focus cell index is in sight
      if (this._ptUtils._horizScrollOutOfBlock || this._ptUtils._vertScrollOutOfBlock)
      {
        var _focusCellIndex = this._selectionFrontier;                        
        if (_focusCellIndex != null)
        {
          if (this.isDataCellInBuffer(_focusCellIndex))
            this.scrollToDataCell(_focusCellIndex);
          else
          {
            // if not in buffer
            
            // TODO: 
            // Issues that could cause existing logic to fetch wrong block:
            // - both ivars could be true if user scrolled aggressively, so should have case for that.
            // - should be passing viewportStartSlice, not bufferStartSlice, to lJTC().
            // - even after those fixes, if block size small enough relative to viewport (or if there are edge cases where the sequence of events was more complicated/different than what is implied by the ivars), then the focus cell might not be in the block that is fetched as a result of this call.  Should check for that case and if so, tweak the long jump fetch params to ensure the focus cell is in the fetched block.
            // Possible improvements to existing logic:
            // - do a short jump if possible
            // - for scrolls to the left or up, it might be more natural to do the long jump so that the focus cell winds up at the end of the viewport, not beginning.
            // Getting rid of all this logic:
            // - if the approximation hack in scrollToDataCell is replaced with a reliable fetch that always fetches the desired cell, perhaps this entire block of code will no longer be necessary.
            
            // Modulo above todo's, logic is this (for horizontal scrolls, vert is analogous) :
            // - If we're scrolling horizontally only, then presumably row of focus cell is in viewport (exceptions?).  
            // - So keep existing viewport start row (currently has buffer start row), so that fetched block will include all viewport rows including focus cell row (if block big enough), so that content won't jump vertically.
            // - Scroll so focus cell's column is viewport start column.  (For left/up scrolls, viewport end column may be better.)
            if (this._ptUtils._horizScrollOutOfBlock) {
              var pane = (_focusCellIndex['row'] < this._ptUtils._splitSlice[1]) ? 0 : 1; // this works when splitSlice == -1, since we use pane 1 in that case
              var startRow = this.getBufferStartSlice(AdfPivotTableUtils.ROW_EDGE, pane);
              this.longJumpToCell(startRow, _focusCellIndex['column']);
            } else if (this._ptUtils._vertScrollOutOfBlock) {
              var pane = (_focusCellIndex['column'] < this._ptUtils._splitSlice[0]) ? 0 : 1; // this works when splitSlice == -1, since we use pane 1 in that case
              var startCol = this.getBufferStartSlice(AdfPivotTableUtils.COLUMN_EDGE, pane);
              this.longJumpToCell(_focusCellIndex['row'], startCol);
            }
          }
        }

        this._ptUtils._horizScrollOutOfBlock = false;
        this._ptUtils._vertScrollOutOfBlock = false;
      }
    }
  }    
}

// this method will get the value of __ACTIVE_CELL_KEY,
// and sync the value with _activeCell variable
// this method should be called whenever there is any mechanism that can potentially change the ACK
AdfDhtmlPivotTablePeer.prototype._syncAckWithAC = function()
{
  //check the status of __ACTIVE_CELL_KEY and make sure that _activeCell is sync with that
  //this is added to handle the case where there is a validation error during a full refresh
  //the __ACTIVE_CELL_KEY and _activeCell do not start with 'null' (which is the usual case) when this happens
  var updatedActiveCellKey = this._getActiveCellKey();      
  if ( updatedActiveCellKey == null) // sync _activeCell with __ACTIVE_CELL_KEY 
    this._activeCell = null;
  else // to sync _activeCell with __ACTIVE_CELL_KEY unconditionally (when trying to fix the full refresh error)
    this._activeCell =  this._getDataCellFromKey(updatedActiveCellKey);
  
}

// this is a helper method that is called after an event is canceled in the event of a client side validation error
// it should re-focus the active cell, realign the row heights and hide the status
// this method is called after canceled scroll, sort, pivot, and drill
AdfDhtmlPivotTablePeer.prototype._clientValidationErrorRecovery = function(activeCellKey)
{
  // when the event is canceled, we want to make sure that the ACK is focused (if there is one)
  // since we are not changing the ACK, we don't need to call _syncAckWithAC()
  this._recover(activeCellKey);
  // when there is a validation error, the row height will change more from the normal editing size
  this._realignValidationErrorRowHeight();
  
  // TBD: usually the currentRequest and tooltip should be cleared in lockstep, so this should probably either do both or neither.  
  // Only one caller, queueFetchEventImmediately, clears currentRequest, and that caller relies on this method to do the corresponding 
  // hideStatus call, so that method should call it directly if the call is removed from this method.
  // All other callers don't modify currentRequest or display the tooltip, so that currentRequest and tooltip would be set only if 
  // they already were set.  Thus, it seems that either both should be unset, or neither should, rather than hiding the tooltip without 
  // cancelling the corresponding request.
  this._hideStatus(); // hide "fetching" tooltip
}

// this method is called by the DRN whenever there is validation error
// it is used to 
// 1. recover __ACTIVE_CELL_KEY 
// 2. re-focus and re-select the corrent cell after validation error
// 3. sync scroll position with viewport and scroll back to the right cell
// NOTE: after calling this method, you have to call _syncAckWithAC() as ACK might have changed
AdfDhtmlPivotTablePeer.prototype._recoverACK = function(actionCTE)
{
  // this block will recover the ACK and _active cell based on the 
  var currentActiveCellKey ;
  if(actionCTE)  
    currentActiveCellKey = this._oldActiveCellKey; // restore old ack if action is CTE
  else // this is a pivot / drill / sort / ... 
    currentActiveCellKey = this._getActiveCellKey();
  this.getComponent().setProperty(AdfRichPivotTable.__ACTIVE_CELL_KEY, currentActiveCellKey, false, AdfUIComponent.PROPAGATE_ALWAYS);
  
  //AdfLogger.LOGGER.severe("verror - restored ACK :" + currentActiveCellKey);
  return currentActiveCellKey;
}

AdfDhtmlPivotTablePeer.prototype._recover = function(activeCellKey)
{
  //AdfLogger.LOGGER.severe("_recover to:" + currentActiveCellKey);

  if(activeCellKey != null)
  {
    // translate key "row:col" into index (!! make sure index is numeric instead of string)
    var dataCellIndex = this._getDataCellIndexFromKey(activeCellKey);              
    // need to undo the scroll     
    if(dataCellIndex != null)
    {
      // bring back focus and highlight in case it's TAB or ENTER on error
      this.selectAndFocusDataCell(dataCellIndex);  
      // make sure scroll position is sync with the block 
      this._readjustScrollPos(); 
      // _readjustScrollPos has to be called first since scrollToDataCell assumes sync scroll position
      this.scrollToDataCell(dataCellIndex);
      // don't show delayed context menu if client validation error occurs
      this.domEventForContextMenuAfterEdit = null;
    }                 
  }

  // a flag that could inform that there was a validation error - 
  // after it's fixed, in the next DRN cycle with no validation error,  
  // fetch nearby blocks to fill up any white space that could possibly be there due to validation error
  // at the moment this is used when there is a dismissCTE while doing fill viewport
  // this._needFetchNearbyBlock = true;
}


// this is a clean up method that should be called when we want to reset all variables that exists between request 
// these variables are usually set in the past (before a request) and 
// will be used as a reference in the future (once the request comes back)
// 
// TBD: This method only takes care of the things that its only caller isn't already doing, e.g. it doesn't call hideStatus().  
// So it probably needs cleanup before being called from elsewhere.  E.g. cancelling block fetches without calling hideStatus means that if the fetch tooltip 
// is displayed for a block fetch, then the tooltip will not get cleared when the response arrives because DRN() will discard it by bailing out before 
// entering the code block that processes the response and calls hideStatus(). 
AdfDhtmlPivotTablePeer.prototype._cleanupStateVariable = function()
{
  // clear the request queue in the event of validation error
  this.setCurrentRequest(null); // this clears not only scroll/fillViewport fetches, but also "partial" requests like pivot.  The only caller already does the latter.
                                // ADS not supported with cell editing, so no need to pass in an adsCounter. (If we did, should we pass -1?).
  this.setServerRequest(null); // unlike currentRequest, unclear whether it's strictly necessary to clear serverRequest here, but it's always safe and conservative to clear serverRequest.

  // cancel the fact that we might have tried to move to a new block while editing 
  //and want to open a non-existing cell for editing
  this._isEditingCellWithFocus = false;

  // cancel the fact that we're trying to navigate to a non-existing cell
  this._unfinishedHeaderKeyboardNavigation = false;    
}

////////////////////////////////
// Generic pivot table routines
AdfDhtmlPivotTablePeer.prototype._isClickToEdit = function()
{
  return (!this._isAda && !this.isAttachmentMode());
}

AdfDhtmlPivotTablePeer.prototype.getClientBlocks = function() { ///////////////////////////// DONE
    var blocks = [];
    var startRow, rows, startCol, cols, blockDesc;
    
    if(this._isAda) {
        // create a datablock for each pane, each of which has exactly one block
        for (var rowPane=this._adaStartPane[1]; rowPane<2; ++rowPane) {
            startRow = this._adaStartSlice[1][rowPane];
            rows = Math.min(this._adaBlockSize[1], this._adaCanvasEndSlice[1][rowPane] - startRow);
            
            for (var colPane=this._adaStartPane[0]; colPane<2; ++colPane) {
                startCol = this._adaStartSlice[0][colPane];
                cols = Math.min(this._adaBlockSize[0], this._adaCanvasEndSlice[0][colPane] - startCol);
                blockDesc = [startRow, rows, startCol, cols];
                blocks.push(blockDesc);
            }
        }
    } else {
        var startPane        = this._ptUtils._startPane;
        var databodyBuffers  = this._ptUtils._databodyBuffers;
        var rowHeaderBuffers = this._ptUtils._rowHeaderBuffers;
        var colHeaderBuffers = this._ptUtils._colHeaderBuffers;
        
        for (var rowPane=startPane[1]; rowPane<2; ++rowPane) {
            var rowOfDatabodyBuffers = databodyBuffers[rowPane];
            var rowHeaderBuffer = rowHeaderBuffers[rowPane];
            var numRows = rowHeaderBuffer.length;
            for (var colPane=startPane[0]; colPane<2; ++colPane) {
                var databodyBuffer = rowOfDatabodyBuffers[colPane];
                var colHeaderBuffer = colHeaderBuffers[colPane];
                var numCols = colHeaderBuffer.length;
                for (var i=0; i<numRows; ++i) {
                    var rowOfDatabodyBlocks = databodyBuffer[i];
                    for (var j=0; j<numCols; ++j) {
                        var block = rowOfDatabodyBlocks[j];
                        if (block) {
                            startRow = this._ptUtils.getIntAttribute(rowHeaderBuffer[i], "_startSlice");
                            rows     = this._ptUtils.getIntAttribute(rowHeaderBuffer[i], "_numSlices");
                            startCol = this._ptUtils.getIntAttribute(colHeaderBuffer[j], "_startSlice");
                            cols     = this._ptUtils.getIntAttribute(colHeaderBuffer[j], "_numSlices");
                            blockDesc = [startRow,rows,startCol,cols];
                            blocks.push(blockDesc);
                        }
                    }
                }
            }
        }
    }

    return blocks;
}

////////////////////////////////
// To be moved to AdfDthmlPivotTablePeer_Common, Generic cell routines  section
/**
 * convert key into DataCellIndex
 * @param cellKey "row:col" format
 * @return DataCellIndex
 */
 AdfDhtmlPivotTablePeer.prototype._getDataCellIndexFromKey = function(cellKey) {
    var cellDetail = cellKey.split(":");  
    if(cellDetail.length == 2) {
        return this.newDataCellIndex(cellDetail[0]*1, cellDetail[1]*1); // *1 convert the strings to number
    }
    return null;
 }

/**
 * convert a DATA key into a DATA cell
 * @param cellKey "row:col" format. this has to be in the DATABODY
 * @return the cell node(element)
 */
AdfDhtmlPivotTablePeer.prototype._getDataCellFromKey = function(cellKey)
{
  var cellIndex = this._getDataCellIndexFromKey(cellKey);
  
  if(cellIndex != null)
  {
    var range = this.newDataCellRange(cellIndex);
    var nodes = this.getNodesInDataCellRange(range);
    if(nodes.length > 0 )
    {
      if(nodes[0] != null)
        return nodes[0];
    }
  }
  return null;
}

/**
 * convert a DATA Cell Index into a DATA cell
 * 
 * @param cellIndex This has to be in the DATABODY
 * @param newBlocks true to look only in new blocks (those flagged with the _new expando)
 *                  false to look only in old blocks (those not flagged with the _new expando)
 *                  omitted/undefined to look in all blocks
 * @return the cell node(element)
 */
AdfDhtmlPivotTablePeer.prototype._getDataCellFromCellIndex = function(cellIndex, newBlocks)
{  
  if(cellIndex != null)
  {
    var range = this.newDataCellRange(cellIndex);
    var nodes = this.getNodesInDataCellRange(range, undefined, newBlocks);
    if(nodes.length > 0 )
        return nodes[0];
  }
  return null;
}

// TBD: is-attachment-mode status can't change once we're inited, right?  If so, maybe follow isPaging model: method is 
// called only once to set the ivar, and then reference ivar directly for efficiency.
AdfDhtmlPivotTablePeer.prototype.isAttachmentMode = function() {
    return this._attachmentMode;
}

/**
 * Do NOT move this to Paging JS Partition (when created), as this is the guard method that is called to *find out* whether
 * we're in Paging mode, i.e. whether it's OK to call the methods in that partition.
 * 
 * Note is static method.
 */
AdfDhtmlPivotTablePeer.isPaging = function(domElement) {
    return (AdfAgent.AGENT.getAttribute(domElement, "_paging") == 'true');
}

//////////////////////////////////////////////////////////////////////////////////
//        BEGIN PAGING METHODS TO EVENTUALLY MOVE TO A "PAGING" JS PARTITION
//////////////////////////////////////////////////////////////////////////////////
// TBD: Also move AdfPivotTableUtils.handlePagingValueChange to the new partition, per the TBD on that method.
//////////////////////////////////////////////////////////////////////////////////

AdfDhtmlPivotTablePeer.prototype._handleClickOnPagingControl = function(domEvent, target)
{
    // TBD: this._isNavBarLink could be updated to not depend on edge and pane to remove the double loop below...
    for (var edge = 0; edge < 2; edge++)
    {
        for (var pane = this._ptUtils._startPane[edge]; pane < 2; pane++)
        {
            if (this._isNavBarLink(target, AdfDhtmlPivotTablePeer._NAVBAR_PREV[edge][pane]))
            {
                AdfDhtmlCommonUtils.addCSSClassName(target, "p_AFActivated");
                this._handlePagePrevClick(domEvent, edge, pane);
                return true;
            }
            else if (this._isNavBarLink(target, AdfDhtmlPivotTablePeer._NAVBAR_NEXT[edge][pane]))
            {
                AdfDhtmlCommonUtils.addCSSClassName(target, "p_AFActivated");
                this._handlePageNextClick(domEvent, edge, pane);
                return true;
            }
            else if (this._isNavBarLink(target, AdfDhtmlPivotTablePeer._NAVBAR_FIRST[edge][pane]))
            {
                AdfDhtmlCommonUtils.addCSSClassName(target, "p_AFActivated");
                this._handlePageFirstClick(domEvent, edge, pane);
                return true;
            }
            else if (this._isNavBarLink(target, AdfDhtmlPivotTablePeer._NAVBAR_LAST[edge][pane]))
            {
                AdfDhtmlCommonUtils.addCSSClassName(target, "p_AFActivated");
                this._handlePageLastClick(domEvent, edge, pane);
                return true;
            }
        }
    }
    return false;
}

AdfDhtmlPivotTablePeer.prototype._getParentMatchingId = function(domElem, id)
{
    var target = domElem;
    var elem = null;
    var rootElem = this.getDomElement();
    while (!elem && target && target != rootElem.parent && target.nodeType == 1)
    {
        if (target.id && target.id.indexOf(id) >= 0)
        {
            elem = target;
            break;
        }
        else
            target = target.parentNode;
    }
    return elem;
}

AdfDhtmlPivotTablePeer.prototype._isNavBarLink = function(target, suffixId)
{
    var linkId = AdfRichUIPeer.CreateSubId(this.getComponent().getClientId(), suffixId);
    if (this._getParentMatchingId(target, linkId) != null)
        return !AdfDhtmlCommonUtils.containsCSSClassName(target, "p_AFDisabled");
    return false;
}

AdfDhtmlPivotTablePeer.prototype._handlePagePrevClick = function(domEvent, edge, pane)
{
    this._pagingControlInput = AdfPivotTableUtils.PAGE_PREV[edge][pane];
    this._adjustScroll = 0;
    var databody = this._ptUtils._databodiesByEdgeAndPane[edge][pane];
    var firstEdgeProperties = this._ptUtils.firstSliceInShiftedViewport(edge, pane, 0);
    var lastEdgeProperties = this._ptUtils.lastSliceInShiftedViewport(edge, pane, 0);
    var firstEdgePixels = 1;
    if (firstEdgeProperties.pixel > 0 && firstEdgeProperties.slice != lastEdgeProperties.slice)
        firstEdgePixels = firstEdgeProperties.pixelsInsideViewport;
    else
        this._adjustScroll = 1;
    var scrollPos = this._ptUtils.getScrollPos(edge, pane);
    var scrollAmount = (((edge == AdfPivotTableUtils.ROW_EDGE) ? databody.clientHeight : databody.clientWidth) - firstEdgePixels);
    if (scrollAmount > scrollPos)
        this._adjustScroll = this._adjustScroll + scrollAmount - scrollPos;
    this._ptUtils.setScrollPos(edge, pane, scrollPos - scrollAmount, true);
    this.HandleDomScroll(this.getComponent(), null);
    AdfAgent.AGENT.eatEvent(domEvent);
}

AdfDhtmlPivotTablePeer.prototype._handlePageFirstClick = function(domEvent, edge, pane)
{
    this._pagingControlInput = AdfPivotTableUtils.PAGE_FIRST[edge][pane];
    this._ptUtils.setScrollPos(edge, pane, 0, true);
    this.HandleDomScroll(this.getComponent(), null);
    AdfAgent.AGENT.eatEvent(domEvent);
}

AdfDhtmlPivotTablePeer.prototype._handlePageLastClick = function(domEvent, edge, pane)
{
    this._pagingControlInput = AdfPivotTableUtils.PAGE_LAST[edge][pane];
    this._ptUtils.setScrollPos(edge, pane, this._ptUtils._maxScrollPos[edge][pane], true);
    this.HandleDomScroll(this.getComponent(), null);
    AdfAgent.AGENT.eatEvent(domEvent);
}

AdfDhtmlPivotTablePeer.prototype._handlePageNextClick = function(domEvent, edge, pane)
{
    this._pagingControlInput = AdfPivotTableUtils.PAGE_NEXT[edge][pane];
    var databody = this._ptUtils._databodiesByEdgeAndPane[edge][pane];
    var lastEdgeProperties = this._ptUtils.lastSliceInShiftedViewport(edge, pane, 0);
    var lastEdgePixels = lastEdgeProperties.pixelsInsideViewport;
    if (lastEdgeProperties.pixelsOutsideViewport <= 0)
        lastEdgePixels = lastEdgeProperties.pixelsOutsideViewport;
    else if (lastEdgeProperties.slice == this._ptUtils.firstSliceInShiftedViewport(edge, pane, 0).slice)
        lastEdgePixels = 0;
    var scrollPos = this._ptUtils.getScrollPos(edge, pane);
    this._ptUtils.setScrollPos(edge, pane, scrollPos + ((edge == AdfPivotTableUtils.ROW_EDGE) ? databody.clientHeight : databody.clientWidth) - lastEdgePixels, true);
    this.HandleDomScroll(this.getComponent(), null);
    AdfAgent.AGENT.eatEvent(domEvent);
}

AdfDhtmlPivotTablePeer.prototype.getPageStartSlice = function(edge)
{
    var firstProperties = this._ptUtils.firstSliceInShiftedViewport(edge,1,0);
    if (firstProperties.pixel > 0)
        return firstProperties.slice + 1;
    else
        return firstProperties.slice;
}

AdfDhtmlPivotTablePeer.prototype.setPageStartSlice = function(edge, start)
{
    var scrollDist = this._ptUtils.scrollDistanceToSlice(edge, 1, start);
    if (scrollDist != null)
    {
        var scrollPos = this._ptUtils.getScrollPos(edge, 1);
        this._ptUtils.setScrollPos(edge, 1, scrollPos + scrollDist, true);
        this.HandleDomScroll(this.getComponent(), null);
    }
    else
    {
        if (AdfPivotTableUtils.ROW_EDGE == edge)
            this.longJumpToCell(start, this.getPageStartSlice(AdfPivotTableUtils.COLUMN_EDGE));
        else
            this.longJumpToCell(this.getPageStartSlice(AdfPivotTableUtils.ROW_EDGE), start);
    }
}

AdfDhtmlPivotTablePeer.prototype.updatePagingControls = function()
{
    for (var edge = 0; edge < 2; edge++)
    {
        for (var pane = this._ptUtils._startPane[edge]; pane < 2; pane++)
        {
            // Check to see if the scroll position needs adjusting before updating the paging input
            if ((this._pagingControlInput == AdfPivotTableUtils.PAGE_PREV[edge][pane] && this.adjustPagingScrollPos(edge, pane, 1)))
                return;
        }
    }
    
    var firstProperties = [[], []];
    var startValue = [[], []];
    var lastProperties = [[], []];
    var endValue = [[], []];
    
    var edgeExtent = [[], []];
    edgeExtent[0][0] = this._ptUtils._splitSlice[0];
    edgeExtent[1][0] = this._ptUtils._splitSlice[1];
    edgeExtent[0][1] = this._ptUtils._edgeExtent[0];
    edgeExtent[1][1] = this._ptUtils._edgeExtent[1];
    
    for (edge = 0; edge < 2; edge++)
    {
        for (var pane = this._ptUtils._startPane[edge]; pane < 2; pane++)
        {
            firstProperties[edge][pane] = this._ptUtils.firstSliceInShiftedViewport(edge, pane, 0);
            startValue[edge][pane] = firstProperties[edge][pane].slice;
            lastProperties[edge][pane] = this._ptUtils.lastSliceInShiftedViewport(edge, pane, 0);
            endValue[edge][pane] = lastProperties[edge][pane].slice;
            
            if (startValue[edge][pane] != -1 && endValue[edge][pane] != -1)
            {
                // Check to see if the scroll position needs to align the first element before updating the paging input
                if (this.alignPagingScrollPos(firstProperties[edge][pane], lastProperties[edge][pane], endValue[edge][pane], edge, pane, edgeExtent[edge][pane], this._ptUtils._databodiesByEdgeAndPane[edge][pane]))
                    return;
            }
            else
                return;
        }
    }
    
    this._updatedPagingInputValue = false;
    this._keepPagingValueChange = [[false, false], [false, false]];
    
    var clearPagingControlInput = true;
    for (edge = 0; edge < 2; edge++)
    {
        /* TBD: Look into using _canvasStartSlice here instead of calculating the edgeStart value */
        var splitSlice = this._ptUtils._splitSlice[edge];
        for (var pane = this._ptUtils._startPane[edge]; pane < 2; pane++)
        {
            if (splitSlice != -1 && pane == 1)
                var edgeStart = splitSlice + 1;
            else
                edgeStart = 1;
            
            if (!this.updatePagingInput(edge, pane, edgeStart, edgeExtent[edge][pane], firstProperties[edge][pane], startValue[edge][pane], lastProperties[edge][pane], endValue[edge][pane]))
                clearPagingControlInput = false;
        }
    }
    
    // clear this._pagingControlInput flag
    if (clearPagingControlInput)
        this._pagingControlInput = null;
}

AdfDhtmlPivotTablePeer.prototype.adjustPagingScrollPos = function(edge, pane, minOffset)
{
    if (this._adjustScroll != null && this._adjustScroll > minOffset)
    {
        var scrollPos = this._ptUtils.getScrollPos(edge, pane);
        if (scrollPos > minOffset)
        {
            this._ptUtils.setScrollPos(edge, pane, scrollPos - this._adjustScroll, true);
            this._adjustScroll = 0;
            this.HandleDomScroll(this.getComponent(), null);
            return true;
        }
        this._adjustScroll = 0;
    }
    return false;
}

AdfDhtmlPivotTablePeer.prototype.updatePagingInput = function(edge, pane, edgeStart, edgeExtent, firstProperties, startValue, lastProperties, endValue)
{
    var pagingControl = this._ptUtils._pagingControl[edge][pane];
    var inputId = this._ptUtils._pagingControlInputId[edge][pane];
    
    if (startValue != -1 && endValue != -1)
    {
        if (startValue != endValue)
        {
            if (firstProperties.pixel > 0)
                startValue++;
            if (lastProperties.pixelsOutsideViewport > 0)
                endValue--;
        }
        
        // Increase start and end values by 1 since they are 0 - indexed
        startValue++;
        endValue++;
        
        var pagingWidth = pagingControl.childNodes[0].offsetWidth;
        var pagingControlInnerContentNode = this.getPagingControlInnerContentNode(pagingControl);
        
        if (pagingControl.offsetWidth < pagingWidth)
        {
            if (pagingControl._pw == null)
            {
                pagingControlInnerContentNode.childNodes[3].style.display = 'none';
                pagingControlInnerContentNode.childNodes[4].style.display = 'none';
                pagingControlInnerContentNode.childNodes[5].style.display = 'none';
                pagingControl._pw = pagingWidth;
            }
        }
        else if (pagingControl._pw == null || parseInt(pagingControl._pw) <= pagingControl.offsetWidth)
        {
            var rangeText = pagingControlInnerContentNode.childNodes[5];
            if (this._pagingRangeText == null)
                this._pagingRangeText = rangeText.innerHTML;
            rangeText.innerHTML = this._pagingRangeText.replace("-", edgeExtent).replace("_", ("- ".concat(endValue)));
            pagingControlInnerContentNode.childNodes[3].style.display = '';
            pagingControlInnerContentNode.childNodes[4].style.display = '';
            pagingControlInnerContentNode.childNodes[5].style.display = '';
            pagingControl._pw = null;
        }
        
        var inputComp = AdfPage.PAGE.findComponent(this.getComponent().getClientId().concat(inputId));
        if (startValue != inputComp.getValue())
        {
            this._keepPagingValueChange[edge][pane] = true;
            inputComp.setValue(''.concat(startValue));
        }
        
        firstArrow = pagingControlInnerContentNode.childNodes[1].childNodes[0];
        prevArrow = pagingControlInnerContentNode.childNodes[2].childNodes[0];
        lastArrow = pagingControlInnerContentNode.childNodes[7].childNodes[0];
        nextArrow = pagingControlInnerContentNode.childNodes[6].childNodes[0];
        
        this.setPagingInputStyles(firstArrow, prevArrow, lastArrow, nextArrow, startValue, endValue, edgeStart, edgeExtent)
        pagingControl.parentNode.style.visibility = 'visible';
        
        return true;
    }
}

AdfDhtmlPivotTablePeer.prototype.alignPagingScrollPos = function(firstProperties, lastProperties, endValue, edge, pane, edgeExtent, databody)
{
    if (this._pagingControlInput == AdfPivotTableUtils.PAGE_FIRST[edge][pane] && firstProperties.pixel > 0)
    {
        this._ptUtils.setScrollPos(edge, pane, 0, true);
        this.HandleDomScroll(this.getComponent(), null);
        return true;
    }
    if (this._pagingControlInput == AdfPivotTableUtils.PAGE_LAST[edge][pane] && lastProperties.pixelsOutsideViewport > 0)
    {
        this._ptUtils.setScrollPos(edge, pane, this._ptUtils._maxScrollPos[edge][pane], true);
        this.HandleDomScroll(this.getComponent(), null);
        return true;
    }
    if (firstProperties.pixel > 0 && !(lastProperties.pixelsOutsideViewport == 0 && (endValue + 1) == edgeExtent && firstProperties.slice != lastProperties.slice))
    {
        var scrollOffset = firstProperties.pixelsInsideViewport;
        
        var scrollPos = this._ptUtils.getScrollPos(edge, pane);
        if (firstProperties.pixel == this._adjustScroll)
            this._ptUtils.setScrollPos(edge, pane, scrollPos - this._adjustScroll, true);
        else
        {
            if ((this._pagingControlInput == AdfPivotTableUtils.PAGE_PREV[edge][pane] || (firstProperties.slice == lastProperties.slice && (endValue + 1) == edgeExtent)) &&
                (firstProperties.pixelsInsideViewport >= (((edge == AdfPivotTableUtils.ROW_EDGE) ? databody.clientHeight : databody.clientWidth) - this._adjustScroll)))
                scrollOffset = firstProperties.pixel * -1;
            this._ptUtils.setScrollPos(edge, pane, scrollPos + scrollOffset, true);
        }
        this._adjustScroll = 0;
        this.HandleDomScroll(this.getComponent(), null);
        return true;
    }
    return false;
}

AdfDhtmlPivotTablePeer.prototype.getPagingControlInnerContentNode = function(pagingControl)
{
    return pagingControl.childNodes[0].childNodes[0].childNodes[0];
}

AdfDhtmlPivotTablePeer.prototype.setPagingInputStyles = function(firstArrow, prevArrow, lastArrow, nextArrow, startValue, endValue, edgeStart, edgeExtent)
{
    if (AdfDhtmlCommonUtils.containsCSSClassName(firstArrow, "p_AFActivated"))
        AdfDhtmlCommonUtils.removeCSSClassName(firstArrow, "p_AFActivated");
    else if (AdfDhtmlCommonUtils.containsCSSClassName(prevArrow, "p_AFActivated"))
        AdfDhtmlCommonUtils.removeCSSClassName(prevArrow, "p_AFActivated");
    else if (AdfDhtmlCommonUtils.containsCSSClassName(lastArrow, "p_AFActivated"))
        AdfDhtmlCommonUtils.removeCSSClassName(lastArrow, "p_AFActivated");
    else if (AdfDhtmlCommonUtils.containsCSSClassName(nextArrow, "p_AFActivated"))
        AdfDhtmlCommonUtils.removeCSSClassName(nextArrow, "p_AFActivated");
    
    var agent = AdfAgent.AGENT;
    
    if (startValue == edgeStart)
    {
        if (!AdfDhtmlCommonUtils.containsCSSClassName(firstArrow, "p_AFDisabled"))
        {
            AdfDhtmlCommonUtils.addCSSClassName(firstArrow, "p_AFDisabled");
            firstArrow.removeAttribute('href');
            AdfDhtmlCommonUtils.addCSSClassName(prevArrow, "p_AFDisabled");
            prevArrow.removeAttribute('href');
        }
    }
    else
    {
        if (AdfDhtmlCommonUtils.containsCSSClassName(firstArrow, "p_AFDisabled"))
        {
            AdfDhtmlCommonUtils.removeCSSClassName(firstArrow, "p_AFDisabled");
            agent.setAttribute(firstArrow, 'href', '#');
            AdfDhtmlCommonUtils.removeCSSClassName(prevArrow, "p_AFDisabled");
            agent.setAttribute(prevArrow, 'href', '#');
        }
    }
    if (endValue == edgeExtent)
    {
        if (!AdfDhtmlCommonUtils.containsCSSClassName(lastArrow, "p_AFDisabled"))
        {
            AdfDhtmlCommonUtils.addCSSClassName(lastArrow, "p_AFDisabled");
            lastArrow.removeAttribute('href');
            AdfDhtmlCommonUtils.addCSSClassName(nextArrow, "p_AFDisabled");
            nextArrow.removeAttribute('href');
        }
    }
    else
    {
        if (AdfDhtmlCommonUtils.containsCSSClassName(lastArrow, "p_AFDisabled"))
        {
            AdfDhtmlCommonUtils.removeCSSClassName(lastArrow, "p_AFDisabled");
            agent.setAttribute(lastArrow, 'href', '#');
            AdfDhtmlCommonUtils.removeCSSClassName(nextArrow, "p_AFDisabled");
            agent.setAttribute(nextArrow, 'href', '#');
        }
    }
}

//////////////////////////////////////////////////////////////////////////////////
//        END PAGING METHODS TO EVENTUALLY MOVE TO A "PAGING" JS PARTITION
//////////////////////////////////////////////////////////////////////////////////


/**
 * clear the framework error message and revalidate the value of the editable control
 * this should be called when there is a custom key assigned by pt for a specific action, 
 * but framework doesn't recognize it
 * @param componentEvent the event  
 * @return {boolean} true if component is valid, false otherwise
 */
AdfDhtmlPivotTablePeer.prototype._clearMessageAndRevalidate = function (componentEvent)
{
  //var component = this.getComponent();
  // work around to ensure error messages are cleared and value is revalidated after mod
  var source = componentEvent.getSource();
  if ( source instanceof AdfUIEditableValue ) 
  {
    // clear messsages for stamp component, otherwise same validation message will continue to stack up
    AdfPage.PAGE.clearMessages(source.getClientId());
    // removed the following line since there will never be a validator associate directly with the pivot table
//    AdfPage.PAGE.clearMessages(component.getClientId());
    source.validate();
    return source.getValid();
  }
  return true;
}

/**
 * The "currentRequest" ivar is used to manage requests sent to the server, e.g. to make sure we don't send a stale 
 * request or keep a stale response.  It is non-null iff there is currently an outstanding request that we still want,
 * in which case it contains that request.
 * 
 * Here, "outstanding" means it has made it past fetchBlocks() (so that it's at least on-deck to be sent to the server), 
 * but the response hasn't yet been received on the client.  As for "still want", a counterexample is if the user scrolled 
 * back to where they started so the fetch is no longer needed.
 * 
 * When this variable has been set to a non-null value, that value stays until either:
 * - a newer request replaces it (e.g. the user kept scrolling, so we want the new request, not the previous one, or the user did a 
 *   pivot/drill/etc., so we want that request instead of the previous one), 
 * - or until it is nulled out due to cancellation (e.g. user scrolled back to where they started so the fetch is no longer needed), 
 * - or until it is nulled out because the response was received.
 * 
 * Cases:
 * - If the current request is a long scroll, short scroll, or fillViewport, then the variable indicates which blocks are in the request.  
 * - If it is a pivot/drill/sort/split (the operations handled by the PARTIAL case in DomReplaceNotify), then it is set to the constant PARTIAL.
 * - If it is a cell editing request, then it is also set to the constant PARTIAL.  (TBD: Perhaps this case merits its own constant?)
 * - If it is an initial fetch, for either immediate or lazy rendering, then this variable is set to the constant INITIAL in InitDomElement.
 * - If there is no current request because no fetch that we still want is pending (no scroll, pivot, etc.), then this variable is null.
 * 
 * This method should always be used rather than setting the variable directly, since it processes the ADS buffer when request is null, and to facilitate logging.
 * 
 * @param adsCounter used only when ADS is on and request is null.  See applyNewBufferedADSEvents() for values.
 */
AdfDhtmlPivotTablePeer.prototype.setCurrentRequest = function(request, adsCounter) {
    //AdfLogger.LOGGER.warning("setCurrentRequest(" + request + ")");
    this._currentRequest = request;
    
    // setCurrentRequest(null) means there's no longer a fetch outstanding, so it's time to apply any ADS events that were buffered in the meantime.  
    if (this._adsEnabled && request==null) // references to methods/etc. in ADS JS partition must be wrapped in "if ADS on" since the partition might not be present when ADS is off
        this.applyNewBufferedADSEvents(adsCounter);
}

/**
 * The "serverRequest" ivar is purely an optimization.  It stores the most recent scroll/fillViewport request actually 
 * sent to the server (as opposed to still being in our "local queue" waiting for _processFetchTimeout to fire) but not yet 
 * receieved back from the server.  Just before sending a scroll/fillViewport request to the server, we check this ivar to 
 * see if the same request was already sent, in which case we avoid sending it again.
 * 
 * serverRequest is either null or a DataFetchEvent, never the INITIAL or PARTIAL constants like currentRequest.
 * serverRequest is null iff there is no relevant request that has been sent to the server but not yet received, where 
 * "relevant" is defined below.
 * 
 * TBD: Rather than just keeping the most recent request, this variable could track *all* relevant requests sent but not yet 
 * received.  The aforementioned "don't send dupe request" logic would check all requests in the list.  This would resolve 
 * the limitation that this optimization only works for the most recently sent request.  
 * 
 * If we decide we no longer want a request that has already been sent and that is still relevant (e.g. user scrolled 
 * back to where they started so the fetch is no longer needed), we clear the other variable (currentRequest), not 
 * this one.  When still relevant, not clearing it is harmless, maintains the "null iff" invariant, and ensures that if that request is once again 
 * needed before it arrives (e.g. the user once again drags the scrollbar back to "need fetch" territory), we don't bother resending it.
 * 
 * It's always conservative and safe to clear this ivar, since that just means we won't attempt to optimize by not sending a request 
 * that's already on its way.
 * 
 * To facilitate logging, this method should always be used rather than setting the variable directly.
 * 
 * Relevant requests:
 * 
 * A request is still relevant iff its blocks are still usable.  (We can cancel a request for the block starting at R42C17 
 * only if a *relevant* block with those coords is already on its way.)  This means the DataAccess has not changed since 
 * the request was sent, so we clear this variable when queueing a pivot/drill/sort request.  Since the request stored in 
 * this variable currently represents blocks only by their startRow/Col, we clear it when queueing an (un)split request as 
 * well, since the block might span the split position but should not now that we've split, or it might not span the split 
 * position but should now that we've unsplit.
 * 
 * Assuming that the response to any existing server request arrives before the response to the new pivot/drill/sort/split 
 * request, it's not strictly necessary to clear this variable when queueing the new request, since no scroll/fillVP requests 
 * will be sent until the new response is received, by which time the arriving response to the existing request will have already 
 * cleared the ivar.  (And if the response to the existing server request arrived *after* the response to the new pivot/drill/sort/split 
 * request, which is hopefully impossible in our framework, then we'd have bigger problems, since if it happened to match currentRequest 
 * then we'd use the irrelevant block.)  But clearing it in these cases is a good idea, and it's always conservative and safe to clear it.
 */
AdfDhtmlPivotTablePeer.prototype.setServerRequest = function(request) {
    //AdfLogger.LOGGER.warning("setServerRequest(" + request + ")");
    this._serverRequest = request;
}

// enable user select
AdfDhtmlPivotTablePeer.prototype.enableUserSelect = function() 
{
    AdfAgent.AGENT.enableUserSelect(this.getDomElement());    
    // see disableUserSelect for usage
    this._enableUserSelect = true;
}

// disable user select
AdfDhtmlPivotTablePeer.prototype.disableUserSelect = function() 
{
    // IE agent attach a select listener to an element that do nothing in order to disable user select
    // calling this multiple times would cause multiple listeners to be attach to the same element
    // this prevents the listener to attach to the element multiple times
    if (this._enableUserSelect == undefined || this._enableUserSelect)
    {
        AdfAgent.AGENT.disableUserSelect(this.getDomElement());    
    }
    this._enableUserSelect = false;
}

/**
 * For testing only.  
 */
AdfDhtmlPivotTablePeer.prototype.initTestLog = function() {
    this.__log = [];
    this.__logIndex = 0; // always indicates where the next one will go, so the entry before this is the last new entry
    this.__logLength = 200; // wraps around after this point, overwriting the oldest entry
    this.__baseTime = new Date().getTime();
}

/**
 * For testing only.  
 */
AdfDhtmlPivotTablePeer.prototype.putInTestLog = function(entry) {
    this.__log[this.__logIndex] = entry;
    if (++this.__logIndex == this.__logLength) 
        this.__logIndex = 0;
}

AdfDhtmlPivotTablePeer.prototype._getBlockInfo = function(buffer)
{
    var result = {};
    for (var i=0; i<buffer.length; i++)
    {
        for (var j=0; j<buffer[i].length; j++)
        {
            var block = buffer[i][j];
            if (block.getAttribute)
            {
                var startRow = parseInt(block.getAttribute("_startrow"), 10);
                var startCol = parseInt(block.getAttribute("_startcol"), 10);
                
                if (result.startRow == null || result.startRow > startRow)
                    result.startRow = startRow;
    
                if (result.startCol == null || result.startCol > startCol)
                    result.startCol = startCol;
                    
                if (block.firstChild && block.firstChild.tagName == "TABLE")
                {
                    var numOfRows = block.firstChild.rows.length - 1;  // first row is dummy row
                    var numOfCols = block.firstChild.rows[0].cells.length - 1; // first cell is dummy cell
                    var endRow = startRow + numOfRows - 1;
                    var endCol = startCol + numOfCols - 1;
    
                    if (result.endRow == null || result.endRow < endRow)
                        result.endRow = endRow;        
    
                    if (result.endCol == null || result.endCol < endCol)
                        result.endCol = endCol;        
                }
            }
        }
    }
    
    return result;
}

/**
 * Notify server that the current range has changed.  This has to be initiated from the client
 * since the peer is the one who might trim data blocks in DomReplaceNotify.
 * @private
 */
AdfDhtmlPivotTablePeer.prototype._getRangeChangeEvent = function(oldBuffer, newBuffer)
{
    var oldDatabodyBuffer = oldBuffer[1][1];
    var newDatabodyBuffer = newBuffer[1][1];
    
    var oldBlockInfo = this._getBlockInfo(oldDatabodyBuffer);
    var newBlockInfo = this._getBlockInfo(newDatabodyBuffer);

    // checks if nothing changed (initial fetch)
    if (oldBlockInfo.startRow == newBlockInfo.startRow &&
        oldBlockInfo.endRow == newBlockInfo.endRow &&
        oldBlockInfo.startCol == newBlockInfo.startCol &&
        oldBlockInfo.endCol == newBlockInfo.endCol)
        return null;

    var component = this.getComponent();
    var event = new AdfPivotTableRangeChangeEvent(component, oldBlockInfo.startRow, oldBlockInfo.startCol, oldBlockInfo.endRow, oldBlockInfo.endCol, 
                                                             newBlockInfo.startRow, newBlockInfo.startCol, newBlockInfo.endRow, newBlockInfo.endCol);

    if (oldDatabodyBuffer.length != newDatabodyBuffer.length)
        event.setBlockRemoved(true);

    return event;
}
