Extjs create a grid feature or grid plugin that se

2019-02-25 05:47发布

问题:

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?

回答1:

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;
    }
});


回答2:

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.