Blog

Tips and Tricks for Ext JS Component Developers

August 27, 2010 | Aaron Conran

Ext JS ships with all the components that you need to build even very sophisticated applications. But, there are occasional cases where you might need to write a custom component. Luckily, the Sencha Community has a rich ecosystem full of quality components for developers to use. Here, we'll cover a few tips and tricks that component authors can use to enhance their user extensions.

Buffering Or Debouncing Method Calls

Ext JS allows you to buffer event handlers so that when you receive a flood of events, it waits a pre-defined amount of time before executing the handler. This is useful for performance and also helps reduce network activity. For example, let's say we have a master-detail relationship in a grid and want to make an Ajax request to update the detail view based on the record. That code could look something like:
 
var sm = this.getSelectionModel();
sm.on('rowselect', this.onGridRowSelect, this, {buffer: 300});
 
Once 300ms have elapsed since the user last selected a row, Ext JS executes the onGridRowSelect metho. This is fairly typical Ext JS event handling. If you are unfamiliar with the options available, you should check out the documentation of addListener. There are many useful configuration options like buffer and single. Under the hood, Ext JS leverages the Ext.util.DelayedTask class to achieve this buffering affect. DelayedTask is a useful class to batch together a bunch of calls into a single operation. This often occurs in component development when you are heavily modifying the DOM and want to avoid performing DOM manipulation in multiple operations. In this case, you should put the functionality that does heavy processing into another function and use a DelayedTask so that it is only executed after the configured delay. When creating a DelayedTask, specify the function that you want to execute as the first argument to the constructor. A common pattern within a class is:
 
somethingTaskDelay: 300,
 
//public api
something: function() {
    if (!this.somethingTask) {
        this.somethingTask = new Ext.util.DelayedTask(this.doSomething, this);
    }
    this.somethingTask.delay(this.somethingTaskDelay);
},
 
//private
doSomething: function() {
    // heavy processing which needs to be buffered
    alert('something executed!');
}
 
This allows users of your component to execute the method 'something' several times in a row and 'doSomething' will only run after the specified 'somethingTaskDelay' configuration. When interacting with the DOM, this simple optimization can prevent unnecessary and expensive operations like DOM reflows.

Allowing Users To Invoke Methods Before A Component Is Rendered

When developing a component, you frequently want to execute a method regardless of whether the component has been rendered or not. One way to do this is to check that the component has been rendered via the 'rendered' flag, then set up a one time event handler to re-invoke the method after it is rendered. This allows the user of your component to invoke the method at any point during the component lifecycle regardless of whether it has been rendered.
 
someMethod: function() {
    if (!this.rendered) {
        this.on('render', this.someMethod, this, {single: true});
        return;
    }
    // typical processing  
}
 
Another common case is to allow a user to invoke a method multiple times but only execute it a single time, once it has been rendered. This can be implemented as follows:
 
someMethod: function() {
    if (!this.rendered && !this.someMethodEventSetup) {
        this.someMethodEventSetup = true;
        this.on('render', this.someMethod, this, {single: true});
        return;
    }
    // typical processing  
}
 

Providing Events At Key Points

Custom components should provide events at critical points, for example when expanding or collapsing. Because your custom component extends from a subclass of Ext.util.Observable, you immediately get the methods 'addEvents' and 'fireEvent'. 'addEvents' enables you to define events on your component. 'fireEvent' allows you to notify an arbitrary number of subscribers that an event has occurred. Look at the source to the default Ext JS components as a guide to where you should add events. Events should:
  • Provide information about what is occuring at that time
  • Should have an associated 'before' event which allows you to cancel a behavior
To define events within your component, you "add" or define them after invoking your 'initComponent' method. Each event that you fire should be defined as an argument to the addEvents method. For example:
 
initComponent: function() {
   MyCustomComponent.superclass.initComponent.call(this);
   this.addEvents('beforeexpand', 'expand');
},
 
To fire the event, invoke the fireEvent method. The first argument is the event name, and any additional arguments will be passed to the event handler. For example:
 
this.fireEvent('expand', this, cmp, e);
 
Assuming we are within an Ext.Container, this exposes the expand event with a method signature of the container itself, the component being expanded and the DOM event that generated the expand behavior. An event handler can be tied to this component like so:
 
myCt.on('expand', function(ct, cmp, e) {
 
});
 
It's important to allow users to cancel actions from occurring by providing a 'before event'. Within Ext JS, if you return false from any 'before event', the event and its associated action will never happen. Because events within Ext JS are synchronous, we wait to ensure that no event handlers return false like so:
 
if (this.fireEvent('beforeexpand', this, cmp, e) !== false) {
    // expand logic
    this.fireEvent('expand', this, cmp, e);
}
 
By using patterns similar to Ext JS default components, developers will find your components intuitive to use and easier to fit within their application.

Documenting Your Code

Documenting your code cannot be stressed enough. Document all public configurations and methods within your component. This allows users to leverage all of the features that you've provided without digging through the code. If you implemented it, them tell your users how to use it! Likewise, if there are things that you've added into the prototype that really are NOT configurable, you should make note of that as well. We use a syntax similar to JSDoc and you can use the Ext Doc project to generate documentation of your own.

Summary

It's been a while since I've blogged but these are a few common issues that I see new component developers face. I hope that these common patterns in component development enhance the quality of the components in the wild. Be sure to check out the components that other users have developed in the Sencha User Extension Ecosystem and share your own user extensions with the Sencha Community. By releasing your code to the community, you can see your component used in ways that you never expected. Part of the art of component development is to make sure that your component works in situations that you didn't initially envision. To other experienced component developers, please share tips and tricks that you've come across or developed the last few years that would be useful for the entire community to adopt. If you're not a custom component developer, but are in need of a custom component we'd love to hear what components you are missing in Ext JS.

There are 14 responses. Add yours.

Sergei Kozlov

1 year ago

Excellent post, thanks! Please, do more of these.

kjordan

1 year ago

Is there any plan to add the buffered listeners to GXT?  That seems pretty handy and I don’t see any way to do that right now in GXT.

Shea Frederick (VinylFox)

1 year ago

Now this is a great use of the Ext JS…I mean Sencha Blog. Keep these coming.

N. Can KIRIK (j-joey)

1 year ago

great tips, and i agree that more will be appreciated.

ps. at the second “invoking events while not rendered” example, if event has been fired second time before component rendered, it will not enter to control block, so event will not stop firing, because someMethodEventSetup is true. imo, control should be done at addListener phase

Ben

1 year ago

Another good tip is to remember to implement onDestroy correctly to avoid browser memory leaks, destroy stores and other internal objects that may have been created, and remove any listeners that were added to external components and observables.

For instance, if you build a component that is going to be used as a tab in a TabPanel, it might add event listeners to the TabPanel via this.ownerCt to know when certain actions happen. It may also need listeners on global stores or state objects within the application. If the component is added as a closable tab, though, it will get destroyed when closed by the user so any events which were added must be removed in the component’s onDestroy method.

Loiane

1 year ago

Great post guysand it is very helpful.
Keep posting tips!
smile

Martin

1 year ago

Excellent! We need as much help as we can :D

Stephan

1 year ago

Is it really required to use addEvents() to register custom events?
Most of the time I forget to use addEvents(), however, firing an event works fine.

Damian Poole

1 year ago

Excellent! More tips like this please! Thanks.

Daniel

1 year ago

Thanks for the tips. Please keep ‘em coming.

David

1 year ago

Thanks !
This is so much what Sencha blog has to be.
Tips & tricks, how-tos and most importantly fo beginners, Best Practices and “Get started with…”

I had hard times fetching the forums for sometime outdated advices, or the API documentation for some small win, in the actual code comments (!) and not talking about the Learning Center which is so not fresh.
The best step-in articles that really helped me get the big picture where actually the ones written by Jozef Sakalos, aka Saki, about writing big apps in Ext JS, or when he explained Ext Direct. His site is a treasure.

Given the other comments, it feels like I’m not the only one in the same mood.

Jonathan

1 year ago

Excellent points. Keep ‘em coming!

Utkarsh

9 months ago

I feel there should be a mechanism where the event is fired asap and AFTER that any new repetitive events should be ignored for X milliseconds.  Given the time it would take the server to process the request, I don’t want to keep my users waiting for an additional 300 ms.

Comments are Gravatar enabled. Your email address will not be shown.

Commenting is not available in this channel entry.