Контекстное меню для Netscape Navigator и Internet Explorer
Почему-то на сайтах, посвященных программированию на JavaScript, традиционно считается, что это невозможно, так как Netscape по щелчку правой клавишей мыши
создает собственное всплывающее меню. Попробуем развеять это заблуждение. Дело в том, что выпадающее меню в Netscape Navigator 7.1 и выше версии (ниже версии
нет в наличии - если код корректно работает в версии Netscape ниже 7.1 - пожалуйста напишите мне), можно отключить с помощью знакомой инструкции:
, но как же создать выпадающее меню, если в Netscape следующий код (работающий в Internet Explorer):
<body oncontextmenu="return false;">
не работает, то есть - функция вызывается, но в Nescape Navigator возникает выпадающее меню, поверх создаваемого нами в функции myfunction. Как же обойти это
ограничение? Я нашел такое решение:
<body oncontextmenu="myfunction(); return false;">
<body oncontextmenu="return myfunction()">
Как вы вполне можете догадаться, функция "myfunction()" в конце своего исполнения возвращает результат - return false, что с точки зрения Netscape Navigator считается корректным. Но только вот такой код не считается корректным у Internet Explorer. Что поделаешь? Война браузеров! Придется воспользоваться таким интересным приемом, как условная трансляция (надеюсь, что подобрал словосочетание правильное, по аналогии с условной компиляцией в Си) браузера Internet Explorer. Дело в том, что Microsoft © позаботилась о создании специальных конструкций, которые для остальных браузеров выглядят как комментарий. С помощью этих конструкций можно создать достаточно сложные ветвления кода. Попробуем создать ветвление кода на основе этих самых конструкций и разберем его чуть ниже.
<script language="JavaScript" type="text/javascript">
<!--
/*@cc_on
@if( true )
document.write( "<body oncontextmenu=\"myfunction(); return false;\">" );
@else*/
document.write( "<body oncontextmenu=\"return myfunction();\">" );
/*@end
@cc_off @*/
//-->
</script>
Разберем первую эту часть скрипта подробно. Вначале определяется тип браузера пользователя, эта часть нам не интересна - такие скрипты можно найти в Internet повсюду. Для начинающих лучше всего воспользоваться редактором с подсветкой синтаксиса. Итак первая интересующая нас инструкция: "/*@cc_on" - это инструкция начала "условной трансляции", обратить внимание стоит только на ее начало - "/*" - это обычный комментарий. Internet Explorer выше пятой версии разбирает даже комментарии, и ищет директивы "условной трансляции" (большой брат следит за тобой!). Теперь присмотримся к следующей строке - тут указана на первый взгляд странная строка: "@if( true )" - условие, которое выполнится в любом случае! Необходимость этого условия не кажется очевидной, но на самом деле все очень просто, если Internet Explorer прочтет эту инструкцию - он выполнит код, который предназначен только ему одному, а строку идущую после директивы "@else*/" пропустит. А теперь предположим, что условия "@if( true ) ... @else" нет - что получится? Internet Explorer выполнит сразу две инструкции - и мы получим в документе сразу два <body> с разными параметрами! Не очень приятная перспектива. Последняя инструкция @cc_off @*/ завершает "условную трансляцию". Таким образом мы получили переносимый код, подходящий для браузеров на основе движка Internet Explorer (Avant, Maxthon, etc...) и движка Gecko (Netscape Navigator, Mozilla, Mozilla Firefox, Mozilla Firebird, etc...).
Создадим примитивный лист стилей (наша задача создать не красивый, а понятный код - украсить его вы можете сами! Наш лист стилей:
<style type="text/css">
<!--
.hidemenu {
display: none;
position: absolute;
}
.showmenu {
display: block;
position: absolute;
}
//-->
</style>
Он отвечает только за отображение и сокрытие контекстного меню, что нам собственно и надо. Теперь надо определить координаты курсора в момент щелчка правой клавиши мыши по документу. Основная проблема заключается в том, что для Netscape Navigator возникает такая проблема, обработчик события event.onmousedown - перекрывает обработчик, заданный в body (в общем-то вполне логично, поскольку event задается относительно окна, а не документа, как в body). И опять возникает ненавистное браузерное контекстное меню. Я решил эту проблему таким образом (если кто знает решение проще - пришлите мне письмо):
if( document.all ) {
} else if( document.getElementById ) {
document.captureEvents( Event.MOUSEMOVE );
document.onmousemove = getCoord;
}
Я стал отслеживать каждое передвижение мыши на экране и записывать координаты курсора в глобальные переменные:
var mouseX;
var mouseY;
function getCoord( event ) {
mouseX = event.pageX;
mouseY = event.pageY;
}
Теперь соберем саму функцию отображения меню:
function myfunction() {
if( document.all ) {
if( event.button == 2 || event.button == 3 ) {
if( !document.all.contextmenu )
return;
var menu = document.all.contextmenu;
menu.style.left = event.offsetX;
menu.style.top = event.offsetY;
menu.className = menuState ? "hidemenu" : "showmenu";
menuState = !menuState;
}
} else if ( document.getElementById ) {
if( !document.getElementById( "contextmenu" ) )
return;
var menu = document.getElementById( "contextmenu" );
menu.style.left = mouseX;
menu.style.top = mouseY;
menu.className = menuState ? "hidemenu" : "showmenu";
menuState = !menuState;
return false;
}
}
Функция не требует особых навыков в JavaScript, чтобы в ней разобраться. Скажу лишь вкратце, устанавливается положение меню на основе координат курсора с ветвлением на два браузера. Разница лишь в том, что Internet Explorer получает координаты из события event, а Netscape Navigator из глобальных переменных. Если вы правильно собрали скрипт - у вас должно получиться нечто подобное:
<html>
<head>
<meta http-equiv="Content-type" content="text/html; charset=Windows-1251">
<title>Context menu</title>
<style type="text/css">
<!--
.hidemenu {
display: none;
position: absolute;
}
.showmenu {
display: block;
position: absolute;
}
//-->
</style>
<script language="JavaScript" type="text/javascript">
<!--
if( document.all ) {
document.onmousedown = myfunction;
} else if( document.getElementById ) {
document.captureEvents( Event.MOUSEMOVE );
document.onmousemove = getCoord;
}
var mouseX;
var mouseY;
var menuState = false;
function getCoord( event ) {
mouseX = event.pageX;
mouseY = event.pageY;
}
function myfunction() {
if( document.all ) {
if( event.button == 2 || event.button == 3 ) {
if( !document.all.contextmenu )
return;
var menu = document.all.contextmenu;
menu.style.left = event.offsetX;
menu.style.top = event.offsetY;
menu.className = menuState ? "hidemenu" : "showmenu";
menuState = !menuState;
}
} else if ( document.getElementById ) {
if( !document.getElementById( "contextmenu" ) )
return;
var menu = document.getElementById( "contextmenu" );
menu.style.left = mouseX;
menu.style.top = mouseY;
menu.className = menuState ? "hidemenu" : "showmenu";
menuState = !menuState;
return false;
}
}
//-->
</script>
<script language="JavaScript" type="text/javascript">
<!--
/*@cc_on
@if( true )
document.write( "<body oncontextmenu=\"myfunction(); return false;\">" );
@else*/
document.write( "<body oncontextmenu=\"return myfunction();\">" );
/*@end
@cc_off @*/
//-->
</script>
<div id="contextmenu" class="hidemenu" style="background-color: Yellow;">
<span><a href="javascript: void( 0 )">Первый пункт меню</a></span><br>
<span><a href="javascript: void( 0 )">Второй пункт меню</a></span><br>
<span><a href="javascript: void( 0 )">Третий пункт меню</a></span>
</div>
</body>
</html>
Для тех же, кто желает "погорячее" привожу код, полностью основанный на "условной трансляции" Microsoft ©. Комментировать код не буду - изложенного выше материала вполне достаточно, чтобы разобраться в нем самому.
<html>
<head>
<meta http-equiv="Content-type" content="text/html; charset=Windows-1251">
<title>Context menu</title>
<style type="text/css">
<!--
.hidemenu {
display: none;
position: absolute;
}
.showmenu {
display: block;
position: absolute;
}
//-->
</style>
<script language="JavaScript" type="text/javascript">
<!--
var menuState = false;
/*@cc_on
@if( true )
document.onmousedown = myfunction;
@else*/
var mouseX;
var mouseY;
if( document.getElementById ) {
document.captureEvents( Event.MOUSEMOVE );
document.onmousemove = getCoord;
}
function getCoord( event ) {
mouseX = event.pageX;
mouseY = event.pageY;
}
/*@end
@cc_off @*/
function myfunction() {
/*@cc_on
@if( true )
if( event.button == 2 || event.button == 3 ) {
if( !document.all.contextmenu )
return;
var menu = document.all.contextmenu;
menu.style.left = event.offsetX;
menu.style.top = event.offsetY;
menu.className = menuState ? "hidemenu" : "showmenu";
menuState = !menuState;
}
@else*/
if( !document.getElementById( "contextmenu" ) )
return;
var menu = document.getElementById( "contextmenu" );
menu.style.left = mouseX;
menu.style.top = mouseY;
menu.className = menuState ? "hidemenu" : "showmenu";
menuState = !menuState;
return false;
/*@end
@cc_off @*/
}
//-->
</script>
<script language="JavaScript" type="text/javascript">
<!--
/*@cc_on
@if( true )
document.write( "<body oncontextmenu=\"myfunction(); return false;\">" );
@else*/
document.write( "<body oncontextmenu=\"return myfunction();\">" );
/*@end
@cc_off @*/
//-->
</script>
<div id="contextmenu" class="hidemenu" style="background-color: Yellow;">
<span><a href="javascript: void( 0 )">Первый пункт меню</a></span><br>
<span><a href="javascript: void( 0 )">Второй пункт меню</a></span><br>
<span><a href="javascript: void( 0 )">Третий пункт меню</a></span>
</div>
</body>
</html>
С точки зрения производительности такой код лучше, поскольку браузерам, на основе движка Gecko, не требуется проверять дополнительные условия. И в завершение этой статьи хочу привести пример создания контекстного меню с помощью технологий, встроенных в Internet Explorer. По сути создается отдельное немодальное окно на основе полученного кода. Этот код не может использовать внешние таблицы стилей (даже встроенные в документ!), поэтому вводить данные о стиле придется контекстно к элементу.
<html>
<head>
<title>Microsoft context menu</title>
<script language="JavaScript" type="text/javascript">
<!--
var winPopup = window.createPopup();
var mWidth = 247;
var mHeight = 67;
function goContext()
{
var winPopupBody = winPopup.document.body;
winPopupBody.innerHTML = winContext.innerHTML;
winPopup.show( event.offsetX, event.offsetY, mWidth, mHeight, document.body );
document.body.onmouseup = closePopup;
}
function closePopup()
{
winPopup.hide();
}
//-->
</script>
</head>
<body oncontextmenu="goContext(); return false">
<div id=winContext style="DISPLAY: none">
<div id="cells">
<div onmouseover="this.style.background='#ffffff'" style="BORDER-RIGHT: black 1px solid; PADDING-RIGHT: 2px; BORDER-TOP: white 1px solid; PADDING-LEFT:
10px; FONT-WEIGHT: bold; FONT-SIZE: 8pt; BACKGROUND: #cccccc; LEFT: 0px; PADDING-BOTTOM: 2px; BORDER-LEFT: white 1px solid; CURSOR: hand; COLOR: black;
PADDING-TOP: 2px; BORDER-BOTTOM: black 1px solid; FONT-FAMILY: verdana; POSITION: relative; TOP: 0px; HEIGHT: 20px"
onclick="parent.oIframe.location.href='#';" onmouseout="this.style.background='#cccccc'"> Главная
страница</div>
<div onmouseover="this.style.background='#ffffff'" style="BORDER-RIGHT: black 1px solid; PADDING-RIGHT: 2px; BORDER-TOP: white 1px solid; PADDING-LEFT:
10px; FONT-WEIGHT: bold; FONT-SIZE: 8pt; BACKGROUND: #cccccc; LEFT: 0px; PADDING-BOTTOM: 2px; BORDER-LEFT: white 1px solid; CURSOR: hand; COLOR: black;
PADDING-TOP: 2px; BORDER-BOTTOM: black 1px solid; FONT-FAMILY: verdana; POSITION: relative; TOP: 0px; HEIGHT: 20px"
onclick="parent.oIframe.location.href='#';" onmouseout="this.style.background='#cccccc'"> Какое-то
действие</div>
<div onmouseover="this.style.background='#ffffff'" style="BORDER-RIGHT: black 1px solid; PADDING-RIGHT: 2px; BORDER-TOP: white 1px solid; PADDING-LEFT:
10px; FONT-WEIGHT: bold; FONT-SIZE: 8pt; BACKGROUND: #cccccc; LEFT: 0px; PADDING-BOTTOM: 2px; BORDER-LEFT: white 1px solid; CURSOR: hand; COLOR: black;
PADDING-TOP: 2px; BORDER-BOTTOM: black 1px solid; FONT-FAMILY: verdana; POSITION: relative; TOP: 0px; HEIGHT: 20px"
onclick="parent.oIframe.location.href='#';" onmouseout="this.style.background='#cccccc'"> Еще что-то
делаем</div>
</div>
</div>
</body>
</html>
Вся сложность создания дочернего окна с помощью технологий Internet Explorer - это необходимость внесения всего кода в новое окно, включая JavaScript и CSS. Результат чего можно видеть в непомерно разросшихся аттрибутах div'ов. Пример взят из MSDN и адаптирован мной для лучшей читаемости кода. Разберем подробнее. Итак, первое действие window.createPopup() - создает дочернее немодальное окно, но не отображает его на экране. Программистам, знакомым с WinAPI хотя бы поверхностно - объяснять это не надо. Далее определяем значения длины и высоты нашего меню (можно автоматизировать этот процесс, но это будет уже лишний код - сейчас главное понять как создается меню) - это переменные mWidth и mHeight. Следующий, интересующий нас оператор, это winPopup.document.body; - мы получаем ссылку на тело документа нового окна. Далее - получаем код из div'a с идентификатором winContext и присваиваем его телу документа созданного окна. Далее - просто отображаем новое окно - winPopup.show. Параметры: координата окна по горизонтали, координа окна по вертикали, ширина окна, высота окна, ссылка на документ-владелец нового окна.
На этом считаю статью законченной, если будут вопросы - пишите на e-mail: [email protected]
Вячеслав Шуранов aka Чайник (DUmmY)