Конспект JS-course

Задачи

Задача 1

Что выведет этот код?

<script>
var body = document.body;

body.innerHTML = "<!--" + body.tagName + "-->"; 

alert(body.firstChild.data); // что выведет?
</script>

Решение

Ответ: BODY.

<script>
var body = document.body;

body.innerHTML = "<!--" + body.tagName + "-->";

alert(body.firstChild.data); // BODY
</script>

Происходящее по шагам:

  1. Заменяем содержимое на комментарий. Он будет иметь вид &lt;!--BODY--&gt;, так как body.tagName == &quot;BODY&quot;. Как мы помним, свойство tagName в HTML всегда находится в верхнем регистре.
  2. Этот комментарий теперь является первым и единственным потомком body.firstChild.
  3. Получим значение data для комментария body.firstChild. Оно равно содержимому узла для всех узлов, кроме элементов. Содержимое комментария: "BODY".

Задача 2

Напишите функцию, которая удаляет элемент из DOM.

Синтаксис должен быть таким: remove(elem), то есть, в отличие от parentNode.removeChild(elem) — без родительского элемента.

<div>Это</div>
<div>Все</div>
<div>Элементы DOM</div>

<script>
  var elem = document.body.children[0];

  function remove(elem) { /* ваш код */ }
  remove(elem);   // <-- функция должна удалить элемент
</script>

Решение

Родителя parentNode можно получить из elem.

Нужно учесть два момента.

  1. Родителя может не быть (элемент уже удален или еще не вставлен).
  2. Для совместимости со стандартным методом нужно вернуть удаленный элемент. Вот так выглядит решение:

     function remove(elem) {
     return elem.parentNode ? elem.parentNode.removeChild(elem) : elem;
     }
    

Задача 3

Напишите функцию insertAfter(elem, refElem), которая добавит elem после узла refElem.

<div>Это</div>
<div>Элементы</div>

<script>
  var elem = document.createElement('div');
  elem.innerHTML = '<b>Новый элемент</b>';

  function insertAfter(elem, refElem) { /* ваш код */ }

  var body = document.body;

  // вставить elem после первого элемента
  insertAfter(elem, body.firstChild); // <--- должно работать

  // вставить elem за последним элементом
  insertAfter(elem, body.lastChild);  // <--- должно работать

</script>

Решение

Для того, чтобы добавить элемент после refElem, мы можем вставить его перед refElem.nextSibling.

Но что если nextSibling нет? Это означает, что refElem является последним потомком своего родителя и можем использовать appendChild.

Код:

function insertAfter(elem, refElem) {
    var parent = refElem.parentNode;
    var next = refElem.nextSibling;
    if (next) {
        return parent.insertBefore(elem, next);
    } else {
        return parent.appendChild(elem);
    }
}

Но код может быть гораздо короче, если использовать фишку со вторым аргументом null метода insertBefore:

function insertAfter(elem, refElem) {
    return refElem.parentNode.insertBefore(elem, refElem.nextSibling);
}

Если нет nextSibling, то второй аргумент insertBefore становится null и тогда insertBefore(elem,null) работает как appendChild.

В решении нет проверки на существование refElem.parentNode, поскольку вставка после элемента без родителя — уже ошибка, пусть она возникнет в функции, это нормально.

Задача 4

Напишите функцию removeChildren, которая удаляет всех потомков элемента.

<table>
  <tr>
    <td>Это</td><td>Все</td><td>Элементы DOM</td>
  </tr>
</table>

<ol>
  <li>Вася</li>
  <li>Петя</li>
  <li>Маша</li>
  <li>Даша</li>
</ol>

<script>
  function removeChildren(elem) { /* ваш код */ }

  removeChildren(document.body.children[0]); // очищает таблицу
  removeChildren(document.body.children[1]); // очищает список
</script>

P.S. Проверьте ваше решение в IE8.

Решение

1) Неправильное решение:

Для начала рассмотрим забавный пример того, как делать не надо:

function removeChildren(elem) {
  for(var k=0; k<elem.childNodes.length;k++) {
    elem.removeChild(elem.childNodes[k]);
  }
}

Если вы попробуете это на практике, то увидите, то это не сработает.

Не сработает потому, что childNodes всегда начинается 0 и автоматически смещается, когда первый потомок удален(т.е. тот, что был вторым, станет первым), поэтому такой цикл по k пропустит половину узлов.

2) Решение через DOM:

Правильное решение:

function removeChildren(elem) {
  while(elem.lastChild) {
    elem.removeChild(elem.lastChild);
  }
}

3) Неправильное решение (innerHTML):

Прямая попытка использовать innerHTML была бы неправильной:

function removeChildren(elem) {
  elem.innerHTML = '';
}

Дело в том, что в IE<9 свойство innerHTML на большинстве табличных элементов (кроме ячеек TH/TD) не работает. Будет ошибка.

4) Верное решение (innerHTML):

Можно завернуть innerHTML в try/catch:

function removeChildren(elem) {
  try {
    elem.innerHTML = '';
  } catch(e) {
    while(elem.firstChild) {
      elem.removeChild(elem.firstChild);
    }
  }
}

Задача 5

Напишите интерфейс для создания списка.

Для каждого пункта:

  1. Запрашивайте содержимое пункта у пользователя с помощью prompt. Создавайте пункт и добавляйте его к UL.
  2. Процесс прерывается, когда пользователь нажимает ESC.
  3. Все элементы должны создаваться динамически.

Пример тут: http://ru.lookatcode.com/files/tutorial/browser/dom/createList.html

Если посетитель вводит теги — в списке они показываются как обычный текст.

P.S. prompt возвращает null, если пользователь нажал ESC.

Решение

<!DOCTYPE HTML>
<html>
<body>
<h1>Создание списка</h1>

<script>
  var ul = document.createElement('ul');
  document.body.appendChild(ul);

  while (true) {
    var data = prompt("Введите текст для пункта списка", "");

    if (data === null) {
       break;
    }

    var li = document.createElement('li');
    li.appendChild(document.createTextNode(data));
    ul.appendChild(li);
  }
</script>

</body>
</html>

Делайте проверку на null в цикле. prompt возвращает это значение только если был нажат ESC.

Контент в LI добавляйте с помощью document.createTextNode, чтобы правильно работали &lt;, &gt; и т.д.

Задача 6

Напишите функцию, которая создаёт вложенный список UL/LI (дерево) из объекта.

Например:

var data = {
  "Рыбы":{
    "Форель":{},
    "Щука":{}
  },

  "Деревья":{
    "Хвойные":{
      "Лиственница":{},
      "Ель":{}
    },
    "Цветковые":{
      "Берёза":{},
      "Тополь":{}
    }
  }
};

Синтаксис:

var container = document.getElementById('container');
createTree(container, data); // создаёт

Результат (дерево):

Выберите один из двух способов решения этой задачи:

  1. Создать строку, а затем присвоить через container.innerHTML.
  2. Создавать узлы через методы DOM.

Если получится – сделайте оба.

Исходный код: http://learn.javascript.ru/play/tutorial/browser/dom/build-tree-src.html

Решение

Решения через рекурсию.

1) http://learn.javascript.ru/play/tutorial/browser/dom/build-tree.html.

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
</head>
<body>

<div id="container"></div>

<script>
  var data = {
    "Рыбы":{
      "Форель":{},
      "Щука":{}
    },

    "Деревья":{
      "Хвойные":{
        "Лиственница":{},
        "Ель":{}
      },
      "Цветковые":{
        "Берёза":{},
        "Тополь":{}
      }

    }
  };

  function createTree(container, obj) {
    container.innerHTML = createTreeText(obj);
  }

  function createTreeText(obj) { // отдельная рекурсивная функция
    var li = '';
    for (var key in obj) {
      li += '<li>' + key + createTreeText(obj[key]) + '</li>';
    }
    if (li) {
      var ul = '<ul>' + li + '</ul>'
    }
    return ul || '';
  }

  var container = document.getElementById('container');
  createTree(container, data);
</script>
</body>
  </html>

2) http://learn.javascript.ru/play/tutorial/browser/dom/build-tree-dom.html.

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
</head>
<body>

<div id="container"></div>

<script>
  var data = {
    "Рыбы":{
      "Форель":{},
      "Щука":{}
    },

    "Деревья":{
      "Хвойные":{
        "Лиственница":{},
        "Ель":{}
      },
      "Цветковые":{
        "Берёза":{},
        "Тополь":{}
      }

    }
  };

  function createTree(container, obj) {
    container.appendChild( createTreeDom(obj) );
  }

  function createTreeDom(obj) {
    // если нет детей, то рекурсивный вызов ничего не возвращает
    // так что вложенный UL не будет создан
    if (isObjectEmpty(obj)) return;

    var ul = document.createElement('ul');

    for (var key in obj) {
      var li = document.createElement('li');
      li.innerHTML = key;

      var childrenUl = createTreeDom(obj[key]);
      if (childrenUl) li.appendChild(childrenUl);

      ul.appendChild(li);
    }

    return ul;
  }


  function isObjectEmpty(obj) {
    for (var key in obj) {
      return false;
    }
    return true;
  }

  var container = document.getElementById('container');
  createTree(container, data);
</script>

</body>
</html>

Задача 7

Напишите функцию, которая умеет генерировать календарь для заданной пары (месяц, год).

Календарь должен быть таблицей, где каждый день — это TD. У таблицы должен быть заголовок с названиями дней недели, каждый день — TH.

Синтаксис: createCalendar(id, year, month).

Такой вызов должен генерировать текст для календаря месяца month в году year, а затем помещать его внутрь элемента с указанным id.

Например: createCalendar(&quot;cal&quot;, 2012, 9) сгенерирует в &lt;div id=&#39;cal&#39;&gt;&lt;/div&gt; следующий календарь:

ПН ВТ СР ЧТ ПТ СБ ВС
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30

Начальный документ со стилями http://learn.javascript.ru/play/tutorial/date/calendar_src.html

Или:

<!DOCTYPE HTML>
<html>
<head>
<style>
table {
  border-collapse: collapse;
}

td, th {
  border: 1px solid black;
  padding: 3px;
  text-align: center;
}

th {
  font-weight: bold;
  background-color: #E6E6E6;
}
</style>
<meta charset="utf-8">
</head>
<body>


<div id="calendar"></div>

<script>

function createCalendar(id, year, month) {
  var elem = document.getElementById(id)

  // ... ваш код, который генерирует в elem календарь
}

createCalendar('calendar', 2011, 1)

</script>
</body>
</html>

P.S. Достаточно сгенерировать календарь, кликабельным его делать не нужно.

Решение

Для решения задачи сгенерируем таблицу в виде строки: &quot;&lt;table&gt;...&lt;/table&gt;&quot;, а затем присвоим в innerHTML.

Алгоритм:

  1. Создать объект даты d = new Date(year, month-1). Это первый день месяца month (с учетом того, что месяцы в JS начинаются от 0, а не от 1).
  2. Ячейки первого ряда пустые от начала и до дня недели d.getDay(), с которого начинается месяц. Создадим их.
  3. Увеличиваем день в d на единицу: d.setDate(d.getDate()+1), и добавляем в календарь очередную ячейку, пока не достигли следующего месяца. При этом последний день недели означает вставку перевода строки &quot;&lt;/tr&gt;&lt;tr&gt;&quot;.
  4. При необходимости, если календарь окончился не на воскресенье – добавить пустые TD в таблицу, чтобы было все ровно.

Код решения:

<!DOCTYPE HTML>
<html>
<head>
<style>
table {
  border-collapse: collapse;
}

td, th {
  border: 1px solid black;
  padding: 3px;
  text-align: center;
}

th {
  font-weight: bold;
  background-color: #E6E6E6;
}
</style>
<meta charset="utf-8">
</head>
<body>


<div id="calendar"></div>

<script>
function createCalendar(id, year, month) {
  var elem = document.getElementById(id);

  var mon = month - 1; // месяцы в JS идут от 0 до 11, а не от 1 до 12
  var d = new Date(year, mon);

  var table = '<table><tr><th>пн</th><th>вт</th><th>ср</th><th>чт</th><th>пт</th><th>сб</th><th>вс</th></tr><tr>';

  // заполнить первый ряд от понедельника
  // и до дня, с которого начинается месяц
  // * * * | 1  2  3  4
  for (var i = 0; i < getDay(d); i++) {
    table += '<td></td>';
  }

  // ячейки календаря с датами
  while(d.getMonth() == mon) {
    table += '<td>'+d.getDate()+'</td>';

    if (getDay(d) % 7 == 6) { // вс, последний день - перевод строки
      table += '</tr><tr>';
    }

    d.setDate(d.getDate()+1);
  }

  // добить таблицу пустыми ячейками, если нужно
  if (getDay(d) != 0) {
    for (var i = getDay(d); i < 7; i++) {
      table += '<td></td>';
    }
  }

  // закрыть таблицу
  table += '</tr></table>';

  // только одно присваивание innerHTML
  elem.innerHTML = table;
}

function getDay(date) { // получить номер дня недели, от 0(пн) до 6(вс)
  var day = date.getDay();
  if (day == 0) day = 7;
  return day - 1;
}



createCalendar("calendar", 2012, 9)
</script>

</body>
</html>