Javascript context menu: small but powerful
You have probably seen more than once javascript implementations of context menus based on popular libraries such as jQuery and prototype. So, they necessarily faced their main shortcomings: inconvenience of the API, a lot of code, resource requirements, love of generating a huge amount of html code. At one point, these problems overpowered my laziness and I decided to deal with them by setting the following tasks:
UPD : posted the project in google code, use, develop:
There is a submenu. Their nesting is theoretically unlimited.
Menu items can be made inaccessible (disabled = true), invisible (visible = false), you can dynamically change caption, icon and add new menu items and submenus.
It works correctly at various borders of the screen areas, a situation is worked out when the menu is in a scrollable div (scroll with the element that called up the menu).
Radio menu: select one of the menu items.
Several options for building and further menu behavior.
For reasons of lack of need, the following functions were removed: creating a menu by ajax-request, right-clicking (does not work everywhere), horizontal menu (it was rarely used).
There is a global collection where all-all menus are collected (linear list). The menu itself is a certain object containing information about its behavior and condition, as well as menu items. A menu item may contain submenus.
To display the menu on the page, you must point to any element of the page (I usually use pictures or links), and also indicate the event of this element by which the menu will be called if it (the event) is different from the click.
One menu can be added to many elements on the page. From this menu it will not multiply, but nevertheless it will know where it was called from (this is fundamentally in the handler).
I tried to make the code as simple and straightforward as possible so that adding new functions and deleting something unnecessary was not difficult. For example, the implementation of a radio menu required five or six lines and 10 minutes of time. Modify the code as you like, but in the comments indicate the author’s link nevertheless.
Menus can be created in different ways, depending on the degree of perversion of the menu that you want to end up with. The simplest thing is to pass an array of actions to the menu designer.
At the exit, get a link to the finished menu. Which can be binded anywhere. Already banned! Although no, I'm lying. It is even simpler: bind an array of actions to an element.
I completely forgot, it can be even simpler: set an array of parameters for these actions instead of actions.
this is equivalent to calling the action constructor
or just setting the action via json
This will cover the bulk of the tasks. But what if we need a menu that dynamically changes depending on external factors? I thought a lot about this, tried various implementations, as a result, one was settled: we pass the function to the menu designer. This function will be called whenever it is necessary to show the menu.
Attention: important!
To optimize the menu as a whole, this function works rather strange. She receives as the only parameter a menu object in full disposal. It should return either a lie (this will mean that the menu does not require redrawing), or truth, or an array of actions. But the array of actions can not be returned, but simply written to the member “a” of the menu object - menu.a = [array of actions], this is equivalent.
Often, the menu depends not only on the state of the environment, but also on what menu item called up. For this menu has a member caller. It contains a link to the DOM element that invoked the menu. For a submenu, this element will be a link to the home element of the parent menu, so it makes sense to look at the parentMenu member containing the link to the parent menu.
A typical function looks like this:
A few words about the object-action. The most important method in it is execute. This method will be called when you click on a menu item. It takes three parameters. The first is the action object itself, the second is the menu, the third is the array-chain of menu calls (it can be useful for complex multi-level menus).
A less important member of the action object is submenu. There may be an array of actions, or a menu generator function.
For the radio behavior of the menu, set the menu.type = 'radio' property in the menu object, and two methods: set (str) and get.
Look at an example, the topic of the radio menu is revealed there.
And the last one. Instead of a bind, you can use a more complicated construction. This will avoid garbage in the house in the form of events of nonexistent elements. Yes, I'm talking about inline calls. There is a $ .cmenu.getCaller (menu) or "overloaded" $ .cmenu.getCaller (event, menu) method in the factory-menu class that will return a line from parameters like this:
This line can be attached to an element.
If you know the implementation better, please do not hide - speak out.
- Minimum html of code generated for the menu (why do we clutter up the HOUSE)
- Laconic js code for creating a menu (call API without copy-paste )
- Optimum flexibility at work (multi-level, dynamically modifiable menus)
- As little code as possible in the library implementation (6302 bytes in uncompressed form)
- The minimum number of jQuery calls (so that you can easily refuse them for those who do not use jQuery)
- Inline-events where it is possible instead of binds (fewer resources will gobble up)
UPD : posted the project in google code, use, develop:
svn checkout js-cmenu.googlecode.com/svn/trunk js-cmenu-read-only
Functional
There is a submenu. Their nesting is theoretically unlimited.
Menu items can be made inaccessible (disabled = true), invisible (visible = false), you can dynamically change caption, icon and add new menu items and submenus.
It works correctly at various borders of the screen areas, a situation is worked out when the menu is in a scrollable div (scroll with the element that called up the menu).
Radio menu: select one of the menu items.
Several options for building and further menu behavior.
For reasons of lack of need, the following functions were removed: creating a menu by ajax-request, right-clicking (does not work everywhere), horizontal menu (it was rarely used).
How it works
There is a global collection where all-all menus are collected (linear list). The menu itself is a certain object containing information about its behavior and condition, as well as menu items. A menu item may contain submenus.
To display the menu on the page, you must point to any element of the page (I usually use pictures or links), and also indicate the event of this element by which the menu will be called if it (the event) is different from the click.
One menu can be added to many elements on the page. From this menu it will not multiply, but nevertheless it will know where it was called from (this is fundamentally in the handler).
I tried to make the code as simple and straightforward as possible so that adding new functions and deleting something unnecessary was not difficult. For example, the implementation of a radio menu required five or six lines and 10 minutes of time. Modify the code as you like, but in the comments indicate the author’s link nevertheless.
Create and call examples
Menus can be created in different ways, depending on the degree of perversion of the menu that you want to end up with. The simplest thing is to pass an array of actions to the menu designer.
var x = $.cmenu.getMenu([
new menuItem('Назад', 'arrow_left',function(){history.back();}),
new menuItem('!Вперед', 'arrow_right',function(){history.forward();}),
new menuItem('Обновить','arrow_refresh',function(){location.href=location.href;})
]);
$('.callMenu').bindMenu(x);
$('#main_link').bindMenu(x);
At the exit, get a link to the finished menu. Which can be binded anywhere. Already banned! Although no, I'm lying. It is even simpler: bind an array of actions to an element.
$('.callMenu').bindMenu([
new menuItem('Назад', 'arrow_left',function(){history.back();}),
new menuItem('!Вперед', 'arrow_right',function(){history.forward();}),
new menuItem('Обновить','arrow_refresh',function(){location.href=location.href;})
]);
I completely forgot, it can be even simpler: set an array of parameters for these actions instead of actions.
$('.callMenu').bindMenu([
['Назад', 'arrow_left',function(){history.back();}],
['!Вперед', 'arrow_right',function(){history.forward();}],
['Обновить','arrow_refresh',function(){location.href=location.href;}]
]);
this is equivalent to calling the action constructor
menuItem = function(caption,icon,execute,submenu)
or just setting the action via json
{
caption:'Caption',
icon:'может быть undefined, кстати, как и все остальные параметры',
visible:true,
disabled:false,
execute:function(){},
submenu:{объект-меню, массив действий или функция, создающая меню — об этом далее}
}
This will cover the bulk of the tasks. But what if we need a menu that dynamically changes depending on external factors? I thought a lot about this, tried various implementations, as a result, one was settled: we pass the function to the menu designer. This function will be called whenever it is necessary to show the menu.
Attention: important!
To optimize the menu as a whole, this function works rather strange. She receives as the only parameter a menu object in full disposal. It should return either a lie (this will mean that the menu does not require redrawing), or truth, or an array of actions. But the array of actions can not be returned, but simply written to the member “a” of the menu object - menu.a = [array of actions], this is equivalent.
Often, the menu depends not only on the state of the environment, but also on what menu item called up. For this menu has a member caller. It contains a link to the DOM element that invoked the menu. For a submenu, this element will be a link to the home element of the parent menu, so it makes sense to look at the parentMenu member containing the link to the parent menu.
A typical function looks like this:
menuGenerator = function (menu) { if (! menu.a) { // initialize the menu return true; // need redrawing } if (myVarChanged ()) {// something happened in the object model menu.a.doAction.disabled = myVarValue (); return true; // need redrawing } if (menu.caller.id = 666) { menu.a.doAction.visibe = false; return true; // need redrawing } return false; // everything is still redrawing is not needed }
A few words about the object-action. The most important method in it is execute. This method will be called when you click on a menu item. It takes three parameters. The first is the action object itself, the second is the menu, the third is the array-chain of menu calls (it can be useful for complex multi-level menus).
A less important member of the action object is submenu. There may be an array of actions, or a menu generator function.
For the radio behavior of the menu, set the menu.type = 'radio' property in the menu object, and two methods: set (str) and get.
Look at an example, the topic of the radio menu is revealed there.
And the last one. Instead of a bind, you can use a more complicated construction. This will avoid garbage in the house in the form of events of nonexistent elements. Yes, I'm talking about inline calls. There is a $ .cmenu.getCaller (menu) or "overloaded" $ .cmenu.getCaller (event, menu) method in the factory-menu class that will return a line from parameters like this:
onmouseout="$.cmenu.lockHiding=false;"
This line can be attached to an element.
If you know the implementation better, please do not hide - speak out.