PDA

View Full Version : [Tip] Long menu overflow



tof
23 Apr 2008, 9:11 AM
Hi,

Most of the answers I found on the forum about menus that are too long (going out of the page body) is "You shouldn't use a menu then".

But I see some cases where a menu is faster for selection than a combo, or anything else; and when it's computer generated, you don't have control on how long is the list.
And, at last, some people are working on small screen (think about EEEpc users :) )

Here's a quick tip :


var assertMenuHeight = function(m) {
var maxHeight = Ext.getBody().getHeight() - 30;
if (m.el.getHeight() > maxHeight) {
m.el.setHeight(maxHeight);
m.el.applyStyles('overflow:auto;');
}
};

Which is used on a menu like this :


var menu = new Ext.menu.Menu({
// long list
items : [{text:1},{text:2},.....{text:43}],

listeners : { beforeshow : assertMenuHeight }

});


HTH,

Animal
23 Apr 2008, 10:17 AM
Good idea.

Hopefully, soon Jack will bring Menu into the Component/Container hierarchy, and that can become a plugin.

watrboy00
23 Apr 2008, 12:14 PM
http://extjs.com/forum/showthread.php?p=149487#post149487

I extended menu to be multi columned. Needs a little work but gets the job done.

tof
24 Apr 2008, 12:54 AM
I also modified your ColumnMenu.
You just have to specify a 'maxHeight' in config, in pixels, and the columns are created automatically.
You can also specify columnWitdth and spacerWidth (defaults to 148px and 5px).


Ext.ux.ColumnMenu = function(config) {
Ext.ux.ColumnMenu.superclass.constructor.call(this, config);
};

Ext.extend( Ext.ux.ColumnMenu , Ext.menu.Menu , {

render: function() {
if (this.el) {
return;
}
var el = this.el = this.createEl();

if (!this.keyNav) {
this.keyNav = new Ext.menu.MenuNav( this );
}

if ( this.plain ) {
el.addClass("x-menu-plain");
}

if ( this.cls ) {
el.addClass( this.cls );
}

this.focusEl = el.createChild({
cls: "x-menu-focus",
href: "#",
onclick: "return false;",
tabIndex:"-1",
tag: "a"
});

var column = null;
var ul;
var columnWidth = this.columnWidth || 148;
var spacerWidth = this.spacerWidth || 5;

this.items.each(function(item) {

if (column === null || column.getHeight() >= this.maxHeight) {
if (column !== null) {
el.createChild({
cls: "x-menu-column x-menu-spacer",
style: "width: " + columnWidth + "px; float: left; padding: 0;",
tag: "div",
cn : [{
html:' ',
style: "width: " + spacerWidth + "px; float: left; padding: 0;",
tag: "div"
}]
});
}

column = el.createChild({
cls: "x-menu-column",
style: "width: " + columnWidth + "px; float: left; padding: 0;",
tag: "div"
});

ul = column.createChild({
cls: "x-menu-list",
tag: "ul"
});
ul.on("click", this.onClick, this);
ul.on("mouseover", this.onMouseOver, this);
ul.on("mouseout", this.onMouseOut, this);

}

var li = document.createElement("li");
li.className = "x-menu-list-item";
ul.dom.appendChild(li);
item.render(li, this);

}, this);

}
});
Ext.reg('columnmenu', Ext.ux.ColumnMenu);

watrboy00
24 Apr 2008, 6:04 AM
Great. I found an issue in IE6 that makes the menu expand off to the right of the screen. Also any column after the first one doesn't have the normal menu item background.

Posting your update in the thread I started and will update it once I fix the other issues.

_eb

watrboy00
24 Apr 2008, 7:25 PM
I posted an update in the other thread. Re factored the code and works/looks a lot better.

nanich
26 Apr 2008, 6:02 AM
Menu is always pointing downwards. Just for the sake of the menu why do we need to show a scrollbar at the body level?? I think this can be removed if we could able to show the dropdown menu above the button instead of showing downwards as it is happening right now.

I need your help in achieving this. please provide some logic which helps me out.

Thanks in advance

watrboy00
27 Apr 2008, 3:36 AM
I am also working on different ways to prevent issues with menu overflow other than just my column implementation. Thinking about the possibility of having scroll zones on the top and bottom of the menu to scroll the items up and down if you only want one column.

NOSLOW
28 May 2008, 6:11 AM
Sweet! Just what I was looking for. I just implemented the grid filtering plugin (http://extjs.com/deploy/dev/examples/grid-filtering/grid-filter.html) and have one filter of type list that filters by username. With over 40 users, the full list wasn't accessible. I see this as a valid issue (another common example would be a filter list on states).

To make this work with the grid filter plugin, I modified the grid\filter\Filter.js file.

First, add the assertMenuHeight function to the top of the file, then
replace this line:


this.menu = new Ext.menu.Menu();

with this line:

this.menu = new Ext.menu.Menu({listeners: { beforeshow: assertMenuHeight}});

Thanks for sharing!

wm003
29 May 2008, 11:36 PM
Thinking about the possibility of having scroll zones on the top and bottom of the menu to scroll the items up and down if you only want one column.
That would be very nice and useful! How is the progress level currently? ;)

areichman
14 Aug 2008, 9:12 AM
I'm not sure if this will work in every case, but I had the same problem in that a long list of menu items were extending past the bottom of the page. My long menu item was part of the GridPanel, where a user can select which columns to show/hide in the view.

Adding the following CSS allowed me to control the size of the menu without any JS additions. This still gives you potentially a really long list instead of a multi-columned layout like described above, but at least its a valid solution.


ul.x-menu-list {
height: expression( this.scrollHeight > 350 ? "350px" : "auto" ); /* sets max-height for IE */
width: 200px;
max-height: 350px;
overflow-y: scroll;
}

mystix
14 Aug 2008, 9:27 AM
I'm not sure if this will work in every case, but I had the same problem in that a long list of menu items were extending past the bottom of the page. My long menu item was part of the GridPanel, where a user can select which columns to show/hide in the view.

Adding the following CSS allowed me to control the size of the menu without any JS additions. This still gives you potentially a really long list instead of a multi-columned layout like described above, but at least its a valid solution.


ul.x-menu-list {
height: expression( this.scrollHeight > 350 ? "350px" : "auto" ); /* sets max-height for IE */
width: 200px;
max-height: 350px;
overflow-y: scroll;
}

@vahid4134 just came up with a menu override to handle long menu lists some days back:
http://extjs.com/forum/showthread.php?t=43884

it's worth checking out.

tonedeaf
3 Sep 2008, 1:08 AM
ul.x-menu-list {
height: expression( this.scrollHeight > 350 ? "350px" : "auto" ); /* sets max-height for IE */
width: 200px;
max-height: 350px;
overflow-y: scroll;
}

I liked your implementation.

Unless your menu has long spelled out items, having a fixed width is not a good option. Also changing to overflow-y: auto will only show the vertical scrollbar if necessary.


ul.x-menu-list {
height: expression( this.scrollHeight > 350 ? "350px" : "auto" ); /* sets max-height for IE */
max-height: 350px;
overflow-y: auto;
}

areichman
3 Sep 2008, 3:21 AM
@tonedeaf-
You are right. The autoscroll-y is probably a better solution. I can't remember if there was an IE6 quirk that forced me to set it to scroll or not. I think I just forced it that way to make sure it would work, since I knew the place I was using it was definitely a long menu list.

As for the fixed width, it was purely looks. My menu items were short text words, and I thought the menu looked funny as a skinny, long, menu. So I widened it even though it added some white space. I thought it looked better though. This could also be done more correctly with min-width, but it would have required another expression hack for IE6 like I used for max-height.

galdaka
3 Sep 2008, 3:28 AM
There is a better and clear way: http://extjs.com/forum/showthread.php?p=209004

I think that in future will be added to a Ext core.

Live demo: http://www.jadacosta.es/extjs/examples/scrollmenu/menus.html


Greetings,

areichman
3 Sep 2008, 3:38 AM
@galdaka-
I've seen that demo already, and while it looks nice, I personally don't like the way it functions. I'd prefer that Ext Core simply implement scrolling for these long menu items. If you have a long menu list, clicking a button to scroll up and down one element at a time like in the demo you noted can be very annoying. Scrolling is so much faster and is so much more natural, as that's the interface the users on the web are used to using for overflow content.

Regardless, I still think my CSS edit is much simpler and straight forward to use until this gets into Core. I don't like adding JS just for the sake of adding JS. And besides, the plugin you pointed out still requires more CSS than my edit does!

To me, the power of Ext is that it adds functionality that is either not possible in CSS or is really hard in CSS and this overflow is perfect and simple fit for CSS. It's exactly the sort of thing CSS is designed to handle.

Thanks...

veroxii
25 Nov 2008, 4:16 PM
I liked your implementation.

Unless your menu has long spelled out items, having a fixed width is not a good option. Also changing to overflow-y: auto will only show the vertical scrollbar if necessary.


ul.x-menu-list {
height: expression( this.scrollHeight > 350 ? "350px" : "auto" ); /* sets max-height for IE */
max-height: 350px;
overflow-y: auto;
}

I had some display issues in IE7 with this, as it would add horizontal scroll bars onto each menu. Changing to the following works well enough for me:



ul.x-menu-list
{
height: expression( this.scrollHeight > 350 ? "350px" : "auto" ); /* sets max-height for IE */
max-height: 350px;
overflow-y: auto;
overflow-x: hidden;
}


-V

Foggy
25 Nov 2008, 11:40 PM
I came up with a other lightweight solution as a Ext.menu.Menu override.
It is very similar to tof's one.
Here is my Code and a screenshot, greets....

Ext.override(Ext.menu.Menu, {
showAt : function(xy, parentMenu, /* private: */_e){
this.parentMenu = parentMenu;
if(!this.el){
this.render();
}
if(_e !== false){
this.fireEvent("beforeshow", this);
xy = this.el.adjustForConstraints(xy);
}
this.el.setXY(xy);

// get max height from body height minus y cordinate from this.el
var maxHeight = Ext.getBody().getHeight() - xy[1];
// store orig element height
if (!this.el.origHeight) {
this.el.origHeight = this.el.getHeight();
}
// if orig height bigger than max height
if (this.el.origHeight > maxHeight) {
// set element with max height and apply scrollbar
this.el.setHeight(maxHeight);
this.el.applyStyles('overflow-y: auto;');
} else {
// set the orig height
this.el.setHeight(this.el.origHeight);
}

this.el.show();
this.hidden = false;
this.focus();
this.fireEvent("show", this);
}
});

BlueCamel
2 Dec 2008, 5:01 PM
Thanks much for the work and different examples in this thread. I hope this is handled in Ext Core in the future. Until then, there are some really nice options here.

Uberdude
12 Oct 2009, 5:42 AM
I successfully used Foggy's solution, but with a few modifications:


Ext.override(Ext.menu.Menu, {
// See http://extjs.com/forum/showthread.php?t=33475&page=2
showAt : function(xy, parentMenu, /* private: */_e) {
this.parentMenu = parentMenu;
if (!this.el) {
this.render();
}
if (_e !== false) {
this.fireEvent("beforeshow", this);
xy = this.el.adjustForConstraints(xy);
}
this.el.setXY(xy);

// Start of extra logic to what is in Ext source code...
// See http://www.extjs.com/deploy/ext/docs/output/Menu.jss.html
// get max height from body height minus y cordinate from this.el
var maxHeight = Ext.getBody().getHeight() - xy[1];
if (this.el.getHeight() > maxHeight) {
// set element with max height and apply vertical scrollbar
this.el.setHeight(maxHeight);
this.el.applyStyles('overflow-y: auto;');
}
// .. end of extra logic to what is in Ext source code

this.el.show();
this.hidden = false;
this.focus();
this.fireEvent("show", this);
}
});Removing the cache of this.el.origHeight not only made the code simpler but fixed a few bugs I encountered:


If you made a menu with 5 items, hide it, add some items, and then show it again the menu will still be 5 items big so the extra items will spill off the bottom. Equally if you make it big to start you end up with empty space on the end when you have fewer items. This arose because we have a singleton context menu.
Menus which auto-expand (e.g. menu containing an Ext.tree.TreePanel) are broken by the caching, works fine without (though you never get a scroll bar).