    /**
     * This plugin can enable a cell to cell drag and drop operation within the same grid view.
     *
     * Note that the plugin must be added to the grid view, not to the grid panel. For example, using {@link Ext.panel.Table viewConfig}:
     *
     *      viewConfig: {
     *          plugins: {
     *              ptype: 'celldragdrop',
     *
     *              // Remove text from source cell and replace with value of emptyText.
     *              applyEmptyText: true,
     *
     *              //emptyText: Ext.String.htmlEncode('<<foo>>'),
     *
     *              // Will only allow drops of the same type.
     *              enforceType: true
     *          }
     *      }
     */
    Ext.define('Ext.ux.CellDragDrop', {
        extend: 'Ext.AbstractPlugin',
        alias: 'plugin.celldragdrop',

        uses: ['Ext.view.DragZone'],

        /**
         * @cfg {Boolean} enforceType
         * Set to `true` to only allow drops of the same type.
         *
         * Defaults to `false`.
         */
        enforceType: false,

        /**
         * @cfg {Boolean} applyEmptyText
         * If `true`, then use the value of {@link #emptyText} to replace the drag record's value after a node drop.
         * Note that, if dropped on a cell of a different type, it will convert the default text according to its own conversion rules.
         *
         * Defaults to `false`.
         */
        applyEmptyText: false,

        /**
         * @cfg {Boolean} emptyText
         * If {@link #applyEmptyText} is `true`, then this value as the drag record's value after a node drop.
         *
         * Defaults to an empty string.
         */
        emptyText: '',

        /**
         * @cfg {Boolean} dropBackgroundColor
         * The default background color for when a drop is allowed.
         *
         * Defaults to green.
         */
        dropBackgroundColor: 'green',

        /**
         * @cfg {Boolean} noDropBackgroundColor
         * The default background color for when a drop is not allowed.
         *
         * Defaults to red.
         */
        noDropBackgroundColor: 'red',

        //<locale>
        /**
         * @cfg {String} dragText
         * The text to show while dragging.
         *
         * Two placeholders can be used in the text:
         *
         * - `{0}` The number of selected items.
         * - `{1}` 's' when more than 1 items (only useful for English).
         */
        dragText: '{0} selected row{1}',
        //</locale>

        /**
         * @cfg {String} ddGroup
         * A named drag drop group to which this object belongs. If a group is specified, then both the DragZones and
         * DropZone used by this plugin will only interact with other drag drop objects in the same group.
         */
        ddGroup: "GridDD",

        /**
         * @cfg {Boolean} enableDrop
         * Set to `false` to disallow the View from accepting drop gestures.
         */
        enableDrop: true,

        /**
         * @cfg {Boolean} enableDrag
         * Set to `false` to disallow dragging items from the View.
         */
        enableDrag: true,

        /**
         * @cfg {Object/Boolean} containerScroll
         * True to register this container with the Scrollmanager for auto scrolling during drag operations.
         * A {@link Ext.dd.ScrollManager} configuration may also be passed.
         */
        containerScroll: false,

        init: function (view) {
            var me = this;

            view.on('render', me.onViewRender, me, {
                single: true
            });
        },

        destroy: function () {
            var me = this;

            Ext.destroy(me.dragZone, me.dropZone);
        },

        enable: function () {
            var me = this;

            if (me.dragZone) {
                me.dragZone.unlock();
            }
            if (me.dropZone) {
                me.dropZone.unlock();
            }
            me.callParent();
        },

        disable: function () {
            var me = this;

            if (me.dragZone) {
                me.dragZone.lock();
            }
            if (me.dropZone) {
                me.dropZone.lock();
            }
            me.callParent();
        },

        onViewRender: function (view) {
            var me = this,
                scrollEl;

            if (me.enableDrag) {
                if (me.containerScroll) {
                    scrollEl = view.getEl();
                }

                me.dragZone = new Ext.view.DragZone({
                    view: view,
                    ddGroup: me.dragGroup || me.ddGroup,
                    dragText: me.dragText,
                    containerScroll: me.containerScroll,
                    scrollEl: scrollEl,
                    getDragData: function (e) {
                        var view = this.view,
                            item = e.getTarget(view.getItemSelector()),
                            record = view.getRecord(item),
                            clickedEl = e.getTarget(view.getCellSelector()),
                            dragEl;

                        if (item) {
                            dragEl = document.createElement('div');
                            dragEl.className = 'x-form-text';
                            dragEl.appendChild(document.createTextNode(clickedEl.textContent || clickedEl.innerText));

                            return {
                                event: new Ext.EventObjectImpl(e),
                                ddel: dragEl,
                                item: e.target,
                                columnName: view.getGridColumns()[clickedEl.cellIndex].dataIndex,
                                record: record
                            };
                        }
                    },

                    onInitDrag: function (x, y) {
                        var self = this,
                            data = self.dragData,
                            view = self.view,
                            selectionModel = view.getSelectionModel(),
                            record = data.record,
                            el = data.ddel;

                        // Update the selection to match what would have been selected if the user had
                        // done a full click on the target node rather than starting a drag from it.
                        if (!selectionModel.isSelected(record)) {
                            selectionModel.select(record, true);
                        }

                        self.ddel.update(el.textContent || el.innerText);
                        self.proxy.update(self.ddel.dom);
                        self.onStartDrag(x, y);
                        return true;
                    }
                });
            }

            if (me.enableDrop) {
                me.dropZone = new Ext.dd.DropZone(view.el, {
                    view: view,
                    ddGroup: me.dropGroup || me.ddGroup,
                    containerScroll: true,

                    getTargetFromEvent: function (e) {
                        var self = this,
                            v = self.view,
                            cell = e.getTarget(v.cellSelector),
                            row, columnIndex;

                        // Ascertain whether the mousemove is within a grid cell.
                        if (cell) {
                            row = v.findItemByChild(cell);
                            columnIndex = cell.cellIndex;

                            if (row && Ext.isDefined(columnIndex)) {
                                return {
                                    node: cell,
                                    record: v.getRecord(row),
                                    columnName: self.view.up('grid').columns[columnIndex].dataIndex
                                };
                            }
                        }
                    },

                    // On Node enter, see if it is valid for us to drop the field on that type of column.
                    onNodeEnter: function (target, dd, e, dragData) {
                        var self = this,
                            destType = target.record.fields.get(target.columnName).type.type.toUpperCase(),
                            sourceType = dragData.record.fields.get(dragData.columnName).type.type.toUpperCase();

                        delete self.dropOK;

                        // Return if no target node or if over the same cell as the source of the drag.
                        if (!target || target.node === dragData.item.parentNode) {
                            return;
                        }

                        // Check whether the data type of the column being dropped on accepts the
                        // dragged field type. If so, set dropOK flag, and highlight the target node.
                        if (me.enforceType && destType !== sourceType) {

                            self.dropOK = false;

                            if (me.noDropCls) {
                                Ext.fly(target.node).addCls(me.noDropCls);
                            } else {
                                Ext.fly(target.node).applyStyles({
                                    backgroundColor: me.noDropBackgroundColor
                                });
                            }

                            return false;
                        }

                        self.dropOK = true;

                        if (me.dropCls) {
                            Ext.fly(target.node).addCls(me.dropCls);
                        } else {
                            Ext.fly(target.node).applyStyles({
                                backgroundColor: me.dropBackgroundColor
                            });
                        }
                    },

                    // Return the class name to add to the drag proxy. This provides a visual indication
                    // of drop allowed or not allowed.
                    onNodeOver: function (target, dd, e, dragData) {
                        return this.dropOK ? this.dropAllowed : this.dropNotAllowed;
                    },

                    // Highlight the target node.
                    onNodeOut: function (target, dd, e, dragData) {
                        var cls = this.dropOK ? me.dropCls : me.noDropCls;

                        if (cls) {
                            Ext.fly(target.node).removeCls(cls);
                        } else {
                            Ext.fly(target.node).applyStyles({
                                backgroundColor: ''
                            });
                        }
                    },

                    // Process the drop event if we have previously ascertained that a drop is OK.
                    onNodeDrop: function (target, dd, e, dragData) {
                        if (this.dropOK) {
                            target.record.set(target.columnName, dragData.record.get(dragData.columnName));
                            if (me.applyEmptyText) {
                                dragData.record.set(dragData.columnName, me.emptyText);
                            }
                            return true;
                        }
                    },

                    onCellDrop: Ext.emptyFn
                });
            }
        }
    });