﻿// 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);
        }
    },
    
     // private
    onLoad : function(){
        this.el.unmask(this.removeMask);
    },

    // private
    onBeforeLoad : function(){
        if(!this.disabled){
            this.el.mask(this.msg, this.msgCls);
        }
    }

    /*
    // show the overlay which disables the grid while grid is loading
    showOverlay: function() {

        $('#gridload_overlay')
            .addClass("gridload_overlayBG")
            .css('opacity', g_overlayOpacityLight)
            .fadeIn(0);

        $('#gridload_loading')
            .fadeIn(0);

        return false;
    },

    // hide the overlay which disables the grid while grid is loading
    hideOverlay: function() {

        $('#gridload_overlay')
        .fadeOut(0, function() {
            $("#gridload_overlay").removeClass("gridload_overlayBG")
            $("#gridload_overlay").addClass("gridload_hide");
        });

        $('#gridload_loading')
            .fadeOut(0);

        return false;
    }
    */
});

// 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,
    m_showingConfirm: false,
    
    // 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,
            isEditable: this.isEditable,
            dragDropHighlightType: this.dragDropHighlightType,
            confirmPanelId: this.confirmPanelId
        };

        // set defaults
        if (typeof(this.dragDropHighlightType == 'undefined')) {
            this.dragDropHighlightType = g_dragDropHighlightType_row;
        }
        
        if (typeof(this.isEditable == 'undefined')) {
            this.isEditable = false;
        }
        
        if (typeof(this.confirmPanelId == 'undefined')) {
            this.confirmPanelId = "";
        }

        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);
        this.on('keydown', this.onKeyDown, this);
        this.on('contextmenu', this.onContextMenu, 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;
    },

    // get the total number of records
    getTotalCount: function() {
        return this.getStore().getTotalCount();
    },
    
    // clear the grid selection
    clearSelection: function() {
        this.getSelectionModel().clearSelections();
    },

    // called when metadata for grid changes
    onMetaChange: function(store, meta) {

        // JSON data returned from server has the column definitions  
        if (typeof (store.reader.jsonData.columns) === 'object') {

            var columnId = 1;
            var columnModel = this.getColumnModel();
            var columns = [];

            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);
        }
    },
    
     // context menu handler
    onContextMenu: function(e) {  
    
        var rowIndex = this.getRowIndexFromEvent(e);
        this.updateSelections(rowIndex);
        
        // focus the grid
        this.getView().focusEl.focus();
        
        // prevent browser showing its context menu
        e.preventDefault();
        e.stopEvent();

        // right button: context menu
        this.contextMenu = this.createActionsContextMenu(this, rowIndex);
        
        // ensure it remains within viewport        
        var windowHeight = $(window).height();
        var menuHeight = ((this.contextMenu.items.length + 1) * g_menuItemHeight) + 2;  // height plus header * border
        
        var coordinates = e.getXY();
        if(coordinates[1] + menuHeight + 10 >= windowHeight) {
            coordinates[1] = coordinates[1] - menuHeight;
        }
     
        this.contextMenu.showAt(coordinates);
    },
    
    // key pressed
    onKeyDown: function(e) {
        if ((e.ctrlKey) && (e.getKey() == g_keyCode_a)) {
        
            // select all / prevent browser select all
            this.getSelectionModel().selectAll();
            e.preventDefault();
            e.stopEvent();
        }   
        else if (e.getKey() == g_keyCode_delete) { 
        
            // remove selected rows
            if(this.isEditable) {
                this.removeSelectedRows();
                this.showConfirm();
            }
        }
    },

    // 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();
    },

    // update the scrollbar
    updateScrollbar: function() {
        CSBfleXcroll(this.getView().scroller.dom);
    },
    
    // retrieve the row index from the event
    getRowIndexFromEvent: function(e)  {
        var rowIndexToReturn    = g_default_xPOPropertyId;
        var t                   = Ext.lib.Event.getTarget(e);
        
        var rowIndex = this.view.findRowIndex(t);
        if (rowIndex !== false) {
            rowIndexToReturn = rowIndex;
        }
        
        return rowIndexToReturn;
    },

    // get the currently selected rows. select rowIndex if no rows are selected
    updateSelections: function(rowIndex) {
        var selections;

        // if we have a vaild row, ensure it is selected, otherwise deselect all rows
        if (rowIndex !== g_default_xPOPropertyId) {
            if (!this.getSelectionModel().isSelected(rowIndex)) {
                this.getSelectionModel().selectRow(rowIndex);
            }
        }
        else {
             this.getSelectionModel().clearSelections();
        }
    },

    // create an array of ids from the selected records
    createSelectedIdArray: function() {
        var idArray = [];
        var selections = this.getSelectionModel().getSelections();

        idArray = idHelper_createIdArray(selections);

        return idArray;
    },

    // 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 current record id
    getCurrentRecordId: function() {
        var recordId = g_default_xPOPropertyId;

        var currentRecord = this.getCurrentRecord();
        if (currentRecord !== null) {
            recordId = currentRecord.data.id;
        }

        return recordId;
    },

    // 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.getStore().getCount();

        for (var iRow = 0; iRow < totalRows; iRow++) {
            gridView.removeRowClass(iRow, "x-grid3-row-autohighlight");
        }
    },

    // configure drop target
    configureDragDrop: function(ddGroup) {
        var dropTargetEl = this.body;
        var grid = this;

        var dropTarget = new Ext.ux.ContentPanelDropTarget(dropTargetEl, {
            ddGroup: ddGroup,
            grid: grid
        });
    },

    // create a context menu
    createEmptyActionsContextMenu: function() {
        return new Ext.menu.Menu();
    },

    // add any generic menu items
    addGenericMenuItems: function(grid, contextMenu, rowIndex) {

        contextMenu.add(new Ext.menu.Item({
            text: g_resourceStrings['Js_Grid_PopupMenu_SelectAll'],
            handler: function() { grid.getSelectionModel().selectAll(); }
        }));

        return contextMenu;
    },

    // remove selected entries
    removeSelectedRows: function() {

        var selections              = this.getSelectionModel().getSelections();
        var firstSelectedRowIndex   = this.getStore().indexOf(selections[0]);
        var lastSelectedRowIndex    = this.getStore().indexOf(selections[selections.length - 1]);
        var selectionCount          = selections.length;
        
        // remove records from store
        // note that we maintain our own list of deleted records so we can send them to the server
        // we can't add an event handler to 'remove' events because it is used when reordering (by removing & insertin)
        // when only reording    
        Ext.each(selections, function(record) {
            this.getStore().remove(record);
            this.getStore().addDeletedRecord(record);
        }, this);
        
        /*
        // select next row
        if (this.getStore().getCount() > (lastSelectedRowIndex) {
            this.getSelectionModel().selectRow(lastSelectedRowIndex - selectionCount);
        }
        else if(this.getStore().getCount() > 0) {
            this.getSelectionModel().selectRow(firstSelectedRowIndex - 1);
        }
        */
        
        // load the same number of entries that we just deleted
        //this.loadExtraEntries(selections.length);
    },
    
    // show a confirmation message
    showConfirm: function() { 
        if(this.confirmPanelId !== "") {
            if(!this.m_showingConfirm) {
                this.m_showingConfirm = true;

                $("#" + this.renderTarget).animate({
                    bottom: "36px"
                 }, g_elementAnimationTime_milliseconds);
                
                $("#" + this.confirmPanelId).animate({
                    height: "35px",
                    bottom: "0"
                 }, g_elementAnimationTime_milliseconds);
                 
                 flexscroll_adjustHeight(this.renderTarget, -36, g_elementAnimationTime_milliseconds);         
             }
         }
    },

    // hide the confirmation message
    hideConfirm: function(duration) {
        if(this.confirmPanelId !== "") { 
            if(this.m_showingConfirm) {
                $("#" + this.renderTarget).animate({
                    bottom: "0"
                 }, duration);
                
                $("#" + this.confirmPanelId).animate({
                    height: "0",
                    bottom: "-1px"
                 }, duration);
                 
                 flexscroll_adjustHeight(this.renderTarget, 36, duration);
                 
                 this.m_showingConfirm = false;
             }
         }
    },
    
     // cancel the modifications
    cancelChanges: function() {
        this.hideConfirm(g_elementAnimationTime_milliseconds);

        var me = this;

        setTimeout(function() {
            me.reload();
        }, g_elementAnimationTime_milliseconds);
    },
    
    // load extra entries
    loadExtraEntries: function() { },
    
    // called when extra entries loaded
    onExtraEntriesLoaded: function(records, options, success) { },
    
    // save changes. override in derived class to do the ajax calls
    saveChanges: function() { },
    
    // called when changed have been saved
    onChangesSaved: function() { }
});

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;
    },
    
    // ------ context menu creation helper functions ------ //

    // create the context menu
    createActionsContextMenu: function(grid, rowIndex) {
        var contextMenu = this.createEmptyActionsContextMenu();

        // add any generic menu items
        contextMenu = this.addGenericMenuItems(grid, contextMenu, rowIndex);

        return contextMenu;
    }
});

// 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.handleDrop(ddSource, e, data);
        this.clearAllDragDestinationIndicators(this.grid.store.getCount());
        this.grid.showConfirm();
    },
    
    // handle the dropping of some records
    handleDrop: function(ddSource, e, data) {
    
        var rowIndex = this.grid.getRowIndexFromEvent(e);
        if (e.rowIndex === g_default_xPOPropertyId) {
            return false;
        }

        // update the store
        var ds = this.grid.getStore();

        // changes for multiselction by Spirit
        var selections = new Array();
        var keys = ds.data.keys;
        for (var key in keys) {
            for (var i = 0; i < data.selections.length; i++) {
                if (keys[key] == data.selections[i].id) {
                    // Exit to prevent drop of selected records on itself.
                    if (rowIndex == key) {
                        return false;
                    }
                    selections.push(data.selections[i]);
                }
            }
        }

        // fix rowindex based on before/after move
        if (rowIndex > data.rowIndex && this.grid.rowPosition < 0) {
            rowIndex--;
        }
        if (rowIndex < data.rowIndex && this.grid.rowPosition > 0) {
            rowIndex++;
        }

        // fix rowIndex for multiselection
        if (rowIndex > data.rowIndex && data.selections.length > 1) {
            rowIndex = rowIndex - (data.selections.length - 1);
        }

        // we tried to move this node before the next sibling, we stay in place
        if (rowIndex == data.rowIndex) {
            return false;
        }
   
        for (var i = 0; i < data.selections.length; i++) {
            ds.remove(ds.getById(data.selections[i].id));
        }

        for (var i = selections.length - 1; i >= 0; i--) {
            ds.insert(rowIndex, selections[i]);
        }     
        
        // re-select the row(s)
        var sm = this.grid.getSelectionModel();
        if (sm) {
            sm.selectRecords(data.selections);
        }
    },

    // 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) {
                        gridView.addRowClass(rowIndex, "x-grid3-dragdrop-topborder");
                    }
                    else {
                        gridView.addRowClass(rowIndex - 1, "x-grid3-dragdrop-bottomborder");
                    }
                }
                else {
                    gridView.addRowClass(rowIndex, "x-grid3-dragdrop-bottomborder");
                }
            }
        }
    },

    // 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
            for (iRow = 0; iRow < totalRows; iRow++) {
                gridView.removeRowClass(iRow, "x-grid3-dragdrop-topborder");
                gridView.removeRowClass(iRow, "x-grid3-dragdrop-bottomborder");
            }
        }
    },

    // 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;
    }
});
