Опубликован: 01.09.2010 | Уровень: для всех | Доступ: свободно | ВУЗ: Сибирский федеральный университет
Самостоятельная работа 5:

Динамический HTML

< Лекция 10 || Самостоятельная работа 5 || Лекция 11 >
Аннотация: Цель. Изучение приёмов динамического формирования HTML-документа.

В данной работе будет создана страница формирования клиентом заказа из набора продуктов. При выборе клиентом продукта, указании количества единиц и нажатии кнопки "Добавить в корзину" к таблице заказа добавляется пункт. Кнопка "Удалить" позволяет удалять пункты из заказа по одному или группой. При всех манипуляциях подсчитывается число единиц заказанных товаров и их суммарная стоимость.

Часть 1. Создание разметки

Статическая часть страницы будет иметь следующий вид ( рис. 15.1).

Начальный вид формы заказа при открытии страницы

Рис. 15.1. Начальный вид формы заказа при открытии страницы

Рассмотрим действия по созданию этой формы пошагово.

1. Создание выпадающего списка (элемент select).

В теле нового документа с именем Lab5.htm создайте элемент div, в котором будет размещена вся разметка данного примера.

Первый элемент - select - должен содержать несколько групп ( optgroup ) по несколько пунктов ( option ). Каждый пункт содержит наименование какого-либо продукта, а его атрибут value - цену (произвольное в данном случае число).

В развёрнутом виде список должен выглядеть так:


Назначьте списку атрибут id="lstProducts".

2. Создание остальной разметки

Следом за выпадающим списком создайте два элемента ввода - input type="text" и input type="button" (примеры приведены в лекции). Текстовому элементу ввода назначьте атрибут id="txtQty", а кнопке - onclick="AddToCart()".

Последний элемент разметки - таблица с заголовком (элемент caption), верхним и нижним колонтитулом (элементы thead и tfoot) и телом (элемент tbody). Описания и примеры применения этих элементов также см. в соответствующей лекции. Таблице назначьте атрибут id="tblOrder".

Раздел tbody оставьте пустым (он будет формироваться динамически), а в колонтитулах, как видно на рисунке, следует расположить по одному элементу управления - checkbox и button. Им сразу задайте обработчики события click следующим образом:

<input type="checkbox" onclick="ToggleCheck(this)" />
<input type="button" value="Удалить отмеченные" onclick="RemoveSelected()" />

Часть 2. Создание сценария, манипулирующего таблицей

1. Добавление пунктов заказа

Создайте скрипт в конце тела документа, и сразу определите обработчики, назначенные элементам управления в предыдущем задании - пока пусть это будут функции с пустым телом.

В начало скрипта поместите следующие команды:

var tbl = document.getElementById('tblOrder');
var oList = document.getElementById('lstProducts');

Ссылки на элементы 'tblOrder' (таблица заказа) и lstProducts (список выбора продукта) будут часто использоваться в коде сценария, поэтому целесообразно определить их единожды.

При нажатии кнопки "Добавить в корзину" в таблицу должны добавляться строки, соответствующие пунктам заказа:


Функция-обработчик этой кнопки была названа AddToCart. Изучите её код и добавьте его в скрипт.

/* Добавление пунктов заказа */
function AddToCart() {
  /* Определяем значение, введённое в текстовое поле */
  var qty = document.getElementById('txtQty').value;
  /* Проверка: распознаётся ли значение как число? Если нет, считаем единицей */
  if (parseFloat(qty) != qty)
    qty = 1;
  /* Вставляем строку в тело таблицы */
  var oRow = tbl.tBodies[0].insertRow(-1);
  /* В добавленную строку вставляем, во-первых, checkbox */
  oRow.insertCell(-1).innerHTML = '<input type="checkbox">';
  /* во-вторых, текст, взятый из списка выбора продуктов */
  oRow.insertCell(-1).innerHTML = oList.options[oList.selectedIndex].text;
  /* в-третьих, цена выбранного продукта */
  oRow.insertCell(-1).innerHTML = oList.value;
  /* далее, количество, указанное в текстовом поле */
  ...
  /* затем стоимость пункта заказа */
  ...
  /* и, наконец, кнопку "Удалить" */
  ...
  /* По окончании вставки строки необходимо пересчитать сумму заказа */
  //Calculate();
}

Код в тех строках, где оставлено многоточие, напишите самостоятельно, опираясь на комментарии. Вызов функции Calculate() оставьте пока закомментированным - эту функцию мы добавим позже, а сначала следует добиться верной работы AddToCart().

2. Отладка сценария.

Лучший способ проследить ход работы сценария - это выполнить его пошагово в отладчике. IE8 предлагает встроенный отладчик в наборе средств разработчика (Меню Сервис - Средства разработчика). Для некоторых других браузеров также существуют отладчики, доступные как плагины - например, FireBug.

Откройте страницу в IE8 и запустите Средства разработчика. На вкладке Сценарий имеются кнопки для запуска отладки и пошагового выполнения команд сценария. На правой панели можно просмотреть локальные переменные (определённые в текущей функции), а также произвольные выражения, включающие объекты модели документа, их свойства и даже вызовы методов.

Поставьте точку останова на начало интересующей нас функции и запустите отладку. Нажав кнопку "Добавить в корзину" на веб-странице, вы тем самым запустите обработчик события click, и отладчик покажет команду в точке останова. Двигайтесь пошагово, наблюдая за всеми объектами, влияющими на логику обработчика.

Отладка помогает выявить те ошибки в программе, которые появляются вследствие неточного понимания смысла тех или иных свойств и методов объектов модели документа - всегда можно посмотреть, чему в действительности равно то или иное свойство, или что выдаёт тот или иной метод. Также можно выявить ошибки, связанные с логикой программы - запускается ли функция при наступлении события, верно ли сформулированы условный и циклический операторы и, наконец, если возникает исключение, то на какой команде и в каком контексте. Но те ошибки, которые связаны с синтаксисом, отладчик вряд ли поможет найти - во многих случаях браузер просто не поймёт сценария с такими ошибками. Особенно тщательно следует следить за соответствием скобок и тэгов, а также символами-разделителями.

3. Функция Calculate.

Создайте функцию Calculate() и уберите комментарий с её вызова в функции AddToCart.

function Calculate() {
  /* Счётчики для количества единиц товара и общей стоимости */
  var qty = 0, amount = 0;
  /* Цикл по всем строкам в теле таблицы */
  for (var i = 0, n = tbl.tBodies[0].rows.length; i < n; i++) {
    /* Увеличиваем qty на значение в 3 столбце текущей строки */
    qty += parseFloat(tbl.tBodies[0].rows[i].cells[3].innerHTML);
    /* Увеличиваем amount на значение в 4 столбце текущей строки */
    amount += parseFloat(tbl.tBodies[0].rows[i].cells[4].innerHTML);
  }
  /* Записываем qty в 3 столбец нижнего колонтитула */
  ...
  /* Записываем amount в 4 столбец нижнего колонтитула */
  ...
}

Код в тех строках, где оставлено многоточие, напишите самостоятельно, опираясь на комментарии.

4. Функция RemoveProduct(elem).

Кнопка "Удалить", динамически вставляемая в правую ячейку каждой строки заказа, должна выполнять свою работу - следовательно, необходимо назначить ей обработчик (назовём его RemoveProduct ) и написать его код. Этот обработчик имеет существенное отличие от предыдущих, которое заключается в том, что смысл его работы зависит от контекста вызова: удалять нужно именно ту строку, в которой кнопка расположена. Таким образом, обработчик должен принимать параметр, определяющий контекст. Это можно сделать различными путями, и здесь мы предлагаем наиболее типичный. Если назначить кнопке обработчик следующим образом: onclick="RemoveProduct(this)", то функция RemoveProduct получит в качестве параметра ссылку на тот объект, который принял событие (в данном случае - щелчок мыши). Очевидно, обладая ссылкой на элемент в таблице, можно каким-то образом определить номер ( index ) строки таблицы, в которой он находится, и применить метод таблицы deleteRow(index). Отношение между строкой и кнопкой "Удалить" такое: строка является контейнером ячейки, которая является контейнером кнопки. Ссылку на контейнер элемента можно получить при помощи свойства parentNode этого элемента. С учётом этих соображений код рассматриваемой функции примет следующий вид:

function RemoveProduct(elem) {
  tbl.deleteRow(elem.parentNode.parentNode.rowIndex);
  Calculate();
}

Вставьте эту функцию в скрипт (и не забудьте правильно описать её вызов в динамическом определении кнопки - см. функцию AddToCart ). Сохраните документ, обновите страницу в браузере и проверьте добавленную функциональность.

5. Функция RemoveSelected.

Код этой функции представлен ниже, и на нём следует остановиться подробнее, поскольку работа по удалению элементов из множества имеет свои тонкости.

function RemoveSelected() {
  /* находим все элементы input в теле таблицы */
  var checks = tbl.tBodies[0].getElementsByTagName('input');
  var i = 0;
  /* начинаем перебор элементов в цикле */
  while (i < checks.length) {
    /* рассматриваем элемент лишь в том случае, если это checkbox и он отмечен */
    if (checks[i].type == 'checkbox' && checks[i].checked)
      /* вызываем функцию, которая удалит строку с пунктом заказа - передаём ей ссылку на checkbox */
      RemoveProduct(checks[i]);
    else
      /* счётчик увеличиваем лишь в том случае, если удаление не было сделано */
      i++;
  }
}

Множество ссылок на все элементы ввода ( input ) внутри тела таблицы ( tbody ) легко получить при помощи метода getElementsByTagName. Этот метод принимает в качестве параметра имя тэга и выдаёт массив ссылок. Важно отметить, что ссылками являются не только элементы массива, но и сам массив - ссылочный, т.е. все изменения в документе автоматически отражаются в этом массиве. Таким образом, удалив строку из таблицы (и тем самым удалив из неё два элемента input - checkbox и button ), мы тем самым уменьшим число элементов в массиве checks на 2. Это следует принимать во внимание при циклической обработке массива.

Номер рассматриваемого в цикле элемента массива обозначен в данном примере переменной i, и отсчёт, как обычно, начинается с нуля. Чтобы правильно сформулировать оператор увеличения этого i, следует задаться вопросом: "Рассмотрев элемент с номером i, элемент с каким номером следует рассматривать далее?" Ответ таков: "Если удаление не было выполнено, то i+1 ; иначе - вновь i (теперь это номер следующего из оставшихся элементов)". Поэтому цикл записан именно таким образом (а не с использованием оператора for ).

6. Функция ToggleCheck.

Последняя функция данного сценария создаёт дополнительное удобство: можно отметить или сбросить сразу все галочки в пунктах заказа, щёлкнув по галочке (элементу checkbox ) в заголовке таблицы. Этому элементу назначена функция-обработчик ToggleCheck, принимающая в качестве параметра this, т.е. ссылку на объект-источник события. Напишите эту функцию самостоятельно - все необходимые для этого приёмы уже рассмотрены.

Окончательный вид работающей страницы показан на рис. 15.2.

Действующая страница заказа

Рис. 15.2. Действующая страница заказа
Дополнительное задание. Сделайте так, чтобы при добавлении продукта, который в заказе уже присутствует, новая строка не добавлялась, а вместо этого увеличивалось бы количество уже заказанных единиц этого продукта. Для этого вам потребуется создать объект, хранящий количества заказанных единиц продукта, в котором ключами будут названия продукта, а значениями - количества. Понадобится, естественно, модифицировать все функции, изменяющие содержание заказа - так, чтобы в объекте эти изменения также отражались.
< Лекция 10 || Самостоятельная работа 5 || Лекция 11 >
Юрий Шах
Юрий Шах

Профессиональный веб-дизайн: Введение в современные веб-технологии
Самостоятельная работа 4

"3. Создание внешней таблицы.

Теперь создайте таблицу с двумя строками. Во второй строке создайте две ячейки - в первую переместите таблицу цифр, а во вторую - таблицу знаков."

Как в ячейку <td> поместить таблицу? Таблица же сама состоит из ячеек. Исходя из задания следует, что <td> может быть родителем для <td>, но это противоречит правилам HTML?
Если не прав - поправьте.
Также прошу разъяснить, как именно выполнить занное условие - поместить в табличную ячейку таблицу цифр, а в другую ячейку - таблицу знаков? 

Елена Сапегова
Елена Сапегова

После прохождения теоретической части пришло письмо об окончании теоретической части курса, будет ли практическая часть?