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/.
Related
I'm trying to pass two parameters oneOfAll and twoOfAll into a container. From the initComponent function I do:
initComponent: function () {
this.callParent(arguments);
console.log('Results', this.oneOfAll, this.twoOfAll);
}
and the browser's console prints the correct data, but when:
items: [
{
xtype: 'button',
text: 'Other click',
id: 'buttonClick',
handler: function(item) {
Ext.Msg.alert('Other Click', '!!!' + this.oneOfAll);
}
},
{
xtype: 'button',
text: 'CLICK',
id: 'buttonClick_2',
handler: function(item) {
Ext.Msg.alert('Other Click', '!!!' + this.twoOfAll);
}
}
]
these values are not seen by the container config, both times undefined is returned.
I have this fiddle: https://fiddle.sencha.com/#view/editor&fiddle/2sk3
How can I pass these parameters into the container and be seen by it?
You can set reference to current component in initComponent method and use them in child component.
Take a look on example:
https://fiddle.sencha.com/#view/editor&fiddle/2skh
Inside the handler, this refers to the item object, not your component.
To fix this, you could try accessing the component by ID, and get its property directly.
For example (using your mypanel id from your fiddle):
{
xtype: 'button',
text: 'Other click',
id: 'buttonClick',
handler: function (item) {
var cmp = Ext.getCmp('mypanel');
Ext.Msg.alert('Other Click', '~~!!' + cmp.oneOfAll);
}
},
Your handler has a different scope and configs which you set are in different.
In order to fetch the values, get the reference of that component first and then try to access.
handler: function(item) {
Ext.Msg.alert('Other Click', '!!!' + item.up().oneOfAll);
Ext.Msg.alert('Other Click', '!!!' + item.up().twoOfAll);
}
Current scenario is I have data manipulation function inside a class and I call this function when I get data from REST service inside my controller loadData function. Then I update the store of my viewModel.
Now I was wondering Is their a way by which I can concentrate the data manipulation function and store update to view model and from controller I call viewmodel function pass the data from rest service.
Yes you can define function inside of viewmodel and call from controller.
In this FIDDLE, I have created a demo using view-model and controller. I hope this will help you or guide you to achieve your requirement.
Code Snippet
Ext.application({
name: 'MYDEMO',
launch: function () {
Ext.define('FormController', {
extend: 'Ext.app.ViewController',
alias: 'controller.formcntr',
//this will fire on get data button tap
onGetDataButtonTap: function (btn, e, eOpts) {
this.getViewModel().doGetData();
},
//this will fire on set data button tap
onSetDataButtonTap: function (btn, e, eOpts) {
this.getViewModel().doSetData();
}
})
Ext.define('MyViewModel', {
extend: 'Ext.app.ViewModel',
alias: 'viewmodel.myViewModel',
//For setting data inside of viewmodel or somthing diffrent opetraion
doSetData: function (data) {
Ext.Msg.alert('Success', 'doSetData method of ViewModel, called from Controller/view');
//set data from here
//or you can put your logic here whatever you want
//Depend on your requirement
},
//For getting data inside of viewmodel or somthing diffrent opetraion
doGetData: function () {
Ext.Msg.alert('Success', 'doGetData method of ViewModel, called from Controller/view');
//return data from here
//or you can put your logic here whatever you want
//Depend on your requirement
}
});
Ext.create({
xtype: 'panel',
title: 'Users Profile',
fullscreen: true,
layout: 'vbox',
controller: 'formcntr',
viewModel: {
type: 'myViewModel'
},
tbar: [{
text: 'GET Data',
handler: 'onGetDataButtonTap'
}, {
text: 'SET Data',
handler: 'onSetDataButtonTap'
}],
renderTo: Ext.getBody()
});
}
});
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 am using a panel (say parentPanel) having another child panel (say childPanel). How can i fire a custom event from childPanel that will be caught by parentPanel?
Like so:
Ext.define('Ext.ux.form.Panel', {
extend: 'Ext.form.Panel',
title: 'Simple Form',
bodyPadding: 5,
width: 350,
layout: 'anchor',
defaults: {
anchor: '100%'
},
defaultType: 'textfield',
initComponent: function() {
var me = this;
me.items = [{
fieldLabel: 'First Name',
name: 'first',
allowBlank: false
}];
me.callParent(arguments);
},
afterRender: function() {
var me = this;
me.callParent(arguments);
// in case of a form you can also use the findField() method
// I used down() because it will work with all sort of containers
me.down('textfield[name=first]').on('change',me.onFirstnameChange,me);
},
onFirstnameChange: function(field) {
// custom handler
}
});
Doing this just on single instances work the same way expect that you will need to use the afterrender event instead of the static template method.
Note:
I this doesn't fit your needs you will need to post a more detailed question (with example code) for a more detailed answer.
Edit:
Adding a custom event to a new component:
initComponent: function() {
// ...
me.addEvents('customeventname');
// ...
}
you can now register listeners to this events on instances of this component and fire the event by calling fireEvent('customeventname', args) where args are the arguments you want to call each listeners with.
I am trying to get better at extending the classes of Extjs, and my evolvement have lead me to this problem:
I have extended an Ext.Panel and I want my extension to have a bottom toolbar with one button as default.
myPanel = Ext.extend(Ext.Panel, {
method: function () {
return 'response!';
},
bbar: new Ext.Toolbar({
items:
[
{
xtype: 'button',
text: 'Hit me!',
handler: function (button, event) {
alert(this.method());
},
scope: this
}
]
})
});
What I haven't learnt yet is why this is not allowed. this is pointing at the global scope and not my extended panel - thus .method() is undefined inside the handler function.
You're defining the bbar on the prototype rather than on a specific object.
Override initComponent and move the bbar definition inside it.
myPanel = Ext.extend(Ext.Panel, {
method: function () {
return 'response!';
},
initComponent: function() {
var bbar = new Ext.Toolbar({
items:
[
{
xtype: 'button',
text: 'Hit me!',
handler: function (button, event) {
alert(this.method());
},
scope: this
}
]
});
// Config object has already been applied to 'this' so properties can
// be overriden here or new properties (e.g. items, tools, buttons)
// can be added, eg:
Ext.apply(this, {
bbar: bbar
});
// Call parent (required)
myPanel.superclass.initComponent.apply(this, arguments);
// After parent code
// e.g. install event handlers on rendered component
}
});
See http://www.sencha.com/learn/Manual:Component:Extending_Ext_Components for a template you can use when extending components
You have to keep in mind that the anonymous object that is the first element of the items array is created in the same scope as the one in which Ext.extend(... is executed.
If you had this:
var o = { 'a': a, 'b': b, scope: this };
you would expect that o.a, o.b, and o.scope would have the same values as a, b, and this in the current scope. Here, it's a little more complex because you are creating an object while creating an array while creating an object, etc., but the reasoning is the same.
What you should do instead is define this.bbar inside the constructor:
myPanel = Ext.extend(Ext.Panel, {
method: function () {
return 'response!';
},
constructor: function(config) {
this.bbar = new Ext.Toolbar({
items:
[
{
xtype: 'button',
text: 'Hit me!',
handler: function (button, event) {
alert(this.method());
},
scope: this
}
]
});
myPanel.superclass.constructor.apply(this, arguments);
}
});