﻿// fixes bug on loadmask checking when grid is not yet rendered
// i.e. if we cancel rendering - see beforeRender below for more info
// also see: http: //extjs.com/forum/showthread.php?t=58959&page=2 for more info
Ext.override(Ext.grid.GridPanel, {
    reconfigure: function(store, colModel) {
        if (this.loadMask && this.loadMask.store) {
            this.loadMask.destroy();
            this.loadMask = new Ext.LoadMask(
                this.bwrap,
				Ext.apply({},
				    { store: store },
				    this.initialConfig.loadMask));
        }
        this.view.initData(store, colModel);
        this.store = store;
        this.colModel = colModel;
        if (this.rendered) {
            this.view.refresh(true);
        }
    }
});


// allows setting of multiple baseParams at once
// courtesy of: http://extjs.net/forum/showthread.php?p=357952
Ext.override(Ext.data.Store, {
    setBaseParam: function(name, value) {
        var bp = this.baseParams || {};
        if (Ext.isObject(name)) {
            Ext.apply(bp, name);
        } else {
            bp[name] = value;
        }
        this.baseParams = bp;
    }
});


// courtesy of: http://erhanabay.com/2009/01/29/dynamic-grid-panel-for-ext-js/
Ext.ux.ContentPanelGridPanel = Ext.extend(Ext.grid.GridPanel, {

    // internal fields
    m_isDraggingOver: false,
    m_isDragging: false,
    m_currentRowIndex: g_default_xPOPropertyId,


    // standard initializer
    initComponent: function() {
        /**  
        * Default configuration options.  
        *  
        * The important point is to define a data store with JsonReader  
        * without configuration and columns with empty array. We are going  
        * to setup our reader with the metaData information returned by the server.  
        * See http://extjs.com/deploy/dev/docs/?class=Ext.data.JsonReader for more  
        * information how to configure your JsonReader with metaData.   
        */
        var config = {
            border: false,
            enableColumnMove: false,
            enableDragDrop: this.enableDragDrop,
            enableColumnHide: false,
            frame: false,
            loadMask: { msg: "." },
            stripeRows: true,
            ds: this.store,
            columns: [],
            applyTo: this.renderTemp,
            dragDropHighlightType: this.dragDropHighlightType
        };

        // set defaults
        if (!this.dragDropHighlightType) {
            this.dragDropHighlightType = g_dragDropHighlightType_row;
        }

        Ext.apply(this, config);
        Ext.apply(this.initialConfig, config);

        Ext.ux.ContentPanelGridPanel.superclass.initComponent.apply(this, arguments);

        var viewConfig = {
            view: new Ext.ux.ContentPanelGridView()
        };

        Ext.apply(this, viewConfig);
        Ext.apply(this.initialConfig, viewConfig);

        this.on('rowcontextmenu', this.onRowContextMenu, this);
        this.on('rowclick', this.onRowClick, this);
        
        // DO NOT uncomment this - it's here as a warning.
        // if we do so, it registers our onClick function as the onclick handler. When our handler calls the base class
        // it in turn calls the registered onClick handler - i.e. our function - and we end up with recursion and stack overflow.
        // all we need to do is override it.
        // this.on('click', this.onClick, this);
    },

    onBeforeLoad: function(options) {
        // show the load mask every time since it isn't shown during reconfigure
        if ((this.loadMask !== null) && (this.loadMask.show))
            this.loadMask.show();
    },

    onLoad: function(records, options) {
    },

    hideLoadMask: function() {
        if ((this.loadMask !== null) && (this.loadMask.hide))
            this.loadMask.hide();
    },

    setDdAddGroups: function(groups) {
        this.ddAddGroups = groups;
    },

    getDdAddGroups: function() {
        return this.ddAddGroups;
    },

    setIsDraggingOver: function(isDraggingOver) {
        this.m_isDraggingOver = isDraggingOver;
    },

    getIsDraggingOver: function() {
        return this.m_isDraggingOver;
    },

    setIsDragging: function(isDragging) {
        this.m_isDragging = isDragging;
    },

    getIsDragging: function() {
        return this.m_isDragging;
    },

    getDragDropHighlightType: function() {
        return this.dragDropHighlightType;
    },

    // gets the current row index
    getCurrentRowIndex: function() {
        return this.m_currentRowIndex;
    },

    // sets the current row index
    setCurrentRowIndex: function(rowIndex) {
        this.m_currentRowIndex = rowIndex;
    },

    onMetaChange: function(store, meta) {

        // JSON data returned from server has the column definitions  
        if (typeof (store.reader.jsonData.columns) === 'object') {

            var columnId = 1;
            var columns = [];
            var columnModel = this.getColumnModel();

            Ext.each(store.reader.jsonData.columns,
                function(column) {
                    column.id = columnId;
                    column.menuDisabled = true;
                    columns.push(column);
                    columnId = columnId + 1;
                });


            // push an extra hidden column to fix extjs layout shortcomings
            columns.push({
                id: columnId,
                header: '',
                dataIndex: '',
                width: 20,
                resizable: false,
                hidden: true
            });

            // configure column model and reconfigure
            columnModel.setConfig(columns);
            this.reconfigure(store, columnModel);

            //  TODO: reconfigure paging toolbar

            // remove attributes from anchor at bottom of grid when using IE7 / IE8 in compatibility mode
            // because it causes the grid to scroll to the bottom each time we click
            // or press a navigation key
            if (($.browser.msie) && (($.browser.version.substring(0, 1)) == "7")) {
                $("a.x-grid3-focus").removeAttr("href");
                $("a.x-grid3-focus").removeAttr("tabIndex");
            }

            try {
                // update custom scrollbar
                CSBfleXcroll.defer(1, window, [this.getView().scroller.dom]);
            }
            catch (ex) {
                ajax_logException(ex);
            }
        }
    },

    // deselect all rows if not over a row and no ctrl or shift key held down
    onClick: function(e, grid) {
    // focus the grid
    this.getView().focusEl.focus();

        if (e.ctrlKey || e.shiftKey) {
            // do nothing
        }
        else {
            var target = e.getTarget();
            var iRowIndex = this.getView().findRowIndex(target);

            if (iRowIndex === false) {
                this.getSelectionModel().clearSelections();
            }
        }

        Ext.ux.ContentPanelGridPanel.superclass.onClick.call(this, e, grid);
    },

    // deselect other selected rows if no ctrl or shift key held down
    onRowClick: function(grid, rowIndex, e) {

        // focus the grid
        this.getView().focusEl.focus();

        if (e.ctrlKey || e.shiftKey) {
            // do nothing
        }
        else {
            this.getSelectionModel().clearSelections();
            this.getSelectionModel().selectRow(rowIndex);
        }
    },

    // deselect other selected rows if no ctrl or shift key held down
    onRowContextMenu: function(grid, rowIndex, e) {

        // focus the grid
        this.getView().focusEl.focus();

        // prevent browser showing its context menu
        e.preventDefault();
        e.stopEvent();

        // right button: context menu
        this.contextMenu.showAt(e.getXY());
    },

    // catch rendering errors so we can at least redisplay the grid without having to redisplay the page
    onRender: function(ct, position) {

        try {
            Ext.ux.ContentPanelGridPanel.superclass.onRender.call(this, ct, position);
        }
        catch (ex) {
            ajax_logException(ex);
        }
    },

    // attempt to make the rendering less error-prone by initially rendering
    // to an element in the main page that always exists.
    // then use jquery to append the rendered html to the element inside the loaded user control
    // note: this only happens once as the grid dom is subsequently updated and not re-rendered
    afterRender: function() {
        $("#" + this.renderTarget).empty();
        $("#" + this.applyTo + " > *").appendTo($("#" + this.renderTarget));

        Ext.ux.ContentPanelGridPanel.superclass.afterRender.call(this);
    },

    // catch rendering errors so we can at least redisplay the grid without having to redisplay the page
    applyToMarkup: function(el) {
        try {
            Ext.ux.ContentPanelGridPanel.superclass.applyToMarkup.call(this, el);
        }
        catch (ex) {
            ajax_logException(ex);
        }
    },

    // retrieve the data from the server once more
    reload: function() {
        this.store.reload();
    },

    // create an array of ids from the selected records, given the type
    createSelectedIdArray: function(type) {

        var idArray = [];
        var selections = this.getSelectionModel().getSelections();

        idArray = this.createIdArray(type, selections);

        return idArray;
    },

    // create an array of ids from the specified records, given the type
    // note how this function differs to that in DevicePanelHelper.js when dealing with playlistentries
    createIdArray: function(type, records) {

        var idArray = [];

        // build up array of ids (could be of filehashids, artists, etc, depending on data.type)
        if (records) {
            if (type == g_extJsGrid_dataTypeTrack) {
                // track: use filehashid (as the id field could be clientdevicefile.id, clientfile.id)
                for (var iTrack = 0; iTrack < records.length; iTrack++) {
                    if ((records[iTrack].data) && (records[iTrack].data.filehashid)) {
                        idArray.push(records[iTrack].data.filehashid);
                    }
                }
            }
            else {
                // everything else: use id field as it can only ever be the id of the type of the object displayed
                for (var iRecord = 0; iRecord < records.length; iRecord++) {
                    if ((records[iRecord].data) && (records[iRecord].data.id)) {
                        idArray.push(records[iRecord].data.id);
                    }
                }
            }
        }

        return idArray;
    },

    // create an array of ids from the specified records, given the type
    // note how this function differs to that in DevicePanelHelper.js when dealing with playlistentries
    createIdTripleArray: function(records) {

        var tripleArray = [];
        var id;

        // build up array of ids (could be of filehashids, artists, etc, depending on data.type)
        if (records) {
            for (var iRecord = 0; iRecord < records.length; iRecord++) {
                if ((records[iRecord].data) && (records[iRecord].data.id)) {

                    // use correct id
                    if (records[iRecord].data.type == g_extJsGrid_dataTypeTrack) {
                        id = records[iRecord].data.filehashid;
                    }
                    else {
                        id = records[iRecord].data.id;
                    }

                    // create triple
                    var triple = {
                        deviceid: records[iRecord].data.deviceid,
                        type: records[iRecord].data.type,
                        id: id
                    };

                    // push onto array
                    tripleArray.push(triple);
                }
            }
        }

        return tripleArray;
    },

    // gets the current record
    getCurrentRecord: function() {
        var currentRecord = null;

        if (this.m_currentRowIndex != g_default_xPOPropertyId) {
            currentRecord = this.store.getAt(this.m_currentRowIndex);
        }

        return currentRecord;
    },

    // get the previous record, given the currently selected row, wrapping if necessary
    getPreviousRecord: function() {
        var previousRecord = null;
        var totalRows = this.store.getTotalCount();

        // wrap to last row
        if (this.m_currentRowIndex <= 0) {
            this.m_currentRowIndex = totalRows - 1;
        }
        else {
            this.m_currentRowIndex = this.m_currentRowIndex - 1;
        }

        var record = this.store.getAt(this.m_currentRowIndex);
        if (record) {
            previousRecord = record;
        }

        return previousRecord;
    },

    // get the previous record id, given the currently selected row, wrapping if necessary
    getPreviousRecordId: function(rowIndex) {
        var recordId = g_default_xPOPropertyId;

        var previousRecord = this.getPreviousRecord();
        if (previousRecord !== null) {
            recordId = previousRecord.data.id;
        }

        return recordId;
    },

    // get the next record, given the currently selected row, wrapping if necessary
    getNextRecord: function() {
        var nextRecord = null;
        var totalRows = this.store.getTotalCount();

        // wrap to first row
        if (this.m_currentRowIndex >= (totalRows - 1)) {
            this.m_currentRowIndex = 0;
        }
        else {
            this.m_currentRowIndex = this.m_currentRowIndex + 1;
        }

        var record = this.store.getAt(this.m_currentRowIndex);
        if (record) {
            nextRecord = record;
        }

        return nextRecord;
    },

    // get the next record id, given the currently selected row, wrapping if necessary
    getNextRecordId: function() {
        var recordId = g_default_xPOPropertyId;

        var nextRecord = this.getNextRecord();
        if (nextRecord !== null) {
            recordId = nextRecord.data.id;
        }

        return recordId;
    },

    // highlight the current row
    autoHighlightCurrentRow: function() {

        // remove all highlighting
        this.clearAllAutoHighlights();

        // highlight the current row
        if (this.m_currentRowIndex != g_default_xPOPropertyId) {
            var row = this.getView().getRow(this.m_currentRowIndex);
            if (row) {
                this.getView().addRowClass(this.m_currentRowIndex, "x-grid3-row-autohighlight");
            }
        }
    },

    // remove the drag indicator from all rows
    clearAllAutoHighlights: function(totalRows) {
        var gridView = this.getView();
        var totalRows = this.store.getCount();

        for (var iRow = 0; iRow < totalRows; iRow++) {
            gridView.removeRowClass(iRow, "x-grid3-row-autohighlight");
        }
    },

    // empty base implementation
    configureDragDrop: function(ddGroup) {
    },

    // create a context menu
    createEmptyActionsContextMenu: function() {
        return new Ext.menu.Menu();
    }
});

Ext.ux.ContentPanelGridView = Ext.extend(Ext.grid.GridView, {

    // correctly test for display of column splitter
    handleHdMove: function(e, t) {
        var hd = this.findHeaderCell(this.activeHdRef);
        if (hd && !this.headersDisabled) {
            var hw = this.splitHandleWidth || 5,
                    r = this.activeHdRegion,
                    x = e.getPageX(),
                    ss = hd.style,
                    cur = '';
            if (this.grid.enableColumnResize !== false) {
                if (x - r.left <= hw &&
                    this.cm.isResizable(this.activeHdIndex - 1) &&
                    this.cm.isResizable(this.activeHdIndex)) {
                    cur = Ext.isAir ? 'move' : Ext.isWebKit ? 'e-resize' : 'col-resize'; // col-resize not always supported
                } else if (r.right - x <= (!this.activeHdBtn ? hw : 2) &&
                    this.cm.isResizable(this.activeHdIndex) &&
                    (this.cm.config.length == this.activeHdIndex ||
                    ((this.cm.config.length > (this.activeHdIndex + 1)) &&
                     (this.cm.isResizable(this.activeHdIndex + 1))))) {
                    cur = Ext.isAir ? 'move' : Ext.isWebKit ? 'w-resize' : 'col-resize';
                }
            }
            ss.cursor = cur;
        }
    },

    // do the column splitter moving correctly
    onColumnSplitterMoved: function(i, w) {
        var cm = this.grid.colModel;

        // only do something if neither column is fixed
        if ((this.cm.config[i].fixed === true) ||
            ((this.cm.config.length > i) && (this.cm.config[i + 1].fixed === true))) {
            return;
        }

        this.userResized = true;

        var oldWidthLeft = cm.getColumnWidth(i);
        var oldWidthRight = 0;
        if (this.cm.config.length > i) {
            oldWidthRight = cm.getColumnWidth(i + 1);
        }

        // check column is > minwidth
        if (w < g_minColumnWidth) {
            w = g_minColumnWidth;
        }

        // calculate new widths
        var deltaWidth = oldWidthLeft - w;
        var newWidthRight = 0;
        var overload = 0;
        if (this.cm.config.length > i) {
            newWidthRight = oldWidthRight + deltaWidth;
        }

        // check right-hand columnn (if present) is > minwidth
        if ((this.cm.config.length > i) && (newWidthRight < g_minColumnWidth)) {
            overload = g_minColumnWidth - newWidthRight;
            newWidthRight = g_minColumnWidth;
            w = w - overload;
        }

        // set widths
        this.cm.setColumnWidth(i, w, true);
        if (this.cm.config.length > i) {
            this.cm.setColumnWidth(i + 1, newWidthRight, true);
        }

        // update widths
        this.updateColumnWidth(i, w);
        if (this.cm.config.length > i) {
            this.updateColumnWidth(i + 1, newWidthRight);
        }

        // sync headers
        this.syncHeaderScroll();

        // fire events
        this.grid.fireEvent('columnresize', i, w);
        if (this.cm.config.length > i) {
            this.grid.fireEvent('columnresize', i + 1, newWidthRight);
        }
    },

    // do the important parts of the layout (fitColumns())
    layout: function() {
        if (!this.mainBody) {
            return; // not rendered
        }

        var g = this.grid;
        var c = g.getGridEl();
        var csize = c.getSize(true);
        var vw = csize.width;

        if (!g.hideHeaders && (vw < 20 || csize.height < 20)) { // display: none?
            return;
        }

        Ext.ux.ContentPanelGridView.superclass.layout.call(this);

        if (this.lastViewWidth != vw) {
            if (this.cm.config.length > 0) {
                this.fitColumns(false, false);
            }
            this.lastViewWidth = vw;
        }
    },

    afterRender: function() {
        try {
            Ext.ux.ContentPanelGridView.superclass.renderUI.call(this);
        }
        catch (ex) {
            ajax_logException(ex);
        }

        this.dragZone = new Ext.ux.ContentPanelGridDragZone(this.grid, { ddGroup: "" });
        this.dragZone.removeFromGroup("default");

        try {
            // show custom scrollbar (container only at this stage)
            CSBfleXcroll(this.scroller.dom);
        }
        catch (ex) {
            ajax_logException(ex);
        }
    },

    // private
    onRowOver: function(e, t) {
        var rowIndex;
        if ((rowIndex = this.findRowIndex(t)) !== false) {
            if (this.grid.getIsDragging() || this.grid.getIsDraggingOver()) {
                // do nothing
            }
            else {
                Ext.ux.ContentPanelGridView.superclass.onRowOver.call(this, e, t);
            }
        }
    },

    // get the class for the row
    getRowClass: function(record, rowIndex) {

        // change the styling for selected rows
        var selectionModel = this.grid.getSelectionModel();
        var cssClass = '';
        var currentRowIndex = this.grid.getCurrentRowIndex();

        if ((currentRowIndex != g_default_xPOPropertyId) && (currentRowIndex == rowIndex)) {
            // highlighted
            cssClass = "x-grid3-row-autohighlight";
        }
        else if (selectionModel.isIdSelected(record.data.id)) {
            // selected
            cssClass = 'x-grid3-row-selected';
        }
        else {
            // normal
        }

        // don't call base class

        return cssClass;
    }
});

// configure drag & drop
Ext.ux.ContentPanelGridDragZone = Ext.extend(Ext.grid.GridDragZone, {

    onBeforeDrag: function(data, e) {
        // prevent dragdrop if necesary
        if (!this.grid.enableDragDrop) {
            return false;
        }
    }
});
    
// override to implement onInitDrag because doesn't get called if we simply extend
Ext.override(Ext.grid.GridDragZone, {

    onInitDrag: function(e, id) {
        // reset the groups every time or we end up with problems in FF
        // possibly due to caching of some sort
        this.groups = null;
        this.groups = Array();

        // custom code to add dragdrop groups
        var dragDropAddGroups = this.grid.getDdAddGroups();
        if (dragDropAddGroups) {
            for (var iGroup = 0; iGroup < dragDropAddGroups.length; iGroup++) {
                this.addToGroup(dragDropAddGroups[iGroup]);
            }
        }

        this.customizeDragImage();
        this.grid.setIsDragging(true);
    },

    endDrag: function(e) {
        this.grid.setIsDragging(false);

        // call base class
        Ext.grid.GridDragZone.superclass.endDrag.call(this, e);
    },

    onDragEnter: function(e, id) {
        var dragEl = Ext.get(this.getDragEl());
        dragEl.setOpacity(1);

        // call base class
        Ext.grid.GridDragZone.superclass.onDragEnter.call(this, e, id);
    },

    onDragOut: function(e, id) {
        // call base class
        Ext.grid.GridDragZone.superclass.onDragOut.call(this, e, id);

        this.customizeDragImage();
    },

    // customize the drag image
    customizeDragImage: function(data) {
        var data = this.dragData;
        var className = "x-grid3-dragicon-track";

        if (data.selections.length <= 9)
            className += "-1";
        else if ((data.selections.length >= 10) && (data.selections.length <= 99))
            className += "-2";
        else if (data.selections.length >= 100)
            className += "-3";

        var dragEl = Ext.get(this.getDragEl());
        dragEl.setWidth(64);
        dragEl.setHeight(43);
        dragEl.setOpacity(0.80);
        dragEl.addClass(className);
        dragEl.dom.innerHTML = data.selections.length;
    }
});

// override to implement custom grid stuff
Ext.ux.ContentPanelDropTarget = Ext.extend(Ext.dd.DropTarget, {

    // standard initializer
    initComponent: function() {

        var grid = this.grid;

        Ext.ux.ContentPanelDropTarget.superclass.initComponent.call(this, arguments);
    },

    notifyOver: function(ddSource, e, data) {
        var target = e.getTarget();
        var iRowIndex = this.grid.getView().findRowIndex(target);
        var iTotalRows = this.grid.store.getCount();

        if ((iRowIndex !== false) && (iRowIndex >= 0)) {
            if (this.grid.getIsDraggingOver()) {

                // remove & re-highlight border to indicate where rows will be dropped
                this.clearAllDragDestinationIndicators(iTotalRows);
                this.showDragDestinationIndicator(e, iRowIndex, iTotalRows);
            }
        }
        else {
            if (this.grid.getIsDraggingOver()) {
                // remove & re-highlight last row to indicate where rows will be dropped
                this.clearAllDragDestinationIndicators(iTotalRows);
                if (target) {
                    var cssClass = target.className;
                    var pos = cssClass.indexOf("x-dd-drag-proxy");
                    if ((pos == -1) && (this.grid.getDragDropHighlightType() == g_dragDropHighlightType_betweenRows)) {
                        this.showDragDestinationIndicator(e, iTotalRows - 1, iTotalRows);
                    }
                }
            }
        }
    },

    notifyEnter: function(ddSource, e, data) {
        // flag that we're in a drag-drop situation
        this.grid.setIsDraggingOver(true);
    },

    notifyOut: function(ddSource, e, data) {
        // flag that we're no longer in a drag-drop situation
        this.grid.setIsDraggingOver(false);
        this.clearAllDragDestinationIndicators(this.grid.store.getCount());
    },

    // called when data is dropped
    notifyDrop: function(ddSource, e, data) {
        // handle dropped data
        this.grid.handleDrop(g_default_xPOPropertyId, data);
        this.clearAllDragDestinationIndicators(this.grid.store.getCount());
    },

    // show the drag indicator
    showDragDestinationIndicator: function(e, rowIndex, totalRows) {
        var gridView = this.grid.getView();
        var row = gridView.getRow(rowIndex);
        if (row) {
            if (this.grid.getDragDropHighlightType() == g_dragDropHighlightType_row) {
                // set indicator on row
                gridView.addRowClass(rowIndex, "x-grid3-row-dragdrophighlight");
            }
            else {
                // set indicator above or below row as appropriate
                var cellClass;
                var cells;
                var iPositionInRow = e.getPageY() - row.getClientRects()[0].top;
                if (iPositionInRow < (row.clientHeight / 2)) {
                    if (rowIndex === 0) {
                        cellClass = ' x-grid3-cell-dragdrop-topborder';
                        cells = gridView.getRow(rowIndex).getElementsByTagName('td');

                    }
                    else {
                        cellClass = ' x-grid3-cell-dragdrop-bottomborder';
                        cells = gridView.getRow(rowIndex - 1).getElementsByTagName('td');
                    }
                }
                else {
                    cellClass = ' x-grid3-cell-dragdrop-bottomborder';
                    cells = gridView.getRow(rowIndex).getElementsByTagName('td');
                }

                // add the class
                if (cells) {
                    for (var i = 0; i < cells.length; i++) {
                        cells[i].className += cellClass;
                    }
                }
            }
        }
    },

    // remove the drag indicator from all rows
    clearAllDragDestinationIndicators: function(totalRows) {
        var gridView = this.grid.getView();
        var iRow = 0;
        if (this.grid.getDragDropHighlightType() == g_dragDropHighlightType_row) {
            // remove all row highlighting
            for (iRow = 0; iRow < totalRows; iRow++) {
                gridView.removeRowClass(iRow, "x-grid3-row-dragdrophighlight");
            }
        }
        else {
            // remove all in-between row highlighting
            var cellClass1 = 'x-grid3-cell-dragdrop-topborder';
            var cellClass2 = 'x-grid3-cell-dragdrop-bottomborder';

            for (iRow = 0; iRow < totalRows; iRow++) {
                var i = 0;
                var cells = gridView.getRow(iRow).getElementsByTagName('td');
                for (i = 0; i < cells.length; i++) {
                    Ext.fly(cells[i]).removeClass(cellClass1);
                    Ext.fly(cells[i]).removeClass(cellClass2);
                }
            }
        }
    },

    // retrieve the position info
    getPositionInfo: function(e) {
        var iRowIndex = this.grid.getView().findRowIndex(e.getTarget());
        var iTotalRows = this.grid.store.getCount();
        var positionInfo;
        var record;

        // check we have a valid row
        if (!(iRowIndex === false)) {

            var row = this.grid.getView().getRow(iRowIndex);
            if (row) {
                // if we're dropping on the lower half of a row, we actually want the position to be one more than the current row
                var iPositionInRow = e.getPageY() - row.getClientRects()[0].top;
                if (iRowIndex === 0) {
                    // over first row
                    if (iPositionInRow < (row.clientHeight / 2)) {
                        // insert at very start
                        positionInfo = {
                            positionQualifierEnum: g_positionQualifierEnum_start,
                            targetElementId: g_default_xPOPropertyId
                        };
                    }
                    else {
                        // insert after first item
                        record = this.grid.store.getAt(iRowIndex);
                        positionInfo = {
                            positionQualifierEnum: g_positionQualifierEnum_afterEntry,
                            targetElementId: record.data.id
                        };
                    }
                }
                else if (iRowIndex == (iTotalRows - 1)) {
                    // over last row
                    if (iPositionInRow < (row.clientHeight / 2)) {
                        // insert after previous item
                        record = this.grid.store.getAt(iRowIndex - 1);
                        positionInfo = {
                            positionQualifierEnum: g_positionQualifierEnum_afterEntry,
                            targetElementId: record.data.id
                        };
                    }
                    else {
                        // insert at end
                        positionInfo = {
                            positionQualifierEnum: g_positionQualifierEnum_end,
                            targetElementId: g_default_xPOPropertyId
                        };
                    }
                }
                else {
                    // over 1..n-1th row                    
                    if (iPositionInRow < (row.clientHeight / 2)) {
                        // insert after previous item
                        record = this.grid.store.getAt(iRowIndex - 1);
                    }
                    else {
                        // insert after current item
                        record = this.grid.store.getAt(iRowIndex);
                    }

                    positionInfo = {
                        positionQualifierEnum: g_positionQualifierEnum_afterEntry,
                        targetElementId: record.data.id
                    };
                }
            }
            else {
                // couldn't retrieve row so just add to end
                positionInfo = {
                    positionQualifierEnum: g_positionQualifierEnum_end,
                    targetElementId: g_default_xPOPropertyId
                };
            }
        }
        else {
            // dropped in empty space so just add to end
            positionInfo = {
                positionQualifierEnum: g_positionQualifierEnum_end,
                targetElementId: g_default_xPOPropertyId
            };
        }

        return positionInfo;
    }
});
