Конспект JS-course

Задачи

Задача 1

Используя JavaScript, сделайте так, чтобы при клике на кнопку исчезал элемент с id="hide".

<!DOCTYPE HTML>
<html>
<head>
    <meta charset="utf-8">
</head>
<body>
    <input type="button" id="hider" value="Нажмите, чтобы спрятать текст"/>
    <div id="hide">Текст</div>
<script>
    /* ваш код */
</script>
</body>
</html>

Решение

<!DOCTYPE HTML>
<html>
<head>
    <meta charset="utf-8">
</head>
<body>
    <input type="button" id="hider" value="Нажмите, чтобы спрятать текст"/>
    <div id="hide">Текст</div>
<script>
    document.getElementById('hider').onclick = function() {
        document.getElementById('hide').style.display = 'none';
    }
</script>
</body>
</html>

Задача 2

Создайте кнопку, при клике на которую, она будет скрывать сама себя.

Решение

Решение задачи заключается в использовании this в обработчике.

<input type="button" onclick="this.style.display='none'" value="Нажми, чтобы меня спрятать"/>

Задача 3

В переменной button находится кнопка.

Изначально обработчиков на ней нет.

Что будет выведено при клике после выполнения кода?

button.addEventListener("click", function() { alert("1"); }, false);

button.removeEventListener("click", function() { alert("1"); }, false);

button.onclick = function() { alert(2); };

Решение

Ответ: будет выведено 1 и 2.

Первый обработчик сработает, так как он не убран вызовом removeEventListener. Для удаления обработчика нужно передать в точности ту же функцию (ссылку на нее), что была назначена, а в коде передается такая же с виду функция, но, тем не менее, это другой объект.

Для того, чтобы удалить функцию-обработчик, нужно где-то сохранить ссылку на неё, например так:

function handler() {
  alert("1");
}

button.addEventListener("click", handler, false);
button.removeEventListener("click", handler, false);

Обработчик button.onclick сработает независимо и в дополнение к назначенному в addEventListener.

Задача 4

Есть список сообщений. Добавьте каждому сообщению по кнопке для его скрытия. Картинка для кнопки удаления:

Расположение кнопок:

<!DOCTYPE HTML>
<html>
<head>
    <link rel="stylesheet" type="text/css" href="messages.css">
    <meta charset="utf-8">
    <style>
        body {
            margin: 10px auto;
            width: 470px;
        }
        h3 {
            margin: 0;
            padding-bottom: .3em;
            font-size: 1.1em;
        }
        p {
            margin: 0;
            padding: 0 0 .5em;
        }
        .pane {
            background: #edf5e1;
            padding: 10px 20px 10px;
            border-top: solid 2px #c4df9b;
        }
    </style>
</head>

<body>

<div>
    <div class="pane">
        <h3>Лошадь</h3>
        <p>Домашняя лошадь — животное семейства непарнокопытных, одомашненный и единственный сохранившийся подвид дикой лошади, вымершей в дикой природе, за исключением небольшой популяции лошади Пржевальского.</p>
    </div>
    <div class="pane">
        <h3>Осёл</h3>
        <p>Домашний осёл или ишак — одомашненный подвид дикого осла, сыгравший важную историческую роль в развитии хозяйства и культуры человека. Все одомашненные ослы относятся к африканским ослам.</p>
    </div>
    <div class="pane">
        <h3>Корова, а также пара слов о диком быке, о волах и о тёлках. </h3>
        <p>Коро́ва — самка домашнего быка, одомашненного подвида дикого быка, парнокопытного жвачного животного семейства полорогих. Самцы вида называются быками, молодняк — телятами, кастрированные самцы — волами. Молодых (до первой стельности) самок называют тёлками.</p>
    </div>
</div>

</body>
</html>

Как лучше отобразить кнопку справа-сверху: через position:absolute или float?

Исходный документ http://learn.javascript.ru/play/tutorial/browser/events/messages-src/index.html

Решение

Алгоритм решения:

  1. Разработать структуру HTML/CSS. Позиционировать кнопку внутри сообщения;
  2. Найти все кнопки;
  3. Присвоить им обработчики;
  4. Обработчик будет ловить событие на кнопке и удалять соответствующий элемент.

Вёрстка:

Исправьте HTML/CSS, чтобы кнопка была в нужном месте сообщения. Кнопку лучше сделать как div, а картинка — будет его background. Это более правильно, чем img, т.к. в данном случае картинка является оформлением кнопки, а оформление должно быть в CSS.

Расположить кнопку справа можно при помощи position: relative для pane, а для кнопки position: absolute + right/top. Так как position: absolute вынимает элемент из потока, то кнопка может перекрыть текст заголовка. Чтобы этого не произошло, можно добавить padding-right к заголовку.

Потенциальным преимуществом способа с position по сравнению с float в данном случае является возможность поместить элемент кнопки в HTML после текста, а не до него.

Обработчики

Для того, чтобы получить кнопку из контейнера, можно найти все IMG в нём и выбрать из них кнопку по className. На каждую кнопку можно повесить обработчик.

Решение

<!DOCTYPE HTML>
<html>
<head>
    <link rel="stylesheet" href="messages.css">
    <meta charset="utf-8">
    <style>
        body {
        margin: 10px auto;
        width: 470px;
        }
        h3 {
            margin: 0;
            padding-bottom: .3em;
            padding-right: 20px;
            font-size: 1.1em;
        }
        p {
            margin: 0;
            padding: 0 0 .5em;
        }
        .pane {
            background: #edf5e1;
            padding: 10px 20px 10px;
            border-top: solid 2px #c4df9b;
            position: relative;
        }

        .remove-button {
            position: absolute;
            top: 10px;
            right: 10px;
            cursor: pointer;
            display: block;
            background: url(delete.gif) no-repeat;
            width: 16px;
            height: 16px;
        }
    </style>
</head>

<body>

<div id="messages-container">
  <div class="pane">
    <h3>Лошадь</h3>
    <p>Домашняя лошадь — животное семейства непарнокопытных, одомашненный и единственный сохранившийся подвид дикой лошади, вымершей в дикой природе, за исключением небольшой популяции лошади Пржевальского.</p>
    <span class="remove-button"></span>
  </div>
  <div class="pane">
    <h3>Осёл</h3>
    <p>Домашний осёл или ишак — одомашненный подвид дикого осла, сыгравший важную историческую роль в развитии хозяйства и культуры человека. Все одомашненные ослы относятся к африканским ослам.</p>
    <span class="remove-button"></span>
  </div>
  <div class="pane">
    <h3>Корова, а также пара слов о диком быке, о волах и о тёлках. </h3>
    <p>Коро́ва — самка домашнего быка, одомашненного подвида дикого быка, парнокопытного жвачного животного семейства полорогих. Самцы вида называются быками, молодняк — телятами, кастрированные самцы — волами. Молодых (до первой стельности) самок называют тёлками.</p>
    <span class="remove-button"></span>
  </div>
</div>

<script>
  var spans = document.getElementById('messages-container').getElementsByTagName('span');

  for(var i=0; i<spans.length; i++) {
    var span = spans[i];
    if (span.className != 'remove-button') continue;

    span.onclick = function() {
      var el = this.parentNode;
      el.parentNode.removeChild(el);
    };
  }
</script>

</body>
</html>

Также решение показано тут: http://learn.javascript.ru/play/tutorial/browser/events/messages/index.html

Для поиска элементов span с нужным классом в нём используется getElementsByTagName с фильтрацией. К сожалению, это единственный способ, доступный в IE 6,7. Если же эти браузеры вам не нужны, то гораздо лучше – искать элементы при помощи querySelector или getElementsByClassName.

Задача 5

Почему в этом документе return false не работает?

<script>
  function handler() {
    alert("...");
    return false;
  }
</script>

<a href="http://w3.org" onclick="handler()">w3.org</a>

По замыслу, переход на w3.org при клике должен отменяться. Однако, на самом деле он происходит.

В чём дело и как поправить, сохранив onclick в HTML?

Решение

Дело в том, что обработчик из атрибута onclick делается браузером как функция с заданным телом.

То есть, он будет таким:

function(event) {
  handler()
}

При этом возвращаемое handler значение никак не используется и не влияет на результат.

Рабочий вариант:

<script>
  function handler() {
    alert("...");
    return false;
  }
</script>

<a href="http://w3.org" onclick="return handler()">w3.org</a>

Альтернатива – передать и использовать объект события для вызова event.preventDefault() (или кросс-браузерного варианта для поддержки старых IE).

<script>
  function handler(event) {
    alert("...");
    event.preventDefault ? event.preventDefault() : (event.returnValue=false);
  }
</script>

<a href="http://w3.org" onclick="handler(event)">w3.org</a>

Задача 6

Эта задача состоит из трёх частей.

  1. Сделайте список, элементы которого можно выделять кликом.
  2. Добавьте мульти-выделение. Если клик с нажатым Ctrl (Cmd под Mac), то элемент добавляется-удаляется из выделенных.
  3. Добавьте выделение промежутков. Если происходит клик с нажатым Shift, то к выделению добавляется промежуток элементов от предыдущего кликнутого до этого. При этом не важно, какое именно действие делал предыдущий клик. Это похоже на то, как работает файловый менеджер в ряде ОС, но чуть проще, так как конкретная реализация выделений различается у разных ОС, и её точное воспроизведение не входит в эту задачу. Исходный документ http://learn.javascript.ru/play/tutorial/browser/events/selectable-list-src.html или исходный код:
<!DOCTYPE HTML>
<html>
<head>
  <meta charset="utf-8">
  <style>
    .selected {
      background: #0f0;
    }
    li {
      cursor: pointer;
    }
  </style>
</head>
<body>

<ul>
<li>Кристофер Робин</li>
<li>Винни-Пух</li>
<li>Ослик Иа</li>
<li>Мудрая Сова</li>
<li>Кролик. Просто кролик.</li>
</ul>

<script>

// ... ваш код 


// --- вспомогательная функция, может понадобиться для части 3 ---
// http://learn.javascript.ru/compare-document-position
function compareDocumentPosition(a, b) {
  return a.compareDocumentPosition ?
    a.compareDocumentPosition(b) :
      (a != b && a.contains(b) && 16) +
        (a != b && b.contains(a) && 8) +
        (a.sourceIndex >= 0 && b.sourceIndex >= 0 ?
          (a.sourceIndex < b.sourceIndex && 4) +
            (a.sourceIndex > b.sourceIndex && 2) :
          1);
}

</body>
</html>

Клик на элементе выделяет только его. Ctrl(Cmd) + Клик добавляет/убирает элемент из выделенных. Shift + Клик добавляет промежуток от последнего кликнутого к выделению.

P.S. В этой задаче можно считать, что в элементах списка может быть только текст, без вложенных тегов.

P.P.S. Обработка одновременного нажатия Ctrl(Cmd) и Shift может быть любой.

Решение

Решение, шаг 1

Выделение одного элемента (http://learn.javascript.ru/play/tutorial/browser/events/selectable-list-1.html):

<script>

var ul = document.getElementsByTagName('ul')[0];

// --- обработчики ---

ul.onclick = function(e) {
  e = e || event;
  var target = e.target || event.srcElement;

  // возможно, клик был внутри списка UL, но вне элементов LI
  if (target.tagName != "LI") return;

  selectSingle(target);
}

ul.onselectstart = ul.onmousedown = function() {
  return false;
};

// --- функции для выделения ---

function selectSingle(li) {
  deselectAll();
  li.className = 'selected';
}

// снятие выделения со всех - перебором
// это медленнее, чем запоминать список текущих выделенных,
// но подойдет для не очень больших (до 1000 элементов) списков
function deselectAll() {
  for(var i=0; i<ul.children.length; i++) {
    ul.children[i].className = '';
  }
}

</script>

Обратите внимание, так как мы поддерживаем выделение самостоятельно, то браузерное выделение отключено.

Кроме того, в реальном коде лучше добавлять/удалять классы более точными методами, а не прямым присвоением className.

Решение, шаг 2

Выделение с Ctrl (http://learn.javascript.ru/play/tutorial/browser/events/selectable-list-2.html):

<script>

var ul = document.getElementsByTagName('ul')[0];

// --- обработчики ---

ul.onclick = function(e) {
  e = e || event;
  var target = e.target || event.srcElement;

  // возможно, клик был внутри списка UL, но вне элементов LI
  if (target.tagName != "LI") return;

  var isMac = navigator.platform.indexOf("Mac") != -1;
  if(isMac ? e.metaKey : e.ctrlKey) { // для Mac проверяем Cmd, т.к. Ctrl + click там контекстное меню
    toggleSelect(target);
  } else {
    selectSingle(target);
  }
}

ul.onselectstart = ul.onmousedown = function() {
  return false;
};

// --- функции для выделения ---

function toggleSelect(li) {
  li.className = (li.className == '') ? 'selected' : '';
}

function deselectAll() {
  for(var i=0; i<ul.children.length; i++) {
    ul.children[i].className = '';
  }
}

function selectSingle(li) {
  deselectAll();
  li.className = 'selected';
}

</script>

Решение, шаг 3

Выделение с Shift (http://learn.javascript.ru/play/tutorial/browser/events/selectable-list-3.html):

<script>

var ul = document.getElementsByTagName('ul')[0];

var lastClickedLi = null;

// --- обработчики ---

ul.onclick = function(e) {
  e = e || event;
  var target = e.target || event.srcElement;

  // возможно, клик был внутри списка UL, но вне элементов LI
  if (target.tagName != "LI") return;

  var isMac = navigator.platform.indexOf("Mac") != -1;
  if(isMac ? e.metaKey : e.ctrlKey) { // для Mac проверяем Cmd, т.к. Ctrl + click там контекстное меню
    toggleSelect(target);
  } else if (e.shiftKey) {
    selectFromLast(target);
  } else {
    selectSingle(target);
  }

  lastClickedLi = target;
}

ul.onselectstart = ul.onmousedown = function() {
  return false;
}

// --- функции для выделения ---

function toggleSelect(li) {
  li.className = (li.className == '') ? 'selected' : '';
}

function selectFromLast(target) {
  var startElem = lastClickedLi || ul.children[0];

  var isLastClickedBefore = compareDocumentPosition(startElem, target) & 4;

  if (isLastClickedBefore) {
    for(var elem = startElem; elem != target; elem = elem.nextSibling) {
      elem.className = 'selected';
    }
  } else {
    for(var elem = startElem; elem != target; elem = elem.previousSibling) {
      elem.className = 'selected';
    }
  }
  elem.className = 'selected';

}


function deselectAll() {
  for(var i=0; i<ul.children.length; i++) {
    ul.children[i].className = '';
  }
}

function selectSingle(li) {
  deselectAll();
  li.className = 'selected';
}


// --- вспомогательная функция ---
// http://learn.javascript.ru/compare-document-position
function compareDocumentPosition(a, b) {
  return a.compareDocumentPosition ?
    a.compareDocumentPosition(b) :
      (a != b && a.contains(b) && 16) +
        (a != b && b.contains(a) && 8) +
        (a.sourceIndex >= 0 && b.sourceIndex >= 0 ?
          (a.sourceIndex < b.sourceIndex && 4) +
            (a.sourceIndex > b.sourceIndex && 2) :
          1);
}

</script>

Задача 7

Напишите код, который запрещает прокрутку страницы при помощи мыши и клавиш.

P.S. Полосу прокрутки посетитель сможет использовать, её никак кроссбраузерно не отключить, разве что убрать при помощи overflow:hidden, что также отключит и прокрутку.

P.P.S. Запрет прокрутки можно использовать при показе диалогового окна, чтобы посетитель мог прокручивать только его, а не саму страницу: если прокручивающее событие случилось за пределами окна — отменяем действие браузера.

Решение

Источниками прокрутки могут быть: клавиши-стрелки, клавиши pageUp, pageDown, Home, End, а также колёсико мыши.

/*
Коды клавиш, которые вызывают прокрутку:
  33, // pageUp
  34, // pageDown
  35, // end
  36, // home
  37, // left
  38, // up
  39, // right
  40  // down
*/

document.onmousewheel = document.onwheel = function() {
  return false;
};

document.addEventListener ("MozMousePixelScroll",
  function() { return false }, false);

document.onkeydown = function(e) {
  if (e.keyCode >= 33 && e.keyCode <= 40) return false;
}