PDA

View Full Version : Ext.ux.GhostBar - A space-saving, fade-in Toolbar



Animal
23 May 2009, 2:47 AM
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 (http://extjs.com/deploy/ext-3.0-rc1.1/docs/?class=Ext.BoxComponent&member=plugins) to any BoxComponent (http://extjs.com/deploy/ext-3.0-rc1.1/docs/?class=Ext.BoxComponent)

To test it, simply:



new Ext.Panel({
renderTo: document.body,
title: 'Test',
width: 600,
height: 400,
plugins: [ new Ext.ux.GhostBar([{ text: 'Click Me' }]) ]
});




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

galdaka
23 May 2009, 6:28 AM
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,

Animal
24 May 2009, 12:47 AM
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.

mjlecomte
26 May 2009, 6:26 AM
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.



<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>

Animal
26 May 2009, 9:38 AM
Excellent work MJ, I've incorporated your fixes and enhancements into post #1

acidtonic
2 Mar 2010, 12:55 PM
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.

acidtonic
3 Mar 2010, 7:43 AM
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.

Animal
3 Mar 2010, 8:56 AM
It needs this to only check for proximity if its owner is visible:



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

Animal
3 Mar 2010, 9:06 AM
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



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

acidtonic
3 Mar 2010, 10:37 AM
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.

acidtonic
3 Mar 2010, 11:57 AM
From what I'm finding, this.ownerCt.isVisible() returns true even when it really isnt.

I'm confused because the ghostbar is a plugin to a panel which is inside a tab panel.

When either the panel shades OR the current tab is changed on the tabpanel, the ghostbar still renders but realigns to (0,0) placing it in the upper left.

I'm not sure why it realigns up there though. It's almost like this.ownerCt is returning the parent of the parent which would be the tab panel, which is visible and thus the toolbar gets drawn.

Animal
3 Mar 2010, 12:05 PM
The ownerCt Panel may be visible, but ITS ownerCt may be hidden. There will have to be a "recursive" parameter to Component.isVisible which goes all the way up the chain.

I'll leave that as an exercise for you.

acidtonic
3 Mar 2010, 1:12 PM
In my case I *always* use the plugin inside a shadeable panel.

My hack was to simply check twice and never any higher. Also added a hide for when it fails the test yet it's visible.

The other thought was to also check the parent component for the "beforeCollapse" event, and if it exists, listen to it and hide() on invocation.

My updated code is here for anyone else solving these problems.


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()) {
if (this.ownerCt.isVisible() && this.ownerCt.ownerCt.isVisible()) {
this.show();
}
} else {
if (!this.ownerCt.isVisible() || !this.ownerCt.ownerCt.isVisible()) {
this.hide();
}
}
o = 1 - (d / this.threshold);
}
this.el.setStyle({
opacity: o,
//'z-index': this.ownerCt.el.zindex+3
'z-index': 20
});
},

Animal
3 Mar 2010, 1:18 PM
How about an override of Component.isVisible?

acidtonic
3 Mar 2010, 2:04 PM
How about an override of Component.isVisible?

That's what I instantly thought, but remembered how much help I got simply trying to extend my first component(zero), and decided I wouldn't undertake that one.

Animal
3 Mar 2010, 2:35 PM
You're going to have to do it sooner or later. It's not as if there isn't deep and complete documentation, and hundreds of examples.

acidtonic
8 Apr 2010, 12:41 PM
I solved this by adding a new function to the GhostBar superclass called "parentsVisible" which simply accepts a start object and traverses it back up until there is no parent. It checks each step for a non visible parent and short circuits as soon as one isn't.

There is one caveat I should mention and that's when the passed object doesnt have a parent. That will return true and that's simply by design since the function doesnt care if the passed object is visible or not, only it's parents. And if it has none, then technically all the parent's are visible.


parentsVisible: function(start) {
var current = start;
//alert(start);
while(current) {
if (current.ownerCt) {
if (current.ownerCt.isVisible()) {
current = current.ownerCt;
} else {
return false;
}
} else {
return true;
}
}
return false;
},
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()) {
if (this.parentsVisible(this)) {
this.show();
}
} else {
if (!this.parentsVisible(this)) {
this.hide();
}
}
o = 1 - (d / this.threshold);
}
this.el.setStyle({
opacity: o,
//'z-index': this.ownerCt.el.zindex+3
'z-index': 20
});
},

acidtonic
9 Apr 2010, 11:30 AM
I also found that my new function didn't properly handle collapsed panels, so i added a quick check for that. Updated code below.


parentsVisible: function(start) {
var current = start;
//alert(start);
while(current) {
if (current.ownerCt) {
if (current.ownerCt.isVisible()) {
if (current.ownerCt.collapsed) { return false; }
current = current.ownerCt;
} else {
return false;
}
} else {
return true;
}
}
return false;
},
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()) {
if (this.parentsVisible(this)) {
this.show();
}
} else {
if (!this.parentsVisible(this)) {
this.hide();
}
}
o = 1 - (d / this.threshold);
}
this.el.setStyle({
opacity: o,
//'z-index': this.ownerCt.el.zindex+3
'z-index': 20
});
},

acidtonic
18 May 2010, 12:04 PM
Posted full version here since someone on IRC asked and said it wasn't obvious which code snippit to use.

This one has my additional function for handling visibility.


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);
},
parentsVisible: function(start) {
var current = start;
//alert(start);
while(current) {
if (current.ownerCt) {
if (current.ownerCt.isVisible()) {
if (current.ownerCt.collapsed) { return false; }
current = current.ownerCt;
} else {
return false;
}
} else {
return true;
}
}
return false;
},
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()) {
if (this.parentsVisible(this)) {
this.show();
}
} else {
if (!this.parentsVisible(this)) {
this.hide();
}
}
o = 1 - (d / this.threshold);
}
this.el.setStyle({
opacity: o,
//'z-index': this.ownerCt.el.zindex+3
'z-index': 20
});
},
});

dias
20 May 2010, 3:18 PM
Hello, i'm using this plugin... it very good. But i have a problem... if the panel where i use this plugin have a scrollbar, the GhostBar is over the scrollbar... it's possible to solve this.

Thanks in advance,

acidtonic
21 May 2010, 5:39 AM
Make the width slightly less than the parent component by enough to not cover the scrollbar. Maybe even have the alignment code check if there are scrollbars first and only shrink it when they are visible.....

Just some thoughts.