Is there really no click event for all ExtJS components - extjs

I'm learning extjs 4.1 and I can't find click event on most of the components - I'm I blind or I'm unable to react on click on other components than button?
What is the reason? HTML supports click on all elements.

Every component can have the click event exposed at the element level. Some components like Ext.button.Button have a click event at the component level.
Here is a nice way to add a click event on a panel:
new Ext.panel.Panel({
listeners: {
click: this.handlePanelClick,
element: 'el'
}
});
edit to respond to comment
The element string is a any element that is a property of the component.
new Ext.panel.Panel({
listeners: {
click: function() {
alert('you clicked the body');
},
element: 'body'
}
});
The Ext docs have a more thorough explanation http://docs.sencha.com/ext-js/4-1/#!/api/Ext.util.Observable-method-addListener

You can add any DOM event pretty easily by accessing the Ext.dom.Element object that almost all visible components contain once it has been rendered.
Simply add a listener to the afterrender event that adds the event you want to the dom object.
Ext.create('Ext.panel.Panel', {
// other panel configs ...
listeners: {
'afterrender': function(panel) {
panel.el.on('click', function() {
alert('clicked');
});
}
}
});
I think there is also a way to do it for all components of a class that extends Ext.util.Observable (all visible components). I haven't done it before so you would have to play around with that.

Using a self-referencing managed listener (cleanest imo):
Ext.create('Ext.panel.Panel', {
listeners: {
'afterrender': function(panel) {
this.mon(this.getEl(), 'click', this.onClick, this)
}
},
onClick: function() {
alert("mmm mon mon mon");
}
});

If you are only listening to click event you can also do this
xtype: 'container',
listeners: {
el: {
click: function() {
alert('I clicked');
},
scope: this
}
}
If you need to listen to an event that the Ext.dom.Element does not fire, you will need to attach it in the 'render' listener, like others have suggested.

Related

change listener doesnt work in extjs

I have a dynamic form and i want to add a listener when change a field value, but i couldnt achive to do that. I added a click listener but when i change it to the change it doesnt work.
thanks in advance.
here is the code below :
panel = Ext.define('MyApp.view.dynamicform.Form', {
extend: 'Ext.form.Panel',
alias: 'widget.dynamicformform',
title: title,
id: 'dynamicform.Form',
bodyPadding: 5,
autoScroll: true,
layout: 'auto',
defaults: {
anchor: '100%'
},
dockedItems: [],
items : genItems(storeData),
listeners: {
afterrender: function (comp) {
var element = comp.getEl();
element.on('change', function(e,el) {
alert('blabla')
});
}
},
initComponent : function() {
this.callParent(arguments);
}
});
when i write click instead of change it works perfectly. I dont get what iam doing wrong.
The afterrender event you have added the listener for is purely for the panel component alone. Therefore trying to attach a change event wont work, since you are trying to do this on the panel:
afterrender: function (comp) {
var element = comp.getEl();
//element is the panel here, not child items such as spinners...
element.on('change', function(e,el) {
alert('blabla')
});
}
You say the click is working, but I think that's just because you are clicking anywhere on the panel including on the child items you are rendering. Instead, the child items coming back in the genItems() need to contain change event listener configs.
EDIT
You could loop through the child items on comp in your afterrender event and for any that are spinners, etc, add the change events that way.

Where to use listeners and where to use controller - Sencha Touch 2

I am confused between the proper usage of Listeners vs Controllers
E.g. for the same button, I can make the handler for the button-tap event in the button description itself as :
Ext.Viewport.add({
xtype: 'button',
centered: true,
text: 'My Button',
listeners: {
tap: function() {
alert("You tapped me");
}
}
});
and also as in a separate controller as below.
Ext.define("NotesApp.controller.Notes", {
extend: "Ext.app.Controller",
config: {
refs: {
newNoteBtn: Get reference to button here
},
control: {
newNoteBtn: {
tap: "onNewNote"
}
}
},
onNewNote: function () {
console.log("onNewNote");
}
});
What is the best practice, and are there trade-offs?
Thanks
To controller or not to controller, that is the question.
Well, technically, nothing would prevent you from doing one or the other. I have established a way how to decide for myself:
I install listeners on view if the job they are doing does not cross boundaries of the view. If we take form as an example, disabling and enabling, showing and hiding of fields, if it depends only on the state of the form stay within the form - no controller.
I delegate the logic to a controller if the action in one view (a button click, for example) influences another view or the whole application.
Again, these are my preferences, you can have another.

How do I add elements to an ExtJS component tpl so that I can refer to them in a listeners config

I can add a listener to the 'el' element of a component like this:
Ext.widget('component',{
listeners: {
click: {
element: 'el', //bind to the underlying el property on the component
fn: function(){ console.log('you clicked the el'); }
}
}
}
But I want to add more elements to this component's tpl and listen for clicks on them. In place of 'el' in the example above, I want to use a custom element 'awesomelink'. How do I reference elements in the tpl from the listeners config?
Here's an unfinished example:
Ext.widget('component',{
tpl : '<div>{content}<a class="awesomelink">click this awesome link</a></div>',
data:{content:'something'},
listeners: {
click: {
element: 'awesomelink', // this doesn't mean anything
fn: function(){ console.log('you clicked the awesomelink'); }
}
}
}
I assume I need an awesomelink property on the component - but how would that relate to the tpl?
Use event delegation:
listeners: {
afterrender: function(c) {
c.getEl().on('click', function(e, t) {
console.log(t);
}, null, {delegate: '.awesomelink'});
}
}
The element config expects the name of a property on the component which contains a reference to a DOM element, where you want to add your listener (refer to the docs).
That's why you can use "el", since all components have this property, but not "awesomelink" (unless you add that property yourself).
However, the click event on the component's underlying element will also fire if you click on child nodes of that element, so you could just check if the target element is your link in the listener:
listeners: {
click: {
element: 'el',
fn: function(e, t) {
if (Ext.get(t).hasCls('awesomelink')) {
console.log('you clicked the awesomelink');
}
}
}
}

Fire event from hyperlink in Sencha Touch 2

How would I fire an event (I am wanting to switch card views) from a plain old html link?
If I reference the link by ID in my controls section no event I tried (click, tap) seems to be triggered.
Thanks!
Once you render the link you can add event listener in this way:
Ext.get('[link id here]').on('click', function(){...}, this);
UPDATE
If you want to fire an event once user clicked on hyperlink, you can simply add this.fireEvent('[name of event here]'); but be aware of this keyword meaning in this function, so you'll have an ability to add listener to it properly... Does it make sense?
Add a click listener to the panel containing the link. In the example the tag has the 'link' class. You can substitute it by your own class/id, as it's done in jQuery.
listeners: {
scope: this,
itemtap: this.onItemtapAction,
click: {
element: 'el',
fn: function (e) {
if (e.getTarget('a.link')) {
// Switch cards here
}
}
}
I used simple Java Script to add a listener to the link.
First, I created an "activate" listener for the container component:
...
listeners: {
activate: function (newActiveItem, container, oldActiveItem, eOpts) {
this.onActivate(newActiveItem, container, oldActiveItem, eOpts);
}
}
...
And here is my function:
onActivate: function (newActiveItem, mainNavView, oldActiveItem, eOpts) {
var me = this;
document.getElementById('logOutLink').addEventListener("click",
function(){
me.onLogOut();
}, false);
},
onLogOut:function(){
alert('log out');
}

Attach ExtJS MVC controllers to DOM elements, not components

Is there a way to use the Ext.app.Controller control() method, but pass in a DOM query? I have a page that contains standard links and would like to add a click handler to them even though they were not created as Ext Buttons.
I've tried
Ext.define('app.controller.TabController', {
extend: 'Ext.app.Controller',
init: function() {
console.log("init");
this.control({
'a': {
click: this.changeTab
}
});
},
changeTab: function() {
alert("new tab!");
}
});
But clicking on links does not fire the alert.
Is there a way to specify a CSS selector with this.control? Or does it only work with components?
I asked this question at SenchaCon this year, the Sencha developers stated that their intent is that DOM listeners should be attached within your view, and the view should abstract them into more meaningful component events and refire them.
For example, suppose you're creating a view called UserGallery that shows a grid of people's faces. Within your UserGallery view class, you would listen for the DOM click event on the <img> tag to receive event and target, and then the view might fire a component event called "userselected" and pass the model instance for the clicked user instead of the DOM target.
The end goal is that only your views should be concerned with things like interface events and DOM elements while the application-level controller only deals with meaningful user intents. Your application and controller code shouldn't be coupled to your markup structure or interface implementation at all.
Sample View
Ext.define('MyApp.view.UserGallery', {
extend: 'Ext.Component'
,xtype: 'usergallery'
,tpl: '<tpl for="users"><img src="{avatar_src}" data-ID="{id}"></tpl>'
,initComponent: function() {
this.addEvents('userselected');
this.callParent(arguments);
}
,afterRender: function() {
this.mon(this.el, 'click', this.onUserClick, this, {delegate: 'img'});
this.callParent(arguments);
}
,onUserClick: function(ev, t) {
ev.stopEvent();
var userId = Ext.fly(t).getAttribute('data-ID');
this.fireEvent('userselected', this, userId, ev);
}
});
Notes on views
Extend "Ext.Component" when all you want is a managed <div>, Ext.Panel is a lot heavier to support things like titlebars, toolbars, collapsing, etc.
Use "managed" listeners when attaching listeners to DOM elements from a component (see Component.mon). Listeners managed by a components will be automatically released when that component gets destroyed
When listening for the same event from multiple DOM elements, use the "delegate" event option and attach the listener to their common parent rather than to individual elements. This performs better and lets you create / destroy child elements arbitrarily without worrying about continuously attaching/removing event listeners to each child. Avoid using something like .select('img').on('click', handler)
When firing an event from a view, Sencha's convention is that the first parameter to the event be scope -- a reference to the view that fired the event. This is convenient when the event is being handled from a controller where you'll need the actual scope of the event handler to be the controller.
Sample Controller
Ext.define('app.controller.myController', {
extend: 'Ext.app.Controller'
,init: function() {
this.control({
'usergallery': {
userselected: function(galleryView, userId, ev) {
this.openUserProfile(userID);
}
}
});
}
,openUserProfile: function(userId) {
alert('load another view here');
}
});
I have found a work around for this problem. It isn't as direct as one may hope, but it leaves all of your "action" code in the controller.
requirment: Wrap the html section of your page in an actual Ext.Component. This will likely be the case either way. So for instance, you may have a simple view that contains your HTML as follows:
Ext.define('app.view.myView', {
extend: 'Ext.panel.Panel',
alias: 'widget.myView',
title: 'My Cool Panel',
html: '<div>This link will open a window</div><br /> <label for="myInput">Type here: </label><input name="myInput" type="text" value="" />',
initComponent: function(){
var me = this;
me.callParent(arguments);
}
});
Then in the controller you use the afterrender event to apply listeners to your DOM elements. In the example below I illustrate both links (a element) and input elements:
Ext.define('app.controller.myController', {
extend: 'Ext.app.Controller',
init: function() {
this.control({
'myView': {
afterrender: function(cmp){
var me = this; //the controller
var inputs = cmp.getEl().select('input'); // will grab all DOM inputs
inputs.on('keyup', function(evt, el, o){
me.testFunction(el); //you can call a function here
});
var links = cmp.getEl().select('a'); //will grab all DOM a elements (links)
links.on('click', function(evt, el, o){
//or you can write your code inline here
Ext.Msg.show({
title: 'OMG!',
msg: 'The controller handled the "a" element! OMG!'
});
});
}
}
});
},
testFunction: function(el) {
var str = 'You typed ' + el.value;
Ext.Msg.show({
title: 'WOW!',
msg: str
});
}
});
And there you have it, DOM elements handled within the controller and adhering to the MVC architecture!
No, this seems not to be possible. The Ext.EventBus listens to events fired by ExtJS components. Your standard DOM elements do not fire those events. Additionally the query is checked with the ExtJS componets is( String selector ) method, wich can't be called by DOM elements. Someone might correct me if i'm wrong, but so i'm quite sure it's not possible, unfortunately.
I also have a solution that works around this problem. I use this technique regardless though as it has other benefits: I created an application wide messaging bus. Its an object in my application that extends Observable, and defines a few events. I can then trigger those events from anywere in my app, including html a links. Any component that wants to listen to those events can relay them and they'll fire as if fired from that component.
Ext.define('Lib.MessageBus', {
extend: 'Ext.util.Observable',
constructor: function() {
this.addEvents(
"event1",
"event2"
);
this.callParent(arguments);
}
});
Then, each other compnent can add this after initialisation:
this.relayEvents('Lib.MessageBus', ['event1','event2']);
and then listen to those events.
You can trigger the events from anything by doing:
Lib.MessageBus.fireEvent('event1', 'param a', 'param b')
and you can do that from anything including html links.
Very handy to fire off events from one part of the app to another.
I had the same problem recently (mixing mvc with some non components).
Just thought I'd throw this in as an answer as it seems pretty simple and works for me :)
Ext.define('app.controller.TabController', {
extend: 'Ext.app.Controller',
init: function() {
console.log("init");
this.control({
/* 'a': {
click: this.changeTab
} */
});
var link = Ext.dom.Query.selectNode('a');
Ext.get(link).on('click', this.changeTab);
},
changeTab: function() {
alert("new tab!");
}
});

Resources