/** * The SortableList plugin gives your list items the ability to be reordered by tapping and * dragging elements within the item. * * The list-sortablehandle is not added to your tpl by default, so it's important that you * manually include it. It's also important to recognize that list-items are not draggable * themselves. You must add an element to the itemTpl for it to be dragged. * * Ext.Viewport.add({ * xtype: 'list', * infinite: true, * plugins: { * sortablelist: true * }, * itemTpl: '<span class="myStyle ' + Ext.baseCSSPrefix + 'list-sortablehandle">' + * '</span>{text}', * data: [{ * text: 'Item 1' * }, { * text: 'Item 2' * }, { * text: 'Item 3' * }] * }); * * The CSS for MyStyle can be anything that creates an element to tap and drag. For this * example we made a simple rectangle like so: * * .myStyle{ * width:30px; * height:20px; * background:gray; * float:left; * } * * Note: You must have infinite set to 'true' when using the SortableList plugin. * */Ext.define('Ext.dataview.plugin.SortableList', { extend: 'Ext.plugin.Abstract', alias: 'plugin.sortablelist', alternateClassName: 'Ext.plugin.SortableList', requires: [ 'Ext.drag.Source', 'Ext.drag.proxy.Original' ], config: { list: null, source: { xclass: 'Ext.drag.Source', handle: '.' + Ext.baseCSSPrefix + 'list-sortablehandle', constrain: { vertical: true }, proxy: { getElement: function(info) { return this.getSource().list.mapToItem(info.initialEvent).el; } } } }, init: function(list) { this.setList(list); }, updateList: function(list) { var source; if (list) { source = this.getSource(); if (source) { source.list = list; source.setElement(list.getRenderTarget()); } } }, applySource: function(source) { if (source) { source = Ext.create(source); } return source; }, updateSource: function(source, oldSource) { var list = this.getList(); Ext.destroy(oldSource); if (source) { source.on({ scope: this, dragstart: 'onDragStart', dragmove: 'onDrag', dragend: 'onDragEnd' }); if (list) { source.list = list; source.setElement(list.getRenderTarget()); } } }, onDragStart: function(source, info) { var list = this.getList(), item = list.mapToItem(info.initialEvent); item.addCls(Ext.baseCSSPrefix + 'item-no-ripple'); // Clear the translate since drag uses left/top, we'll set it back to // use translate in onDragEnd item.translate(0, 0); info.item = item; info.startIndex = item.getRecordIndex(); info.listTop = list.element.getY(); // y page coordinate (drag is in page) info.itemHeight = item.el.measure('h'); info.halfHeight = info.itemHeight / 2; list.stickItem(item, { floated: true }); }, onDrag: function(source, info) { var list = this.getList(), gaps = {}, // "cursor page y - list's el page y" localizes y to list client coords: clientY = Math.max(0, info.cursor.current.y - info.listTop), // add list's currently rendered scroll top = y pix in infinite range: idx = list.bisectPosition(clientY + list.getVisibleTop() + info.halfHeight); // bisectPos returns index in dataItems[], so convert to store index: info.index = idx = idx + list.renderInfo.indexTop; gaps[idx] = info.itemHeight; list.setGaps(gaps); }, onDragEnd: function(source, info, e) { var me = this, list = me.getList(), item = info.item, style = item.el.dom.style, store = list.getStore(), index = info.index, compareItem = list.mapToItem(store.getAt(index)), top, pos, startIndex, rec; item.getTranslatable().on('animationend', function() { if (me.destroyed) { return; } startIndex = info.startIndex; rec = item.getRecord(); list.stickItem(item); list.setGaps(null); item.removeCls(Ext.baseCSSPrefix + 'item-no-ripple'); if (startIndex === index) { return; } store.insert(index, rec); index = store.indexOf(rec); list.fireEvent('dragsort', list, list.mapToItem(rec), index); }, me, { single: true }); // Since we are dragging inside the scroller we do not want this event to bubble up to the // scroller. This will cause the scroller to scroll and the list to 'jump'. e.stopPropagation(); pos = compareItem ? compareItem.$y0 : list.mapToItem(store.getAt(index - 1)).$y1; // Dragging uses left/top, so move the coordinate space back // to use translation. This is essentially a no-op as far as // the position is concerned. See onDragStart. top = item.element.getTop(true); style.left = style.top = ''; item.translate(0, top); item.translate(null, pos, { duration: 100 }); }});