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);
}
});
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
}
});
Update 2: This confirms the cpuchartclicked event is being fired, because the alert('hello') works. What I need is to be able to respond to that event in my controller.
items: [
{
xtype: 'image',
itemId: 'graphiteChartCPU',
src: '',
listeners:{
'afterrender':function (comp) {
comp.getEl().on('click', function (el) {
this.fireEvent('cpuchartclicked');
}, this);
},
'cpuchartclicked':function () {
alert('hello');
}
}
}
]
Update: With the following, I am setting the scope for the on click handler. fireEvent() seems to be working now, but still not hearing the event in controller.
items: [
{
xtype: 'image',
itemId: 'graphiteChartCPU',
src: '',
listeners:{
'afterrender':function (comp) {
comp.getEl().on('click', function (el) {
alert('on click handler');
this.fireEvent('cpuchartclicked');
}, this);
}
}
}
]
// Listen Application Event
me.application.on({
cpuchartclicked: me.onChartClicked,
scope: me
});
I'm trying to fire a custom event on an image el so when the image is clicked the controller hears the event.
But I get an error cmp.fireEvent() is not a function?
items: [{
xtype: 'image',
itemId: 'graphiteChartCPU',
src: '',
listeners:{
'afterrender':function (comp) {
comp.getEl().on('click', function (el) {
el.fireEvent('cpuchartclicked');
});
}
}
}]
me.application.on({
cpuchartclicked: this.onChartClicked,
scope: this
});
You are confusing Components and Elements. The afterrender event listener is set on the Image Component, and receives the Component itself as the first argument (that you named el instead, incorrectly).
Then, in the afterrender listener, you retrieve the main DOM element for that Component, which happens to be an <img> tag, and set click listener on the element object, which is an instance of Ext.dom.Element.
The click event signature is not what you expect; the first argument is an Ext.util.Event object that does not have a fireEvent method. Hence the error.
I would suggest looking up event signatures in the docs before using them. Also try to add a debugger statement before the line that blows up, and see what variables are passed in and what is going on. Using Chrome or Firefox debugger can be immensely helpful here.
Solved it, though I am wondering if this is the best way to do it.
I fire the custom event via the image, and then listen for that event on the image as well in the controller.
items: [
{
xtype: 'image',
itemId: 'graphiteChartCPU',
src: '',
listeners:{
'afterrender':function (comp) {
comp.getEl().on('click', function (el) {
this.fireEvent('cpuchartclicked');
}, this);
}
}
}
]
'myContainer image':
{
'cpuchartclicked': me.onChartClicked
}
you must bind to the image element like that:
{
xtype: 'image',
itemId: 'graphiteChartCPU',
src: '',
listeners: {
el: {
click: function() {
console.log("Click");
}
}
}
}
it solve my problem in ext5
I using extensible-1.5.1 and i run app in
extensible-1.5.1/examples/calendar/TestApp/test-app.html
I try to custom Event Form window by add a new textfield into this form. Here is form default
But i can't find file to edit.
I think that in extensible-1.5.1\src\calendar\form\EventWindow.js. But when i remove src folder then project still working and nothing change?
How to do that thanks
Edit
I found that in extensible-all-debug.js file. But that file is really complex
How to config to use data in extensible-1.5.1\src\ like calendar at extjs example
You are correct in that the class you need to use is Extensible.calendar.form.EventWindow. However, instead of editing that file, you should extend that class and make your own version of it. You can use that file as a guide, and override the getFormItemConfigs function to modify the form as you need it:
Ext.define("MyApp.view.EventWindow", {
extend: "Extensible.calendar.form.EventWindow",
modal: true,
enableEditDetails: false,
initComponent: function()
{
this.callParent();
},
getFormItemConfigs: function() {
var items = [/*your form items here */];
return items;
},
//... other stuff here maybe...
});
Then, you can override the Extensible.calendar.view.AbstractCalendar to use the class you just made:
Ext.define("MyApp.view.AbstractCalendarOverride", {
override: 'Extensible.calendar.view.AbstractCalendar',
getEventEditor : function()
{
this.editWin = this.ownerCalendarPanel.editWin;
if(!this.editWin)
{
//Change this line:
this.ownerCalendarPanel.editWin = Ext.create('MyApp.view.EventWindow', {
id: 'ext-cal-editwin',
calendarStore: this.calendarStore,
modal: this.editModal,
enableEditDetails: this.enableEditDetails,
listeners: {
'eventadd': {
fn: function(win, rec, animTarget) {
//win.hide(animTarget);
win.currentView.onEventAdd(null, rec);
},
scope: this
},
'eventupdate': {
fn: function(win, rec, animTarget) {
//win.hide(animTarget);
win.currentView.onEventUpdate(null, rec);
},
scope: this
},
'eventdelete': {
fn: function(win, rec, animTarget) {
//win.hide(animTarget);
win.currentView.onEventDelete(null, rec);
},
scope: this
},
'editdetails': {
fn: function(win, rec, animTarget, view) {
// explicitly do not animate the hide when switching to detail
// view as it looks weird visually
win.animateTarget = null;
win.hide();
win.currentView.fireEvent('editdetails', win.currentView, rec);
},
scope: this
},
'eventcancel': {
fn: function(win, rec, animTarget){
this.dismissEventEditor(null, animTarget);
win.currentView.onEventCancel();
},
scope: this
}
}
});
}
// allows the window to reference the current scope in its callbacks
this.editWin.currentView = this;
return this.editWin;
}
});
This may not give you exactly what you need, but hopefully it puts you on the right track.
I create some items in initComponent()
Problem is, this.items somehow referes to the class variable, not to the instance variable.
So when I make two instances, I end up with two buttons.
items: [],
initComponent: function() {
this.items.push( { xtype: 'button', ... }) ;
this.callParent( arguments );
}
Since I have to use push, every time new elements get pushed in.
Is there some instance equivalent to this.items where I can modify the definition before the button gets created or do I have to check for duplicates manually?
You shouldn't return this.callParent( arguments );
Just this is enough:
initComponent: function() {
var me = this;
me.items = { xtype: 'button', ... }; //Are you sure items is filled up here?
me.callParent();
}
Also if you're writing your own 'component' and you want to pass parameters in the Ext.create I always do the following:
constructor: function (config) {
var me = this;
Ext.apply(me, config);
me.callParent();
}
This will overwrite your items declaration in your class with the one you hand in the Ext.create()
You could overload the constructor and tweak the config in there:
constructor: function(config)
{
config.items.someButton = { xtype: 'button', ... };
this.callParent([config]);
}
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/.