Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > JavaScript: Общие вопросы > клик снаружи дива


Автор: katka 18.10.2006, 09:38
мне кажется, вопрос довольно распространённый, только я никак не найду похожую тему. если была - покажите, пожалуйста.
мы пишем имитацию выпадающих списков, и нам нужно скрывать список (див) при клике мимо него. есть функция hide_all_selects(), но на какое событие её повесить? что бы вы посоветовали?

Автор: Avb 18.10.2006, 10:53
http://forum.vingrad.ru/index.php?showtopic=115646

Автор: katka 18.10.2006, 10:59
о! спасибо. сама не нашла.

Автор: Burrr 18.10.2006, 13:46
Можно проще - воспользоваться свойством cancelBubble объекта event.
1) На body повесить обработчик, скрывающий выпадушку по клику
2) На саму выпадушку повесить обработчик, который запретит событию распространятся вверх по дереву DOM
Код
evt = event || window.event;
evt.cancelBubble = true;

Автор: Avb 18.10.2006, 14:36
Цитата(Burrr @  18.10.2006,  13:46 Найти цитируемый пост)
1) На body повесить обработчик, скрывающий выпадушку по клику
2) На саму выпадушку повесить обработчик, который запретит событию распространятся вверх по дереву DOM


Но в таком случае нужно учесть, что обработчик, скрывающий выпадушку, будет вызываться при каждом клике в пределах документа. Если в нем кроме divElement.style.display = 'none' ничего нет, то тогда все ОК. А если есть дополнительные действия, то нужно смотреть по ситуации.


Burrr, не так ли?

Автор: Burrr 18.10.2006, 14:42
Avb, совершенно верно, Но я бы не сказал, что в этом есть какие-то затруднения. Обычно я пишу обработчик так:
Код
document.onclick = function() {
     firstFunc();
     secondFunc();
     //...
}

Вписываем туда функций, сколько влезет...

Автор: Avb 18.10.2006, 15:52
Burrr, я имел в виду не невозможность выполнять несколько функций, а возможные затупления, если вдруг при каждом скрытии дивов необходимо выполнять какой-то "тяжелый" код. 
И то это чисто теоретически, т. к. этот тяжелый код можно не выполнять, если див невидимый.
Просто katka, должна это учесть и при необходимости добавить дополнительные проверки.

Автор: katka 18.10.2006, 16:54
Цитата(Burrr @  18.10.2006,  13:46 Найти цитируемый пост)

Код

evt = event || window.event;
evt.cancelBubble = true;


smile  куда это писать? там, где обрабатываем клик на выпадушку? мм.. что такое event?  smile

Автор: Burrr 18.10.2006, 16:59
Код

<a onclick="func(event);return false;" href="">link</a>
<script type="text/javascript">
     var evt = event || window.event;
     evt.cancelBubble = true;
     // дальше открываешь выпадушку ну или какие-нить действия
</script>

Автор: katka 18.10.2006, 17:11
мне во все функции с обработкой кликов пихать евент в скобочки? блин. простите, ради бога, но я ничего не поняла опять ((
вот, давайте с примером..
Код

<div id="select42" onClick="show_options(42)"></div>

в моём идиллическом мире нет никаких евентов, к сожалению  smile

Автор: Burrr 18.10.2006, 17:20
Цитата
мне во все функции с обработкой кликов пихать евент в скобочки?

Да, а в чем проблема? smile
Это нужно для мозиллы, ИЕ работает на window.event.
Код

<div id="select42" onClick="show_options(42, event)"></div>
<script type="text/javascript">
function show_options(num, evt) {
     evt = evt || window.event;
     evt.cancelBubble = true;
     // дальше открываешь выпадушку ну или какие-нить действия
}
</script>

Автор: katka 18.10.2006, 17:36
Burrr, спасибо!! рработает!! красота!

Автор: Avb 18.10.2006, 18:08
Ну, и я тут примерчик набросал:
Код

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
    <title></title>
<script type="text/javascript">
window.onload = init; //После загрузки html-я вызываем init

function init() {
    // Навешиваем обработчики события
    document.body.onclick = closeDiv; 
    document.getElementById("select42").onclick = doNothing;  
    document.getElementById("show").onclick = openDiv;

}

function closeDiv() {
    //Прячем наш див и показываем див show
    document.getElementById("select42").style.display = "none";
    document.getElementById("show").style.display = "block";
}

function openDiv(event) {
    //Показываем наш див и прячем див show 
    document.getElementById("select42").style.display = "block";
    document.getElementById("show").style.display = "none";
    //Останавливаем распространение события
    var event = event || window.event;
    if (event.stopPropagation) {
      event.stopPropagation();
    } else {
      event.cancelBubble = true;
    }
    
}

function doNothing(event) {
    //Прячем див show
    document.getElementById("show").style.display = "none";
    //Останавливаем распространение события
    var event = event || window.event;
    if (event.stopPropagation) {
      event.stopPropagation();
    } else {
      event.cancelBubble = true;
    }
}
</script>
</head> 
<body>
<div id="show" style="display:none">Показать див снова</div>
<br>
<br>
<div id="select42" style="border: 1px solid black">Это наш див. Если кликнуть мышкой внутри него ничего не произойдет,
если снаружи - див спрячется.</div>
<br>
<br>
<div>Всякий другой контент</div>
<div>Всякий другой контент</div>
<div>Всякий другой контент</div>
<div>Всякий другой контент</div>
<div>Всякий другой контент</div>
<div>Всякий другой контент</div>
<div>Всякий другой контент</div>
<div>Всякий другой контент</div>
<div>Всякий другой контент</div>
<div>Всякий другой контент</div>
<div>Всякий другой контент</div>
<div>Всякий другой контент</div>
<div>Всякий другой контент</div>

</body>
</html>


Добавлено @ 18:22 
katka, советую почитать раздел Events на http://www.quirksmode.org/js/contents.html для лучшего понимания обработки событий.

Автор: Nicholas_S 18.10.2006, 19:39
Есть более изысканное решение для "отлова" событий вне объекта.
Заключается он в использовании методов setCapture()/releaseCapture().
Например:

Код

  <div id="myDiv" onClick="this.innerHTML += ' click ';">Test div</div>
  <input type="button" onClick="document.getElementById('myDiv').setCapture()"  value="Capture the div">


После нажатия на кнопку слой будет принимать событие onClick из любой области документа.
Фича поддерживается IE. Чтобы расширить на Mozilla/Opera, можно определить прототипы для элементов документа.
Например, следующим образом:

Код

if (window.HTMLElement)
{
    var element = HTMLElement.prototype;
    // Список событий для "отлова"
    var capture = ["click", "mousedown", "mouseup", "mousemove", "mouseover", "mouseout"];

    element.setCapture = function()
    {
        var self = this;
        var flag = false;
        this._capture = function(e)
        {
            if (flag) return;
            flag = true;
            var event = document.createEvent("MouseEvents");
            event.initMouseEvent(e.type,
                e.bubbles, e.cancelable, e.view, e.detail,
                e.screenX, e.screenY, e.clientX, e.clientY,
                e.ctrlKey, e.altKey, e.shiftKey, e.metaKey,
                e.button, e.relatedTarget);
            self.dispatchEvent(event);
            flag = false;
        }

        for (var i=0; i<capture.length; i++)
            window.addEventListener(capture[i], this._capture, true);
    }

    element.releaseCapture = function()
    {
        for (var i=0; i<capture.length; i++)
        {
          if (this._capture)
            window.removeEventListener(capture[i], this._capture, true);
        }

        this._capture = null;
    }
}  


Вуаля.

Автор: Zeroglif 18.10.2006, 22:08
Цитата(Burrr @  18.10.2006,  17:20 Найти цитируемый пост)
Код

<div id="select42" onClick="show_options(42, event)"></div>
<script type="text/javascript">
function show_options(num, evt) {
     evt = evt || window.event;
     evt.cancelBubble = true;
}
</script>

Строчка evt = evt || window.event; для такого примера не обязательна, и cancelBubble - это всё же не комильфо для тех браузеров, что знают и умеют stopPropagation. smile

Автор: Burrr 19.10.2006, 10:33
Цитата
Строчка evt = evt || window.event; для такого примера не обязательна, и cancelBubble - это всё же не комильфо для тех браузеров, что знают и умеют stopPropagation.


Zeroglif, проверял работу cancelBubble в ИЕ 6, Opera 7.5+, NN 7.1+, Mozilla 1.7, FF 1,5 - везде работает. По идее cancelBubble изначально поддерживалась только ИЕ, но если сейчас оно работает и во всех остальных бпаузерах, то зачем плодить методы ;)

Автор: AKS 19.10.2006, 11:00
Burrr, 
Цитата

проверял работу cancelBubble в ИЕ 6, Opera 7.5+, NN 7.1+, Mozilla 1.7, FF 1,5 - везде работает

Это св-во - http://developer.mozilla.org/en/docs/cancelBubble!

Nicholas_S, 
Цитата

...более изысканное решение... в использовании методов setCapture()/releaseCapture().

А что "изысканного" может быть в куче дополнительного кода?

Автор: katka 19.10.2006, 12:22
Avb, спасибо, почитаю обязательно. а код у меня в ие ничего не показывает (

Nicholas_S, гигантский код какой )) вуаля, действительно )


Zeroglif, а как комильфо писать? stopPropagation вместо cancelBubble? или как?

Автор: Zeroglif 19.10.2006, 12:52
Цитата(katka @  19.10.2006,  12:22 Найти цитируемый пост)
stopPropagation вместо cancelBubble? или как?


Код

evt.stopPropagation ? evt.stopPropagation() : evt.cancelBubble = true;

Автор: Avb 19.10.2006, 13:14
katka, код нужно сохранить в utf-8. Ну или поменять кодировку на windows-1251.

Автор: katka 19.10.2006, 13:17
Zeroglif, ага, ясно. спасибо.

Avb, да теперь всё видно. спасибо за пример

Автор: Avb 19.10.2006, 13:18
Цитата(katka @  19.10.2006,  12:22 Найти цитируемый пост)
stopPropagation вместо cancelBubble? или как? 



Цитата(Zeroglif @  19.10.2006,  12:52 Найти цитируемый пост)
evt.stopPropagation ? evt.stopPropagation() : evt.cancelBubble = true;
 - вот так.

А если надо еще и предотвратить выполнение default-ного действия (например, отменить переход по ссылке или сабмит формы), то так 
Код

    if (event.preventDefault) {
      event.preventDefault();
      event.stopPropagation();
    } else {
      event.returnValue = false;
      event.cancelBubble = true;
    }


Автор: katka 19.10.2006, 13:24
Цитата(Avb @  19.10.2006,  13:18 Найти цитируемый пост)
А если надо еще и предотвратить выполнение default-ного действия
кстати, да! предотвратить-то тоже надо. как-то я не подумала, спасибо.

Автор: Nicholas_S 19.10.2006, 14:45
AKS, katka, изысканность определяется не только размером, но и реализацией.
Методы setCapture/releaseCapture было созданы для подобного рода ситуаций. Если требуется отловить клик вне элемента только в IE, дополнительный код не нужен. К тому же, он не такой и большой, как может показаться:
Код

if (window.HTMLElement)
{
    var element = HTMLElement.prototype;
    var capture = ["click", "mousedown", "mouseup", "mousemove", "mouseover", "mouseout"];
    element.setCapture = function()
    {
        var self = this, f = 0;
        this._capture = function(e)
        {
            if (f) return;
            f = 1;
            var event = document.createEvent("MouseEvents");
            with (e) event.initMouseEvent(type, bubbles, cancelable, view, detail, screenX, screenY, clientX, clientY,
                                                            ctrlKey, altKey, shiftKey, metaKey, button, relatedTarget);
            self.dispatchEvent(event);
            f = 0;
        }
        for (var i=0; i<capture.length; i++) 
          window.addEventListener(capture[i], this._capture, true);
    }
    element.releaseCapture = function()
    {
        for (var i=0; i<capture.length; i++)
          if (this._capture) window.removeEventListener(capture[i], this._capture, true);
        this._capture = null;
    }
} 

Автор: katka 19.10.2006, 15:10
Nicholas_S, да нет, мы кроссбраузерно пишем.
а код прекрасен. хотя я его и не понимаю, начиная с самой первой строчки  smile
только я уже по-другому сделала, как мне товарищи насоветовали. я почитаю про setCapture/releaseCapture обязательно, только напрягает вот этот сопроводительный код для не-ие.

Автор: Avb 19.10.2006, 15:16
Nicholas_S, если не трудно, ради интереса, посмотри мой пример выше из поста 
Цитата

Ну, и я тут примерчик набросал:

и реализуй то же самое с помощью setCapture/releaseCapture

Просто мне кажется, что это метод не определяет клик вне слоя, а перехватывает все события как внутри слоя, так и вне его. Как узнать, что кликнули именно вне слоя?

Автор: Nicholas_S 19.10.2006, 16:14
katka, (в стиле кота Матроскина) если хоочешь - объяснюу. smile 

Avb, ты прав, метод мерехватывает события как вне слоя, так и внутри его.
Чтобы определить, на каком элементе кликнули, достаточно обратиться к параметру event.srcElement (для IE) или event.originalTarget (FF).
Можно посмотреть след. код, покликав на странице:
Код

<div id="myDiv" onClick="this.innerHTML += ' click on '+event.srcElement.tagName+'';">Test</div>
<input type="button" onClick="document.getElementById('myDiv').setCapture()"  value="Capture the div">

Автор: AKS 19.10.2006, 16:37
Nicholas_S, 
Цитата

изысканность определяется не только размером

Да бог с ней, с изысканостью! Тут есть еще кое-что - ведь нужно эмулировать и возможность использования булева параметра, который есть в оригинальном методе. Я вот думал, думал - ничего не придумал. А Вы знаете, как это сделать? 

Автор: katka 19.10.2006, 16:59
Nicholas_S, ну ладно. первая строчка — это просто проверка на браузер, так ведь? а вот чего там дальше..
ну, давай. объясняй, матроскин ) что такое, во-первых, этот HTMLElement и его prototype? или давай на обучающую тему по е-мейл пообщаемся, что ли? а то развели тут учебный класс  smile 

Автор: Nicholas_S 19.10.2006, 19:22
AKS, параметр подразумевает, что все дочерние объекты будут включать свои собственные обработчики событий. Теоретически, можно реализовать. Если возникнет необходимость - дай знать, попробуем что-нибудь придумать.


katka, если есть желание - давай электропочтой. Какой ты там говоришь у тебя ящик?  smile

Автор: 12345c 19.10.2006, 19:42
katka, нет, почтой - на другие темы общайтесь, а про Javascript - здесь smile

Автор: AKS 19.10.2006, 19:42
Nicholas_S, 
Цитата

Если возникнет необходимость...

Не-е-е, я повозился и бросил (ну их, эти эмуляции)! Там кстати с параметром false имеется ввиду, что элемент не будет перехватывать события детишек, и это, как раз о-о-очень не легко, а может даже не возможно!
Я сделал по-простому - четыре функции и полностью решается проблема автора темы (все кроссбраузерно и места занимает столько, сколько сама эмуляция - чего еще желать?):

Автор: Nicholas_S 19.10.2006, 20:49
Подобные эмуляции стоит писать тогда, когда, скажем, они входят в какой-нибудь framework, библиотеку для пользователей, которым нужен идентичный набор методов и свойств для всех браузеров. Но это отнюдь не панацея - если можно реализовать все проще, то почему бы и нет. Я просто предложил один из вариантов.
А дописать данный код для "детишек", AKS, в пару строк не получится, я с тобой согласен.

Автор: Burrr 20.10.2006, 10:55
Господа!
Цель должна оправдывать средства. Не знаю насколько сложное приложение пишет katka, но есть предположение, что кода, который написал Avb на первой странице более чем достаточно. Я к нему только бы добавил проверку, чтобы ф-ия closeDiv вызывалась только тогда, когда этот див виден, и всё!
stopPropargation & cancelBubble будет вполне достаточно.
Возможно тяжеловесная ф-ия скрытия будет запускаться только в случае надобности. Проблема решена, и незачем писать лишние километры кода!

Автор: AKS 20.10.2006, 15:23
Burrr, 
Цитата

Я к нему только бы добавил проверку, чтобы ф-ия closeDiv вызывалась только тогда...

А я бы добавил возможность использовать методы addEventListener/attachEvent...

Автор: Burrr 20.10.2006, 15:27
AKS
Цитата

А я бы добавил возможность использовать методы addEventListener/attachEvent...

Зачем, что это даст в данном конкретном случае? На какой элеиент ты собираешься вешать событие этими методами?

Автор: katka 20.10.2006, 15:29
Nicholas_S, велели тут разговаривать )) так что такое HTMLElement?

Автор: AKS 20.10.2006, 15:55
Burrr, Вы знаете, на мой взгляд удобней использовать методы addEventListener/attachEvent вместо такого:
Код

document.onclick = function() {
     firstFunc();
     secondFunc();
     //...
}

"Почему?" - спросите Вы. Потому, что я напишу, затем кто-то допишет, а потом кто-то еще что-то добавит (это те, кто будет заниматься поддержкой странички в дальнейшем), и никому не придется лезть в чужой код, искать document.onclick, чтобы туда вставить что-то свое. Такая вот своеобразная и всеми любимая независимость получается...
Я вчера кое-что писал - сейчас оформлю для наглядности и покажу...

Автор: Nicholas_S 20.10.2006, 16:18
katka, если научным языком, то HTMLElement это производный класс от Element, т.е. класс-предок. Ими являются элементы страницы как то: DIV, A, TABLE и проч. Но такое определение элементов страницы отсутствует в IE, поэтому строка if (window.HTMLElement), как ты уже заметила, "определяет" тип браузера, т.е. код ветки будет выполнен, например, для FF/Opera, но не для IE.

var element = HTMLElement.prototype:
Механизм прототипов (prototypes) в JS позволяет определять новые методы/свойства для объектов, другими словами, используя поле "prototype" можно добавить несуществующий метод, например, для всех строковых переменных: 

String.prototype.calcSpaces = function () {[здесь подсчитываем количество пробелов строки]}

Далее для любой переменной строкового типы можно вызвать этот метод: var spacesCnt = myString.calcSpaces();
------------
var capture = ["click", "mousedown", "mouseup", "mousemove", "mouseover", "mouseout"];
Это просто массив со списком имен событий, для которых мы определяли перехват.
В принципе, в нашем случае нужен был только клик, поэтому можно было просто задавать далее строку "click".

element.setCapture = function() {...}

Это ^ как раз определение нового метода setCapture() для всех HTML-элементов страницы, или, если записать по-другому:

HTMLElement.prototype.setCapture = function() {...}

this._capture = function(e) {...}

Здесь ^ создается обработчик событий, т.е. данная функция будет вызываться каждый раз, когда происходит како-либо событие на каком-либо HTML элементе. Параметр "e" - объект самого события (event).

var event = document.createEvent("MouseEvents");
with (e) event.initMouseEvent(type, bubbles, cancelable, view, detail, screenX, screenY, clientX, clientY,
                                                ctrlKey, altKey, shiftKey, metaKey, button, relatedTarget);

Создаем ^ новый объект события на основе перехватываемого (e).

self.dispatchEvent(event);

^ Генерируем созданное событие на HTML элементе (в IE есть еще подобный метод fireEvent(eventType)).

window.addEventListener(capture[i], this._capture, true);

^ Регистрируем события (которые перечислены в массиве) для HTML-элемента.

И, наконец, element.releaseCapture = function() {...} - дерегистрируем события.

Вот и все.  smile 

Автор: AKS 20.10.2006, 17:26
Burrr, предлагаю маленький архивчик со страничкой и сценарием. Сделал все с использованием методов addEventListener/attachEvent  (даже на страничке разместил цитату из стандарта на тему "как выгодно использование" этих методов ;) )...

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)