How to change background of hovered and pressed extjs-button dynamically

20,649

Solution 1

Using some more specific css selectors you can take advantage of Ext's pressedCls and overCls configs. http://docs.sencha.com/ext-js/4-1/#!/api/Ext.button.Button-cfg-pressedCls http://docs.sencha.com/ext-js/4-1/source/Button2.html#Ext-button-Button-cfg-overCls

.x-btn.my-over {
    background: blue;
}
/*Ext is not consistent */
.x-btn.x-btn-my-pressed {
    background: red;
}


new Ext.button.Button({
    overCls : 'my-over',
    pressedCls : 'my-pressed',
    //needs to be true to have the pressed cls show
    enableToggle : true,
    text : 'My button',
    renderTo : Ext.getBody(),
})

Edit for a more dynamic, generic solution

    Ext.define('DynamicStyleState', {
    alias : 'plugin.dynamicStyleState',
    extend : Ext.AbstractPlugin,
    /**
     * @cfg
     */
    elementProperty : 'el',

    /**
     * @property
     */
    pressed : false,

    init : function(component) {
        this.component = component;
        this.component.on('afterrender', this._onAfterrender, this);
    },

    /**
     * @protected
     */
    clearStyle : function() {
        this.el.setStyle({
            background : ''
        });
    },
    /**
     * @protected
     * meant to be overriden
     */
    getHoverStyle : function() {
        return {
            background : 'blue'
        };
    },
    /**
     * @protected
     * meant to be overriden
     */
    getPressedStyle : function() {
        return {
            background : 'red'
        };
    },

    _onAfterrender : function() {
        this.el = this.component[this.elementProperty];
        this.el.hover(this._onElementMouseIn, this._onElementMouseOut, this);
        this.el.on('click', this._onElementClick, this);
    },

    _onElementMouseIn : function() {
        if(!this.pressed) {
            this.el.setStyle(this.getHoverStyle());
        }
    },

    _onElementMouseOut : function() {
        if(!this.pressed) {
            this.clearStyle();
        }
    },

    _onElementClick : function(e) {
        this.pressed = !this.pressed;
        if(this.pressed) {
            this.el.setStyle(this.getPressedStyle());
        } else {
            //mimic mouse in
            if(e.within(this.el)) {
                this.el.setStyle(this.getHoverStyle());
            }
        }
    }
});

Since you want something that works on more than just button this should work on any component.

Here is an example usage:

    var headerHoverColor = new Ext.form.field.Text({
    fieldLabel : 'Header hover color',
    value : 'orange',
    renderTo : Ext.getBody()
});

var headerPressedColor = new Ext.form.field.Text({
    fieldLabel : 'Header pressed color',
    value : 'black',
    renderTo : Ext.getBody()
})

new Ext.panel.Panel({
    plugins : {
        ptype : 'dynamicStyleState',
        elementProperty : 'body'
    },
    header : {
        plugins : {
            ptype : 'dynamicStyleState',
            getHoverStyle : function() {
                return {
                    background : headerHoverColor.getValue()
                }
            },
            getPressedStyle : function() {
                return {
                    background : headerPressedColor.getValue()
                }
            }
        }
    },
    height : 300,
    width : 300,
    renderTo : Ext.getBody(),
    title : 'Panel'
});

Solution 2

Ok, i just found a solution which should cover all the problems described above:

I add both, the pressed and hovered style directly to the header.

To make sure that the header won't be polluted i just remove the style-elements dynamically with jQuery's detach-function from the header before appending new ones.

Working example

And the code for the dynamic changing of the background:

function updateBackground() {
    var theWin = win || this, // neccessary because of the call from the afterrender-event
    btn = theWin.down('#btnTest'),
    bckgr = theWin.down('#btnBckgr').getValue(),
    bckgrHover = theWin.down('#btnBckgrHover').getValue(),
    bckgrPressed = theWin.down('#btnBckgrPressed').getValue();

    // Set normal background as button-style
    // Should be ok
    $('#' + btn.id).css('background', bckgr);

    // Set the background for hovered and pressed button as document style
    // Remove style-element if it already exists to not pollute the document-head
    $('head style:contains("' + btn.id + ':hover")').detach();
    $('<style type="text/css"> #' + btn.id + ':hover { background: ' + bckgrHover + ' !important } </style>').appendTo('head');

    $('head style:contains("' + btn.id + '.x-btn-pressed")').detach();
    $('<style type="text/css"> .x-body #' + btn.id + '.x-btn-pressed { background: ' + bckgrPressed + ' !important } </style>').appendTo('head');
};

Seems to work for me but I'm still open for any suggestions/improvements to this!

Thanks.

Share:
20,649
suamikim
Author by

suamikim

Updated on July 29, 2022

Comments

  • suamikim
    suamikim almost 2 years

    Hy there,

    i need the ability to change the background-color for the different states (normal, hover, pressed) of a button dynamically.

    What i've came up with so far is the following: http://jsfiddle.net/suamikim/c3eHh/

    Ext.onReady(function() {
        function updateBackground() {
            var theWin = win || this, // neccessary because of the call from the afterrender-event
                btn = theWin.down('#btnTest'),
                bckgr = theWin.down('#btnBckgr').getValue(),
                bckgrHover = theWin.down('#btnBckgrHover').getValue(),
                bckgrPressed = theWin.down('#btnBckgrPressed').getValue();
    
            // Set normal background as button-style
            // Should be ok
            $('#' + btn.id).css('background', bckgr);
    
            // Toggle background when hover-state changes
            // Are there any downsides if this function gets called everytime the updateBackground-method is called? Do i have to dispose anything before binding the functions again?
            $('#' + btn.id).hover(function() {
                    $('#' + btn.id).css('background', bckgrHover);
                }, function() {
                    $('#' + btn.id).css('background', bckgr);
                }
            );
    
            // Set the background for pressed button as document style
            // Problems:
            //     - Pollutes the document-header if called multiple times...
            //     - Background does not change anymore on hover for pressed button because of the !important, but without important it wouldn't show the pressed background at all...
            $('<style type="text/css"> #' + btn.id + '.x-btn-pressed { background: ' + bckgrPressed + ' !important } </style>').appendTo('head');
        };
    
        // Create a window with the propertygrid
        var win = Ext.create('Ext.window.Window', {
            width: 800,
            height: 200,
            layout: 'fit',
    
            tbar: {
                defaultType: 'textfield',
                defaults: {
                    listeners: {
                        blur: updateBackground
                    }
                },
                items: [
                    'Background (CSS):', { itemId: 'btnBckgr', value: '#77ABD8' },
                    'Background hover (CSS):', { itemId: 'btnBckgrHover', value: '#CC1C1C' },
                    'Background pressed (CSS):', { itemId: 'btnBckgrPressed', value: '#7D4FC6' }
                ]
            },
    
            items: [{
                xtype: 'button',
                itemId: 'btnTest',
                text: 'test button',
                enableToggle: true
            }],
    
            listeners: {
                'afterrender': updateBackground
            }
        }).show();
    });
    

    This works basically but there are some questions left to me:

    1)

    Is it ok to call the hover-function of jquery everytime the updateBackground-method is called?

    According to the jquery-doc (http://api.jquery.com/hover/) this functions binds the 2 given functions to the mouse-enter- & mouse-leave-events of the given element.

    Does this mean that it binds new functions everytime i call it or does it update the already bound ones or does it dispose the already bound ones automatically before binding the new ones...

    In short: do i have to dispose anything before binding the new functions with $.hover?

    2)

    I set the style for the pressed state as document-style which means that the document-header gets polluted if the updateBackground-function is called often (everytime one of the text-fields fires the blur-event in my test-case).

    Is there any better way to achieve the same goal or can i remove the already set styles before adding new ones?

    3)

    The background for hover-state doesn't appear if the button is pressed because of the !important-flag in the pressed-style. I can't just remove this flag because the background of the pressed-state wouldn't show correctely without it... Any solutions?

    In general i'd be open for every suggestion on how to solve this problem in a completely different way.

    Thanks,

    mik


    Edit:

    The code above is just an example. Actually i need to be able to change the background-attribute for other ext-controls than buttons too (panels, checkboxes, ...) but i think that i can adopt a working solution for buttons to those other controls myself.

    Please keep this in mind and don't specify the answer too much on buttons. It should be as generic as possible...