This question has the answer to adding the tooltip:
Extjs4 set tooltip on each column hover in gridPanel
I have a follow up question to the most upvoted answer to this question, which is modifying the renderer function to add the tool tip like below:
{
xtype : 'gridcolumn',
dataIndex : 'status',
text : 'Status',
renderer : function(value, metadata) {
metadata.tdAttr = 'data-qtip="' + value + '"';
return value;
}
}
I want to have a grid plugin or feature which sets a custom tooltip using the above implementation.
Question is how can I add my stuff but at the same time not take away a user defined renderer function being used for a particular grid. Essentially I want to add the tooltip feature for all grids, but not take away the ability to specify custom renderers for some columns on some of the grids.
I guess what could be a possible alternative is another less invasive place where I could modify this metadata.tdAttr value. Maybe an event that someone would know about?
In your plugins init
method, you will be able to loop through the columns of the grid (the constructor and initComponent
methods of the grid will have already been called at this point).
That means that you can inspect each column to see if the user has setup a custom renderer. If there is none, you can put your one, and if there is one, you can chain the existing renderer with yours.
EDIT
Ext.define('My.Plugin', {
init: function(grid) {
// You may not need the scope, but if you do, this binding will
// allow to preserve the scope configured in the column...
var pluginRenderer = Ext.Function.bind(this.renderer, this);
Ext.each(grid.query('gridcolumn'), function(col) {
var renderer = col.renderer;
col.renderer = renderer
? Ext.Function.createSequence(renderer, pluginRenderer)
: pluginRenderer;
});
}
,renderer: function(value, md) {
// ...
// You must return, in case your renderer is the only one (if
// there is another one, this return will be ignored by createSequence)
return value;
}
});
I took the accepted answer to the next logical conclusion:
And made this into complete configurable plugin
based solution, that requires very little configuration to use. And plays nicely with Sencha Architect.
Motivation
The main motivation for researching this and getting it to work is that the DateColumn
has the renderer
config parameter marked as hidden
so that Sencha Architect doesn't allow you to modify it in the Config
panel. I understand why and it drove me to this solution, which I think is the correct one and the most maintainable and reusable in the long run.
At the time of this writing:
I am using Sencha Architect 3.x and ExtJS 4.2.2, these instructions are included on how to apply these features in that environment. If you don't use Sencha Architect, you will just have to create and manage the files yourself. I find it extremely productive when working on any size ExtJS project.
First is the actual GridPlugin
:
Add a JSResource
to your project, set the url
to js/CellToolTip.js
, and then copy the following code as its contents.
js/CellToolTip.js
Ext.define('Ext.grid.plugin.CellToolTip', {
extend: 'Ext.AbstractPlugin',
alias: 'plugin.CellQTip',
config: {
debug: false
},
init: function(grid) {
// You may not need the scope, but if you do, this binding will
// allow to preserve the scope configured in the column...
var pluginRenderer = Ext.Function.bind(this.renderer, this);
Ext.each(grid.query('gridcolumn'), function(col) {
var renderer = col.renderer;
col.renderer = renderer ? Ext.Function.createSequence(renderer, pluginRenderer) : pluginRenderer;
});
},
renderer: function(value, metaData, record, rowIndex, colIndex, store, view) {
var col = view.getGridColumns()[colIndex];
var fn = this[col.itemId];
if (fn) {
metaData.tdAttr = fn(value, metaData, record, rowIndex, colIndex, store, view);
}
return value;
}
});
Here is how you apply it to a GridPanel
:
You add what is called a Process Config
function to each GridPanel
that you want to apply the plugin to:
What this does is it acts as an interceptor function and allows you to modify the config
of the Component
before it is actually applied.
processBlobInfoGridPanel: function(config) {
var ttp = Ext.create('Ext.grid.plugin.CellToolTip', {
createdOn: function(value, metaData, record, rowIndex, colIndex, store, view) { return 'data-qtip="' + value.toUTCString() + '"'; },
byteSize: function(value, metaData, record, rowIndex, colIndex, store, view) { return 'data-qtip="' + value + ' bytes"'; }
});
config.plugins = [ttp];
return config;
},
How to add these to a Sencha Architect project:
First what this does is creates the plugin
with its own config where each attribute is the itemId
of each Column
that you want to apply the plugin to.
The function that is associated with each Column
has the exact same signature as the renderer
config.
Here the function implementations are short and in a single line. This is just an and example is for brevity.
ToolTip functions explained:
createdOn
adds a ToolTip
for each Cell
in that Column
as the time in UTC
.
byteSize
adds a ToolTip
for each Cell
in that Column
that shows the raw number of bytes as a detail.
Then it adds the instance of the configured plugin to the config
of the GridPanel
and returns the instance of the config
.
Bonus Class
In Sencha Architect I created a Column
to represent the Size
of Resources
, I wanted to show the byte size as a human readable format with the largest appropriate interval. So I added a renderer
function to do this.
I realized I was going to need this Column
on multiple GridPanel
instances so promoted it to a Class.
The way you do this is you right click on the Column
and pick Promote to Class
. Sencha Architect then creates the following code and puts it in a file in your app/view
directory with the name of the file equal to the userClassName
you specified in the Config
panel.
Ext.define('AdminApp.view.ByteSize', {
extend: 'Ext.grid.column.Column',
alias: 'widget.byteSize',
itemId: 'byteSize',
dataIndex: 'size',
text: 'Size',
tooltip: 'Byte size in human readable format',
initComponent: function() {
var me = this;
me.callParent(arguments);
},
renderer: function(value, metaData, record, rowIndex, colIndex, store, view) {
// convert bytes into a human readable format"
var bytes = value;
if (bytes>=1073741824) {bytes=(bytes/1073741824).toFixed(2)+' GB';}
else if (bytes>=1048576) {bytes=(bytes/1048576).toFixed(2)+' MB';}
else if (bytes>=1024) {bytes=(bytes/1024).toFixed(2)+' KB';}
else if (bytes>1) {bytes=bytes+' bytes';}
else if (bytes==1) {bytes=bytes+' byte';}
else {bytes='0 byte';}
return bytes;
}
});
Now the original instance of this Column
is pointing to this new Class
:
columns: [
{
xtype: 'byteSize',
itemId: 'byteSize'
}
]
Now to use this in other GridPanel
instances in Sencha Architect, just right click on the Column
instance that is linked to this new class and select Duplicate
. Then drag the duplicated instance to the GridPanel
you want to use the ByteSize
class and drop it in the list of Columns
. Then configure each cloned instance independently of the others.
Any changes to the default or behavior of the ByteClass
automatically affect all the instances.
Roadmap
This is a first working minimally viable solution, there are obvious enhancements that can be made. For example, I am want to fix it where it appends to metaData.tdAttr
if there is already content instead of blindly over-writing what is there.
Any enhancements will be done to this Gist on GitHub.