1. #1
    Sencha - Ext JS Dev Team Animal's Avatar
    Join Date
    Mar 2007
    Location
    Notts/Redwood City
    Posts
    30,508
    Vote Rating
    56
    Animal has a spectacular aura about Animal has a spectacular aura about Animal has a spectacular aura about

      0  

    Default Ext.ux.GhostBar - A space-saving, fade-in Toolbar

    Ext.ux.GhostBar - A space-saving, fade-in Toolbar


    When you need to see a lot of data in your Component, but any Toolbar Buttons are secondary, and may not be wanted, this class fades in a top or bottom Toolbar whenever the mouse approaches the region where the top or bottom toolbar would be.

    It is a Toolbar subclass which functions as a plugin to any BoxComponent

    To test it, simply:

    Code:
    new Ext.Panel({
        renderTo: document.body,
        title: 'Test',
        width: 600,
        height: 400,
        plugins: [ new Ext.ux.GhostBar([{ text: 'Click Me' }]) ]
    });
    Code:
    Ext.override(Ext.lib.Region, {
        /**
         * Returns the shortest distance between this Region and another Region.
         * Either or both Regions may be Points.
         * @param {Region} r The other Region
         * @return {Number} The shortest distance in pixels between the two Regions.
         */
        getDistanceBetween: function(r) {
    
    //      We may need to mutate r, so make a copy.
            r = Ext.apply({}, r);
            
    //      Translate r to the left of this
            if (r.left > this.right) {
                var rWidth = r.right - r.left;
                r.left = this.left - (r.left - this.right) - rWidth;
                r.right = r.left + rWidth;
            }
    
    //      Translate r above this
            if (r.top > this.bottom) {
                var rHeight = r.bottom - r.top;
                r.top = this.top - (r.top - this.bottom) - rHeight;
                r.bottom = r.top + rHeight;
            }
    
    //      If r is directly above
            if (r.right > this.left) {
                return this.top - r.bottom;
            }
    
    //      If r is directly to the left
            if (r.bottom > this.top) {
                return this.left - r.right;
            }
    
    //      r is on a diagonal path
            return Math.round(Math.sqrt(Math.pow(this.top - r.bottom, 2) + Math.pow(this.left - r.right, 2)));
        }
    });
    
    /**
     * @class Ext.ux.GhostBar
     * @extends Ext.Toolbar
     * A Toolbar class which attaches as a plugin to any BoxComponent, and fades in at the configured
     * position whenever the mouse is brought within a configurable threshold. eg: <code><pre>
    new Ext.Panel({
        renderTo: document.body,
        title: 'Test',
        width: 600,
        height: 400,
        plugins: [ new Ext.ux.GhostBar([{ text: 'Click Me' }]) ]
    });
    </pre></code>
     */
    Ext.ux.GhostBar = Ext.extend(Ext.Toolbar, {
    
        listenerAdded: false,
    
        cache: [],
    
        /**
         * @cfg {Number} threshold The number of pixels around the toolbar position in which
         * fading is performed.
         */
        threshold: 100,
    
        /**
         * @cfg {String} position The alignment of this Toolbar, <code><b>top</b></code>, or <code><b>bottom</b></code>.
         * Defaults to <code><b>bottom</b></code>.
         */
        /**
         * @cfg {Array} offsets A two element Array containing the [x, y] offset from the default position
         * in which to display the Toolbar.
         */
    
        initComponent: function() {
    
    //      Only use one mousemove listener. Check the cache of GhostBars for proximity on each firing
            if (!this.listenerAdded) {
                Ext.getDoc().on('mousemove', Ext.ux.GhostBar.prototype.onDocMouseMove, Ext.ux.GhostBar.prototype);
                this.listenerAdded = true;
            }
            this.renderTo = document.body;
            this.hideMode = 'visibility';
            Ext.ux.GhostBar.superclass.initComponent.apply(this, arguments);
        },
    
        onRender: function() {
            Ext.ux.GhostBar.superclass.onRender.apply(this, arguments);
            this.el.setStyle({
                position: 'absolute'
            });
            this.hide();
            this.cache.push(this);
        },
    
        init: function(c) {
            this.ownerCt = c;
            c.on({
                render: this.onClientRender,
                scope: this,
                single: true
            });
            c.onPosition = c.onPosition.createSequence(this.onClientPosition, this);
            c.onResize = c.onResize.createSequence(this.onClientResize, this);
        },
    
        onClientRender: function() {
            this.clientEl = this.ownerCt.getLayoutTarget ? this.ownerCt.getLayoutTarget() : this.ownerCt.el;
        },
    
        onClientResize: function() {
            this.setWidth(this.clientEl.getWidth(true));
            this.syncPosition();
        },
    
        onClientPosition: function() {
            this.syncPosition();
        },
    
        syncPosition: function() {
            var offsets = [this.clientEl.getBorderWidth('l'), 0];
            if (this.offsets) {
                offsets[0] += this.offsets[0];
                offsets[1] += this.offsets[1];
            }
            this.el.alignTo(this.clientEl, (this.position == 'top') ? 'tl-tl' : 'bl-bl', offsets);
            this.region = this.el.getRegion();
        },
    
        onDocMouseMove: function(e) {
            for (var i = 0; i < this.cache.length; i++) {
                this.checkMousePosition.call(this.cache[i], e);
            }
        },
    
        checkMousePosition: function(e) {
            this.syncPosition();
            var o = 1, d = this.region.getDistanceBetween(e.getPoint());
            if (d > this.threshold) {
                this.hide();
            } else if (d > 0) {
    
    //          Mouse is within range of this Toolbar, so show it if its not already visible
                if (!this.isVisible()) {
                    this.show();
                }
                o = 1 - (d / this.threshold);
            }
            var z = Ext.num(this.ownerCt.el.getStyle('zIndex'));
            this.el.setStyle({
                opacity: o,
                'zIndex': (typeof z == 'number') ? z + 3 : 'auto'
            });
        },
    
        onDestroy: function() {
    //      Uncache this Toolbar when we are destroyed
            this.cache.splice(this.cache.indexOf(this), 1);
            Ext.ux.GhostBar.superclass.onDestroy.apply(this, arguments);
        }
    });

  2. #2
    Sencha User galdaka's Avatar
    Join Date
    Mar 2007
    Location
    Spain
    Posts
    1,166
    Vote Rating
    -1
    galdaka is an unknown quantity at this point

      0  

    Default


    Excellent work Animal (As always)

    I have strange issue in IE6. I attach screenshot and example to extract in examples\panel directory of Ext 3.0 distribution.

    Greetings,
    Attached Images
    Attached Files

  3. #3
    Sencha - Ext JS Dev Team Animal's Avatar
    Join Date
    Mar 2007
    Location
    Notts/Redwood City
    Posts
    30,508
    Vote Rating
    56
    Animal has a spectacular aura about Animal has a spectacular aura about Animal has a spectacular aura about

      0  

    Default


    I think there is a bug with Ext 3.0 mousemove handling which is breaking things on IE.

    http://extjs.com/forum/showthread.php?p=333612

    Anyway, I have refactored and posted a more efficient version which only uses one mousemove listener, and in the handler, checks through a cache of GhostBars checking each for mouse proximity.

    Also, I sync the toolbar with its client Element on each mouse move so that collapsing other elements in the page does not affect where the toolbar should be.

  4. #4
    Ext User
    Join Date
    Jul 2007
    Location
    Florida
    Posts
    9,996
    Vote Rating
    6
    mjlecomte will become famous soon enough mjlecomte will become famous soon enough

      0  

    Default


    If you have a couple of these plugged into a window, for example, you may not want the z-index hardcoded. I corrected a couple of typos and modified for dynamic z-index in code below.

    Code:
    <html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    
        <title>Ghost components</title>
    
        <link rel="stylesheet" type="text/css" href="../../resources/css/ext-all.css" />
    
        <script type="text/javascript" src="../../adapter/ext/ext-base.js"></script>
        <script type="text/javascript" src="../../ext-all.js"></script>
    
        <script type="text/javascript">
    Ext.BLANK_IMAGE_URL = '../../resources/images/default/s.gif';
    
    
    Ext.override(Ext.lib.Region, {
        /**
         * Returns the shortest distance between this Region and another Region.
         * Either or both Regions may be Points.
         * @param {Region} r The other Region
         * @return {Number} The shortest distance in pixels between the two Regions.
         */
        getDistanceBetween: function(r) {
    
    //      We may need to mutate r, so make a copy.
            r = Ext.apply({}, r);
            
    //      Translate r to the left of this
            if (r.left > this.right) {
                var rWidth = r.right - r.left;
                r.left = this.left - (r.left - this.right) - rWidth;
                r.right = r.left + rWidth;
            }
    
    //      Translate r above this
            if (r.top > this.bottom) {
                var rHeight = r.bottom - r.top;
                r.top = this.top - (r.top - this.bottom) - rHeight;
                r.bottom = r.top + rHeight;
            }
    
    //      If r is directly above
            if (r.right > this.left) {
                return this.top - r.bottom;
            }
    
    //      If r is directly to the left
            if (r.bottom > this.top) {
                return this.left - r.right;
            }
    
    //      r is on a diagonal path
            return Math.round(Math.sqrt(Math.pow(this.top - r.bottom, 2) + Math.pow(this.left - r.right, 2)));
        }
    });
    
    /**
     * @class Ext.ux.GhostBar
     * @extends Ext.Toolbar
     * A Toolbar class which attaches as a plugin to any BoxComponent, and fades in at the configured
     * position whenever the mouse is brought within a configurable threshold. eg: <code><pre>
    new Ext.Panel({
        renderTo: document.body,
        title: 'Test',
        width: 600,
        height: 400,
        plugins: [ new Ext.ux.GhostBar([{ text: 'Click Me' }]) ]
    });
    </pre></code>
     */
    Ext.ux.GhostBar = Ext.extend(Ext.Toolbar, {
    
        listenerAdded: false,
    
        cache: [],
    
        /**
         * @cfg {Number} threshold The number of pixels around the toolbar position in which
         * fading is performed.
         */
        threshold: 100,
    
        /**
         * @cfg {String} position The alignment of this Toolbar, <code><b>top</b></code>, or <code><b>bottom</b></code>.
         * Defaults to <code><b>bottom</b></code>.
         */
        /**
         * @cfg {Array} offsets A two element Array containing the [x, y] offset from the default position
         * in which to display the Toolbar.
         */
    
        initComponent: function() {
    
    //      Only use one mousemove listener. Check the cache of GhostBars for proximity on each firing
            if (!this.listenerAdded) {
                Ext.getDoc().on('mousemove', Ext.ux.GhostBar.prototype.onDocMouseMove, Ext.ux.GhostBar.prototype);
                this.listenerAdded = true;
            }
            this.renderTo = document.body;
            this.hideMode = 'visibility';
            Ext.ux.GhostBar.superclass.initComponent.apply(this, arguments);
        },
    
        onRender: function() {
            Ext.ux.GhostBar.superclass.onRender.apply(this, arguments);
            this.el.setStyle({
                position: 'absolute'
            });
            this.hide();
            this.cache.push(this);
        },
    
        init: function(c) {
            this.ownerCt = c;
            c.on({
                render: this.onClientRender,
                scope: this,
                single: true
            });
            c.onPosition = c.onPosition.createSequence(this.onClientPosition, this);
            c.onResize = c.onResize.createSequence(this.onClientResize, this);
        },
    
        onClientRender: function() {
            this.clientEl = this.ownerCt.getLayoutTarget ? this.ownerCt.getLayoutTarget() : this.ownerCt.el;
        },
    
        onClientResize: function() {
            this.setWidth(this.clientEl.getWidth(true));
            this.syncPosition();
        },
    
        onClientPosition: function() {
            this.syncPosition();
        },
    
        syncPosition: function() {
            var offsets = [this.clientEl.getBorderWidth('l'), 0];
            if (this.offsets) {
                offsets[0] += this.offsets[0];
                offsets[1] += this.offsets[1];
            }
            this.el.alignTo(this.clientEl, (this.position == 'top') ? 'tl-tl' : 'bl-bl', offsets);
            this.region = this.el.getRegion();
        },
    
        onDocMouseMove: function(e) {
            for (var i = 0; i < this.cache.length; i++) {
                this.checkMousePosition.call(this.cache[i], e);
            }
        },
    
        checkMousePosition: function(e) {
            this.syncPosition();
            var o = 1, d = this.region.getDistanceBetween(e.getPoint());
            if (d > this.threshold) {
                this.hide();
            } else if (d > 0) {
    
    //          Mouse is within range of this Toolbar, so show it if its not already visible
                if (!this.isVisible()) {
                    this.show();
                }
                o = 1 - (d / this.threshold);
            }
            this.el.setStyle({
                opacity: o,
                'z-index': this.ownerCt.el.zindex+3
            });
        },
    
        onDestroy: function() {
    //      Uncache this Toolbar when we are destroyed
            this.cache.splice(this.cache.indexOf(this), 1);
            Ext.ux.GhostBar.superclass.onDestroy.apply(this, arguments);
        }
    });
    
    
    Ext.onReady(function(){
    
        new Ext.Window({
            renderTo: document.body,
            title: 'Test',
            html: 'test',
            width: 600,
            height: 400,
            plugins: [ new Ext.ux.GhostBar([{ text: 'Click Me' }]) ]
        }).show();
    
        new Ext.Window({
            renderTo: document.body,
            title: 'Test',
            html: 'test',
            width: 600,
            height: 400,
            plugins: [ 
                new Ext.ux.GhostBar({
                    items:[
                        { text: 'Click Me' }
                    ]
                })
            ]
        }).show();
    
    });
        </script>
    
    </head>
    <body></body>
    </html>

  5. #5
    Sencha - Ext JS Dev Team Animal's Avatar
    Join Date
    Mar 2007
    Location
    Notts/Redwood City
    Posts
    30,508
    Vote Rating
    56
    Animal has a spectacular aura about Animal has a spectacular aura about Animal has a spectacular aura about

      0  

    Default


    Excellent work MJ, I've incorporated your fixes and enhancements into post #1

  6. #6
    Ext User
    Join Date
    Mar 2010
    Posts
    15
    Vote Rating
    0
    acidtonic is on a distinguished road

      0  

    Default


    I've found this tremendously useful and wanted to say thanks!

    I'm having a few issues though... when used as a plugin for a Panel that can shade, the toolbar will appear at the top left of the page as if anchored to 0,0 whenever the panel is shaded. But when the panel is unshaded, it goes back to where it should be.

    More importantly though, I'm trying to use this inside a custom panel that I extended which works great. But as soon as I put that panel inside a tabpanel i get errors.

    "var offsets = [this.clientEl.getBorderWidth('l'), 0];" with the error "this.clientEL is undefined."

    Any thoughts? I can provide a link to the live dev site if you want to see it misbehaving.

  7. #7
    Ext User
    Join Date
    Mar 2010
    Posts
    15
    Vote Rating
    0
    acidtonic is on a distinguished road

      0  

    Default


    I ended up solving the big issue.... All that's remaining is the GhostBar showing up at the top of the screen for containers that are hidden or shaded.

    Example to reproduce....

    Make two panels that can shade, put a ghostbar in each with just a checkbox inside the ghostbar. Now check the checkbox in one ghostbar and shade that panel..... Now you'll find the ghostbar appearing at the top left of the page and you can tell its the one from the panel because the checkbox is checked.

    Same thing happens for having this inside a tab panel. Whatever ghostbars are in tabs not currently shown will show up at the top of the page.

    Also is there any easy way to prevent the toolbar from shading, but rather have it fully display when the mouse is close enough?

    EDIT: The ghostbar appearing at the top issue only seems to happen on firefox but not IE6 or 7.

  8. #8
    Sencha - Ext JS Dev Team Animal's Avatar
    Join Date
    Mar 2007
    Location
    Notts/Redwood City
    Posts
    30,508
    Vote Rating
    56
    Animal has a spectacular aura about Animal has a spectacular aura about Animal has a spectacular aura about

      0  

    Default


    It needs this to only check for proximity if its owner is visible:

    Code:
        onDocMouseMove: function(e) {
            for (var i = 0; i < this.cache.length; i++) {
            	if (this.cache[i].ownerCt.isVisible()) {
    	            this.checkMousePosition.call(this.cache[i], e);
    	        }
            }
        },

  9. #9
    Sencha - Ext JS Dev Team Animal's Avatar
    Join Date
    Mar 2007
    Location
    Notts/Redwood City
    Posts
    30,508
    Vote Rating
    56
    Animal has a spectacular aura about Animal has a spectacular aura about Animal has a spectacular aura about

      0  

    Default


    OK, here's the full thing.

    Now with a fullVisibilityZone config so you can specify a proximity zone in which you want 100% opacity. Defaults to 50px.

    So fading now is from 51px to 150px

    Code:
    Ext.override(Ext.lib.Region, {
        /**
         * Returns the shortest distance between this Region and another Region.
         * Either or both Regions may be Points.
         * @param {Region} r The other Region
         * @return {Number} The shortest distance in pixels between the two Regions.
         */
        getDistanceBetween: function(r) {
    
    //      We may need to mutate r, so make a copy.
            r = Ext.apply({}, r);
            
    //      Translate r to the left of this
            if (r.left > this.right) {
                var rWidth = r.right - r.left;
                r.left = this.left - (r.left - this.right) - rWidth;
                r.right = r.left + rWidth;
            }
    
    //      Translate r above this
            if (r.top > this.bottom) {
                var rHeight = r.bottom - r.top;
                r.top = this.top - (r.top - this.bottom) - rHeight;
                r.bottom = r.top + rHeight;
            }
    
    //      If r is directly above
            if (r.right > this.left) {
                return this.top - r.bottom;
            }
    
    //      If r is directly to the left
            if (r.bottom > this.top) {
                return this.left - r.right;
            }
    
    //      r is on a diagonal path
            return Math.round(Math.sqrt(Math.pow(this.top - r.bottom, 2) + Math.pow(this.left - r.right, 2)));
        }
    });
    
    /**
     * @class Ext.ux.GhostBar
     * @extends Ext.Toolbar
     * A Toolbar class which attaches as a plugin to any BoxComponent, and fades in at the configured
     * position whenever the mouse is brought within a configurable threshold. eg: <code><pre>
    new Ext.Panel({
        renderTo: document.body,
        title: 'Test',
        width: 600,
        height: 400,
        plugins: [ new Ext.ux.GhostBar([{ text: 'Click Me' }]) ]
    });
    </pre></code>
     */
    Ext.ux.GhostBar = Ext.extend(Ext.Toolbar, {
    
        listenerAdded: false,
    
        cache: [],
        
        /**
         * @cfg {Number} threshold The number of pixels around the toolbar position in which
         * opacity is 100%.
         */
        fullVisibilityZone: 50,
    
        /**
         * @cfg {Number} threshold The number of pixels around the full visibility zone in which
         * fading is performed.
         */
        threshold: 100,
    
        /**
         * @cfg {String} position The alignment of this Toolbar, <code><b>top</b></code>, or <code><b>bottom</b></code>.
         * Defaults to <code><b>bottom</b></code>.
         */
        /**
         * @cfg {Array} offsets A two element Array containing the [x, y] offset from the default position
         * in which to display the Toolbar.
         */
    
        initComponent: function() {
    
    //      Only use one mousemove listener. Check the cache of GhostBars for proximity on each firing
            if (!this.listenerAdded) {
                Ext.getDoc().on('mousemove', Ext.ux.GhostBar.prototype.onDocMouseMove, Ext.ux.GhostBar.prototype);
                this.listenerAdded = true;
            }
            this.renderTo = document.body;
            this.hideMode = 'visibility';
            Ext.ux.GhostBar.superclass.initComponent.apply(this, arguments);
        },
    
        onRender: function() {
            Ext.ux.GhostBar.superclass.onRender.apply(this, arguments);
            this.el.setStyle({
                position: 'absolute'
            });
            this.hide();
            this.cache.push(this);
        },
    
        init: function(c) {
            this.ownerCt = c;
            c.on({
                render: this.onClientRender,
                scope: this,
                single: true
            });
            c.onPosition = c.onPosition.createSequence(this.onClientPosition, this);
            c.onResize = c.onResize.createSequence(this.onClientResize, this);
        },
    
        onClientRender: function() {
            this.clientEl = this.ownerCt.getLayoutTarget ? this.ownerCt.getLayoutTarget() : this.ownerCt.el;
        },
    
        onClientResize: function() {
            this.setWidth(this.clientEl.getWidth(true));
            this.syncPosition();
        },
    
        onClientPosition: function() {
            this.syncPosition();
        },
    
        syncPosition: function() {
            var offsets = [this.clientEl.getBorderWidth('l'), 0];
            if (this.offsets) {
                offsets[0] += this.offsets[0];
                offsets[1] += this.offsets[1];
            }
            this.el.alignTo(this.clientEl, (this.position == 'top') ? 'tl-tl' : 'bl-bl', offsets);
            this.region = this.el.getRegion();
        },
    
        onDocMouseMove: function(e) {
            for (var i = 0; i < this.cache.length; i++) {
            	if (this.cache[i].ownerCt.isVisible()) {
    	            this.checkMousePosition.call(this.cache[i], e);
    	        }
            }
        },
    
        checkMousePosition: function(e) {
            this.syncPosition();
            var o = 1, d = this.region.getDistanceBetween(e.getPoint());
            if (d > this.threshold + this.fullVisibilityZone) {
                this.hide();
            } else if ((d -= this.fullVisibilityZone) > 0) {
    
    //          Mouse is within range of this Toolbar, so show it if its not already visible
                if (!this.isVisible()) {
                    this.show();
                }
                o = 1 - (d / this.threshold);
            }
            this.el.setStyle({
                opacity: o,
                'z-index': this.ownerCt.el.zindex+3
            });
        },
    
        onDestroy: function() {
    //      Uncache this Toolbar when we are destroyed
            this.cache.splice(this.cache.indexOf(this), 1);
            Ext.ux.GhostBar.superclass.onDestroy.apply(this, arguments);
        }
    });

  10. #10
    Ext User
    Join Date
    Mar 2010
    Posts
    15
    Vote Rating
    0
    acidtonic is on a distinguished road

      0  

    Default


    The new fading is really nice.

    I do however still have the bug about ghostbars showing up when a panel with one is collapsed.

    The same exact URL I pm'ed you previously shows that behavior in firefox. Just navigate to another tab or shade any panel and move the mouse to the top left of the browser and the panels will appear when they shouldnt.