I'm using the rowediting plugin and have a beforeedit listener. I need to find a way to make an Ext.Ajax.request in my beforeedit function to determine if the row should be locked or not. The reason is to make sure some other user isn't already editing a particular row. If I find the row should be locked I would return false.
Anything I've tried with defer hasn't stopped the function from completing and essentially returning true. Ext.Promise looks like what I might need, but I have no idea how to use it.
First disable how ExtJS starts row editing by default. Where you define your grid, within the plugins part, add this:
plugins: [{
ptype: 'rowediting',
onCellClick: function(view, cell, colIdx, record, row, rowIdx, e) {
return; // this will disable starting row editor on click
}
}]
Then add a custom handler to start row editing, make the Ajax call there, and only start editing if the row is editable by your logic. For example you can add an action column with edit icon:
columns: [{
// add the other columns
},{
xtype: 'actioncolumn',
width: 40,
items: [{
iconCls: ...,
tooltip: ...,
handler: function(grid, rowIndex, colIndex) {
const plugin = grid.grid.getPlugin('rowEditing');
// mask the grid for the time of the Ajax call
grid.mask('Please, wait...');
Ext.Ajax.request({
url: ...,
success: function(response, opts) {
grid.unmask();
// if it is ok to edit according to the response
if (...) {
plugin.startEdit(rowIndex);
}
},
failure: function(response, opts) {
grid.unmask();
},
}
}]
}]
Related
I am creating a page which will dynamically generate collapsed panels. When a user expands these panels, it will perform a GET request and populate this generated panel with the JSON response. The idea is to perform a sort of lazy-load or as-needed load, as the amount of data that would be shown initially can get overwhelming.
However, I can't seem to get the listeners for my panels to work.
Here is the code, which generates the panels through a button's click function:
xtype : 'button',
listeners : {
click : function (button, e, eOpts) {
console.log("Click function");
Ext.Ajax.request({
url: 'data/Countries.json',
success: function(response, options) {
var data = Ext.JSON.decode(response.responseText).results;
var container = Ext.getCmp('panelContainer');
container.removeAll();
for (i = 0; i < data.length; i++)
{
container.add({
xtype: 'panel',
title: 'Country Name - ' + data[i].countryName,
collapsible: true,
listeners: {
expand: function() {
Ext.Ajax.request({
url: 'data/CountryData.json',
success: function(response, options) {
var data = Ext.JSON.decode(response.responseText).results;
var me = this;
me.add({
xtype: 'grid',
store: Ext.create('Ext.data.Store',
{
fields : [{
name: 'gdp'
}, {
name: 'rank'
}, {
name: 'founded'
}, {
name: 'governor'
}, {
name: 'notes'
}], //eo fields
data: data.information,
}),// eo store
columns: [
{ text: 'GDP', dataIndex: 'gdp'},
{ text: 'rank', dataIndex: 'rank'},
{ text: 'Date', dataIndex: 'founded'},
{ text: 'name', dataIndex: 'governor'},
{ text: 'Notes', dataIndex: 'notes', flex: 1, cellWrap: true}
], //eo columns
autoLoad: true
});
},
failure: function(response, options) {}
});
},
collapse: function() {
console.log("Collapse function");
var me = this;
me.removeAll();
}
}//eo panel listeners
});//eo cont.add()
}//eo for loop
}, //eo success
failure: function(response, options) {
//HTTP GET request failure
}//eo failure
});//eo Ajax request
} //eo click
}//eo button listeners
Originally, the panels were dynamically generated along with their populated grids from the click event, which worked perfectly. By wrapping the grid creation in a listener on the dynamically generated panel to create a load-as-needed, I can't get the expand or collapse listeners to trigger.
Searching around, one possible solution I haven't tried is to create a custom component and call it through its xtype rather than build everything in-line, which would let me define listeners there instead of nesting them in a function (this is better as well for readable and reusable code, but I'm just trying to get to the root of the issue for now).
Is there an issue with listeners on dynamically generated panels? What is the reason that the event triggers for collapse and expand aren't firing?
Thanks for all the help!
I'm still have a few issues, but as my main question was about firing the listeners, I'll write the solution I reached.
The issue I had was getting listeners to fire in a dynamically generated element. This led to nested listener functions, and I hadn't defined a scope. I had tried pagep's solution of setting the defaultListenerScope, but for me personally I didn't see a change.
I instead wrapped the listener functions into their own functions and called then through the listener like this:
listeners: {
expand: 'expandFunction',
collapse: 'collapseFunction'
},//eo panel listeners
expandFunction: function() {
//Do Ajax request and add grid to panel
},
collapseFunction: function() {
//Remove all child elements from this panel
}
Instead of doing this:
listeners: {
expand: function() {
//Do Ajax request and add grid to panel
},
collapse: function() {
//Remove all child elements from this panel
}
},//eo panel listeners
By wrapping the info this way, I was able (to a certain degree) to remove the nesting of listeners with generated elements. I also created a custom component and placed these listeners with the component I was generating. My only issue now is populating the generated element, since I am getting Uncaught TypeError: Cannot read property 'add' of undefined when trying to reference the itemId of my component.
My final simplified code, which generates a collapsed panel on button-click and populates it with generated data when expanded, looks like this:
//View.js
click: function (button, e, eOpts) {
Ext.Ajax.request({
url: 'data/Countries.json',
success: function(response, options) {
var data = Ext.JSON.decode(response.responseText).results;
var container = Ext.getCmp('panelContainer');
console.log(container);
container.removeAll();
for (i = 0; i < data.length; i++)
{
container.add({
xtype: 'customPanel',
title: data[i].country
});
}
});
//customPanel.js
Ext.define('MyApp.view.main.CustomPanel', {
extend: 'Ext.panel.Panel',
alias: 'widget.customPanel',
xtype: 'panel',
collapsible: true,
collapsed: true,
listeners: {
expand: 'expandFunction',
collapse: 'collapseFunction'
},//eo panel listeners
expandFunction: function() {
//Do Ajax request and add grid to panel
},
collapseFunction: function() {
//Remove all child elements from this panel
}
});
I have a panel with around 10 items. For all those items I have implemented "after render" event handler in respective controllers of each item.
This is how I have registered afterrender events in Controller's init method:
init: function() {
this.control({
'#myFirstPanel': {
afterrender: this.afterrender
}
});
}
My panel code is like:
Ext.define('App.view.popUpWindow', {
extend: 'Ext.panel.Panel',
initComponent: function(){
this.callParent();
},
id: 'popup',
layout: 'fit',
items: [
{
xtype: 'panel'
items: [
{
// One of my custom component
xtype:'FirstGrid'
}....//10 items in total
]
}]
});
When I get hasListeners property of my panel's object after my app loads by following code
Ext.getCmp('popup').hasListeners
It returns me this object
{afterrender: 10, tabchange: 1}
Now when I destroy my panel by this command
Ext.getCmp('popup').destroy()
and open my app again(my app is a sub Extjs app of a another Extjs app)
Ext.getCmp('popup').hasListeners
It returns me this object
{afterrender: 20, tabchange: 2}
My question is that why even after call to destroy(), hasListeners is some how keeping record of old Listeners? I know this object doesn't show the number of listener (I read api) but what is going here then?
Can someone explain this to me? As I have to manually remove old listeners(my other question about this), why not .destroy() is automatically destroying all references?
Thank you.
I'm trying to load the text of a label on the beforerender event. So I attached the beforender event... as below
{
xtype: 'label',
text: 'VOID',
listeners: {
beforerender: {
fn: me.onLabelBeforeRender,
scope: me
}
},
Ext.Ajax.request({
url: '/who',
method: 'GET',
params: {
id: 1
},
success: function(response){
var text = Ext.decode(response.responseText);
alert(text);
// process server response here
}
});
And now I would like to change the label from VOID into the response value of /who However I fail to see how I can access that label in a decent way. Of course I can add an id use getcmp however that seems so clumsy, or is that the way to go?
Actually, as far as performance goes, an id + Ext.getCmp() is the most efficient option. See this question for more.
It will definitely be faster than adding a listener, for which you get quite a healthy call stack.
define the label like this
{
xtype: 'label',
text: 'VOID',
itemId:'someLabel',
listeners: {
beforerender: {
fn: me.onLabelBeforeRender,
scope: me
}
}
now because you are keeping the scope as me which im assuming is some ancestor of label so with in the onLabelBeforeRender function you can access me by the this keyword. so in that function retrieve the label as
var label = this.down('label[itemId="someLabel"]');
label.setText('WHATEVER YOU WANT HERE');
I have an action column in my grid which is needed to perform lots of non-trivial operations after click on it. I don't want to use the handler method only to avoid duplicity in my code. I want to handle the click event from the controller method which can be called from more sides.
Here is my definition of action column:
{
header: translator.translate('actions'),
xtype: 'actioncolumn',
width: 50,
items:[{
id : 'detailContactPerson',
icon : '/resources/images/pencil.png',
tooltip: translator.translate('show_detail')
}]
},
But now I don't know how to write the Component query definition to set up listener.
init: function() {
this.control({
'detailContactPerson': {
click: function(obj) {
var contactPerson = obj.up('container').contactPerson;
this.detail(contactPerson);
}
},
Second way I've tried is to call the method of controller directly from handler method. It looks like this:
{
header: translator.translate('actions'),
xtype: 'actioncolumn',
width: 50,
items:[{
id : 'detailContactPerson',
icon : '/resources/images/pencil.png',
handler: function(contactPerson){
Project.controller.contactPerson.detail(contactPerson);
},
tooltip: translator.translate('show_detail')
}
But unfortunately it isn't supported way to call controller method (No method exception raised).
Could someone help me to construct working Component query, or show some example how to call controller method from outside?
try actioncolumn#detailContactPerson
or you can listene to actioncolumn 's click event
see this: http://www.sencha.com/forum/showthread.php?131299-FIXED-EXTJSIV-1767-B3-ActionColumn-bug-and-issues
init: function() {
this.control({
'contact button[action=add]':{
click: this.addRecord
},
'contact button[action=delete]':{
click: function(){this.deleteRecord()}
},
'contact actioncolumn':{
click: this.onAction
}
});
},
onAction: function(view,cell,row,col,e){
//console.log(this.getActioncolumn(),arguments, e.getTarget())
var m = e.getTarget().className.match(/\bicon-(\w+)\b/)
if(m){
//选择该列
this.getGrid().getView().getSelectionModel().select(row,false)
switch(m[1]){
case 'edit':
this.getGrid().getPlugin('rowediting').startEdit({colIdx:col,rowIdx:row})
break;
case 'delete':
var record = this.getGrid().store.getAt(row)
this.deleteRecord([record])
break;
}
}
}
BTW.I prefer to use these to instead of AcionColumn
Ext.ux.grid.column.ActionButtonColumn
Ext.ux.grid.RowActions
I have a better way: add new events on your view where are presents the actioncolumns:
{
xtype:'actioncolumn',
align:'center',
items:[
{
tooltip:'info',
handler:function (grid, rowIndex, colIndex) {
var rec = grid.getStore().getAt(rowIndex);
//this is the view now
this.fireEvent('edit', this, rec);
},
scope:me
},
....
me.callParent(arguments);
me.addEvents('edit')
then on your controller:
.....
this.control({
'cmp_elenco':{
'edit':function(view,record){//your operations here}
....
I too wanted to handle logic for the actioncolumn in a controller. I am not certain if this is better or worse than simply using one of the other plugins mentioned, however this is how I was able to get it to work.
Things to note:
the id config property in the items array of the actioncolumn
does nothing at all, the icons will still receive a generated id
the items are NOT components, they are simply img elements
you can add an id to the actioncolumn itself to target a specific instance of actioncolumn
each icon (or item in the actioncolumn) is given a class of x-action-col-# where # is an index beginning with 0.
For example, in the columns definition of my grid I have:
header: 'ACTION',
xtype: 'actioncolumn',
id: 'myActionId',
width: 50,
items: [{
icon: 'resources/icons/doThisIcon.png',
tooltip: 'Do THIS'
},{
icon: 'resources/icons/doThatIcon.png',
tooltip: 'Do THAT'
}
]
and in the controller:
init: function(){
this.control({
'actioncolumn#myActionId': {
click: function(grid,cell,row,col,e){
var rec = grid.getStore().getAt(row);
var action = e.target.getAttribute('class');
if (action.indexOf("x-action-col-0") != -1) {
console.log('You chose to do THIS to ' + rec.get('id')); //where id is the name of a dataIndex
}
else if (action.indexOf("x-action-col-1") != -1) {
console.log('You chose to do THAT to ' + rec.get('id'));
}
}
}
}
Using this method, you can place all logic for any given action column in the controller.
Here is a way to avoid declaring the handler (no need to use addEvents, ExtJS 4.1.1) :
Ext.grid.column.Action override :
Ext.grid.column.Action.override({
constructor: function () {
this.callParent(arguments);
Ext.each(this.items, function () {
var handler;
if (this.action) {
handler = this.handler; // save configured handler
this.handler = function (view, rowIdx, colIdx, item, e, record) {
view.up('grid').fireEvent(item.action, record);
handler && handler.apply(this, arguments);
};
}
});
}
});
Action column config :
{
xtype: 'actioncolumn',
items: [{
icon: 'edit.png',
action: 'edit'
}]
}
Controller :
this.control({
'grid': {
edit: function (record) {}
}
});
You can also follow this example http://onephuong.wordpress.com/2011/09/15/data-grid-action-column-in-extjs-4/.
I'm new to Extjs library and I cannot figure out how to add listener to some events in GridPanel. As I'm filling the grid asynchronously I want my function to be executed when we add new element to grid panel.
I found (I think) correct event:
added( Ext.Component this, Ext.container.Container container, Number pos )
but I cannot find a correct place to put the listener as this function is executed only once on the page load:
Ext.define('MyApp.NewsGrid', {
extend: 'Ext.grid.GridPanel',
alias: 'widget.newsgrid',
initComponent: function() {
Ext.apply(this, {
title: 'News',
store: newsStore,
viewConfig: {
plugins: [{
pluginId: 'preview',
ptype: 'preview',
bodyField: 'testo',
expanded: false
}],
listeners: {
add: function() {
alert("add executed");
},
added: function() {
alert("added executed");
}
}
},
....
The problem is that there are different ways to add listeners (inside and outside viewConfig hash, inside and outside component definition etc...) and I cannot figure out one that works in this case. What makes it even more frustrating is that there are many places in the internet with documentation for version 3 or even without specifying the version.
You should listen the add event of Store:
initComponent: function() {
// somewhere inside initComponent...
this.getStore().on("add", function(store, records) {
alert(records.length + " records added");
}, this);
}