Ext JS Reordering a drag and drop list - extjs

I have followed the tutorial over at http://www.sencha.com/learn/Tutorial:Custom_Drag_and_Drop_Part_1
It is great, however now I need pointers on how to add functionally of being to be able reorder a single list. At the moment when I drop a item on the list it is appended at the end. However I wish to be able to drag a item between two others or to the front then drop it there.
Any advice appreciated, thanks.

I found Ext.GridPanel row sorting by drag and drop working example in blog http://hamisageek.blogspot.com/2009/02/extjs-tip-sortable-grid-rows-via-drag.html
It worked fine for me. Here my js code:
app.grid = new Ext.grid.GridPanel({
store: app.store,
sm: new Ext.grid.RowSelectionModel({singleSelect:false}),
cm: new Ext.grid.ColumnModel({
columns: app.colmodel
}),
ddGroup: 'dd',
enableDragDrop: true,
listeners: {
"render": {
scope: this,
fn: function(grid) {
// Enable sorting Rows via Drag & Drop
// this drop target listens for a row drop
// and handles rearranging the rows
var ddrow = new Ext.dd.DropTarget(grid.container, {
ddGroup : 'dd',
copy:false,
notifyDrop : function(dd, e, data){
var ds = grid.store;
// NOTE:
// you may need to make an ajax call
// here
// to send the new order
// and then reload the store
// alternatively, you can handle the
// changes
// in the order of the row as
// demonstrated below
// ***************************************
var sm = grid.getSelectionModel();
var rows = sm.getSelections();
if(dd.getDragData(e)) {
var cindex=dd.getDragData(e).rowIndex;
if(typeof(cindex) != "undefined") {
for(i = 0; i < rows.length; i++) {
ds.remove(ds.getById(rows[i].id));
}
ds.insert(cindex,data.selections);
sm.clearSelections();
}
}
// ************************************
}
})
// load the grid store
// after the grid has been rendered
this.store.load();
}
}
}
});

If you had hbox layout with 3 Grid side by side
new Ext.Panel(
{
layout: "hbox",
anchor: '100% 100%',
layoutConfig:
{
align: 'stretch',
pack: 'start'
},
items: [GridPanel1, GridPanel2, GridPanel3
})
then you must juse grid El instead of container
var ddrow = new Ext.dd.DropTarget(grid.getEl(), { ....

Related

Delete row within sortable: true in Grid with Local Data

I try to make a gridpanel with local data http://jsfiddle.net/8um4T/
This grid has add and delete within sortable: true in column, I will delete record by id (but my way fail with large data)
Here is my data
var simpleData = [];
var store = new Ext.data.ArrayStore({
fields: ['id', 'name',],
data: simpleData
});
for (i = 0; i < 20; i++) {
simpleData.push({id:''+i+'', name: 'name'+i});
}
store.loadData(simpleData);
My tbar with a add button
tbar:[
{
text:'Add',
handler:function(){
simpleData.push({id:'x', name: 'name'});
store.loadData(simpleData);
}
}
]
My action column
{
header: '',
xtype: 'actioncolumn'
, width: 50
, items: [{ // Delete button
icon: 'http://whatisextjs.com/BAHO/icons/cancel.png',
tooltip: 'Delete'
, handler: function(grids, rowIndex, colindex) {
var record = grid.getStore().getAt(rowIndex);
//Delete item in array
// if data is large will don't working
Ext.each(simpleData, function (items, idx) {
if (items.id == record.data.id) {
simpleData.splice(idx, 1);
}
});
//Delete record in store
grid.store.removeAt(rowIndex);
}
}]
}
if mydata is small then delete button will working. My data in my example is 20 record and that don't work
my idea is remove record from store after assign for simpleData. But how to do that or has another anyway to fix my problem thanks
//grid.store.removeAt(rowIndex);
// simpleData = grid.store.data; // my idea is (but how)
You need to break the loop after splicing the array by returning false. You are taking away one of the elements but the loop is not aware of it. So the last element will be undefined.
The loop will fail because the last 'items' parameter is not an object. So "items" is not an object and does not have an id.
The result is that you are removing that object from the array but you never get to the grid.store.removeAt(rowIndex); sentence. I would do:
Ext.each(simpleData, function (items, idx) {
if (items.id == record.data.id) {
simpleData.splice(idx, 1);
return false;
}
});

Dynamic scrolling in combobox ext 4.0

I am using extjs 4.0 and having a combobox with queryMode 'remote'. I fill it with data from server. The problem is that the number of records from server is too large, so I thought it would be better to load them by parts. I know there is a standart paginator tool for combobox, but it is not convinient because needs total number of records.
Is there any way to add dynamic scrolling for combobox? When scrolling to the bottom of the list I want to send request for the next part of records and add them to the list. I can not find appropriate listener to do this.
The following is my solution for infinite scrolling for combobox, Extjs 4.0
Ext.define('TestProject.testselect', {
extend:'Ext.form.field.ComboBox',
alias: ['widget.testselect'],
requires: ['Ext.selection.Model', 'Ext.data.Store'],
/**
* This will contain scroll position when user reaches the bottom of the list
* and the store begins to upload data
*/
beforeRefreshScrollTop: 0,
/**
* This will be changed to true, when there will be no more records to upload
* to combobox
*/
isStoreEndReached : false,
/**
* The main thing. When creating picker, we add scroll listener on list dom element.
* Also add listener on load mask - after load mask is hidden we set scroll into position
* that it was before new items were loaded to list. This prevents 'jumping' of the scroll.
*/
createPicker: function() {
var me = this,
picker = me.callParent(arguments);
me.mon(picker, {
'render' : function() {
Ext.get(picker.getTargetEl().id).on('scroll', me.onScroll, me);
me.mon(picker.loadMask, {
'hide' : function() {
Ext.get(picker.id + '-listEl').scroll("down", me.beforeRefreshScrollTop, false);
},
scope: me
});
},
scope: me
});
return picker;
},
/**
* Method which is called when user scrolls the list. Checks if the bottom of the
* list is reached. If so - sends 'nextPage' request to store and checks if
* any records were received. If not - then there is no more records to load, and
* from now on if user will reach the bottom of the list, no request will be sent.
*/
onScroll: function(){
var me = this,
parentElement = Ext.get(me.picker.getTargetEl().id),
parentElementTop = parentElement.getScroll().top,
scrollingList = Ext.get(me.picker.id+'-items');
if(scrollingList != undefined) {
if(!me.isStoreEndReached && parentElementTop >= scrollingList.getHeight() - parentElement.getHeight()) {
var multiselectStore = me.getStore(),
beforeRequestCount = multiselectStore.getCount();
me.beforeRefreshScrollTop = parentElementTop;
multiselectStore.nextPage({
params: this.getParams(this.lastQuery),
callback: function() {
me.isStoreEndReached = !(multiselectStore.getCount() - beforeRequestCount > 0);
}
});
}
}
},
/**
* Took this method from Ext.form.field.Picker to collapse only if
* loading finished. This solve problem when user scrolls while large data is loading.
* Whithout this the list will close before finishing update.
*/
collapse: function() {
var me = this;
if(!me.getStore().loading) {
me.callParent(arguments);
}
},
/**
* Reset scroll and current page of the store when loading all profiles again (clicking on trigger)
*/
doRawQuery: function() {
var me = this;
me.beforeRefreshScrollTop = 0;
me.getStore().currentPage = 0;
me.isStoreEndReached = false;
me.callParent(arguments);
}
});
When creating element, should be passed id to the listConfig, also I pass template for list, because I need it to be with id. I didn't find out more elegant way to do this. I appreciate any advice.
{
id: 'testcombo-multiselect',
xtype: 'testselect',
store: Ext.create('TestProject.testStore'),
queryMode: 'remote',
queryParam: 'keyword',
valueField: 'profileToken',
displayField: 'profileToken',
tpl: Ext.create('Ext.XTemplate',
'<ul id="ds-profiles-boundlist-items"><tpl for=".">',
'<li role="option" class="' + Ext.baseCSSPrefix + 'boundlist-item' + '">',
'{profileToken}',
'</li>',
'</tpl></ul>'
),
listConfig: {
id: 'testcombo-boundlist'
}
},
And the store:
Ext.define('TestProject.testStore',{
extend: 'Ext.data.Store',
storeId: 'teststore',
model: 'TestProject.testModel',
pageSize: 13, //the bulk of records to receive after each upload
currentPage: 0, //server side works with page numeration starting with zero
proxy: {
type: 'rest',
url: serverurl,
reader: 'json'
},
clearOnPageLoad: false //to prevent replacing list items with new uploaded items
});
Credit to me1111 for showing the way.
Ext.define('utils.fields.BoundList', {
override:'Ext.view.BoundList',
///#function utils.fields.BoundList.loadNextPageOnScroll
///Add scroll listener to load next page if true.
///#since 1.0
loadNextPageOnScroll:true,
///#function utils.fields.BoundList.afterRender
///Add scroll listener to load next page if required.
///#since 1.0
afterRender:function(){
this.callParent(arguments);
//add listener
this.loadNextPageOnScroll
&&this.getTargetEl().on('scroll', function(e, el){
var store=this.getStore();
var top=el.scrollTop;
var count=store.getCount()
if(top>=el.scrollHeight-el.clientHeight//scroll end
&&count<store.getTotalCount()//more data
){
//track state
var page=store.currentPage;
var clearOnPageLoad=store.clearOnPageLoad;
store.clearOnPageLoad=false;
//load next page
store.loadPage(count/store.pageSize+1, {
callback:function(){//restore state
store.currentPage=page;
store.clearOnPageLoad=clearOnPageLoad;
el.scrollTop=top;
}
});
}
}, this);
},
});
You can implement the infinite grid as list of the combobox.
Look at this example to implement another picker:
http://www.sencha.com/forum/showthread.php?132328-CLOSED-ComboBox-using-Grid-instead-of-BoundList
If anyone needs this in ExtJS version 6, here is the code:
Ext.define('Test.InfiniteCombo', {
extend: 'Ext.form.field.ComboBox',
alias: ['widget.infinitecombo'],
/**
* This will contain scroll position when user reaches the bottom of the list
* and the store begins to upload data
*/
beforeRefreshScrollTop: 0,
/**
* This will be changed to true, when there will be no more records to upload
* to combobox
*/
isStoreEndReached: false,
/**
* The main thing. When creating picker, we add scroll listener on list dom element.
* Also add listener on load mask - after load mask is hidden we set scroll into position
* that it was before new items were loaded to list. This prevents 'jumping' of the scroll.
*/
createPicker: function () {
var me = this,
picker = me.callParent(arguments);
me.mon(picker, {
'afterrender': function () {
picker.on('scroll', me.onScroll, me);
me.mon(picker.loadMask, {
'hide': function () {
picker.scrollTo(0, me.beforeRefreshScrollTop,false);
},
scope: me
});
},
scope: me
});
return picker;
},
/**
* Method which is called when user scrolls the list. Checks if the bottom of the
* list is reached. If so - sends 'nextPage' request to store and checks if
* any records were received. If not - then there is no more records to load, and
* from now on if user will reach the bottom of the list, no request will be sent.
*/
onScroll: function () {
var me = this,
parentElement = me.picker.getTargetEl(),
scrollingList = Ext.get(me.picker.id + '-listEl');
if (scrollingList != undefined) {
if (!me.isStoreEndReached && me.picker.getScrollY() + me.picker.getHeight() > parentElement.getHeight()) {
var store = me.getStore(),
beforeRequestCount = store.getCount();
me.beforeRefreshScrollTop = me.picker.getScrollY();
store.nextPage({
params: this.getParams(this.lastQuery),
callback: function () {
me.isStoreEndReached = !(store.getCount() - beforeRequestCount > 0);
}
});
}
}
},
/**
* Took this method from Ext.form.field.Picker to collapse only if
* loading finished. This solve problem when user scrolls while large data is loading.
* Whithout this the list will close before finishing update.
*/
collapse: function () {
var me = this;
if (!me.getStore().loading) {
me.callParent(arguments);
}
},
/**
* Reset scroll and current page of the store when loading all profiles again (clicking on trigger)
*/
doRawQuery: function () {
var me = this;
me.beforeRefreshScrollTop = 0;
me.getStore().currentPage = 1;
me.isStoreEndReached = false;
me.callParent(arguments);
}
});
First of all thanks to #me1111 for posting the code for dynamic rendering on scroll. That code is working for me after doing a small change.
tpl: Ext.create('Ext.XTemplate',
'<ul id="testcombo-boundlist-items"><tpl for=".">',
'<li role="option" class="' + Ext.baseCSSPrefix + 'boundlist-item' + '">',
'{profileToken}',
'</li>',
'</tpl></ul>'
),
listConfig: {
id: 'testcombo-boundlist'
}
in code from <ul id="ds-profiles-boundlist-items"><tpl for="."> i have changed id to "testcombo-boundlist-items". as we defined id as 'testcombo-boundlist' in listConfig.
Before modification, in onScroll method scrollingList = Ext.get(me.picker.id + '-items'); i am getting scrollList as a null. because me.picker.id will return 'testcombo-boundlist' on this we are appending '-items' so it became 'testcombo-boundlist-items' but this id does not exists. so iam getting null.
After modification of id in <ul id="testcombo-boundlist-items"> we have a list object on "testcombo-boundlist-items". so i got a object.
Hope this small change will help.

Drag and Drop from Grid to Tree and backwards

Since a few days now I try to change an ExtJs ['Grid to Tree Drag and Drop' Example][1] to work in both directions. But all I get is an 'almost' working application.
Now it works as far as I can drag an item from grid to tree, within tree, within grid but if i drag it from tree to grid, it doesn't drop. It just shows the green hook.
I also tried to differ the ddGroup from tree and grid, but then nothing works anymore.
This is too much for an ExtJs beginner.
// Stücklisten Grid
stuecklistengrid = Ext.extend(Ext.grid.GridPanel, {
initComponent:function() {
var config = {
store:itemPartStore
,columns:[{
id:'PART_ITE_ID'
,header:"PART_ITE_ID"
,width:200, sortable:true
,dataIndex:'PART_ITE_ID'
},{
header:"IS_EDITABLE"
,width:100
,sortable:true
,dataIndex:'IS_EDITABLE'
},{
header:"IS_VISIBLE"
,width:100
,sortable:true
,dataIndex:'IS_VISIBLE'
}]
,viewConfig:{forceFit:true}
}; // eo config object
// apply config
Ext.apply(this, Ext.apply(this.initialConfig, config));
this.bbar = new Ext.PagingToolbar({
store:this.store
,displayInfo:true
,pageSize:10
});
// call parent
stuecklistengrid.superclass.initComponent.apply(this, arguments);
} // eo function initComponent
,onRender:function() {
// call parent
stuecklistengrid.superclass.onRender.apply(this, arguments);
// load the store
this.store.load({params:{start:0, limit:10}});
} // eo function onRender
});
Ext.reg('examplegrid', stuecklistengrid);
// Stücklisten Tree
var CheckTree = new Ext.tree.TreePanel({
root:{ text:'root', id:'root', expanded:true, children:[{
text:'Folder 1'
,qtip:'Rows dropped here will be appended to the folder'
,children:[{
text:'Subleaf 1'
,qtip:'Subleaf 1 Quick Tip'
,leaf:true
}]
},{
text:'Folder 2'
,qtip:'Rows dropped here will be appended to the folder'
,children:[{
text:'Subleaf 2'
,qtip:'Subleaf 2 Quick Tip'
,leaf:true
}]
},{
text:'Leaf 1'
,qtip:'Leaf 1 Quick Tip'
,leaf:true
}]},
loader:new Ext.tree.TreeLoader({preloadChildren:true}),
enableDD:true,
ddGroup:'grid2tree',
id:'tree',
region:'east',
title:'Tree',
layout:'fit',
width:300,
split:true,
collapsible:true,
autoScroll:true,
listeners:{
// create nodes based on data from grid
beforenodedrop:{fn:function(e) {
// e.data.selections is the array of selected records
if(Ext.isArray(e.data.selections)) {
// reset cancel flag
e.cancel = false;
// setup dropNode (it can be array of nodes)
e.dropNode = [];
var r;
for(var i = 0; i < e.data.selections.length; i++) {
// get record from selectons
r = e.data.selections[i];
// create node from record data
e.dropNode.push(this.loader.createNode({
text:r.get('PART_ITE_ID')
,leaf:true
,IS_EDITABLE:r.get('IS_EDITABLE')
,IS_VISIBLE:r.get('IS_VISIBLE')
}));
}
// we want Ext to complete the drop, thus return true
return true;
}
// if we get here the drop is automatically cancelled by Ext
}}
}
});
// Stücklisten Container
var itemPartList = new Ext.Container({
id: 'itemPartList',
title: 'Stücklisten',
border:false,
layout:'border',
items:[CheckTree, {
xtype:'examplegrid'
,id:'SLgrid'
,title:'Grid'
,region:'center'
,layout:'fit'
,enableDragDrop:true
,ddGroup:'grid2tree'
,listeners: {
afterrender: {
fn: function() {
// This will make sure we only drop to the view scroller element
SLGridDropTargetEl2 = Ext.getCmp('SLgrid').getView().scroller.dom;
SLGridDropTarget2 = new Ext.dd.DropTarget(SLGridDropTargetEl2, {
ddGroup : 'grid2tree',
notifyDrop : function(ddSource, e, data){
var records = ddSource.dragData.selections;
Ext.each(records, ddSource.grid.store.remove,
ddSource.grid.store);
Ext.getCmp('SLgrid').store.add(records);
return true
}
});
}
}
}
}]
});
You need to implement the onNodeDrop event of the grid. See http://docs.sencha.com/ext-js/4-1/#!/api/Ext.grid.header.DropZone-method-onNodeDrop

ExtJS 4 > Row Editor Grid > How to Change "Update" Button Text

Is there any way to change the text of "Update" button in ExtJS-4 Row Editor Grid ?
Good question, I had a look through the source code and whilst there is nothing inside the RowEditing plugin, in the class it extends 'RowEditor.js' there is the following:
Ext.define('Ext.grid.RowEditor', {
extend: 'Ext.form.Panel',
requires: [
'Ext.tip.ToolTip',
'Ext.util.HashMap',
'Ext.util.KeyNav'
],
saveBtnText : 'Update',
cancelBtnText: 'Cancel',
...
});
So I'd assume you'd just need to override the 'saveBtnText' in your instance of 'Ext.grid.plugin.RowEditing' as it calls the parent constructor with callParent(arguments) in the RowEditing class
Not that easy and not without hacking in undocumented areas. The problem is, that the Ext.grid.plugin.RowEditing directly instantiates the Ext.grid.RowEditor without allowing you to pass in configuration options. So in general you have to override the initEditor() method in the plugin and instantiate your own row editor:
// ...
plugins: [{
ptype: 'rowediting',
clicksToEdit: 2,
initEditor: function() {
var me = this,
grid = me.grid,
view = me.view,
headerCt = grid.headerCt;
return Ext.create('Ext.grid.RowEditor', {
autoCancel: me.autoCancel,
errorSummary: me.errorSummary,
fields: headerCt.getGridColumns(),
hidden: true,
// keep a reference..
editingPlugin: me,
renderTo: view.el,
saveBtnText: 'This is my save button text', // <<---
cancelBtnText: 'This is my cancel button text' // <<---
});
},
}],
// ...
For ExtJS 4
Ext.grid.RowEditor.prototype.cancelBtnText = "This is cancel";
Ext.grid.RowEditor.prototype.saveBtnText = "This is update";
This solution is to define the prototype of rowEditors. that means that this config is than general.
If you want to change it just for one editor, or if you want to get different configs , the prototype is definitely not the solution.
look at source code :
initEditorConfig: function(){
var me = this,
grid = me.grid,
view = me.view,
headerCt = grid.headerCt,
btns = ['saveBtnText', 'cancelBtnText', 'errorsText', 'dirtyText'],
b,
bLen = btns.length,
cfg = {
autoCancel: me.autoCancel,
errorSummary: me.errorSummary,
fields: headerCt.getGridColumns(),
hidden: true,
view: view,
// keep a reference..
editingPlugin: me
},
item;
for (b = 0; b < bLen; b++) {
item = btns[b];
if (Ext.isDefined(me[item])) {
cfg[item] = me[item];
}
}
return cfg;
}`
this method inits the rowEditor, and there's a loop on btns Array:
btns Array :
btns = ['saveBtnText', 'cancelBtnText', 'errorsText', 'dirtyText']
for (b = 0; b < bLen; b++) {
item = btns[b];
if (Ext.isDefined(me[item])) {
cfg[item] = me[item];
}
}
In this loop foreach string in btnArray it's searched if exists in cfg the same string property, if it's found it's added to config. You just have to manage that this loop finds what you want to modify:
Example: we want to change the text of save button:
the property saveBtnText which is the first item of btns Array must exists in cfg:
if (Ext.isDefined(me[item])) {
cfg[item] = me[item];
}
this search if property exists : if (Ext.isDefined(me[item]))
if saveBtnText already exists in rowEditor properties then:
cfg[item] = me[item];
and the additional config property will be set!!

extjs rowexpander how to expand all

I'm using Ext.ux.grid.RowExpander
var expander = new Ext.ux.grid.RowExpander({
tpl : new Ext.Template(
'<p>{history}</p>'
)
});
it's used in my grid:
var grid = new Ext.grid.GridPanel({
store: store,
columns: [
expander,
...
Now i want all expander rows to be expanded by deafult.
I'm trying to use expander.expandRow(grid.view.getRow(0)); (i think if i make it, i'll be able to use a for loop :) but i get an error
this.mainBody is undefined # ***/ext/ext-all.js:11
Please, help me to expand all rows of my grid! Thanks!
You can do this with a loop, it's quite simple...
for(i = 0; i <= pageSize; i++) {
expander.expandRow(i);
}
Where pageSize is the number of records per page in your grid. Alternatively you could use the store's count (probably a more scalable solution)...
for(i = 0; i <= grid.getStore().getCount(); i++) {
expander.expandRow(i);
}
In 4.1.3 i use this method
function expand() {
var expander = grid.plugins[0];
var store = grid.getStore();
for ( i=0; i < store.getCount(); i++ ) {
if (expander.isCollapsed(i)) {
expander.toggleRow(i, store.getAt(i));
}
}
}
If your Grid uses a DirectStore or some other RPC mechanism, you may want to listen for the store's load event:
grid.store.addListener('load', function() {
var expander = grid.plugins;
for(i = 0; i < grid.getStore().getCount(); i++) {
expander.expandRow(i);
}
}
Btw: It should be "i < ..." instead of "i <= ...".
You can declare a grouping object and then call it from within your GridPanel:
// grouping
var grouping = Ext.create('Ext.grid.feature.Grouping',{
startCollapsed: true, // sets the default init collapse/expand all
});
var grid = new Ext.grid.GridPanel({
store: store,
columns: [
expander,
...
Then add this code in the body of you GridPanel:
// collapse/expand all botton
tbar: [{
text: 'collapse all',
handler: function (btn) {
grouping.collapseAll();
}
},{
text: 'expand all',
handler: function (btn) {
grouping.expandAll();
}
}],
It will add two buttons that expand/collapse all the groups.
If you want everything to be expanded/collapsed by default notice the 'startCollapsed' variable above.

Resources