<commit_before>(function ($)
{
  // register namespace
  $.extend(true, window, {
    "cadc": {
      "vot": {
        "Viewer": Viewer,
        "CHECKBOX_SELECTOR_COLUMN_ID": "_checkbox_selector",
        "ROW_SELECT_DISABLED_KEY": "_ROW_SELECT_DISABLED_",
        "datatype": {
          "NUMERIC": "NUMERIC",
          "STRING": "STRING",
          "DATETIME": "DATETIME"
        },
        "DEFAULT_CELL_PADDING_PX": 8,
        "events": {
          "onSort": new jQuery.Event("cadcVOTV:onSort"),
          "onColumnOrderReset": new jQuery.Event("cadcVOTV:onColumnOrderReset"),
          "onRowsChanged": new jQuery.Event("cadcVOTV:onRowsChanged"),
          "onDataLoaded": new jQuery.Event("cadcVOTV:onDataLoaded"),
          "onUnitChanged": new jQuery.Event("cadcVOTV:onUnitChanged")
        }
      }
    }
  });

  /**
   * Create a VOView object.  This is here to package everything together.
   *
   * @param targetNodeSelector  The target node selector to place the this.
   * @param options             The options object.
   * editable: true/false,
   * enableAddRow: true/false,
   * showHeaderRow: true/false,
   * enableCellNavigation: true/false,
   * asyncEditorLoading: true/false,
   * forceFitColumns: true/false,
   * explicitInitialization: true/false,
   * topPanelHeight: Number,
   * headerRowHeight: Number,
   * showTopPanel: true,
   * sortColumn: Start Date
   * sortDir: asc/desc
   * @constructor
   */
  function Viewer(targetNodeSelector, options)
  {
    var _self = this;
    var $_lengthFinder =
      $("#lengthFinder")
      || $("<div id='lengthFinder'></div>").appendTo($(document.body));
    this.grid = null;
    this.columnManager = options.columnManager ? options.columnManager : {};
    this.rowManager = options.rowManager ? options.rowManager : {};

    this.$emptyResultsMessage =
      $(options.emptyResultsMessageSelector)
      ||
      $("<div class=\"cadcvotv-empty-results-message\">No results returned.</div>")
        .appendTo($(".grid-container"));

    this.columns = [];
    // displayColumns: columns that are actually in the Grid.
    this.displayColumns = options.displayColumns ? options.displayColumns : [];
    this.resizedColumns = {};  // Columns the user has resized.
    this.columnFilters = options.columnFilters ? options.columnFilters : {};
    this.columnFilterPluginName = options.columnFilterPluginName || "default";
    this.updatedColumnSelects = {};
    this.targetNodeSelector = targetNodeSelector;
    this.columnOptions = options.columnOptions ? options.columnOptions : {};
    this.options = options;
    this.options.forceFitColumns = options.columnManager
      ? options.columnManager.forceFitColumns
      : false;
    this.options.asyncPostRenderDelay = options.asyncPostRenderDelay || 0;

    // This is the TableData for a VOTable.  Will be set on load.
    this.longestValues = {};

    this.sortcol = options.sortColumn;
    this.sortAsc = options.sortDir == "asc";

    // story 1584 - variable viewport height
    this.variableViewportHeight = options.variableViewportHeight
      ? options.variableViewportHeight
      : false;
    this.viewportOffset = 0;

    this.rowCountMessage = options.rowCountMessage ? options.rowCountMessage :
                           defaultRowCountMessage;

    this.atDataLoadComplete = options.atDataLoadComplete
        ? options.atDataLoadComplete : defaultDataLoadComplete;
    this.atPageInfoChanged = options.atPageInfoChanged
        ? options.atPageInfoChanged : defaultPageChanging;

    /**
     * @param input  Object representing the input.
     *
     * One of xmlDOM or json or url is required.
     *
     * input.xmlDOM = The XML DOM Object
     * input.json = The JSON Object
     * input.csv = The CSV text
     * input.url = The URL of the input.  The Content-Type will dictate how to
     *             build it.  This is the only way to stream CSV.
     * @param completeCallback  Callback function when complete.
     * @param errorCallBack     Callback function with jqXHR, status, message
     *                    (Conforms to jQuery error callback for $.ajax calls).
     */
    function build(input, completeCallback, errorCallBack)
    {
      // Keep the empty results stuff hidden.
      $(getTargetNodeSelector()).removeClass("cadcvotv-empty-results-overlay");
      _self.$emptyResultsMessage.hide();

      new cadc.vot.Builder(getOptions().maxRowLimit,
                           input,
                           function (voTableBuilder)
                           {
                             voTableBuilder.subscribe(cadc.vot.onDataLoadComplete,
                                                      function (event, args)
                                                      {
                                                        if (args)
                                                        {
                                                          setLongestValues(args.longestValues);
                                                        }

                                                        if (input.xmlDOM)
                                                        {
                                                          var voTable =
                                                            args.builder.getVOTable();

                                                          if (!hasDisplayColumns)
                                                          {
                                                            refreshColumns(
                                                              voTable.getMetadata().getFields());
                                                          }

                                                          // Setup the Grid and DataView to be loaded.
                                                          _self.init();

                                                          load(args.builder.getVOTable(),
                                                               false, true);
                                                        }

                                                        resetColumnWidths();

                                                        // Display spinner only
                                                        // if paging is off
                                                        if (!usePager())
                                                        {
                                                          _self.atDataLoadComplete(getTotalRows(), getCurrentRows(), getHeaderLabel());
                                                        }

                                                        var $gridHeaderIcon =
                                                          getHeader().find("img.grid-header-icon");

                                                        // clear the wait icon
                                                        $gridHeaderIcon.attr("src", "/cadcVOTV/images/transparent-20.png");

                                                        if (getRows().length === 0)
                                                        {
                                                          $(getTargetNodeSelector()).addClass("cadcvotv-empty-results-overlay");
                                                          _self.$emptyResultsMessage.show();
                                                        }

                                                        sort();

                                                        trigger(cadc.vot.events.onDataLoaded,
                                                                args);
                                                      });

                             var hasDisplayColumns =
                               (_self.displayColumns
                                && (_self.displayColumns.length > 0));

                             // Set up to stream.
                             if (input.url || input.csv)
                             {
                               var inputFields =
                                 input.tableMetadata.getFields();
                               var $resultsGridHeader = getHeader();
                               var $gridHeaderIcon =
                                 getHeader().find("img.grid-header-icon");

                               // Display spinner only if paging is off
                               if (!usePager())
                               {
                                 var $gridHeaderStyle =
                                   $resultsGridHeader.prop("style");

                                 // remove any background color resulting from
                                 // previous warning message
                                 if ($gridHeaderStyle)
                                 {
                                   $gridHeaderStyle.backgroundColor = "";
                                 }

                                 // add a spinner to the header bar to indicate
                                 // streaming has begun
                                 if ($gridHeaderIcon)
                                 {
                                   $gridHeaderIcon.attr("src", "/cadcVOTV/images/PleaseWait-small.gif");
                                 }
                               }

                               /*
                                * We need to refresh columns twice; once to
                                * display something while the data is streaming,
                                * and again to update the column widths based
                                * on data.
                                *
                                * jenkinsd 2013.12.20
                                */
                               if (!hasDisplayColumns)
                               {
                                 refreshColumns(inputFields);
                               }

                               // Setup the Grid and DataView to be loaded.
                               _self.init();

                               voTableBuilder.subscribe(cadc.vot.onPageAddStart,
                                                        function ()
                                                        {
                                                          getDataView().beginUpdate();

                                                          // Notify that data is loading.
                                                          _self.atPageInfoChanged(
                                                            getTotalRows(),
                                                            getCurrentRows(),
                                                            getHeaderLabel());
                                                        });

                               voTableBuilder.subscribe(cadc.vot.onPageAddEnd,
                                                        function ()
                                                        {
                                                          getDataView().endUpdate();

                                                          // Sorting as data
                                                          // loads.  Not sure
                                                          // if this is a good
                                                          // idea or not.
                                                          // jenkinsd
                                                          // 2014.05.09 WebRT
                                                          // 53730
                                                          //sort();
                                                        });

                               voTableBuilder.subscribe(cadc.vot.onRowAdd,
                                                        function (event, row)
                                                        {
                                                          addRow(row, null);
                                                        });
                             }

                             voTableBuilder.build(
                               voTableBuilder.buildRowData);

                             if (completeCallback)
                             {
                               completeCallback(voTableBuilder);
                             }
                           }, errorCallBack);
    }

    function defaultRowCountMessage(totalRows, rowCount)
    {
      return "Showing " + totalRows + " rows (" + rowCount
             + " before filtering).";
    }

    function getRowCountMessage(totalRows, rowCount)
    {
      return _self.rowCountMessage(totalRows, rowCount);
    }

    function getTargetNodeSelector()
    {
      return _self.targetNodeSelector;
    }

    function getPagerNodeSelector()
    {
      return "#pager";
    }

    function getHeaderNodeSelector()
    {
      return "div.grid-header";
    }

    function getHeader()
    {
      return $(getTargetNodeSelector()).prev();
    }

    function getHeaderLabel()
    {
      return getHeader().find(".grid-header-label");
    }

    function getColumnManager()
    {
      return _self.columnManager;
    }

    function getRowManager()
    {
      return _self.rowManager;
    }

    function getColumns()
    {
      return _self.columns;
    }

    function getColumnOptions()
    {
      return _self.columnOptions;
    }

    function getOptionsForColumn(columnLabel)
    {
      return getColumnOptions()[columnLabel]
        ? getColumnOptions()[columnLabel] : {};
    }

    function getResizedColumns()
    {
      return _self.resizedColumns;
    }

    function getUpdatedColumnSelects()
    {
      return _self.updatedColumnSelects;
    }

    function isFilterable(column)
    {
      var globallyFilterable = getColumnManager().filterable || false;
      var columnFilterable = column.filterable || globallyFilterable;

      return (columnFilterable === true);
    }

    /**
     * Obtain whether the global fitMax or per column fitMax option has been
     * set.
     *
     * @param columnID    The column ID to check.
     */
    function isFitMax(columnID)
    {
      var columnOptions = getOptionsForColumn(columnID);
      var fitMaxEnabled = (getOptions().fitMax === true);

      if (columnOptions)
      {
        if (columnOptions.fitMax === true)
        {
          fitMaxEnabled = true;
        }
        else if (columnOptions.fitMax === false)
        {
          fitMaxEnabled = false;
        }
      }

      return fitMaxEnabled;
    }

    function getColumnFilters()
    {
      return _self.columnFilters;
    }

    function setColumnFilter(columnID, filterValue)
    {
      $(getTargetNodeSelector()).find("input[id='" + columnID
                                      + "_filter']").val(filterValue);
    }

    function columnFiltersEmpty()
    {
      for (var cf in _self.columnFilters)
      {
        var nextFilter = _self.columnFilters[cf];

        if (nextFilter && $.trim(nextFilter))
        {
          return false;
        }
      }

      return true;
    }

    function getColumnFilterPluginName()
    {
      return _self.columnFilterPluginName;
    }

    function clearColumnFilters()
    {
      _self.columnFilters = {};
    }

    /**
     * Obtain a column from the Grid by its unique ID.
     * @param columnID    The Column ID.
     * @returns {Object} column definition.
     */
    function getGridColumn(columnID)
    {
      var existingColumnIndex = getGrid().getColumnIndex(columnID);

      var col;

      if (!isNaN(existingColumnIndex))
      {
        col = getGrid().getColumns()[existingColumnIndex];
      }
      else
      {
        col = null;
      }

      return col;
    }

    /**
     * Obtain the index of the given column ID.  Return the index, or -1 if it
     * does not exist.
     *
     * @param columnID
     * @returns {number}
     */
    function getColumnIndex(columnID)
    {
      var allCols = getColumns();

      for (var i = 0; i < allCols.length; i++)
      {
        var nextCol = allCols[i];

        if (nextCol.id == columnID)
        {
          return i;
        }
      }

      return -1;
    }

    /**
     * Obtain a column from the CADC VOTV column cache by its unique ID.
     * @param columnID    The Column ID.
     * @returns {Object} column definition.
     */
    function getColumn(columnID)
    {
      var columnIndex = getColumnIndex(columnID);

      var col;

      if (columnIndex || (columnIndex === Number(0)))
      {
        col = getColumns()[columnIndex];
      }
      else
      {
        col = null;
      }

      return col;
    }

    function addColumn(columnObject)
    {
      _self.columns.push(columnObject);
    }

    function setColumns(cols)
    {
      _self.columns = cols.slice(0);
    }

    function clearColumns()
    {
      _self.columns.length = 0;
    }

    /**
     * Add a VOTable Row.
     *
     * @param row       The cadc.vot.Row object.
     * @param rowIndex  The optional row index.
     */
    function addRow(row, rowIndex)
    {
      var cellArray = row.getCells();
      var dataRow = {};

      dataRow["id"] = row.getID();
      for (var ci = 0, cl = cellArray.length; ci < cl; ci++)
      {
        var cell = cellArray[ci];
        var cellFieldID = cell.getField().getID();
        dataRow[cellFieldID] = cell.getValue();
      }

      if (getRowManager().isRowDisabled)
      {
        dataRow[cadc.vot.ROW_SELECT_DISABLED_KEY] =
          getRowManager().isRowDisabled(row);
      }

      if (rowIndex)
      {
        getDataView().getItems()[rowIndex] = dataRow;
      }
      else
      {
        getDataView().getItems().push(dataRow);
      }
    }

    function clearRows()
    {
      var data = getDataView();
      data.beginUpdate();
      data.getItems().length = 0;
      data.endUpdate();
    }

    function getDataView()
    {
      return getGrid().getData();
    }

    function setGrid(gridObject)
    {
      _self.grid = gridObject;
    }

    function getSelectedRows()
    {
      return getGrid().getSelectedRows();
    }

    function getRow(_index)
    {
      return getDataView().getItem(_index);
    }

    function getRows()
    {
      return getDataView().getItems();
    }

    function getGrid()
    {
      return _self.grid;
    }

    function refreshGrid()
    {
      var g = getGrid();
      g.updateRowCount();
      g.invalidateAllRows();
      g.resizeCanvas();
    }

    function getGridHeaderHeight()
    {
      return ($(".grid-header").height() +
              $(".slick-header").height() +
              $(".slick-headerrow").height());
    }

    /**
     * Call if supporting a variable viewport height, and there's a static
     * header that not part of the grid container.
     */
    function setViewportOffset(offset)
    {
      _self.viewportOffset = (offset + getGridHeaderHeight());
    }

    function setViewportHeight()
    {
      if (_self.variableViewportHeight)
      {
        $(_self.targetNodeSelector).
          height($(window).height() - _self.viewportOffset);
      }
    }


    /**
     * Tell the Grid to sort.  This exists mainly to set an initial sort column
     * on the Grid.
     */
    function sort()
    {
      if (_self.sortcol)
      {
        var isAscending = (_self.sortAsc || (_self.sortAsc == 1));
        getGrid().setSortColumn(_self.sortcol, isAscending);

        trigger(cadc.vot.events.onSort, {
          sortCol: _self.sortcol,
          sortAsc: isAscending
        });
      }
    }

    /**
     * Set the sort column.  Here mainly for testing.
     *
     * @param _sortColumn   The column ID to use.
     */
    function setSortColumn(_sortColumn)
    {
      _self.sortcol = _sortColumn;
    }

    function getOptions()
    {
      return _self.options;
    }

    function setOptions(_optionsDef)
    {
      _self.options = _optionsDef;
    }

    function usePager()
    {
      return getOptions() && getOptions().pager;
    }

    function getLongestValues()
    {
      return _self.longestValues;
    }

    function getLongestValue(_columnID)
    {
      return getLongestValues()[_columnID];
    }

    function setLongestValues(_longestValues)
    {
      _self.longestValues = _longestValues;
    }

    /**
     * Get the columns that are to BE displayed.
     * @return {Array}    Array of Column objects.
     */
    function getDisplayColumns()
    {
      if (!_self.displayColumns || (_self.displayColumns.length == 0))
      {
        setDisplayColumns(getDefaultColumns().slice(0));
      }
      return _self.displayColumns;
    }

    /**
     * Get the columns that ARE CURRENTLY displayed.  Useful for saving for
     * future profile usage (i.e. restoring previous session).
     *
     * @return {Array}    Array of Column objects.
     */
    function getDisplayedColumns()
    {<commit_msg>WebRT 72454: Fix for empty results.<commit_after>(function ($)
{
  // register namespace
  $.extend(true, window, {
    "cadc": {
      "vot": {
        "Viewer": Viewer,
        "CHECKBOX_SELECTOR_COLUMN_ID": "_checkbox_selector",
        "ROW_SELECT_DISABLED_KEY": "_ROW_SELECT_DISABLED_",
        "datatype": {
          "NUMERIC": "NUMERIC",
          "STRING": "STRING",
          "DATETIME": "DATETIME"
        },
        "DEFAULT_CELL_PADDING_PX": 8,
        "events": {
          "onSort": new jQuery.Event("cadcVOTV:onSort"),
          "onColumnOrderReset": new jQuery.Event("cadcVOTV:onColumnOrderReset"),
          "onRowsChanged": new jQuery.Event("cadcVOTV:onRowsChanged"),
          "onDataLoaded": new jQuery.Event("cadcVOTV:onDataLoaded"),
          "onUnitChanged": new jQuery.Event("cadcVOTV:onUnitChanged")
        }
      }
    }
  });

  /**
   * Create a VOView object.  This is here to package everything together.
   *
   * @param targetNodeSelector  The target node selector to place the this.
   * @param options             The options object.
   * editable: true/false,
   * enableAddRow: true/false,
   * showHeaderRow: true/false,
   * enableCellNavigation: true/false,
   * asyncEditorLoading: true/false,
   * forceFitColumns: true/false,
   * explicitInitialization: true/false,
   * topPanelHeight: Number,
   * headerRowHeight: Number,
   * showTopPanel: true,
   * sortColumn: Start Date
   * sortDir: asc/desc
   * @constructor
   */
  function Viewer(targetNodeSelector, options)
  {
    var _self = this;
    var $_lengthFinder =
      $("#lengthFinder")
      || $("<div id='lengthFinder'></div>").appendTo($(document.body));
    this.grid = null;
    this.columnManager = options.columnManager ? options.columnManager : {};
    this.rowManager = options.rowManager ? options.rowManager : {};

    this.$emptyResultsMessage =
      $(options.emptyResultsMessageSelector)
      ||
      $("<div class=\"cadcvotv-empty-results-message\">No results returned.</div>")
        .appendTo($(".grid-container"));

    this.columns = [];
    // displayColumns: columns that are actually in the Grid.
    this.displayColumns = options.displayColumns ? options.displayColumns : [];
    this.resizedColumns = {};  // Columns the user has resized.
    this.columnFilters = options.columnFilters ? options.columnFilters : {};
    this.columnFilterPluginName = options.columnFilterPluginName || "default";
    this.updatedColumnSelects = {};
    this.targetNodeSelector = targetNodeSelector;
    this.columnOptions = options.columnOptions ? options.columnOptions : {};
    this.options = options;
    this.options.forceFitColumns = options.columnManager
      ? options.columnManager.forceFitColumns
      : false;
    this.options.asyncPostRenderDelay = options.asyncPostRenderDelay || 0;

    // This is the TableData for a VOTable.  Will be set on load.
    this.longestValues = {};

    this.sortcol = options.sortColumn;
    this.sortAsc = options.sortDir == "asc";

    // story 1584 - variable viewport height
    this.variableViewportHeight = options.variableViewportHeight
      ? options.variableViewportHeight
      : false;
    this.viewportOffset = 0;

    this.rowCountMessage = options.rowCountMessage ? options.rowCountMessage :
                           defaultRowCountMessage;

    this.atDataLoadComplete = options.atDataLoadComplete
        ? options.atDataLoadComplete : defaultDataLoadComplete;
    this.atPageInfoChanged = options.atPageInfoChanged
        ? options.atPageInfoChanged : defaultPageChanging;

    /**
     * @param input  Object representing the input.
     *
     * One of xmlDOM or json or url is required.
     *
     * input.xmlDOM = The XML DOM Object
     * input.json = The JSON Object
     * input.csv = The CSV text
     * input.url = The URL of the input.  The Content-Type will dictate how to
     *             build it.  This is the only way to stream CSV.
     * @param completeCallback  Callback function when complete.
     * @param errorCallBack     Callback function with jqXHR, status, message
     *                    (Conforms to jQuery error callback for $.ajax calls).
     */
    function build(input, completeCallback, errorCallBack)
    {
      // Keep the empty results stuff hidden.
      $(getTargetNodeSelector()).removeClass("cadcvotv-empty-results-overlay");
      _self.$emptyResultsMessage.hide();

      new cadc.vot.Builder(getOptions().maxRowLimit,
                           input,
                           function (voTableBuilder)
                           {
                             voTableBuilder.subscribe(cadc.vot.onDataLoadComplete,
                                                      function (event, args)
                                                      {
                                                        if (args)
                                                        {
                                                          setLongestValues(args.longestValues);
                                                        }

                                                        if (input.xmlDOM)
                                                        {
                                                          var voTable =
                                                            args.builder.getVOTable();

                                                          if (!hasDisplayColumns)
                                                          {
                                                            refreshColumns(
                                                              voTable.getMetadata().getFields());
                                                          }

                                                          // Setup the Grid and DataView to be loaded.
                                                          _self.init();

                                                          load(args.builder.getVOTable(),
                                                               false, true);
                                                        }

                                                        resetColumnWidths();

                                                        // Display spinner only
                                                        // if paging is off
                                                        if (!usePager())
                                                        {
                                                          _self.atDataLoadComplete(getTotalRows(), getCurrentRows(), getHeaderLabel());
                                                        }

                                                        var $gridHeaderIcon =
                                                          getHeader().find("img.grid-header-icon");

                                                        // clear the wait icon
                                                        $gridHeaderIcon.attr("src", "/cadcVOTV/images/transparent-20.png");

                                                        if (getRows().length === 0)
                                                        {
                                                          $(getTargetNodeSelector()).addClass("cadcvotv-empty-results-overlay");
                                                          _self.$emptyResultsMessage.show();
                                                        }

                                                        sort();

                                                        trigger(cadc.vot.events.onDataLoaded,
                                                                args);
                                                      });

                             var hasDisplayColumns =
                               (_self.displayColumns
                                && (_self.displayColumns.length > 0));

                             // Set up to stream.
                             if (input.url || input.csv)
                             {
                               var inputFields =
                                 input.tableMetadata.getFields();
                               var $resultsGridHeader = getHeader();
                               var $gridHeaderIcon =
                                 getHeader().find("img.grid-header-icon");

                               // Display spinner only if paging is off
                               if (!usePager())
                               {
                                 var $gridHeaderStyle =
                                   $resultsGridHeader.prop("style");

                                 // remove any background color resulting from
                                 // previous warning message
                                 if ($gridHeaderStyle)
                                 {
                                   $gridHeaderStyle.backgroundColor = "";
                                 }

                                 // add a spinner to the header bar to indicate
                                 // streaming has begun
                                 if ($gridHeaderIcon)
                                 {
                                   $gridHeaderIcon.attr("src", "/cadcVOTV/images/PleaseWait-small.gif");
                                 }
                               }

                               /*
                                * We need to refresh columns twice; once to
                                * display something while the data is streaming,
                                * and again to update the column widths based
                                * on data.
                                *
                                * jenkinsd 2013.12.20
                                */
                               if (!hasDisplayColumns)
                               {
                                 refreshColumns(inputFields);
                               }

                               // Setup the Grid and DataView to be loaded.
                               _self.init();

                               voTableBuilder.subscribe(cadc.vot.onPageAddStart,
                                                        function ()
                                                        {
                                                          getDataView().beginUpdate();

                                                          // Notify that data is loading.
                                                          _self.atPageInfoChanged(
                                                            getTotalRows(),
                                                            getCurrentRows(),
                                                            getHeaderLabel());
                                                        });

                               voTableBuilder.subscribe(cadc.vot.onPageAddEnd,
                                                        function ()
                                                        {
                                                          getDataView().endUpdate();

                                                          // Sorting as data
                                                          // loads.  Not sure
                                                          // if this is a good
                                                          // idea or not.
                                                          // jenkinsd
                                                          // 2014.05.09 WebRT
                                                          // 53730
                                                          //sort();
                                                        });

                               voTableBuilder.subscribe(cadc.vot.onRowAdd,
                                                        function (event, row)
                                                        {
                                                          addRow(row, null);
                                                        });
                             }

                             voTableBuilder.build(
                               voTableBuilder.buildRowData);

                             if (completeCallback)
                             {
                               completeCallback(voTableBuilder);
                             }
                           }, errorCallBack);
    }

    function defaultRowCountMessage(totalRows, rowCount)
    {
      return "Showing " + totalRows + " rows (" + rowCount
             + " before filtering).";
    }

    function getRowCountMessage(totalRows, rowCount)
    {
      return _self.rowCountMessage(totalRows, rowCount);
    }

    function getTargetNodeSelector()
    {
      return _self.targetNodeSelector;
    }

    function getPagerNodeSelector()
    {
      return "#pager";
    }

    function getHeaderNodeSelector()
    {
      return "div.grid-header";
    }

    function getHeader()
    {
      return $(getTargetNodeSelector()).prev();
    }

    function getHeaderLabel()
    {
      return getHeader().find(".grid-header-label");
    }

    function getColumnManager()
    {
      return _self.columnManager;
    }

    function getRowManager()
    {
      return _self.rowManager;
    }

    function getColumns()
    {
      return _self.columns;
    }

    function getColumnOptions()
    {
      return _self.columnOptions;
    }

    function getOptionsForColumn(columnLabel)
    {
      return getColumnOptions()[columnLabel]
        ? getColumnOptions()[columnLabel] : {};
    }

    function getResizedColumns()
    {
      return _self.resizedColumns;
    }

    function getUpdatedColumnSelects()
    {
      return _self.updatedColumnSelects;
    }

    function isFilterable(column)
    {
      var globallyFilterable = getColumnManager().filterable || false;
      var columnFilterable = column.filterable || globallyFilterable;

      return (columnFilterable === true);
    }

    /**
     * Obtain whether the global fitMax or per column fitMax option has been
     * set.
     *
     * @param columnID    The column ID to check.
     */
    function isFitMax(columnID)
    {
      var columnOptions = getOptionsForColumn(columnID);
      var fitMaxEnabled = (getOptions().fitMax === true);

      if (columnOptions)
      {
        if (columnOptions.fitMax === true)
        {
          fitMaxEnabled = true;
        }
        else if (columnOptions.fitMax === false)
        {
          fitMaxEnabled = false;
        }
      }

      return fitMaxEnabled;
    }

    function getColumnFilters()
    {
      return _self.columnFilters;
    }

    function setColumnFilter(columnID, filterValue)
    {
      $(getTargetNodeSelector()).find("input[id='" + columnID
                                      + "_filter']").val(filterValue);
    }

    function columnFiltersEmpty()
    {
      for (var cf in _self.columnFilters)
      {
        var nextFilter = _self.columnFilters[cf];

        if (nextFilter && $.trim(nextFilter))
        {
          return false;
        }
      }

      return true;
    }

    function getColumnFilterPluginName()
    {
      return _self.columnFilterPluginName;
    }

    function clearColumnFilters()
    {
      _self.columnFilters = {};
    }

    /**
     * Obtain a column from the Grid by its unique ID.
     * @param columnID    The Column ID.
     * @returns {Object} column definition.
     */
    function getGridColumn(columnID)
    {
      var existingColumnIndex = getGrid().getColumnIndex(columnID);

      var col;

      if (!isNaN(existingColumnIndex))
      {
        col = getGrid().getColumns()[existingColumnIndex];
      }
      else
      {
        col = null;
      }

      return col;
    }

    /**
     * Obtain the index of the given column ID.  Return the index, or -1 if it
     * does not exist.
     *
     * @param columnID
     * @returns {number}
     */
    function getColumnIndex(columnID)
    {
      var allCols = getColumns();

      for (var i = 0; i < allCols.length; i++)
      {
        var nextCol = allCols[i];

        if (nextCol.id == columnID)
        {
          return i;
        }
      }

      return -1;
    }

    /**
     * Obtain a column from the CADC VOTV column cache by its unique ID.
     * @param columnID    The Column ID.
     * @returns {Object} column definition.
     */
    function getColumn(columnID)
    {
      var columnIndex = getColumnIndex(columnID);

      var col;

      if (columnIndex || (columnIndex === Number(0)))
      {
        col = getColumns()[columnIndex];
      }
      else
      {
        col = null;
      }

      return col;
    }

    function addColumn(columnObject)
    {
      _self.columns.push(columnObject);
    }

    function setColumns(cols)
    {
      _self.columns = cols.slice(0);
    }

    function clearColumns()
    {
      _self.columns.length = 0;
    }

    /**
     * Add a VOTable Row.
     *
     * @param row       The cadc.vot.Row object.
     * @param rowIndex  The optional row index.
     */
    function addRow(row, rowIndex)
    {
      var cellArray = row.getCells();
      var dataRow = {};

      dataRow["id"] = row.getID();
      for (var ci = 0, cl = cellArray.length; ci < cl; ci++)
      {
        var cell = cellArray[ci];
        var cellFieldID = cell.getField().getID();
        dataRow[cellFieldID] = cell.getValue();
      }

      if (getRowManager().isRowDisabled)
      {
        dataRow[cadc.vot.ROW_SELECT_DISABLED_KEY] =
          getRowManager().isRowDisabled(row);
      }

      if (rowIndex)
      {
        getDataView().getItems()[rowIndex] = dataRow;
      }
      else
      {
        getDataView().getItems().push(dataRow);
      }
    }

    function clearRows()
    {
      var data = getDataView();
      data.beginUpdate();
      data.getItems().length = 0;
      data.endUpdate();
    }

    function getDataView()
    {
      return getGrid().getData();
    }

    function setGrid(gridObject)
    {
      _self.grid = gridObject;
    }

    function getSelectedRows()
    {
      return getGrid().getSelectedRows();
    }

    function getRow(_index)
    {
      return getDataView().getItem(_index);
    }

    function getRows()
    {
      return getDataView().getItems();
    }

    function getGrid()
    {
      return _self.grid;
    }

    function refreshGrid()
    {
      var g = getGrid();
      g.updateRowCount();
      g.invalidateAllRows();
      g.resizeCanvas();
    }

    function getGridHeaderHeight()
    {
      return ($(".grid-header").height() +
              $(".slick-header").height() +
              $(".slick-headerrow").height());
    }

    /**
     * Call if supporting a variable viewport height, and there's a static
     * header that not part of the grid container.
     */
    function setViewportOffset(offset)
    {
      _self.viewportOffset = (offset + getGridHeaderHeight());
    }

    function setViewportHeight()
    {
      if (_self.variableViewportHeight)
      {
        $(_self.targetNodeSelector).
          height($(window).height() - _self.viewportOffset);
      }
    }


    /**
     * Tell the Grid to sort.  This exists mainly to set an initial sort column
     * on the Grid.
     */
    function sort()
    {
      if (_self.sortcol)
      {
        var isAscending = (_self.sortAsc || (_self.sortAsc == 1));
        getGrid().setSortColumn(_self.sortcol, isAscending);

        trigger(cadc.vot.events.onSort, {
          sortCol: _self.sortcol,
          sortAsc: isAscending
        });
      }
    }

    /**
     * Set the sort column.  Here mainly for testing.
     *
     * @param _sortColumn   The column ID to use.
     */
    function setSortColumn(_sortColumn)
    {
      _self.sortcol = _sortColumn;
    }

    function getOptions()
    {
      return _self.options;
    }

    function setOptions(_optionsDef)
    {
      _self.options = _optionsDef;
    }

    function usePager()
    {
      return getOptions() && getOptions().pager;
    }

    function getLongestValues()
    {
      return _self.longestValues;
    }

    function getLongestValue(_columnID)
    {
      var vals = getLongestValues();

      if (vals && vals.hasOwnProperty(_columnID))
      {
        return vals[_columnID];
      }
      else
      {
        return -1;
      }
    }

    function setLongestValues(_longestValues)
    {
      _self.longestValues = _longestValues;
    }

    /**
     * Get the columns that are to BE displayed.
     * @return {Array}    Array of Column objects.
     */
    function getDisplayColumns()
    {
      if (!_self.displayColumns || (_self.displayColumns.length == 0))
      {
        setDisplayColumns(getDefaultColumns().slice(0));
      }
      return _self.displayColumns;
    }

    /**
     * Get the columns that ARE CURRENTLY displayed.  Useful for saving for
     * future profile usage (i.e. restoring previous session).
     *
     * @return {Array}    Array of Column objects.
     */
    function getDisplayedColumns()
    {
{'fraction_non_alphanumeric': 0.08666572130837587, 'fraction_numerical': 0.0021743240688220835, 'mean_word_length': 1.102300392507577, 'pattern_counts': {'":': 32, '<': 15, '<?xml version=': 0, '>': 13, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 6}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 320, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5829077', 'n_tokens_mistral': 9800, 'n_tokens_neox': 9472, 'n_words': 2076}