Основы JavaScript

Создание и изменение HTML

Разбить на страницы
Показывать лекцию целиком

Введение

В этой лекции рассматриваются основы использования JavaScript для управления контентом страницы, включая отображение и скрытие частей страницы, добавление нового кода HTML и его удаление. В конце вы поймете, что наиболее фундаментальной вещью, для чего используется JavaScript, является изменение контента страницы, и вы узнаете, как лучше всего это делать.

Лекция имеет следующее содержание:

  • Скрытие и отображение
  • Пример скрытия и отображения
  • Регулярные выражения
  • Соединение работающего кода со страницей
  • Создание HTML
  • Заключение
  • Контрольные вопросы
  • Скрытие и отображение

    Легче всего начать с управления кодом HTML, который уже имеется, скрывая или отображая элементы, которые находятся на странице. Для этого можно использовать JavaScript, который позволяет изменять стили элементов. Стили CSS уже являются мощным средством описания внешнего вида элемента, и одной из составляющих того, как выглядит элемент, является вопрос, отображается ли элемент вообще. Свойство CSS display является ключом для отображения и скрытия элемента: если задать его как display:none;, элемент будет скрыт. Представьте параграф следующего вида:

    <p id="mypara" style="display: none">I am a paragraph</p>

    Этот параграф будет невидим на странице. JavaScript позволяет динамически добавлять стиль display: none для элемента и удалять его.

    JavaScript предоставляет возможность получить ссылку на элемент HTML. Например, var el = document.getElementById("nav"); выдает ссылку на элемент с id="nav". Когда ссылка на элемент получена, можно с помощью атрибута style изменить примененный к нему код CSS. Например, параграф "mypara" выше в настоящий момент скрыт (в нем задано display: none ). Чтобы показать его, задайте атрибут стиля display как block:

    var el = document.getElementById('mypara');
    el.style.display = 'block';

    И вот, параграф появился. Задание CSS на элементе через атрибут style делает то же самое, что задание его в атрибуте style в самом HTML, поэтому код выше, задающий el.style.display = 'block', добивается того же эффекта, что и размещение style="display: block" прямо в коде HTML. За исключением того, что он является динамическим. Скрытие любого элемента также просто:

    var el = document.getElementById('mypara');
    el.style.display = 'none';

    Пример скрытия и отображения

    С теорией все хорошо, но здесь может быть полезен более практический пример. Представьте множество вкладок, где щелчок на самой вкладке показывает ее и скрывает все остальные.

    Вот пример множества вкладок:

    <ol class="tablinks">
      <li><a href="#tab1">Tab 1</a></li>
      <li><a href="#tab2">Tab 2</a></li>
      <li><a href="#tab3">Tab 3</a></li>
    </ol>
    
    <div class="tab" id="tab1">This is tab 1</div>
    <div class="tab" id="tab2">This is tab 2</div>
    <div class="tab" id="tab3">This is tab 3</div>

    В разделе head файла примера (см. действующий пример tabs.html по адресу http://dev.opera.com/articles/view/creating-and-modifying-html/tabs.html), вы найдете следующий код CSS и JavaScript (который будет обычно помещаться во внешние файлы и импортироваться в HTML, но здесь все содержится в одном файле, чтобы упростить просмотр). Часть кода выглядит устрашающе; не беспокойтесь, мы рассмотрим это.

    <style type="text/css">
    ol.tablinks {
      margin: 0; padding: 0;
    }
    ol.tablinks li {
      float: left;
      border: 2px solid red;
      border-width: 2px 2px 0 2px;
      background: #eee;
      list-style: none;
      padding: 5px;
      margin: 0;
    }
    ol.tablinks li a {
      text-decoration: none;
      color: black;
    }
    div.tab {
      clear: left;
      border: 2px solid red;
      border-width: 1px 2px 2px 2px;
    }
    </style>
    
    <script type="text/javascript">
    var tabify = {
      hasClass: function(el,c) {
        return el.className.match(new RegExp('(\\s|^)'+c+'(\\s|$)'));        
      },
      addClass: function(el,c) {
        if (!tabify.hasClass(el,c)) el.className += " " + c;
      },
      removeClass: function(el,c) {
        if (tabify.hasClass(el,c)) {
          el.className=el.className.replace(new RegExp('(\\s|^)'+c+'(\\s|$)'),' ');
        }
      },
      hideAllTabs: function(ol) {
        var links = ol.getElementsByTagName("a");
        for (var i=0; i<links.length; i++) {
          tabify.setTabFromLink(links[i], "none");
       }
      },
      setTabFromLink: function(link, style) {
        var dest = link.href.match(/#(.*)$/)[1];
        document.getElementById(dest).style.display = style;
        if (style == "none") {
            tabify.removeClass(link, "active");
        } else {
            tabify.addClass(link, "active");
        }
      },
      addEvent: function(obj, type, fn) {
        if ( obj.attachEvent ) {
          obj['e'+type+fn] = fn;
          obj[type+fn] = function(){obj['e'+type+fn]( window.event );};
          obj.attachEvent('on'+type, obj[type+fn] );
        } else {
          obj.addEventListener( type, fn, false );
        }
      },  
      init: function() {
        var ols = document.getElementsByTagName("ol");
        for (var i=0; i<ols.length; i++) {
          var ol = ols[i];
          if (!/(^|\s)tablinks(\s|$)/.test(ol.className)) { continue; }
          tabify.addEvent(ol, "click", function(e) {
            var target = window.event ? window.event.srcElement : e.target;
            if (target.nodeName.toLowerCase() === "a") {
              tabify.hideAllTabs(e.target.parentNode.parentNode);
              tabify.setTabFromLink(e.target, "block");
              if (e) e.preventDefault();
              if (window.event) window.event.returnValue = false;
              return false;
            }
          }, true);
          tabify.hideAllTabs(ol);
          tabify.setTabFromLink(ol.getElementsByTagName("a")[0], "block");
        }
      }
    };
    tabify.addEvent(window, "load", tabify.init);
    </script>

    Каждая вкладка является ссылкой. Каждая ссылка содержит обработчик событий onclick. Этот обработчик событий делает две вещи: он скрывает все div (используя подход display: none, показанный выше), а затем показывает div, соответствующий этой вкладке, задавая style этого div как display: block.

    Можно видеть, что код HTML задан таким образом, чтобы все работало даже в отсутствие сценария, хотя щелчок на ссылке не показывает и не скрывает вкладку, ссылки переходят на соответствующую вкладку, поэтому страница по-прежнему действует корректно, и имеет семантический смысл даже в браузерах без JavaScript. Это важно: такая техника часто называется "постепенным улучшением" (или несколько лет назад называлась "постепенное снижение эффективности", но больше так не говорят). Это важно не только для людей, которые используют современный браузер, но с отключенным JavaScript, но и для множества других типов пользователей. Вспомогательные технологии, такие как считыватели экрана для слепых пользователей, существенно полагаются на то, что структура HTML будет семантической и содержательной, и их поддержка для усовершенствований JavaScript не такая хорошая, как для зрячих пользователей. Мобильные браузеры также обладают не очень большой поддержкой сценариев. Процессоры поиска считывают HTML, но не сценарий - можно сказать, что Google является самым ненасытным слепым пользователем браузера. Вот в чем состоит значение термина "последовательное улучшение": начните со страницы, которая имеет смысл, и отобразите ее содержимое в самой простой рабочей среде, такой как web-браузер с поддержкой только текста, а затем постепенно добавляйте к ней дополнительные функции, так чтобы на каждом шаге этого пути сайт оставался по-прежнему пригодным к употреблению и функциональным.

    Весь код JavaScript в основном присутствует в двух частях: часть, которая фактически делает работу, и часть, которая соединяет ее с HTML. Код, который фактически делает работу в этом примере, достаточно тривиален:

    отображение вкладки, соответствующей определенной ссылке, с помощью двух строчек кода JavaScript:

    var dest = link.href.match(/#(.*)$/)[1];
    document.getElementById(dest).style.display = "block";

    Ссылки, если вы помните, выглядят следующим образом <a href="#tab1">Tab 1</a>, и поэтому первая строка использует регулярное выражение (см. Примечание ниже) для извлечения той части ссылки, которая появится после символа # ; в данном примере это будет строка tab1. Эта часть ссылки будет такой же как ID соответствующего div (потому что, как упоминалось, страница создается таким образом, чтобы иметь семантический смысл, поэтому "вкладка" ссылается на свой div ). Затем мы извлекаем ссылку на этот div с помощью document.getElementById, и задаем style.display как block, как было показано ранее.

    Регулярные выражения

    Регулярные выражения являются разновидностью мини-языка-программирования, созданного для решения проблемы "синтаксического анализа" текста - например, ответить на вопросы типа "появляется ли данная строка внутри другой строки?" и "в строке 'abc123def' какие имеются цифры между 'c' и 'd'?" Они являются очень мощным инструментом, но также достаточно сложным. Ниже находится описание, для чего предназначены регулярные выражения; пока, однако, можете принять это на веру, и вернуться к описанию позже.

    Используемое в этом примере регулярное выражение имеет вид /#(.*)$/. Каждый символ регулярного выражения создан для представления последовательности символов в некоторой целевой строке, идея состоит в том, чтобы выразить по частям, как составлена целевая строка.

  • / … / - косая черта указывает начало и конец регулярного выражения, также как двойные или одиночные кавычки используются для "строки"
  • # - хэш-символ (#) фактически означает "совпадает с хэш-символом"
  • ( … ) - скобки означают "здесь находится часть строки, которая понадобится позже"
  • .* - означает "любые символы"; на самом деле это точка (.), означающая "один любой символ", за которой следует звездочка (*), означающая "повторить любое количество раз"
  • $ - знак доллара означает "конец строки"
  • Поэтому наше регулярное выражение описывает "подходящий шаблон" строки, состоящей из "хэша, за которым следуют любые символы". link.href является местом назначения рассматриваемой ссылки (например, #tab1, и так как эта строка подходит под описание "хэш, за которым следуют любые символы"), то "подходящий шаблон" применим к этой строке.

    link.href.match(our_regexp), вернет, поэтому, результат true, а не false ; в действительности будет возвращен массив из двух элементов ["#tab1", "tab1"]. Первый является текстом, который соответствует всему регулярному выражению, а второй является текстом, соответствующим символам в кавычках, поэтому скобки здесь и присутствуют - чтобы пометить эту часть строки как "этот фрагмент нас интересует". tab1 является строкой, которая нам требуется, поэтому мы можем извлечь ее из возвращаемого массива (это второй элемент, поэтому его извлекает индекс [1] - массивы начинают нумерацию с нуля.)

    Соединение работающего кода со страницей

    Как упоминалось выше, имеется две части кода: часть, которая фактически выполняет работу, и часть, которая соединяет ее с HTML. Соединение работающего кода с HTML осуществляется с помощью событий. В данном конкретном примере мы используем два события: событие загрузки окна, которое используется, чтобы сказать "все начинается", и событие щелчка на списке вкладок, которое запускается, когда пользователь щелкает на вкладке. Когда загружается страница, требуется выполнить код соединения, а код соединения должен соединить событие щелчка на вкладке с кодом выше, который отображает соответствующую вкладку.

    Выполнение кода в ответ на событие делается в большинстве браузеров с помощью функции addEventListener, и с помощью функции attachEvent в Internet Explorer. Здесь мы создаем функцию "оболочку", которая делает правильный выбор, в зависимости от того, что поддерживается:

    addEvent: function(obj, type, fn) {
      if ( obj.attachEvent ) {
        obj['e'+type+fn] = fn;
        obj[type+fn] = function(){obj['e'+type+fn]( window.event );};
        obj.attachEvent('on'+type, obj[type+fn] );
      } else {
        obj.addEventListener( type, fn, false );
      }
    }

    (Не беспокойтесь слишком много о том, как это работает; примите пока просто на веру - вы поймете код лучше, когда станете более опытным пользователем JavaScript.) Эта функция получает три параметра, obj, type, и fn, которые являются соответственно "объектом, порождающим событие ", "интересующим нас событием ", и "функцией, которая выполняется при возникновении события ". При загрузке страницы необходимо выполнить нашу функцию tabify.init ; функция tabify.init затем позаботится о присоединении события click для каждой вкладки.

    addEvent(window, "load", tabify.init);

    Как можно видеть из структуры кода HTML выше, множество вкладок в действительности представляется как упорядоченный список с class="tablinks". Поэтому функция tabify.init должна сделать следующее:

  • Найти все элементы <ol> на странице с классом tablinks
  • Соединить с событием click каждого элемента <ol> некоторый код, который делает следующее:
  • Точно определяет, на какой ссылке вкладки щелкнул пользователь
  • Определяет, какая реальная вкладка соответствует этой ссылке вкладки
  • Показывает эту одну вкладку
  • Скрывает все остальные вкладки
  • Функция init, которая все это делает, выглядит следующим образом:

    init: function() {
      var ols = document.getElementsByTagName("ol");
      for (var i=0; i<ols.length; i++) {
        var ol = ols[i];
        if (!/(^|\s)tablinks(\s|$)/.test(ol.className)) { continue; }
        tabify.addEvent(ol, "click", function(e) {
          var target = window.event ? window.event.srcElement : e.target;
          if (target.nodeName.toLowerCase() === "a") {
            tabify.hideAllTabs(e.target.parentNode.parentNode);
            tabify.setTabFromLink(e.target, "block");
            if (e) e.preventDefault();
            if (window.event) window.event.returnValue = false;
            return false;
          }
        }, true);
        tabify.hideAllTabs(ol);
        tabify.setTabFromLink(ol.getElementsByTagName("a")[0], "block");
      }
    }

    Давайте пройдем по этой функции шаг за шагом, просматривая по очереди, что делает каждая часть:

    var ols = document.getElementsByTagName("ol");
      for (var i=0; i<ols.length; i++) {
        var ol = ols[i];
        if (!/(^|\s)tablinks(\s|$)/.test(ol.className)) { continue; }
      }

    Этот фрагмент находит все элементы <ol> на странице с классом tablinks - и затем извлекает список всех <ol> и для каждого проверяет "если этот элемент не имеет класс 'tablinks', пропустить его". (Проверка класса делается с помощью регулярного выражения; continue означает "пропустить этот элемент и перейти к следующему".)

    tabify.addEvent(ol, "click", function(e) {
      ...
    });

    Этот фрагмент присоединяет некоторый код к событию click каждого <ol>.

    var target = window.event ? window.event.srcElement : e.target;

    Этот фрагмент точно определяет, на какой ссылке вкладки щелкнул пользователь …

    tabify.setTabFromLink(e.target, "block");

    …затем этот фрагмент, показывает эту единственную вкладку …

    tabify.hideAllTabs(e.target.parentNode.parentNode);

    … и наконец, эта строка скрывает все другие вкладки.

    Функции setTabFromLink и hideAllTabs также присутствуют в коде; они используют показанные выше методы для скрытия или отображения вкладки. Посмотрите на то, как они работают - они являются отдельными функциями, так как часто полезно иметь блок кода, который вызывается более чем из одного места, и поместить его в отдельную функцию. (Это облегчает понимание кода и позволяет повторно использовать его в нескольких местах. Этот подход часто называют "разбиением" или "факторизацией" кода в функцию.)

    В ней выполняется также небольшая "дополнительная" работа, которая демонстрирует добавление и удаление атрибутов. Функция setTabFromLink не только отображает соответствующую вкладку, но задает также class="active" на активной вкладке. Она делает это с помощью трех вспомогательных функций: hasClass, removeClass, и addClass. Таким же образом, как мы скрыли все вкладки, а затем показали активную, мы используем removeClass для удаления "active" из className всех ссылок на вкладки, а затем addClass, чтобы добавить "active" в одну ссылку на вкладку, которая действительно активна. Простое добавление класса в ссылку может показаться бессмысленным - в конечном счете классы невидимы - но это зацепка для стилевого оформления. Мы можем (и должны) делать так, чтобы ссылки с class="active" появлялись с другим оформлением, добавляя код CSS:

    ol.tablinks li a {
      background-color: red;
    }
    
    ol.tablinks li a.active {
      background-color: white;
    }

    Поэтому теперь "активная" вкладка выводится белой, в то время как другие вкладки будут красными. Использование JavaScript для добавления и удаления классов является очень распространенной техникой, которую вы должны использовать, где возможно; CSS хорошо подходит для управления компоновкой, расположением и внешним видом элементов HTML, и поэтому использование JavaScript для изменения классов этих элементов означает, что они могут приобретать различные стили CSS. Это будет идеальным объединением; JavaScript делает элементы динамическими, но фактически сам изменяет очень немного. Добавьте просто класс и предоставьте CSS возможность выполнить остальную работу.

    Создание HTML

    Однако сценарии для DOM значительно более мощный инструмент, чем просто средство для изменения свойств CSS существующего HTML. Можно также динамически создавать новый код HTML, который никогда не присутствовал на странице с самого начала. Например, на сайте новостей технологии Slashdot, ссылка в комментариях выводит место назначения ссылки в квадратных скобках, поэтому ссылка вида <a href="http://opera.com"> A web browser </a> будет выводиться как <a href="http://opera.com">A web browser</a> [opera.com]. (Это было сделано, чтобы исключить использование фальшивых ссылок, направляющих пользователя в нежелательное место.) Добавление этого небольшого кода HTML, выводящего домен места назначения каждой ссылки на странице, легко делается с помощью JavaScript.

    Создание новых элементов HTML делается с помощью функции ) содержащую действующий пример). Код HTML примера выглядит следующим образом:

    <p id="start">This is some text that has 
    <a href="http://www.w3.org/TR/html4/struct/links.html">links</a> in it
    to various places, including <a href="http://www.opera.com">Opera</a>,
    <a href="http://www.bbc.co.uk/">the BBC</a> and an internal link to
    <a href="#start">the beginning of this section</a>. All the external links
    should have <span>[domain]</span> after them.</p>

    Код JavaScript выглядит следующим образом:

    <script type="text/javascript">
    var linksuffix = {
      addEvent: function(obj, type, fn) {
        if ( obj.attachEvent ) {
          obj['e'+type+fn] = fn;
          obj[type+fn] = function(){obj['e'+type+fn]( window.event );};
          obj.attachEvent('on'+type, obj[type+fn] );
        } else {
          obj.addEventListener( type, fn, false );
        }
      },  
      init: function() {
        var links = document.getElementById("linksuffixtext").getElementsByTagName("a");
        for (var i=0; i<links.length; i++) {
          var matches = links[i].href.match(/^http:\/\/(.*?)\//);
          if (matches) {
            var linkdomain = matches[1];
            var span = document.createElement("span");
            var spantext = document.createTextNode(" ["+linkdomain+"]");
            span.appendChild(spantext);
            links[i].parentNode.insertBefore(span, links[i].nextSibling);
          }
        }
      }
    };
    linksuffix.addEvent(window, "load", linksuffix.init);
    </script>

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

    var links = document.getElementsByTagName("a");
    for (var i=0; i<links.length; i++) {
      var matches = links[i].href.match(/^http:\/\/(.*?)\//);
      if (matches) {
        var linkdomain = matches[1];
        var span = document.createElement("span");
        var spantext = document.createTextNode(" ["+linkdomain+"]");
        span.appendChild(spantext);
        links[i].parentNode.insertBefore(span, links[i].nextSibling);
      }
    }

    Этот код разбивается следующим образом:

    var links = document.getElementsByTagName("a");
    for (var i=0; i<links.length; i++) {
      ...
    }

    Сначала он находит все ссылки ( getElementsByTagName("a") ) в документе

    var matches = links[i].href.match(/^http:\/\/(.*?)\//);
    if (matches) {
      ...
    }

    Эта строка использует регулярное выражение на каждой ссылке, чтобы определить, что место назначение начинается с http://что-то/. Если это так …

    var linkdomain = matches[1];
    var span = document.createElement("span");
    var spantext = document.createTextNode(" ["+linkdomain+"]");
    span.appendChild(spantext);

    … следующая часть сначала получает "домен ссылки", часть www.opera.com ссылки. Затем с помощью document.createElement создается элемент <span>. Затем создается "textNode" ("текстовыйУзел"). В то время как сами элементы HTML создаются с помощью document.createElement, весь текст в документе HTML фактически содержится в "textNode", и их необходимо создать отдельно. Нет необходимости беспокоиться об этом (или даже знать об этом) при написании реального HTML, но нужно знать об этом при создании элементов с помощью сценария DOM. Текст в textNode является фактически " [домен]", созданный конкатенацией (соединением вместе) строк. Наконец, эта часть использует метод appendChild элемента <span>, чтобы поместить textNode внутрь span.

    links[i].parentNode.insertBefore(span, links[i].nextSibling);

    Эта строка добавляет span в документ. В этом месте span является ссылкой на элемент HTML, который выглядит следующим образом:

    <span> [example.com]</span>

    Этот элемент, однако, не является частью документа. Он не является пока частью никакого документа, он просто находится в некоторой неопределенности. Добавление элемента в документ можно сделать одним из двух способов: используя appendChild как выше, или используя insertBefore. Функция appendChild добавляет наш элемент в конце существующего элемента (именно поэтому она называет append ). Так как мы хотим добавить ее в середине существующего элемента, нам нужно использовать insertBefore. Вспомните, что наш текущий фрагмент кода HTML выглядит примерно следующим образом:

    <p>... text that has 
    <a href="http://www.w3.org/TR/html4/struct/links.html">links</a> in it
    to ...

    Это приводит к дереву DOM, показанному на рисунке 9.1:

    (рис 9.1) Дерево DOM tree перед добавлением элемента span после ссылки

    Мы хотим вставить новый span между <a> и textNode "in it to", чтобы он появлялся после <a>. Это приведет к дереву DOM как на рисунке 9.2.

    (рис 9.2) Дерево DOM после добавления элемента span

    или, более просто, коду HTML следующего вида

    <p>... text which has 
    <a href="http://www.w3.org/TR/html4/struct/links.html">links</a>
    <span> [domain]</span> in it to ...

    Здесь было бы удобно иметь возможность сказать: "вставьте новый span после ссылки". К сожалению функции insertAfter нет. Вместо этого мы должны вставлять его перед элементом после ссылки (несколько запутано, но подумайте об этом, и все встанет на место). Краткая запись для выражения "вещь после элемента помеченного el " будет el.nextSibling. Функция insertBefore должна вызываться на элементе, в который происходит вставка, т.е. на предке <p> ссылки, легко доступной с помощью link.parentNode. Поэтому весь вызов, как выше, будет

    links[i].parentNode.insertBefore(span, links[i].nextSibling);

    То есть, находим предка ( <p> ) ссылки, которая обрабатывается ( links[i] ) в данный момент, и вставляем созданный элемент span перед элементом после ссылки ( links[i].nextSibling ). Использование HTML в виде дерева DOM таким образом и вставка новых элементов в него, может вначале вызывать непонимание, но скоро все становится понятным при достаточной практике.

    Заключение

    HTML предоставляет структуру страниц, а CSS предоставляет описание внешнего вида. А JavaScript приносит гибкость; структура HTML и стили CSS становятся динамическими.

    Можно время от времени изменять внешний вид страниц и их работу, на основе того, что делают пользователи. Следующий шаг: от хорошо-продуманной и хорошо-структурированной информации к данным, которые изменяются в зависимости от требований пользователя. Можно показывать и скрывать элементы, изменять стили и внешний вид, и добавлять новый код HTML или удалять старый, когда это может потребоваться.

    Контрольные вопросы

  • Как задать свойство display CSS элемента, чтобы скрыть элемент?
  • В чем разница между элементом и текстовым узлом?
  • Укажите две причины, почему важно постепенное улучшение.
  • В чем разница между appendChild и insertBefore?
  • Почему мы используем функцию addClass, а не просто соединяем имя нового класса с атрибутом className существующего элемента?
  • В следующей структуре HTML что такое будет document.getElementById("marker").nextSibling?
  • <p>This is a <strong>block of HTML with 
      <span id="marker">various markup</span> in it</strong>.</p>

    Об авторе

    Стюарт Лэнгридж вполне возможно единственный человек в мире, который имеет степень бакалавра по программированию и философии. Когда он не занят с компьютерами, он программист на JavaScript, Django, и Python в компании Canonical (http://www.canonical.com/), автор книги DHTML Utopia (http://www.sitepoint.com/books/dhtml1/) издательства SitePoint, или пьет приличное пиво. Он также составляет одну четвертую команды LugRadio (http://lugradio.org/presenters/), первого в мире радио-шоу, посвященного Free and Open Source Software. Результаты его разносторонних интересов в web, программировании сценариев, создании программного обеспечения с открытым кодом, и всего остального, что проплывает за окном, можно найти по адресу kryogenix.org (http://kryogenix.org/); Стюарта можно встретить на улице в поисках места для курения.

    Страницы:

    Введение

    В этой лекции рассматриваются основы использования JavaScript для управления контентом страницы, включая отображение и скрытие частей страницы, добавление нового кода HTML и его удаление. В конце вы поймете, что наиболее фундаментальной вещью, для чего используется JavaScript, является изменение контента страницы, и вы узнаете, как лучше всего это делать.

    Лекция имеет следующее содержание:

  • Скрытие и отображение
  • Пример скрытия и отображения
  • Регулярные выражения
  • Соединение работающего кода со страницей
  • Создание HTML
  • Заключение
  • Контрольные вопросы
  • Скрытие и отображение

    Легче всего начать с управления кодом HTML, который уже имеется, скрывая или отображая элементы, которые находятся на странице. Для этого можно использовать JavaScript, который позволяет изменять стили элементов. Стили CSS уже являются мощным средством описания внешнего вида элемента, и одной из составляющих того, как выглядит элемент, является вопрос, отображается ли элемент вообще. Свойство CSS display является ключом для отображения и скрытия элемента: если задать его как display:none;, элемент будет скрыт. Представьте параграф следующего вида:

    <p id="mypara" style="display: none">I am a paragraph</p>

    Этот параграф будет невидим на странице. JavaScript позволяет динамически добавлять стиль display: none для элемента и удалять его.

    JavaScript предоставляет возможность получить ссылку на элемент HTML. Например, var el = document.getElementById("nav"); выдает ссылку на элемент с id="nav". Когда ссылка на элемент получена, можно с помощью атрибута style изменить примененный к нему код CSS. Например, параграф "mypara" выше в настоящий момент скрыт (в нем задано display: none ). Чтобы показать его, задайте атрибут стиля display как block:

    var el = document.getElementById('mypara');
    el.style.display = 'block';

    И вот, параграф появился. Задание CSS на элементе через атрибут style делает то же самое, что задание его в атрибуте style в самом HTML, поэтому код выше, задающий el.style.display = 'block', добивается того же эффекта, что и размещение style="display: block" прямо в коде HTML. За исключением того, что он является динамическим. Скрытие любого элемента также просто:

    var el = document.getElementById('mypara');
    el.style.display = 'none';

    Пример скрытия и отображения

    С теорией все хорошо, но здесь может быть полезен более практический пример. Представьте множество вкладок, где щелчок на самой вкладке показывает ее и скрывает все остальные.

    Вот пример множества вкладок:

    <ol class="tablinks">
      <li><a href="#tab1">Tab 1</a></li>
      <li><a href="#tab2">Tab 2</a></li>
      <li><a href="#tab3">Tab 3</a></li>
    </ol>
    
    <div class="tab" id="tab1">This is tab 1</div>
    <div class="tab" id="tab2">This is tab 2</div>
    <div class="tab" id="tab3">This is tab 3</div>

    В разделе head файла примера (см. действующий пример tabs.html по адресу http://dev.opera.com/articles/view/creating-and-modifying-html/tabs.html), вы найдете следующий код CSS и JavaScript (который будет обычно помещаться во внешние файлы и импортироваться в HTML, но здесь все содержится в одном файле, чтобы упростить просмотр). Часть кода выглядит устрашающе; не беспокойтесь, мы рассмотрим это.

    <style type="text/css">
    ol.tablinks {
      margin: 0; padding: 0;
    }
    ol.tablinks li {
      float: left;
      border: 2px solid red;
      border-width: 2px 2px 0 2px;
      background: #eee;
      list-style: none;
      padding: 5px;
      margin: 0;
    }
    ol.tablinks li a {
      text-decoration: none;
      color: black;
    }
    div.tab {
      clear: left;
      border: 2px solid red;
      border-width: 1px 2px 2px 2px;
    }
    </style>
    
    <script type="text/javascript">
    var tabify = {
      hasClass: function(el,c) {
        return el.className.match(new RegExp('(\\s|^)'+c+'(\\s|$)'));        
      },
      addClass: function(el,c) {
        if (!tabify.hasClass(el,c)) el.className += " " + c;
      },
      removeClass: function(el,c) {
        if (tabify.hasClass(el,c)) {
          el.className=el.className.replace(new RegExp('(\\s|^)'+c+'(\\s|$)'),' ');
        }
      },
      hideAllTabs: function(ol) {
        var links = ol.getElementsByTagName("a");
        for (var i=0; i<links.length; i++) {
          tabify.setTabFromLink(links[i], "none");
       }
      },
      setTabFromLink: function(link, style) {
        var dest = link.href.match(/#(.*)$/)[1];
        document.getElementById(dest).style.display = style;
        if (style == "none") {
            tabify.removeClass(link, "active");
        } else {
            tabify.addClass(link, "active");
        }
      },
      addEvent: function(obj, type, fn) {
        if ( obj.attachEvent ) {
          obj['e'+type+fn] = fn;
          obj[type+fn] = function(){obj['e'+type+fn]( window.event );};
          obj.attachEvent('on'+type, obj[type+fn] );
        } else {
          obj.addEventListener( type, fn, false );
        }
      },  
      init: function() {
        var ols = document.getElementsByTagName("ol");
        for (var i=0; i<ols.length; i++) {
          var ol = ols[i];
          if (!/(^|\s)tablinks(\s|$)/.test(ol.className)) { continue; }
          tabify.addEvent(ol, "click", function(e) {
            var target = window.event ? window.event.srcElement : e.target;
            if (target.nodeName.toLowerCase() === "a") {
              tabify.hideAllTabs(e.target.parentNode.parentNode);
              tabify.setTabFromLink(e.target, "block");
              if (e) e.preventDefault();
              if (window.event) window.event.returnValue = false;
              return false;
            }
          }, true);
          tabify.hideAllTabs(ol);
          tabify.setTabFromLink(ol.getElementsByTagName("a")[0], "block");
        }
      }
    };
    tabify.addEvent(window, "load", tabify.init);
    </script>

    Каждая вкладка является ссылкой. Каждая ссылка содержит обработчик событий onclick. Этот обработчик событий делает две вещи: он скрывает все div (используя подход display: none, показанный выше), а затем показывает div, соответствующий этой вкладке, задавая style этого div как display: block.

    Можно видеть, что код HTML задан таким образом, чтобы все работало даже в отсутствие сценария, хотя щелчок на ссылке не показывает и не скрывает вкладку, ссылки переходят на соответствующую вкладку, поэтому страница по-прежнему действует корректно, и имеет семантический смысл даже в браузерах без JavaScript. Это важно: такая техника часто называется "постепенным улучшением" (или несколько лет назад называлась "постепенное снижение эффективности", но больше так не говорят). Это важно не только для людей, которые используют современный браузер, но с отключенным JavaScript, но и для множества других типов пользователей. Вспомогательные технологии, такие как считыватели экрана для слепых пользователей, существенно полагаются на то, что структура HTML будет семантической и содержательной, и их поддержка для усовершенствований JavaScript не такая хорошая, как для зрячих пользователей. Мобильные браузеры также обладают не очень большой поддержкой сценариев. Процессоры поиска считывают HTML, но не сценарий - можно сказать, что Google является самым ненасытным слепым пользователем браузера. Вот в чем состоит значение термина "последовательное улучшение": начните со страницы, которая имеет смысл, и отобразите ее содержимое в самой простой рабочей среде, такой как web-браузер с поддержкой только текста, а затем постепенно добавляйте к ней дополнительные функции, так чтобы на каждом шаге этого пути сайт оставался по-прежнему пригодным к употреблению и функциональным.

    Весь код JavaScript в основном присутствует в двух частях: часть, которая фактически делает работу, и часть, которая соединяет ее с HTML. Код, который фактически делает работу в этом примере, достаточно тривиален:

    отображение вкладки, соответствующей определенной ссылке, с помощью двух строчек кода JavaScript:

    var dest = link.href.match(/#(.*)$/)[1];
    document.getElementById(dest).style.display = "block";

    Ссылки, если вы помните, выглядят следующим образом <a href="#tab1">Tab 1</a>, и поэтому первая строка использует регулярное выражение (см. Примечание ниже) для извлечения той части ссылки, которая появится после символа # ; в данном примере это будет строка tab1. Эта часть ссылки будет такой же как ID соответствующего div (потому что, как упоминалось, страница создается таким образом, чтобы иметь семантический смысл, поэтому "вкладка" ссылается на свой div ). Затем мы извлекаем ссылку на этот div с помощью document.getElementById, и задаем style.display как block, как было показано ранее.

    Регулярные выражения

    Регулярные выражения являются разновидностью мини-языка-программирования, созданного для решения проблемы "синтаксического анализа" текста - например, ответить на вопросы типа "появляется ли данная строка внутри другой строки?" и "в строке 'abc123def' какие имеются цифры между 'c' и 'd'?" Они являются очень мощным инструментом, но также достаточно сложным. Ниже находится описание, для чего предназначены регулярные выражения; пока, однако, можете принять это на веру, и вернуться к описанию позже.

    Используемое в этом примере регулярное выражение имеет вид /#(.*)$/. Каждый символ регулярного выражения создан для представления последовательности символов в некоторой целевой строке, идея состоит в том, чтобы выразить по частям, как составлена целевая строка.

  • / … / - косая черта указывает начало и конец регулярного выражения, также как двойные или одиночные кавычки используются для "строки"
  • # - хэш-символ (#) фактически означает "совпадает с хэш-символом"
  • ( … ) - скобки означают "здесь находится часть строки, которая понадобится позже"
  • .* - означает "любые символы"; на самом деле это точка (.), означающая "один любой символ", за которой следует звездочка (*), означающая "повторить любое количество раз"
  • $ - знак доллара означает "конец строки"
  • Поэтому наше регулярное выражение описывает "подходящий шаблон" строки, состоящей из "хэша, за которым следуют любые символы". link.href является местом назначения рассматриваемой ссылки (например, #tab1, и так как эта строка подходит под описание "хэш, за которым следуют любые символы"), то "подходящий шаблон" применим к этой строке.

    link.href.match(our_regexp), вернет, поэтому, результат true, а не false ; в действительности будет возвращен массив из двух элементов ["#tab1", "tab1"]. Первый является текстом, который соответствует всему регулярному выражению, а второй является текстом, соответствующим символам в кавычках, поэтому скобки здесь и присутствуют - чтобы пометить эту часть строки как "этот фрагмент нас интересует". tab1 является строкой, которая нам требуется, поэтому мы можем извлечь ее из возвращаемого массива (это второй элемент, поэтому его извлекает индекс [1] - массивы начинают нумерацию с нуля.)

    Соединение работающего кода со страницей

    Как упоминалось выше, имеется две части кода: часть, которая фактически выполняет работу, и часть, которая соединяет ее с HTML. Соединение работающего кода с HTML осуществляется с помощью событий. В данном конкретном примере мы используем два события: событие загрузки окна, которое используется, чтобы сказать "все начинается", и событие щелчка на списке вкладок, которое запускается, когда пользователь щелкает на вкладке. Когда загружается страница, требуется выполнить код соединения, а код соединения должен соединить событие щелчка на вкладке с кодом выше, который отображает соответствующую вкладку.

    Выполнение кода в ответ на событие делается в большинстве браузеров с помощью функции addEventListener, и с помощью функции attachEvent в Internet Explorer. Здесь мы создаем функцию "оболочку", которая делает правильный выбор, в зависимости от того, что поддерживается:

    addEvent: function(obj, type, fn) {
      if ( obj.attachEvent ) {
        obj['e'+type+fn] = fn;
        obj[type+fn] = function(){obj['e'+type+fn]( window.event );};
        obj.attachEvent('on'+type, obj[type+fn] );
      } else {
        obj.addEventListener( type, fn, false );
      }
    }

    (Не беспокойтесь слишком много о том, как это работает; примите пока просто на веру - вы поймете код лучше, когда станете более опытным пользователем JavaScript.) Эта функция получает три параметра, obj, type, и fn, которые являются соответственно "объектом, порождающим событие ", "интересующим нас событием ", и "функцией, которая выполняется при возникновении события ". При загрузке страницы необходимо выполнить нашу функцию tabify.init ; функция tabify.init затем позаботится о присоединении события click для каждой вкладки.

    addEvent(window, "load", tabify.init);

    Как можно видеть из структуры кода HTML выше, множество вкладок в действительности представляется как упорядоченный список с class="tablinks". Поэтому функция tabify.init должна сделать следующее:

  • Найти все элементы <ol> на странице с классом tablinks
  • Соединить с событием click каждого элемента <ol> некоторый код, который делает следующее:
  • Точно определяет, на какой ссылке вкладки щелкнул пользователь
  • Определяет, какая реальная вкладка соответствует этой ссылке вкладки
  • Показывает эту одну вкладку
  • Скрывает все остальные вкладки
  • Функция init, которая все это делает, выглядит следующим образом:

    init: function() {
      var ols = document.getElementsByTagName("ol");
      for (var i=0; i<ols.length; i++) {
        var ol = ols[i];
        if (!/(^|\s)tablinks(\s|$)/.test(ol.className)) { continue; }
        tabify.addEvent(ol, "click", function(e) {
          var target = window.event ? window.event.srcElement : e.target;
          if (target.nodeName.toLowerCase() === "a") {
            tabify.hideAllTabs(e.target.parentNode.parentNode);
            tabify.setTabFromLink(e.target, "block");
            if (e) e.preventDefault();
            if (window.event) window.event.returnValue = false;
            return false;
          }
        }, true);
        tabify.hideAllTabs(ol);
        tabify.setTabFromLink(ol.getElementsByTagName("a")[0], "block");
      }
    }

    Давайте пройдем по этой функции шаг за шагом, просматривая по очереди, что делает каждая часть:

    var ols = document.getElementsByTagName("ol");
      for (var i=0; i<ols.length; i++) {
        var ol = ols[i];
        if (!/(^|\s)tablinks(\s|$)/.test(ol.className)) { continue; }
      }

    Этот фрагмент находит все элементы <ol> на странице с классом tablinks - и затем извлекает список всех <ol> и для каждого проверяет "если этот элемент не имеет класс 'tablinks', пропустить его". (Проверка класса делается с помощью регулярного выражения; continue означает "пропустить этот элемент и перейти к следующему".)

    tabify.addEvent(ol, "click", function(e) {
      ...
    });

    Этот фрагмент присоединяет некоторый код к событию click каждого <ol>.

    var target = window.event ? window.event.srcElement : e.target;

    Этот фрагмент точно определяет, на какой ссылке вкладки щелкнул пользователь …

    tabify.setTabFromLink(e.target, "block");

    …затем этот фрагмент, показывает эту единственную вкладку …

    tabify.hideAllTabs(e.target.parentNode.parentNode);

    … и наконец, эта строка скрывает все другие вкладки.

    Функции setTabFromLink и hideAllTabs также присутствуют в коде; они используют показанные выше методы для скрытия или отображения вкладки. Посмотрите на то, как они работают - они являются отдельными функциями, так как часто полезно иметь блок кода, который вызывается более чем из одного места, и поместить его в отдельную функцию. (Это облегчает понимание кода и позволяет повторно использовать его в нескольких местах. Этот подход часто называют "разбиением" или "факторизацией" кода в функцию.)

    В ней выполняется также небольшая "дополнительная" работа, которая демонстрирует добавление и удаление атрибутов. Функция setTabFromLink не только отображает соответствующую вкладку, но задает также class="active" на активной вкладке. Она делает это с помощью трех вспомогательных функций: hasClass, removeClass, и addClass. Таким же образом, как мы скрыли все вкладки, а затем показали активную, мы используем removeClass для удаления "active" из className всех ссылок на вкладки, а затем addClass, чтобы добавить "active" в одну ссылку на вкладку, которая действительно активна. Простое добавление класса в ссылку может показаться бессмысленным - в конечном счете классы невидимы - но это зацепка для стилевого оформления. Мы можем (и должны) делать так, чтобы ссылки с class="active" появлялись с другим оформлением, добавляя код CSS:

    ol.tablinks li a {
      background-color: red;
    }
    
    ol.tablinks li a.active {
      background-color: white;
    }

    Поэтому теперь "активная" вкладка выводится белой, в то время как другие вкладки будут красными. Использование JavaScript для добавления и удаления классов является очень распространенной техникой, которую вы должны использовать, где возможно; CSS хорошо подходит для управления компоновкой, расположением и внешним видом элементов HTML, и поэтому использование JavaScript для изменения классов этих элементов означает, что они могут приобретать различные стили CSS. Это будет идеальным объединением; JavaScript делает элементы динамическими, но фактически сам изменяет очень немного. Добавьте просто класс и предоставьте CSS возможность выполнить остальную работу.

    Создание HTML

    Однако сценарии для DOM значительно более мощный инструмент, чем просто средство для изменения свойств CSS существующего HTML. Можно также динамически создавать новый код HTML, который никогда не присутствовал на странице с самого начала. Например, на сайте новостей технологии Slashdot, ссылка в комментариях выводит место назначения ссылки в квадратных скобках, поэтому ссылка вида <a href="http://opera.com"> A web browser </a> будет выводиться как <a href="http://opera.com">A web browser</a> [opera.com]. (Это было сделано, чтобы исключить использование фальшивых ссылок, направляющих пользователя в нежелательное место.) Добавление этого небольшого кода HTML, выводящего домен места назначения каждой ссылки на странице, легко делается с помощью JavaScript.

    Создание новых элементов HTML делается с помощью функции ) содержащую действующий пример). Код HTML примера выглядит следующим образом:

    <p id="start">This is some text that has 
    <a href="http://www.w3.org/TR/html4/struct/links.html">links</a> in it
    to various places, including <a href="http://www.opera.com">Opera</a>,
    <a href="http://www.bbc.co.uk/">the BBC</a> and an internal link to
    <a href="#start">the beginning of this section</a>. All the external links
    should have <span>[domain]</span> after them.</p>

    Код JavaScript выглядит следующим образом:

    <script type="text/javascript">
    var linksuffix = {
      addEvent: function(obj, type, fn) {
        if ( obj.attachEvent ) {
          obj['e'+type+fn] = fn;
          obj[type+fn] = function(){obj['e'+type+fn]( window.event );};
          obj.attachEvent('on'+type, obj[type+fn] );
        } else {
          obj.addEventListener( type, fn, false );
        }
      },  
      init: function() {
        var links = document.getElementById("linksuffixtext").getElementsByTagName("a");
        for (var i=0; i<links.length; i++) {
          var matches = links[i].href.match(/^http:\/\/(.*?)\//);
          if (matches) {
            var linkdomain = matches[1];
            var span = document.createElement("span");
            var spantext = document.createTextNode(" ["+linkdomain+"]");
            span.appendChild(spantext);
            links[i].parentNode.insertBefore(span, links[i].nextSibling);
          }
        }
      }
    };
    linksuffix.addEvent(window, "load", linksuffix.init);
    </script>

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

    var links = document.getElementsByTagName("a");
    for (var i=0; i<links.length; i++) {
      var matches = links[i].href.match(/^http:\/\/(.*?)\//);
      if (matches) {
        var linkdomain = matches[1];
        var span = document.createElement("span");
        var spantext = document.createTextNode(" ["+linkdomain+"]");
        span.appendChild(spantext);
        links[i].parentNode.insertBefore(span, links[i].nextSibling);
      }
    }

    Этот код разбивается следующим образом:

    var links = document.getElementsByTagName("a");
    for (var i=0; i<links.length; i++) {
      ...
    }

    Сначала он находит все ссылки ( getElementsByTagName("a") ) в документе

    var matches = links[i].href.match(/^http:\/\/(.*?)\//);
    if (matches) {
      ...
    }

    Эта строка использует регулярное выражение на каждой ссылке, чтобы определить, что место назначение начинается с http://что-то/. Если это так …

    var linkdomain = matches[1];
    var span = document.createElement("span");
    var spantext = document.createTextNode(" ["+linkdomain+"]");
    span.appendChild(spantext);

    … следующая часть сначала получает "домен ссылки", часть www.opera.com ссылки. Затем с помощью document.createElement создается элемент <span>. Затем создается "textNode" ("текстовыйУзел"). В то время как сами элементы HTML создаются с помощью document.createElement, весь текст в документе HTML фактически содержится в "textNode", и их необходимо создать отдельно. Нет необходимости беспокоиться об этом (или даже знать об этом) при написании реального HTML, но нужно знать об этом при создании элементов с помощью сценария DOM. Текст в textNode является фактически " [домен]", созданный конкатенацией (соединением вместе) строк. Наконец, эта часть использует метод appendChild элемента <span>, чтобы поместить textNode внутрь span.

    links[i].parentNode.insertBefore(span, links[i].nextSibling);

    Эта строка добавляет span в документ. В этом месте span является ссылкой на элемент HTML, который выглядит следующим образом:

    <span> [example.com]</span>

    Этот элемент, однако, не является частью документа. Он не является пока частью никакого документа, он просто находится в некоторой неопределенности. Добавление элемента в документ можно сделать одним из двух способов: используя appendChild как выше, или используя insertBefore. Функция appendChild добавляет наш элемент в конце существующего элемента (именно поэтому она называет append ). Так как мы хотим добавить ее в середине существующего элемента, нам нужно использовать insertBefore. Вспомните, что наш текущий фрагмент кода HTML выглядит примерно следующим образом:

    <p>... text that has 
    <a href="http://www.w3.org/TR/html4/struct/links.html">links</a> in it
    to ...

    Это приводит к дереву DOM, показанному на рисунке 9.1:

    (рис 9.1) Дерево DOM tree перед добавлением элемента span после ссылки

    Мы хотим вставить новый span между <a> и textNode "in it to", чтобы он появлялся после <a>. Это приведет к дереву DOM как на рисунке 9.2.

    (рис 9.2) Дерево DOM после добавления элемента span

    или, более просто, коду HTML следующего вида

    <p>... text which has 
    <a href="http://www.w3.org/TR/html4/struct/links.html">links</a>
    <span> [domain]</span> in it to ...

    Здесь было бы удобно иметь возможность сказать: "вставьте новый span после ссылки". К сожалению функции insertAfter нет. Вместо этого мы должны вставлять его перед элементом после ссылки (несколько запутано, но подумайте об этом, и все встанет на место). Краткая запись для выражения "вещь после элемента помеченного el " будет el.nextSibling. Функция insertBefore должна вызываться на элементе, в который происходит вставка, т.е. на предке <p> ссылки, легко доступной с помощью link.parentNode. Поэтому весь вызов, как выше, будет

    links[i].parentNode.insertBefore(span, links[i].nextSibling);

    То есть, находим предка ( <p> ) ссылки, которая обрабатывается ( links[i] ) в данный момент, и вставляем созданный элемент span перед элементом после ссылки ( links[i].nextSibling ). Использование HTML в виде дерева DOM таким образом и вставка новых элементов в него, может вначале вызывать непонимание, но скоро все становится понятным при достаточной практике.

    Заключение

    HTML предоставляет структуру страниц, а CSS предоставляет описание внешнего вида. А JavaScript приносит гибкость; структура HTML и стили CSS становятся динамическими.

    Можно время от времени изменять внешний вид страниц и их работу, на основе того, что делают пользователи. Следующий шаг: от хорошо-продуманной и хорошо-структурированной информации к данным, которые изменяются в зависимости от требований пользователя. Можно показывать и скрывать элементы, изменять стили и внешний вид, и добавлять новый код HTML или удалять старый, когда это может потребоваться.

    Контрольные вопросы

  • Как задать свойство display CSS элемента, чтобы скрыть элемент?
  • В чем разница между элементом и текстовым узлом?
  • Укажите две причины, почему важно постепенное улучшение.
  • В чем разница между appendChild и insertBefore?
  • Почему мы используем функцию addClass, а не просто соединяем имя нового класса с атрибутом className существующего элемента?
  • В следующей структуре HTML что такое будет document.getElementById("marker").nextSibling?
  • <p>This is a <strong>block of HTML with 
      <span id="marker">various markup</span> in it</strong>.</p>

    Об авторе

    Стюарт Лэнгридж вполне возможно единственный человек в мире, который имеет степень бакалавра по программированию и философии. Когда он не занят с компьютерами, он программист на JavaScript, Django, и Python в компании Canonical (http://www.canonical.com/), автор книги DHTML Utopia (http://www.sitepoint.com/books/dhtml1/) издательства SitePoint, или пьет приличное пиво. Он также составляет одну четвертую команды LugRadio (http://lugradio.org/presenters/), первого в мире радио-шоу, посвященного Free and Open Source Software. Результаты его разносторонних интересов в web, программировании сценариев, создании программного обеспечения с открытым кодом, и всего остального, что проплывает за окном, можно найти по адресу kryogenix.org (http://kryogenix.org/); Стюарта можно встретить на улице в поисках места для курения.

    Вернуться к учебному плану