API Docs for: 0.0.8-0
Show:

File: javascript\src\RAMP\Modules\datagrid.js

/**
* Datagrid submodule.
*
* @module RAMP
* @submodule Datagrid
* @main Datagrid
*/

/**
* The Datagrid class represents the side bar table shown next to the map. The data grid displays all map objects in a text format and allows the user to see more 
* details (same as clicking the map object) and navigate to the object. This class create the UI panel, events, and event-handles for the data grid conatiner.
*
* @class Datagrid
* @static
* @uses dojo/_base/declare
* @uses dojo/_base/lang
* @uses dojo/query
* @uses dojo/_base/array
* @uses dojo/dom-class
* @uses dojo/dom-attr
* @uses dojo/dom-construct
* @uses dojo/topic
* @uses dojo/on
* @uses esri/layers/FeatureLayer
* @uses esri/tasks/query
* @uses Ramp
* @uses GraphicExtension
* @uses GlobalStorage
* @uses Gui
* @uses DatagridClickHandler
* @uses Util
* @uses Array
* @uses Dictionary
* @uses PopupManager
* @uses TmplHelper
*/

/*global define, window, tmpl */
/*jslint white: true */

define([
/* Dojo */
    "dojo/_base/declare", "dojo/_base/lang", "dojo/query", "dojo/_base/array", "dojo/dom-class",
    "dojo/dom-attr", "dojo/dom-construct", "dojo/topic", "dojo/on",

/* Text */
     "dojo/text!./templates/datagrid_template.json",
     "dojo/text!./templates/extended_datagrid_template.json",

// Esri
        "esri/layers/FeatureLayer", "esri/tasks/query",

// Ramp
        "ramp/ramp", "ramp/graphicExtension", "ramp/globalStorage", "ramp/datagridClickHandler", "ramp/map",
        "ramp/eventManager",

// Util
         "utils/util", "utils/array", "utils/dictionary", "utils/popupManager", "utils/tmplHelper"],

    function (
    // Dojo
        declare, lang, dojoQuery, dojoArray, domClass, domAttr,
        domConstruct, topic, dojoOn,

    //Text
        data_grid_template_json,
        extended_datagrid_template_json,

    // Esri
        FeatureLayer, EsriQuery,

    // Ramp
        Ramp, GraphicExtension, GlobalStorage, DatagridClickHandler, RampMap, EventManager,

    // Util
        utilMisc, utilArray, utilDict, popupManager, tmplHelper) {
        "use strict";

        var config,
            layerConfig,
            gridConfig,

        // The jquery table
            oTable,

        // The JQuery Object representing the grid
            jqgrid,

        // A Boolean used to keep track of whether or not the grid should apply an
        // extent filter when the datagrid gets selected
            extentFilterExpired = true,

            zoomToGraphic,

            lastExtent,

        // Stores functions used to sort the datagrid
            sortFcns = {},

        // The name of the current sort function
            currentSortFcn = "ascending",

        // Name of the attribute used to store the oid
        // in the details and zoomTo buttons
            featureOidField = "feature-oid",

        // Name of the attribute used to store the feature url
        // in the details and zoomTo buttons
            featureUrlField = "feature-url",

            ui = (function () {
                /**
                * Creates a data grid row that has the following features:
                * highlight for a give graphic
                * un-highlight
                * scroll to for a give graphic
                *
                * @method createRowPrototype
                * @private
                * @param cssClass {String} the style that highlights the row.
                * @return {Object} an object containing features of a data grid row
                */

                function createRowPrototype(cssClass) {
                    var index = -1,
                        node = null;

                    /**
                    * Returns the index of the given graphic object in the data grid
                    *
                    * @method getGraphicIndex
                    * @param {Object} graphic
                    * @private
                    */
                    function getGraphicIndex(graphic) {
                        var targetData = getDataObject(graphic),
                            data = jqgrid.dataTable().fnGetData();

                        return utilArray.binaryIndexOf(data, function (entry) {
                            return sortFcns[currentSortFcn](entry, targetData);
                        });
                    }

                    return {
                        graphic: null,

                        focusedButton: null,

                        /**
                        * Navigate to the page the row is in and scroll to it. Returns true
                        * if the row exists in the data grid, false otherwise.
                        *
                        * @method navigateToRow
                        * @return {Boolean} A value indicating is the navigation is sucessful
                        * @private
                        */
                        navigateToRow: function () {
                            if (index !== -1) {
                                // Figure out which page the entry is in and navigate to that page
                                var page = Math.floor(index / gridConfig.rowsPerPage);
                                jqgrid.dataTable().fnPageChange(page);
                                jqgridTableWrapper.scrollTo(node, 300, {
                                    axis: "y",
                                    offset: {
                                        left: 0,
                                        top: -node.height() * 1.5
                                    }
                                });

                                return true;
                            }
                            return false;
                        },

                        /**
                        * Finds a row node corresponding to the given graphic object.
                        *
                        * @method update
                        * @private
                        * @return {{node: jObject, page: number}} A row node that displays graphic information. If none found, returns an object with empty jNode.
                        */
                        update: function () {
                            if (this.graphic) {
                                index = getGraphicIndex(this.graphic);
                                if (index !== -1) {
                                    node = $(jqgrid.dataTable().fnGetNodes(index));
                                }
                            } else {
                                index = -1;
                            }
                        },
                        /**
                       * Finds a row node corresponding to the given graphic object.
                       *
                       * @method getNode
                       * @private
                       * @return {{node: jObject, page: number}} A row node that displays graphic information. If none found, returns an object with empty jNode.
                       */
                        getNode: function () {
                            this.update();
                            return node;
                        },
                        /**
                     * Highlights the the given graphic object using the specified cssClass.
                     *
                     * @method activate
                     * @private
                     */
                        activate: function () {
                            if (this.graphic) {
                                this.getNode().addClass(cssClass);

                                if (this.focusedButton) {
                                    node.find(this.focusedButton).focus();
                                    this.focusedButton = null;
                                }
                            }
                        },
                     /**
                     * Removes a specified cssClass from a given graphic object in the data grid
                     *
                     * @method deactivate
                     * @private
                     */
                        deactivate: function () {
                            
                            if (this.graphic) {
                                this.getNode().removeClass(cssClass);
                                this.graphic = null;
                            }
                        }
                    };
                }

                var highlightRow = createRowPrototype("selected-row"),
                    zoomlightRow = createRowPrototype("highlighted-row"),

                    sectionNode,

                    datagridStatusLine,
                    datagridGlobalToggles,
                    jqgridTableWrapper,

                    _isReady = false; // indicates if the UI has fully rendered
                /**
                     * Generates a data grid row data with a checkbox to be used in template
                     *
                     * @method generateToggleButtonDataForTemplate
                     * @private
                     * @return {String} the generated row data object.
                     */
                function generateToggleButtonDataForTemplate() {
                    
                    // TODO: if no more modification is needed, this can be omitted and merged in the higher level.
                    // create object for template
                    // TODO: JKW
                    // Note, the following object is for passing to the template, properties
                    // were guessed. Need to add more detail later on. Empty values were provided
                    // eg. there were value of "" assigned to the toggle button template, in the code
                    // provided. A temporary property name "attribute" is used. Not sure if this will be
                    // used by ECDMP, therefore, leave the empty value in the template
                    var toggleButtonData = { "buttonLabel": "Sort", "classAddition": "font-medium global-button", "someAttribute": "" };

                    return toggleButtonData;
                }
                /**
                     * Creates a Data table based on the grid configuration specified in the application config object. See http://www.datatables.net/usage/columns  for addition information on config parameters.
                     *
                     * @method createDatatable
                     * @private
                     */
                function createDatatable() {
                    

                    var jqg_layout = dojoArray.map(gridConfig.gridColumns, function (column, i) {
                        return {
                            "sTitle": column.title,
                            "sWidth": column.width,
                            "sType": column.sortType,
                            "sClass": column.alignment ? "" : "center",
                            "mRender": undefined,
                            "aTargets": [i]
                        };
                    });

                    oTable = jqgrid.dataTable({
                        "bSort": false, // Disable sorting since we're sorting the array ourselves
                        "sDom": '<"jqgrid_table_wrapper"t><"status-line "p>',
                        "aoColumns": jqg_layout,
                        "bFilter": false,
                        "bInfo": false,
                        "bLengthChange": false,
                        "bPaginate": true,
                        "iDisplayLength": gridConfig.rowsPerPage,
                        "sPaginationType": "ramp",
                        "bDestroy": true,
                        "oLanguage": config.gridstrings,
                        "bDeferRender": true,
                        "fnDrawCallback": function () {
                            topic.publish(EventManager.Datagrid.DRAW_COMPLETE);
                        }
                    }).on("page", function () {
                        topic.publish(EventManager.GUI.SUBPANEL_DOCK, { origin: "datagrid" });

                        console.log("subPanleDock");
                    });

                    jqgridTableWrapper = sectionNode.find(".jqgrid_table_wrapper");
                }
                /**
                     * Initialize tooltips for the data grid
                     *
                     * @method initTooltips
                     * @private
                     */
                function initTooltips() {
                    popupManager.registerPopup(sectionNode, "hoverIntent",
                        function () {
                            if (this.target.attr("title")) {
                                if (this.target.isOverflowed()) {
                                    this.target.tooltipster({ theme: '.tooltipster-dark' }).tooltipster("show");
                                } else {
                                    this.target.removeAttr("title");
                                }
                            }
                        },
                        {
                            handleSelector: ".point-name, .category-name",
                            useAria: false,
                            timeout: 500
                        }
                    );
                }
                /**
                     * Adds event-handling for buttons inside the data grid's row elements (i.e 'Details', 'Zoom To' buttons)
                     *
                     * @method setButtonEvents
                     * @private
                     */
                function setButtonEvents() {

                    sectionNode.on("click", "button.details", function () {
                        var buttonNode = $(this),
                            node = buttonNode.parents(".record-row").parent().parent();  //TODO: replace with better selector

                        highlightRow.focusedButton = "button.details";

                        if (highlightRow.graphic && highlightRow.getNode()[0] === node[0]) {
                            DatagridClickHandler.onDetailDeselect();
                        } else {
                            var graphic = getGraphicFromButton(buttonNode);

                            DatagridClickHandler.onDetailSelect(buttonNode, graphic);
                        }
                    });

                    // Event handling for "Zoom To" button
                    sectionNode.on("click", "button.zoomto", function (evt) {
                        var zoomNode = $(this);

                        zoomlightRow.focusedButton = "button.zoomto";

                        // Zoom To
                        if (zoomNode.text() === config.stringResources.txtGrid_zoomTo) {
                            handleGridEvent(evt, function () {
                                zoomToGraphic = getGraphicFromButton(zoomNode);

                                //store the current extent, then zoom to point.
                                lastExtent = RampMap.getMap().extent.clone();

                                DatagridClickHandler.onZoomTo(RampMap.getMap().extent.clone(), zoomToGraphic);

                                // Update "zoom back" text after the extent change, if we update it
                                // before the extent change, it won't work since the datagrid gets
                                // repopulated after an extent change
                                utilMisc.subscribeOnce(EventManager.Datagrid.EXTENT_FILTER_END, function () {
                                    // Find the first node with the same oid, featureUrl
                                    var newNode = $(String.format("button.zoomto[data-{0}='{1}'][data-{2}='{3}']:eq(0)",
                                                    featureOidField, GraphicExtension.getOid(zoomToGraphic),
                                                    featureUrlField, zoomToGraphic.getLayer().url));
                                    newNode.text(config.stringResources.txtGrid_zoomBack);
                                });
                            });
                        } else { // Zoom back
                            DatagridClickHandler.onZoomBack(zoomToGraphic);
                            zoomNode.text(config.stringResources.txtGrid_zoomTo);
                        }
                    });

                    sectionNode.on("click", "button.global-button", function () {
                        var buttonNode = $(this);

                        if (currentSortFcn === "ascending") {
                            buttonNode.addClass("state-expanded");
                            currentSortFcn = "descending";
                        } else {
                            buttonNode.removeClass("state-expanded");
                            currentSortFcn = "ascending";
                        }
                        applyExtentFilter();
                    });
                    //Adds an event trigger for the expansion of the data grid control
                    sectionNode.on("click", "button.expand", function () {
                        console.log("grid expanded!");
                        topic.publish("gui/grid/expand");
                    });
                    popupManager.registerPopup(sectionNode, "hover, focus",
                        function (d) {
                            this.target.removeClass("wb-invisible");
                            d.resolve();
                        },
                        {
                            handleSelector: "tr",

                            targetSelector: ".record-controls",

                            closeHandler: function (d) {
                                this.target.addClass("wb-invisible");
                                d.resolve();
                            },

                            activeClass: "background-light",
                            useAria: false
                        }
                    );
                }
                /**
                     * Apply's or removes the scrollbar from the data grid based on the height of its container.
                     *
                     * @method initScrollListeners
                     * @private
                     */
                function initScrollListeners() {
                   
                    jqgridTableWrapper.scroll(function () {
                        var currentScroll = jqgridTableWrapper.scrollTop();
                        if (currentScroll === 0) {
                            datagridGlobalToggles.removeClass("scroll");
                        } else {
                            datagridGlobalToggles.addClass("scroll");
                        }
                    });

                    jqgridTableWrapper.scroll(function () {
                        var currentScroll = jqgridTableWrapper.scrollTop() + jqgridTableWrapper.outerHeight() - datagridStatusLine.outerHeight();

                        if (currentScroll - datagridGlobalToggles.outerHeight() === jqgrid.height()) {
                            datagridStatusLine.removeClass("scroll");
                        } else {
                            datagridStatusLine.addClass("scroll");
                        }
                    });
                }
                /**
                    * Highlights the row according to the graphic stored in the event. Sets the hightlightRow variable to the graphic object inside the sent event
                    *
                    * @method highlightrowShow
                    * @private
                    * @param {Object} event A thrown event that contains a graphic object inside the grid
                    */
                function highlightrowShow(event) {
                    highlightrowHide();

                    highlightRow.graphic = event.graphic;

                    if (event.scroll) {
                        ui.activateRows();
                        //applyExtentFilter();
                    }
                }
                /**
                    * Un-highlights the row that is currently highlighted
                    *
                    * @method highlightrowHide
                    * @private
                    */
                function highlightrowHide() {
                    highlightRow.deactivate();

                    DatagridClickHandler.onZoomCancel();
                }
                /**
                    * Stores the graphic in the given event in the variable zoomlightRow
                    *
                    * @method zoomlightrowShow
                    * @private
                    * @param {Object} event A thrown event that contains a graphic object inside the grid
                    */
                function zoomlightrowShow(event) {
                     zoomlightRow.graphic = event.graphic;
                }
                /**
                    * De-activiates the row stored in zoomlightRow
                    *
                    * @method zoomlightrowHide
                    * @private
                    */
                function zoomlightrowHide() {
                    zoomlightRow.deactivate();
                }
                /**
                 * Registers event handlers for following events:
                 * datagrid/highlightrow-show (!!2 handlers for this event!!)
                 * datagrid/zoomlightrow-show
                 * datagrid/zoomlightrow-hide
                 * @method initUIListeners
                 * @private
                 */
                function initUIListeners() {
                    topic.subscribe(EventManager.Datagrid.HIGHLIGHTROW_SHOW, highlightrowShow);

                    topic.subscribe(EventManager.Datagrid.HIGHLIGHTROW_HIDE, highlightrowHide);

                    topic.subscribe(EventManager.Datagrid.ZOOMLIGHTROW_SHOW, zoomlightrowShow);

                    topic.subscribe(EventManager.Datagrid.ZOOMLIGHTROW_HIDE, zoomlightrowHide);
                }

                return {
                    /**
                     * The constructor method for the data grid. Adds the grid's panel to the UI, adds the data rows, and creates all event triggers
                     * 
                     * @method init
                     * @constructor
                     * 
                     */
                    init: utilMisc.once(
                        function () {
                            // using template to generate global checkboxes
                            var globalCheckBoxesData = generateToggleButtonDataForTemplate();
                            var templateData = { "buttons": globalCheckBoxesData, "tableId": "jqgrid", "tableCss": "display table-condensed table-simplify" };
                            var section;
                            var templateKey = "";
                            tmpl.cache = {};

                            templateKey = "datagrid_manager_Template"; // Ramp.getLayerConfig(layerUrl).mapTipSettings.anchorTemplate;
                            tmpl.templates = JSON.parse(tmplHelper.stringifyTemplate(data_grid_template_json));

                            // generate the content using rowData and given template
                            section = tmpl(templateKey, templateData);

                            sectionNode = $("#gridpane");

                            // fade out the loading animation
                            sectionNode.addClass('animated fadeOut');
                            window.setTimeout(
                                function () {
                                    jqgrid = sectionNode
                                        .empty().append(section)
                                        .find("table");

                                    createDatatable();

                                    sectionNode
                                            .removeClass("fadeOut")
							                .addClass('animated fadeIn');

                                    datagridGlobalToggles = sectionNode.find('#datagridGlobalToggles');
                                    datagridStatusLine = sectionNode.find('.status-line');

                                    initTooltips();
                                    setButtonEvents();
                                    initScrollListeners();
                                    initUIListeners();

                                    //add button role to sortable cols
                                    //dojoQuery(".sorting").attr("role", "button");

                                    //remove alert role from table body
                                    dojoQuery("tbody").removeAttr("role");

                                    _isReady = true;
                                    applyExtentFilter();

                                    window.setTimeout(function () { sectionNode.removeClass('animated fadeIn'); }, 400);
                                },
                                300
                            );
                        }
                    ),
                    /**
                     * Indicates that the Data grid is fully rendered
                     * @method isReady
                     * @returns {Boolean} A flag indicating the render status of the data grid
                     */
                    isReady: function () {
                        return _isReady;
                    },

                    /**
                    * Removes the animating CSS class to prevent flickering
                    *
                    * @method onHide
                    * @private
                    */
                    onHide: utilMisc.once(
                        function () {
                            sectionNode.removeClass('animated fadeIn');
                        }
                    ),

                    /**
                    * Adjusts the width of the data grid panel to accommodate the scrollbar.
                    *
                    * @method adjustPanelWidth
                    */
                    adjustPanelWidth: function () {
                        utilMisc.adjustWidthForSrollbar(jqgridTableWrapper, [datagridGlobalToggles, datagridStatusLine]);
                    },
                    /**
                    * Navigates the grid to a row. Based on the following precedent: 1. User selected point, 2. Highlighted row, 3. zoomed row, 4. first row in the grid.
                    *
                    * @method activateRows
                    */
                    activateRows: function () {
                        // If there was previously a selected point,
                        // navigate to the correct page and highlight it in the data grid
                        highlightRow.update();
                        zoomlightRow.update();

                        // Scroll to the highlighted row first, if it fails,
                        // scroll to the zoomed row
                        if (!highlightRow.navigateToRow()) {
                            zoomlightRow.navigateToRow();
                        }

                        // set the focus on the first button in the data grid
                        // is not a really good idea to just move focus around
                        //jqgrid.dataTable().find("button:first").focus();

                        zoomlightRow.activate();
                        highlightRow.activate();

                        this.capturePanel();
                    },
                    /**
                     * publishes the subPanel_Capture event to the GUI class
                     * @method capturePanel
                     */
                    capturePanel: function () {
                        if (highlightRow.graphic) {
                            topic.publish(EventManager.GUI.SUBPANEL_CAPTURE, {
                                target: highlightRow.getNode().find(".record-controls"),
                                consumeOrigin: "datagrid",
                                origin: "datagrid"
                            });
                        }
                    }
                };
            } ());

        /**
        * Returns the config Object for the given featureLayerUrl
        *
        * @method getGridConfig
        * @param {String} url
        * @return {Object} grid config
        */
        function getGridConfig(url) {
            return Ramp.getLayerConfig(url).datagrid;
        }

        /**
        * A handler that handlers the Enter key press and Click mouse event of the data grid.
        * It is actually a binder that binds the key / mouse event to a handler specified.
        * This is wired up to grid cells in the bootstrapper to achieve click/keypress functions
        *
        * @method handleGridEvent
        * @private
        * @param {Event} e the event object
        * @param {Function} fcn the callback function
        */
        function handleGridEvent(e, callback) {
            if ((e.type === "keypress" && e.keyCode === 13) || e.type === "click") {
                callback();
                //Ramp.setHTML(oid); // just update info hit
            }
        } 
        /**
         * Gets all layer data in the current map extent that are visible, and put the data into the data grid.
         * @method applyExtentFilter
         */
        function applyExtentFilter() {
            var visibleFeatures = {},
                visibleGridLayers = RampMap.getVisibleFeatureLayers(),
                q = new EsriQuery();

            q.geometry = RampMap.getMap().extent;
            q.outFields = ["*"];

            var deferredList = dojoArray.map(visibleGridLayers, function (gridLayer) {
                return gridLayer.queryFeatures(q).then(function (features) {
                    if (features.features.length > 0) {
                        var layer = features.features[0].getLayer(),
                            layerUrl = layer.url;

                        if (!layer.visible) {
                            visibleFeatures[layerUrl] = [];
                        } else {
                            visibleFeatures[layerUrl] = features.features;
                        }
                    }
                });
            });

            // Execute this only after all the deferred objects has resolved
            utilMisc.afterAll(deferredList, function () {
                fetchRecords(visibleFeatures);
            });
        }

        /**
        * Given a map feature, return a data object used to represent the feature in the data grid.
        *
        * @method getDataObject
        * @private
        * @param {Object} feature the feature needs to be represented in the data grid
        * return {Array} an array representing the data the given feature contains.
        */
        function getDataObject(feature) {
            //TODO call the template here???

            var url = feature.getLayer().url;
            //attribute = feature.attributes;

            //Remember, case sensitivity MATTERS in the attribute name.

            var innerArray,
                tmplData;

            //TODO change to have a new parameter that indicates what kind of data object we want:
            //     a summary object or full grid object.  For now, hardcode to true to always pick summary

            if (true) {
                var sumTemplate = getGridConfig(url).summaryRowTemplate;

                tmpl.cache = {};

                //TODO can we cache this parsed value?  we will use this template possibly twice for every feature.
                //     will save some processing if we dont stringify and parse it every darn time.
                tmpl.templates = JSON.parse(tmplHelper.stringifyTemplate(data_grid_template_json));

                //bundle feature into the template data object
                tmplData = tmplHelper.dataBuilder(feature, url);

                //result of template placed in an array
                innerArray = [tmpl(sumTemplate, tmplData)];
            } else {
                //make array containing values for each column in the full grid
                var row = [];

                tmpl.cache = {};

                tmpl.templates = JSON.parse(tmplHelper.stringifyTemplate(extended_datagrid_template_json));

                //bundle feature into the template data object
                tmplData = tmplHelper.dataBuilder(feature, url);

                // retrieve extendedGrid config object
                var extendedGrid = getGridConfig(url).extendedGridColumns;

                // add columnIdx property, and set initial value
                tmplData.columnIdx = 0;

                // process each column and add to row
                for (var i = 0; i < extendedGrid.length; i++) {
                    // set columnIdx for template
                    tmplData.columnIdx = i;
                    row.push(tmpl(extendedGrid[i].columnTemplate, tmplData));
                }

                innerArray = row;
            }

            //TODO may want to move the generation of this custom object to a separate area, as this data will be useful in
            //     both the summary and full grid state.  Will need some thinking

            // Includes fields that are useful which are not derived from the config.featureSources
            // this should not draw, as there will be no column defined for it
            innerArray.push({
                "featureUrl": url,
                "layerName": Ramp.getLayerConfig(url).displayName,
                "feature": feature
            });

            return innerArray;
        }

        /**
        * Populate the data grid with data in visibleFeatures
        *
        * @method fetchRecords
        * @param {Array} visibleFeatures a dictionary mapping
        * service url to an array of feature objects
        * @private
        */
        function fetchRecords(visibleFeatures) {
            jqgrid.dataTable().fnClearTable();
            jqgrid.dataTable().fnPageChange(0);

            if (Object.keys(visibleFeatures).isEmpty()) {
                return;
            }

            var data = [];

            //for each feature layer
            utilDict.forEachEntry(visibleFeatures, function (key, features) {
                //for each feature in a specific layer
                data = data.concat(dojoArray.map(features, function (feature) {
                    //return the appropriate data object for the feature (.map puts them in array form)

                    // "cache" the data object so we don't have to generate it again
                    if (!feature.rampDataObj) {
                        //TODO need an extended and summary data object to handle both grid states
                        feature.rampDataObj = getDataObject(feature);
                    }

                    return feature.rampDataObj;
                }));
            });

            //TODO keep sort for now just for sanity.  will overhaul later
            data.sort(sortFcns[currentSortFcn]);

            utilMisc.subscribeOnce(EventManager.Datagrid.DRAW_COMPLETE, function () {
                // Draw complete fires after fnAddData is complete and the datagrid UI
                // finishes updating
                ui.activateRows();

                ui.adjustPanelWidth();

                topic.publish(EventManager.Datagrid.EXTENT_FILTER_END);
            });

            //add the data to the grid
            try {
                jqgrid.dataTable().fnAddData(data);
            } catch (e) {
                console.error(e);
            }

            // NOTE: fnAddData should be the last thing that happens in this function
            // if you want to add something after this point, use the fnDrawCallback
            // function in the jqgrid initialization
        }

        /**
        * Returns the graphic object of a feature layer which is contained in the given buttonNode.
        *
        * @method getGraphicFromButton
        * @private
        * @param {JObject} buttonNode   the node containing the feature layer
        * @return {Object}   the graphic object of the feature layer.
        */
        function getGraphicFromButton(buttonNode) {
            var featureUrl = buttonNode.data(featureUrlField),
            // Need to parse the index into an integer since it
            // comes as a String
                oid = parseInt(buttonNode.data(featureOidField)),
                featureLayer = RampMap.getFeatureLayer(featureUrl),

                graphic = utilArray.binaryFind(featureLayer.graphics,
                    function (a_graphic) {
                        return GraphicExtension.getOid(a_graphic) - oid;
                    });

            return graphic;
        }

        /**
        * Binding event handling for events:
        * filterManager/layer-visibility-toggled
        * filterManager/global-layer-visibility-toggled
        * datagrid/applyExtentFilter
        *
        * @method initListeners
        * @private
        */

        function initListeners() {
            topic.subscribe(EventManager.FilterManager.LAYER_VISIBILITY_TOGGLED, function () {
                extentFilterExpired = true;
            });

            topic.subscribe(EventManager.FilterManager.GLOBAL_LAYER_VISIBILITY_TOGGLED, function () {
                extentFilterExpired = true;
            });

            /* UI EVENTS */
            topic.subscribe(EventManager.GUI.TAB_SELECTED, function (arg) {
                if ((arg.tabName === "datagrid") && (extentFilterExpired)) {
                    extentFilterExpired = false;
                    applyExtentFilter();
                } else if ((arg.tabName === "datagrid") && (!extentFilterExpired)) {
                    ui.capturePanel();
                } else {
                    ui.adjustPanelWidth();
                }
            });

            topic.subscribe(EventManager.GUI.TAB_DESELECTED, function (arg) {
                if (arg.tabName === "datagrid") {
                    ui.onHide();

                    topic.publish(EventManager.GUI.SUBPANEL_DOCK, {
                        origin: "datagrid"
                    });
                    console.log("subPanleDock");
                }
            });

            topic.subscribe(EventManager.Datagrid.APPLY_EXTENT_FILTER, function () {
                if (!ui.isReady()) {
                    ui.init();
                }

                applyExtentFilter();
            });
        }

        return {
            init: function () {
                /// <summary>
                /// initialize the datagrid. must be called before any properties can be accessed.
                /// </summary>
                config = GlobalStorage.config;
                layerConfig = config.featureLayers;
                gridConfig = layerConfig[0].datagrid;  //this is just to configure the structure of the grid.  since all layers have same structure, just pick first one

                initListeners();

                /**
                * Sort by feature name, then by oid, then by feature url (alphabetically ascending)
                *
                * @method featureSorter
                * @private
                * @param {Object} e1 custom data object
                * @param {Object} e2
                * @return {Integer} a positive integer if e1 is greater (in sort order) than e2, a negative integer
                * if e2 is greater than e1, 0 if the two are considered equal by sort order
                */
                function featureSorter(e1, e2) {
                    var result = e1[0].localeCompare(e2[0]);
                    if (result === 0) {
                        result = e1[1] - e2[1]; // oids are integers
                        if (result === 0) {
                            result = e1.last().featureUrl.localeCompare(e2.last().featureUrl);
                        }
                    }
                    return result;
                }

                /**
                * Sorts by layer name then by feature (alphabetically ascending)
                *
                * @method sortFcns
                * @param {Object} e1 custom data object
                * @param {Object} e2
                * @return {Array} A array of sorted objects
                */
                sortFcns["default"] = function (e1, e2) {
                    var layerName1 = e1.last().layerName,
                        layerName2 = e2.last().layerName,
                        result = layerName1.localeCompare(layerName2);

                    if (result === 0) {
                        result = featureSorter(e1, e2);
                    }

                    return result;
                };

                sortFcns.ascending = featureSorter;

                sortFcns.descending = function (e1, e2) {
                    // Swap the order the parameters are passed in to make it
                    // descending
                    return featureSorter(e2, e1);
                };
            } //InitDataGrid
        };
    });