15. Работа с документами

Клиентский JavaScript предназначен для того, чтобы превращать статические HTML-документы в интерактивные веб-приложения. Работа с содержимым вебстраниц – главное предназначение JavaScript. Данная глава является одной из наиболее важных в этой книге – здесь рассказывается о том, как это делается.

В главах 13 и 14 говорилось, что каждое окно, вкладка и фрейм веб-браузера представлено объектом Window. Каждый объект Window имеет свойство document, ссылающееся на объект Document. Этот объект Document и является темой обсуждения данной главы. Однако объект Document не является автономным объектом. Он является центральным объектом обширного API, известного как объектная модель документа (Document Object Model, DOM), который определяет порядок доступа к содержимому документа.

Эта глава начинается с описания базовой архитектуры DOM, а затем она расскажет:

В заключительном разделе этой главы рассматриваются различные особенности документов, включая свойство referrer, метод write() и приемы получения текста, выделенного в документе.

содержание 15.1. Обзор модели DOM содержание

Объектная модель документа (Document Object Model, DOM) – это фундаментальный прикладной программный интерфейс, обеспечивающий возможность работы с содержимым HTML- и XML-документов. Прикладной программный интерфейс (API) модели DOM не особенно сложен, но в нем существует множество архитектурных особенностей, которые вы должны знать

Прежде всего, следует понимать, что вложенные элементы HTML- или XML-документов представлены в виде дерева объектов DOM. Древовидное представление HTML-документа содержит узлы, представляющие элементы или теги, такие как <body> и <p>, и узлы, представляющие строки текста. HTML-документ также может содержать узлы, представляющие HTML-комментарии. Рассмотрим следующий простой HTML-документ:

<html>
  <head>
    <title>Sample Document</title>
  </head>
  <body>
    <h1>An HTML Document</h1>
    <p>This is a <i>simple</i> document.
</html>

DOM-представление этого документа приводится на рис. 15.1.

Тем, кто еще не знаком с древовидными структурами в компьютерном программировании, полезно узнать, что терминология для их описания была заимствована у генеалогических деревьев. Узел, расположенный непосредственно над данным узлом, называется родительским по отношению к данному узлу. Узлы, расположенные на один уровень ниже другого узла, являются дочерними по отношению к данному узлу. Узлы, находящиеся на том же уровне и имеющие того же родителя, называются братьями. Узлы, расположенные на любое число уровней ниже другого узла, являются его потомками. Родительские, прародительские и любые другие узлы, расположенные выше данного узла, являются его предками.

cheme

Рис. 15.1. Древовидное представление HTML-документа

Каждый прямоугольник на рис. 15.1 является узлом документа, который представлен объектом Node. О свойствах и методах объекта Node будет рассказываться в некоторых разделах, следующих ниже, кроме того, описания этих свойств вы, найдете в справочной статье Node в четвертой части книги. Обратите внимание, что на рисунке изображено три различных типа узлов. Корнем дерева является узел Document, который представляет документ целиком. Узлы, представляющие HTML-элементы, являются узлами типа Element, а узлы, представляющие текст, – узлами типа Text.
Document, Element и Text – это подклассы класса Node, и для них имеются отдельные справочные статьи в четвертой части книги.
Document и Element являются двумя самыми важными классами в модели DOM, и большая часть главы посвящена знакомству с их свойствами и методами.

Тип Node и его подтипы образуют иерархию типов, изображенную на рис. 15.2. Обратите внимание на формальные отличия между обобщенными типами Document и Element и типами HTMLDocument и HTMLElement. Тип Document представляет HTML- или XML-документ, а класс Element представляет элемент этого документа. Подклассы HTMLDocument и HTMLElement представляют конкретно HTML-документ и его элементы. В этой книге часто используются имена обобщенных классов Document и Element, даже когда подразумеваются HTML-документы. То же самое относится и к справочному разделу книги: свойства и методы типов HTMLDocument и HTMLElement описываются в справочных статьях Document и Element.

cheme

Рис. 15.2. Неполная иерархия классов узлов документов

На рис. 15.2 следует также отметить наличие большого количества подтипов класса HTMLElement, представляющих конкретные типы HTML-элементов. Каждый из них определяет JavaScript-свойства, отражающие HTML-атрибуты конкретного элемента или группы элементов (раздел 15.4.1). Некоторые из этих специфических классов определяют дополнительные свойства или методы, которые не являются отражением синтаксиса языка разметки HTML. Более подробно эти классы и их дополнительные особенности рассматриваются в справочном разделе книги.

Наконец, обратите внимание, что на рис. 15.2 изображены некоторые типы узлов, которые нигде до сих пор не упоминались. Узлы Comment представляют HTML- или XML-комментарии. Поскольку комментарии являются обычными текстовыми строками, эти узлы во многом подобны узлам Text, представляющим отображаемый текст документа. Тип CharacterData, обобщенный предок типов Text и Comment, определяет методы, общие для узлов этих двух типов. Узлы типа Attr представляют XML- или HTML-атрибуты, но они практически никогда не используются, потому что класс Element определяет методы, позволяющие интерпретировать атрибуты, как пары имя/значение, а не как узлы документа. Объект DocumentFragment (не изображен на рисунке) – это разновидность узлов, которая практически никогда не встречается в документах: он представляет последовательность узлов, не имеющих общего родителя. Объекты DocumentFragment удобно использовать при выполнении манипуляций над документами, и подробнее об этом типе рассказывается в разделе 15.6.4. Модель DOM также определяет несколько редко используемых типов, представляющих, например, объявления типа документа и инструкции обработки XML.

содержание 15.2. Выбор элементов документа содержание

Работа большинства клиентских программ на языке JavaScript так или иначе связана с манипулированием элементами документа. В ходе выполнения эти программы могут использовать глобальную переменную document, ссылающуюся на объект Document. Однако чтобы выполнить какие-либо манипуляции с элементами документа программа должна каким-то образом получить или выбрать объекты Element, ссылающиеся на эти элементы документа. Модель DOM определяет несколько способов выборки элементов. Выбрать элемент или элементы документа можно:

Все эти приемы выборки элементов описываются в следующих подразделах.

содержание 15.2.1. Выбор элементов по значению атрибута id

Все HTML-элементы имеют атрибуты id. Значение этого атрибута должно быть уникальным в пределах документа – никакие два элемента в одном и том же документе не должны иметь одинаковые значения атрибута id. Выбрать элемент по уникальному значению атрибута id можно с помощью метода getElementById() объекта Document. Этот метод уже использовался в примерах глав 13 и 14:

var section1 = document.getElementById("section1");

Это самый простой и самый распространенный способ выборки элементов. Если сценарию необходимо иметь возможность манипулировать каким-то определенным множеством элементов документа, задайте этим элементам атрибуты id и используйте возможность их поиска по их id. Если потребуется отыскать более одного элемента по значению атрибута id, можно воспользоваться удобной функцией getElements(), реализация которой приводится в примере 15.1.

Пример 15.1. Поиск нескольких элементов по значениям атрибута id

В версиях Internet Explorer ниже IE8 метод getElementById() выполняет поиск значений атрибутов id без учета регистра символов и, кроме того, возвращает элементы, в которых будет найдено совпадение со значением атрибута name.

содержание 15.2.2. Выбор элементов по значению атрибута name

HTML-атрибут name первоначально предназначался для присваивания имен элементам форм, и значение этого атрибута использовалось, когда выполнялась отправка данных формы на сервер. Подобно атрибуту id, атрибут name присваивает имя элементу. Однако в отличие от id значение атрибута name не обязано быть уникальным: одно и то же имя могут иметь сразу несколько элементов, что вполне обычно при использовании в формах радиокнопок и флажков. Кроме того, в отличие от id атрибут name допускается указывать лишь в некоторых HTML-элементах, включая формы, элементы форм и элементы <iframe> и <img>.

Выбрать HTML-элементы, опираясь на значения их атрибутов name, можно с помощью метода getElementsByName() объекта Document:

var radiobuttons = document.getElementsByName("favorite_color");

Метод getElementsByName() определяется не классом Document, а классом HTMLDocument, поэтому он доступен только в HTML-документах и не доступен в XML-документах. Он возвращает объект NodeList, который ведет себя как доступный только для чтения массив объектов Element. В IE метод getElementsByName() возвращает также элементы, значения атрибутов id которых совпадает с указанным значением. Чтобы обеспечить совместимость с разными версиями браузеров, необходимо внимательно подходить к выбору значений атрибутов и не использовать одни и те же строки в качестве значений атрибутов name и id.

Мы видели в разделе 14.7, что наличие атрибута name в некоторых HTML-элементах приводит к автоматическому созданию свойств с этими именами в объекте Window. To же относится и к объекту Document. Наличие атрибута name в элементе <form>, <img>, <iframe>, <applet>, <embed> или <object> (но только в том элементе <object>, который не имеет вложенных объектов с альтернативным содержимым) приводит к созданию свойства в объекте Document, имя которого совпадает со значением атрибута (при этом предполагается, что объект документа еще не имеет свойства с этим именем).

Если существует только один элемент с указанным именем, значением автоматически созданного свойства документа станет сам элемент. Если таких элементов несколько, значением свойства будет объект NodeList, играющий роль массива элементов. Как было показано в разделе 14.7, для именованных элементов <iframe> создаются особые свойства документа: они ссылаются не на объекты Element, а на объекты Window, представляющие фреймы.

Это означает, что некоторые элементы могут быть выбраны по их именам простым обращением к свойствам объекта Document:

// Получить ссылку на объект Element для элемента <form name="shipping_address">
var form = document.shipping_address;

Причины, почему не следует использовать автоматически создаваемые свойства окна, которые описываются в разделе 14.7, в равной степени применимы и к автоматически создаваемым свойствам документа. Если вам потребуется отыскать именованные элементы, лучше всего это сделать явно с помощью метода getElementsByName().

содержание 15.2.3. Выбор элементов по типу

Метод getElementsByTagName() объекта Document позволяет выбрать все HTML- или XML-элементы указанного типа (или по имени тега). Например, получить подобный массиву объект, доступный только для чтения, содержащий объекты Element всех элементов <span> в документе, можно следующим образом:

var spans = document.getElementsByTagName("span");

Подобно методу getElementsByName() метод getElementsByTagName() возвращает объект NodeList (подробнее класс NodeList описывается во врезке в этом же разделе). Элементы документа включаются в массив NodeList в том же порядке, в каком они следуют в документе, т. е. первый элемент <p> в документе можно выбрать так:

var firstpara = document.getElementsByTagName("p")[0];

Имена HTML-тегов не чувствительны к регистру символов, и когда getElementsByTagName() применяется к HTML-документу, он выполняет сравнение с именем тега без учета регистра символов. Переменная spans, созданная выше, например, будет включать также все элементы <span>, которые записаны как <SPAN>.

Можно получить NodeList, содержащий все элементы документа, если передать методу getElementsByTagName() шаблонный символ «*».

Класс Element тоже определяет метод getElementsByTagName(). Он действует точно так же, как и версия метода в классе Document, но выбирает только элементы, являющиеся потомками для элемента, относительно которого вызывается метод. То есть отыскать все элементы <span> внутри первого элемента <p> можно следующим образом:

var firstpara = document.getElementsByTagName("p")[0];
var firstParaSpans = firstpara.getElementsByTagName("span");

По историческим причинам класс HTMLDocument определяет специальные свойства для доступа к узлам определенных типов. Свойства images, forms и links, например, ссылаются на объекты, которые ведут себя как массивы, доступные только для чтения, содержащие элементы <img>, <form> и <a> (но только те теги <a>, которые имеют атрибут href). Эти свойства ссылаются на объекты HTMLCollection, которые во многом похожи на объекты NodeList, но дополнительно могут индексироваться значениями атрибутов id и name. Ранее мы узнали, как можно получить ссылку на именованный элемент <form> с помощью такого выражения:

document.shipping_address

С помощью свойства document.forms обращение к форме, имеющей атрибут name (или id), можно записать более конкретно:

document.forms.shipping_address;

Объект HTMLDocument также определяет свойства-синонимы embeds и plugins, являющиеся коллекциями HTMLCollection элементов <embed>. Свойство anchors является нестандартным, но с его помощью можно получить доступ к элементам <a> , имеющим атрибут name, но не имеющим атрибут href. Свойство scripts определено стандартом HTML5 и является коллекцией HTMLCollection элементов <script> , но к моменту написания этих строк оно было реализовано не во всех браузерах.

Кроме того, объект HTMLDocument определяет два свойства, каждое из которых ссылается не на коллекцию, а на единственный элемент. Свойство document.body представляет элемент <body> HTML-документа, а свойство document.head – элемент <head>. Эти свойства всегда определены в документе: даже если в исходном документе отсутствуют элементы <head> и <body>, браузер создаст их неявно. Свойство document.Element объекта Document ссылается на корневой элемент документа. В HTML-документах он всегда представляет элемент <html>.

Объекты NodeList и HTMLCollection

Методы getElementsByName() и getElementsByTagName() возвращают объекты NodeList, а такие свойства, как document.images и document.forms, являются объектами HTMLCollection.

Эти объекты являются объектами, подобными массивам, доступным только для чтения (раздел 7.11). Они имеют свойство length и могут индексироваться (только для чтения) подобно настоящим массивам. Содержимое объекта NodeList или HTMLCollection можно обойти с помощью стандартного цикла, например:

Для объектов NodeList и HTMLCollection нельзя непосредственно вызывать методы класса Array, но их можно вызывать косвенно:

var content = Array.prototype.map.call(document.getElementsByTagName("p"),
                                       function(e) { return e.innerHTML; });

Объекты HTMLCollection могут иметь дополнительные именованные свойства и могут индексироваться не только числами, но и строками.

По историческим причинам оба объекта, NodeList и HTMLCollection, могут также играть роль функций: вызов их с числовым или строковым аргументом равносилен операции индексирования числом или строкой. Однако использование этой причудливой особенности может сбивать с толку.

Интерфейсы обоих объектов, NodeList и HTMLCollection, проектировались под другие языки программирования, не такие динамические, как JavaScript. Оба определяют метод item(). Он принимает целое число и возвращает элемент с этим индексом. Однако в программах на языке JavaScript нет нужды использовать этот метод, так как можно использовать простую операцию индексирования массива. Аналогично HTMLCollection определяет метод namedItem(), возвращающий значение именованного свойства, но в программах на языке JavaScript вместо него можно использовать операции индексирования массива и обращения к свойствам.

Одна из наиболее важных и интересных особенностей объектов NodeList и HTMLCollection состоит в том, что они не являются статическими слепками документа, а продолжают «жить», и списки элементов, которые они представляют, изменяются по мере изменения документа. Если вызвать метод getElementsByTagName('div') для документа, в котором отсутствуют элементы <div>, он вернет объект NodeList, свойство length которого будет равно 0. Если затем вставить в документ новый элемент <div>, этот элемент автоматически станет членом коллекции NodeList, а ее свойство length станет равно 1.

Обычно такая динамичность элементов NodeList и HTMLCollection бывает весьма полезна. Однако если добавлять или удалять элементы из документа в процессе итераций по коллекции NodeList, потребуется предварительно создать статическую копию объекта NodeList:

var snapshot = Array.prototype.slice.call(nodelist, 0);

содержание 15.2.4. Выбор элементов по классу CSS

Значением HTML-атрибута class является список из нуля или более идентификаторов, разделенных пробелами. Он дает возможность определять множества связанных элементов документа: любые элементы, имеющие в атрибуте class один и тот же идентификатор, являются частью одного множества. Слово class зарезервировано в языке JavaScript, поэтому для хранения значения HTML-атрибута class в клиентском JavaScript используется свойство className. Обычно атрибут class используется вместе с каскадными таблицами стилей CSS с целью применить общий стиль отображения ко всем членам множества, и мы еще будем рассматривать эту тему в главе 16. Однако кроме этого стандарт HTML5 определяет метод getElementsByClassName(), позволяющий выбирать множества элементов документа на основе идентификаторов в их атрибутах class.

Подобно методу getElementsByTagName(), метод getElementsByClassName() может вызываться и для HTML-документов, и для HTML-элементов, и возвращает «живой» объект NodeList, содержащий все потомки документа или элемента, соответствующие критерию поиска. Метод getElementsByClassName() принимает единственный строковый аргумент, но в самой строке может быть указано несколько идентификаторов, разделенных пробелами. Соответствующими будут считаться элементы, атрибуты class которых содержат все указанные идентификаторы. Порядок следования идентификаторов не имеет значения. Обратите внимание, что и в атрибуте class, и в аргументе метода getElementsByClassName() идентификаторы классов разделяются пробелами, а не запятыми. Ниже приводится несколько примеров использования метода getElementsByClassName():

Современные браузеры отображают HTML-документы в «режиме совместимости» или в «стандартном режиме» в зависимости от строгости объявления <!DOCTYPE> в начале документа. Режим совместимости поддерживается для сохранения обратной совместимости, и одна из его особенностей состоит в том, что идентификаторы классов в атрибуте class и каскадных таблицах стилей CSS нечувствительны к регистру символов. Метод getElementsByClassName() следует алгоритму сопоставления, используемому таблицами стилей. Если документ отображается в режиме совместимости, метод сравнивает строки без учета регистра символов. В противном случае сравнение выполняется с учетом регистра символов.

К моменту написания этих строк метод getElementsByClassName() был реализован во всех текущих браузерах, за исключением IE8 и более ранних версий. Однако IE8 поддерживает описываемый в следующем разделе метод querySelectorAll(), на основе которого можно реализовать метод getElementsByClassName().

содержание 15.2.5. Выбор элементов с использованием селекторов CSS

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

(определение селекторов CSS3 можно найти по адресу http://www.w3.org/TR/css3-selectors/)

однако несколько примеров помогут прояснить их основы. Элементы можно описать с помощью имени тега и атрибутов id и class:

В более общем случае элементы можно выбирать, опираясь на значения атрибутов:

Эти простейшие селекторы можно комбинировать:

С помощью селекторов можно также определять взаимоотношения между элементами:

Селекторы можно комбинировать для выбора нескольких элементов или множеств элементов:

Как видите, селекторы CSS позволяют выбирать элементы всеми способами, описанными выше: по значению атрибута id и name, по имени тега и по имени класса. Наряду со стандартизацией селекторов CSS3 другой стандарт консорциума W3C, известный как «Selectors API» (API селекторов), определяет методы JavaScript для получения элементов, соответствующих указанному селектору.

Стандарт «Selectors API» не является частью стандарта HTML5, но тесно связан с ним. Подробности смотрите по адресу http://www.w3.org/TR/selectors-api/.

Ключевым в этом API является метод querySelectorAll() объекта Document. Он принимает единственный строковый аргумент с селектором CSS и возвращает объект NodeList, представляющий все элементы документа, соответствующие селектору. В отличие от ранее описанных методов выбора элементов объект NodeList, возвращаемый методом querySelectorAll(), не является «живым»: он хранит элементы, которые соответствовали селектору на момент вызова метода, и не отражает последующие изменения в документе. В случае отсутствия элементов, соответствующих селектору, метод querySelectorAll() вернет пустой NodeList. Если методу querySelectorAll() передать недопустимую строку, он возбудит исключение.

В дополнение к методу querySelectorAll() объект документа также определяет метод querySelector(), подобный методу querySelectorAll(), - с тем отличием, что он возвращает только первый (в порядке следования в документе) соответствующий элемент или null, в случае отсутствия соответствующих элементов.

Эти два метода также определяются классом Elements (и классом DocumentFragment, о котором рассказывается в разделе 15.6.4). Когда они вызываются относительно элемента, поиск соответствия заданному селектору выполняется во всем документе, а затем результат фильтруется так, чтобы в нем остались только потомки использованного элемента. Такой подход может показаться противоречащим здравому смыслу, так как он означает, что строка селектора может включать предков элемента, для которого выполняется сопоставление.

Обратите внимание, что стандарт CSS определяет псевдоэлементы :first-line и :first-letter. В CSS им соответствуют не фактические элементы, а части текстовых узлов. Они не будут обнаруживать совпадений, если использовать их вместе с методом querySelectorAll() или querySelector(). Кроме того, многие браузеры не возвращают результатов сопоставления с псевдоклассами :link и :visited, потому что в противном случае это позволило бы получать информацию об истории посещений страниц пользователем.

Методы querySelector() и querySelectorAll() поддерживают все текущие браузеры. Тем не менее, обратите внимание, что спецификации этих методов не требуют поддержки селекторов CSS3: производителям браузеров предлагается реализовать поддержку того же набора селекторов, который поддерживается в каскадных таблицах стилей. Текущие браузеры, кроме IE, поддерживают селекторы CSS3. IE7 и 8 поддерживают селекторы CSS2. (Ожидается, что IE9 будет поддерживать CSS3.)

Метод querySelectorAll() является идеальным инструментом выбора элементов: это очень мощный механизм, с помощью которого клиентские программы на языке JavaScript могут выбирать элементы документа для выполнения операций над ними. К счастью, селекторы CSS можно использовать даже в браузерах, не имеющих собственной поддержки метода querySelectorAll(). Похожий механизм запросов на основе селекторов в библиотеке jQuery (глава 19) является центральной парадигмой программирования. Веб-приложения на основе jQuery могут использовать переносимый, совместимый с разными типами браузеров эквивалент метода querySelectorAll(), который называется $().

Программный код, выполняющий в библиотеке jQuery сопоставление с селекторами CSS, был реструктурирован и вынесен в самостоятельную библиотеку с именем Sizzle, которая была заимствована фреймворком Dojo и другими клиентскими библиотеками.

Самостоятельная версия библиотеки Sizzle доступна по адресу http://sizzlejs.com.

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

содержание 15.2.6. document.all[]

До того, как модель DOM была стандартизована, в IE4 была реализована коллекция document.all[], представляющая все элементы (кроме текстовых узлов Text) в документе. Впоследствии коллекцию document.all[] заменили стандартные методы, такие как getElementByld() и getElementsByTagName(), и теперь она считается устаревшей и не должна использоваться. Однако в свое время появление этой коллекции произвело целую революцию, и даже сейчас все еще можно встретить сценарии, использующие ее следующими способами:

содержание 15.3. Структура документа и навигация по документу содержание

После выбора элемента документа иногда бывает необходимо отыскать структурно связанные части документа (родитель, братья, дочерний элемент). Объект Document можно представить как дерево объектов Node, как изображено на рис. 15.1. Тип Node определяет свойства, позволяющие перемещаться по такому дереву, которые будут рассматриваться в разделе 15.3.1. Существует еще один прикладной интерфейс навигации по документу как дереву объектов Element. Этот более новый (и часто более простой в использовании) прикладной интерфейс рассматривается в разделе 15.3.2.

содержание 15.3.1. Документы как деревья узлов

Объект Document, его объекты Element и объекты Text, представляющие текстовые фрагменты в документе, – все они являются объектами Node. Класс Node определяет следующие важные свойства:

parentNode
Родительский узел данного узла или null для узлов, не имеющих родителя, таких как Document.
childNodes
Доступный только для чтения подобный массиву объект (NodeList), обеспечивающий «живое» представление дочерних узлов.
firstChild, lastChild
Первый и последний дочерние узлы или null, если данный узел не имеет дочерних узлов.
nextSibling, previousSibling
Следующий и предыдущий братские узлы. Братскими называются два узла, имеющие одного и того же родителя. Порядок их следования соответствует порядку следования в документе. Эти свойства связывают узлы в двусвязный список.
nodeType
Тип данного узла. Узлы типа Document имеют значение 9 в этом свойстве. Узлы типа Element – значение 1. Текстовые узлы типа Text – значение 3. Узлы типа Comments – значение 8, и узлы типа DocumentFragment – значение 11.
nodeValue
Текстовое содержимое узлов Text и Comment.
nodeName
Имя тега элемента Element, в котором все символы преобразованы в верхний регистр.

С помощью этих свойств класса Node можно следующими способами сослаться на второй дочерний узел первого дочернего узла объекта Document:

document.childNodes[0].childNodes[1]
document.firstChild.firstChild.nextSibling

Допустим, что рассматриваемый документ имеет следующий вид:

<html><head><title>Test</title></head><body>Hello World!</body></html>

Тогда вторым дочерним узлом первого дочернего узла будет элемент <body>. В свойстве nodeType он содержит значение 1 и в свойстве nodeName – значение «BODY».

Однако, обратите внимание, что этот прикладной интерфейс чрезвычайно чувствителен к изменениям в тексте документа. Например, если в этот документ добавить единственный перевод строки между тегами <html> и <head>, этот символ перевода строки станет первым дочерним узлом (текстовым узлом Text) первого дочернего узла, а вторым дочерним узлом станет элемент <head>, а не </body>.

содержание 15.3.2. Документы как деревья элементов

Когда основной интерес представляют сами элементы документа, а не текст в них (и пробельные символы между ними), гораздо удобнее использовать прикладной интерфейс, позволяющий интерпретировать документ как дерево объектов Element, игнорируя узлы Text и Comment, которые также являются частью документа.

Первой частью этого прикладного интерфейса является свойство children объектов Element. Подобно свойству childNodes его значением является объект NodeList. Однако в отличие от свойства childNodes список children содержит только объекты Element. Свойство children – нестандартное свойство, но оно реализовано во всех текущих браузерах. В IE это свойство было реализовано уже очень давно, и большинство других браузеров последовали его примеру. Последним основным браузером, реализовавшим его, стал Firefox 3.5.

Обратите внимание, что узлы Text и Comment не имеют дочерних узлов. Это означает, что описанное выше свойство Node. parentNode никогда не возвращает узлы типа Text или Comment. Значением свойства parentNode любого объекта Element всегда будет другой объект Element или корень дерева – объект Document или DocumentFragment.

Второй частью прикладного интерфейса навигации по элементам документа являются свойства объекта Element, аналогичные свойствам доступа к дочерним и братским узлам объекта Node:

firstElementChild, lastElementChild
Похожи на свойства firstChild и lastChild, но возвращают дочерние элементы.
nextElementSibling, previousElementSibling
Похожи на свойства nextSibling и previousSibling, но возвращают братские элементы.
childElementCount
Количество дочерних элементов. Возвращает то же значение, что и свойство children.length.

Эти свойства доступа к дочерним и братским элементам стандартизованы и реализованы во всех текущих браузерах, кроме IE.

http://www.w3.org/TR/ElementTraversal.

Поскольку прикладной интерфейс навигации по элементам документа реализован не во всех браузерах, вам может потребоваться определить переносимые функции навигации, как в примере 15.2.

Пример 15.2. Переносимые функции навигации по документу

Определение собственных методов элементов

Все текущие браузеры (включая IE8 и выше) реализуют модель DOM таким образом, что типы, подобные типам Element и HTMLDocument, являются классами, такими же как классы String и Array.

(IE8 поддерживает возможность расширения прототипов объектов Element, HTMLDocument и Text, но не поддерживает ее для объектов Node, Document, HTMLElement и всех подтипов типа HTMLElement)

Они не имеют конструкторов (как создавать новые объекты Element, будет показано далее в этой главе), но они имеют объекты-прототипы, которые вы можете расширять своими методами:

Element.prototype.next = function() {
  if (this.nextElementSibling) return this.nextElementSibling;
  var sib = this.nextSibling;
  while(sib && sib.nodeType !== 1) sib = sib.nextSibling;
  return sib;
};

Функции, представленные в примере 15.2, не были реализованы в виде методов объекта Element лишь по той причине, что такая возможность не поддерживается в IE7.

Однако возможность расширения типов DOM может пригодиться для реализации особенностей, характерных для IE, в других браузерах. Как отмечалось выше, нестандартное свойство children объекта Element было впервые реализовано в IE и только потом – в других браузерах. Используя следующий программный код, можно реализовать это свойство в браузерах, не поддерживающих его, таких как Firefox 3.0:

// Реализация свойства Element.children в браузерах, не поддерживающих его
// Обратите внимание, что этот метод возвращает статический массив, а не "живой" NodeList
if (!document.documentElement.children) {
  Element.prototype.__defineGetter__("children", function() {
    var kids = [];
    for(var c = this.firstChild; c != null; c = c.nextSibling)
    if (c.nodeType === 1) kids.push(c);
    return kids;
  });
}

Метод __defineGetter__ (упоминавшийся в разделе 6.7.1) не относится к стандартным, но его вполне можно использовать для обеспечения переносимости в таком программном коде, как этот.

содержание 15.4. Атрибуты содержание

HTML-элементы состоят из имени тега и множества пар имя/значение, известных как атрибуты. Например, элемент <a>, определяющий гиперссылку, в качестве адреса назначения ссылки использует значение атрибута href. Значения атрибутов HTML-элементов доступны в виде свойств объектов HTMLElement, представляющих эти элементы. Кроме того, модель DOM определяет и другие механизмы получения и изменения значений XML-атрибутов и нестандартных HTML-атрибутов. Подробнее об этом рассказывается в следующих подразделах.

содержание 15.4.1. HTML-атрибуты как свойства объектов Element

Объекты HTMLElement, представляющие элементы HTML-документа, определяют свойства, доступные для чтения/записи, соответствующие HTML-атрибутам элементов. Объект HTMLElement определяет свойства для поддержки универсальных HTTP-атрибутов, таких как id, title, lang и dir, и даже свойства-обработчики событий, такие как onclick. Специализированные подклассы класса Element определяют атрибуты, характерные для представляемых ими элементов. Например, узнать URL-адрес изображения можно, обратившись к свойству src объекта HTMLElement, представляющего элемент <img>:

Аналогично можно устанавливать атрибуты элемента <form>, определяющие порядок отправки формы:

Имена атрибутов в разметке HTML не чувствительны к регистру символов, в отличие от имен свойств в языке JavaScript. Чтобы преобразовать имя атрибута в имя свойства в языке JavaScript, его нужно записать символами в нижнем регистре. Однако, если имя атрибута состоит из более чем одного слова, первый символ каждого слова, кроме первого, записывается в верхнем регистре, например: def aultChecked и tablndex.

Имена некоторых HTML-атрибутов совпадают с зарезервированными словами языка JavaScript. Имена свойств, соответствующих таким атрибутам, начинаются с приставки «htmb. Например, HTML-атрибуту for (элемента <label>) в языке JavaScript соответствует свойство с именем htmlFor. Очень важный HTML-атрибут class, имя которого совпадает с зарезервированным (но не используемым) в языке JavaScript словом «class», является исключением из этого правила: в программном коде на языке JavaScript ему соответствует свойство className. Мы еще встретимся со свойством className в главе 16.

Свойства, представляющие HTML-атрибуты, обычно имеют строковые значения. Если атрибут имеет логическое или числовое значение (например, атрибуты defаultChecked и maxLength элемента <input>), значением соответствующего свойства будет логическое или числовое значение, а не строка. Значениями атрибутов обработчиков событий всегда являются объекты Function (или null). Спецификация HTML5 определяет несколько атрибутов (таких как атрибут form элемента <input> и родственных ему элементов), которые преобразуются в фактические объекты Element. Наконец, значением свойства style любого HTML-элемента является объект CSSStyleDeclaration, а не строка. Поближе с этим важным свойством мы познакомимся в главе 16.

Обратите внимание, что основанный на свойствах прикладной интерфейс получения доступа к значениям атрибутов не позволяет удалять атрибуты из элементов. В частности, для этих целей нельзя использовать оператор delete. Для этой цели можно использовать прием, который описывается в следующем разделе.

содержание 15.4.2. Доступ к нестандартным HTML-атрибутам

Как описывалось выше, тип HTMLElement и его подтипы определяют свойства, соответствующие стандартным атрибутам HTML-элементов. Однако тип Element определяет дополнительные методы getAttribute() и setAttribute(), которые можно использовать для доступа к нестандартным HTML-атрибутам, а также обращаться к атрибутам элементов XML-документа:

var image = document.images[0];
var width = parseInt(image.getAttribute("WIDTH"));
image.setAttribute("class", "thumbnail");

Приведенный пример демонстрирует два важных различия между этими методами и описанным выше прикладным интерфейсом, основанным на свойствах. Во-первых, эти дополнительные методы интерпретируют значения всех а трибутов как строки. Метод getAttribute() никогда не вернет число, логическое значение или объект. Во-вторых, данные методы принимают стандартные имена атрибутов, даже если эти имена совпадают с зарезервированными словами языка JavaScript. Имена атрибутов HTML-элементов нечувствительны к регистру символов.

Класс Element также определяет два родственных метода hasAttribute() и removeAttribute(). Первый из них проверяет присутствие атрибута с указанным именем, а второй удаляет атрибут. Эти методы особенно удобны при работе с логическими атрибутами: для этих атрибутов (таких как атрибут disabled HTML-форм) важно их наличие или отсутствие в элементе, а не их значения.

Если вам приходится работать с XML-документами, содержащими атрибуты из других пространств имен, вы можете использовать варианты этих четырех методов, позволяющие указывать имя пространства имен: getAttributeNS(), setAttributeNS(), hasAttributeNS() и removeAttributeNS(). Вместо единственного строкового аргумента с именем атрибута эти методы принимают два аргумента. В первом передается URI-идентификатор, определяющий пространство имен. Во втором аргументе обычно передается неквалифицированное локальное имя атрибута из этого пространства имен. Исключением является метод setAttributeNS(), которому во втором атрибуте необходимо передавать квалифицированное имя атрибута, включающее идентификатор пространства имен. Более полная информация об этих методах доступа к атрибутам из других пространств имен приводится в четвертой части книги.

содержание 15.4.3. Атрибуты с данными

Нередко бывает желательно добавить в HTML-элементы дополнительные данные, обычно когда предусматривается возможность выбора этих элементов в JavaScript-сценариях и выполнения некоторых операций с ними. Иногда это можно реализовать, добавив специальные идентификаторы в атрибут class. В ряде случаев, когда речь заходит о более сложных данных, программисты прибегают к использованию нестандартных атрибутов. Как отмечалось выше, для чтения и изменения значений нестандартных атрибутов можно использовать методы getAttribute() и setAttribute(). Платой за это будет несоответствие документа стандарту.

Стандарт HTML5 предоставляет решение этой проблемы. В документах, соответствующих стандарту HTML5, все атрибуты, имена которых состоят только из символов в нижнем регистре и начинаются с приставки «data-», считаются допустимыми. Эти «атрибуты с данными» не оказывают влияния на представление элементов, в которых присутствуют, и обеспечивают стандартный способ включения дополнительных данных без нарушения стандартов.

Кроме того, стандарт HTML5 определяет в объекте Element свойство dataset. Это свойство ссылается на объект со свойствами, имена которых соответствуют именам атрибутов data- без приставки. То есть свойство dataset.x будет хранить значение атрибута data-x. Имена атрибутов с дефисами отображаются в имена свойств с переменным регистром символов: атрибут data-jquery-test превратится в свойство dataset.jqueryTest.

Ниже приводится более конкретный пример. Допустим, что в документе имеется следующий фрагмент разметки:

<span class="sparkline" data-ymin="0" data-ymax="10">
1 1 1 2 2 3 4 5 5 4 3 5 6 7 7 4 2 1
</span>

Sparkline – это маленькое изображение, обычно некоторый график, предназначенное для отображения в потоке текста. Чтобы сгенерировать такое изображение, необходимо извлечь значение атрибута с данными, как показано ниже:

// Предполагается, что в браузере поддерживается метод Array.map(),
// определяемый стандартом ES5 (или реализована его имитация)
var sparklines = document.getElementsByClassName("sparkline");
for(var i = 0; i < sparklines.length; i++) {
  var dataset = sparklines[i].dataset;
  var ymin = parseFloat(dataset.ymin);
  var ymax = parseFloat(dataset.ymax);
  var data = sparklines[i].textContent.split(" ").map(parseFloat);
  drawSparkline(sparklines[i], ymin, ymax, data); // Еще не реализована
}

На момент написания этих строк свойство dataset еще не было реализовано в текущих браузерах, поэтому представленное выше решение можно было бы реализовать так:

var sparklines = document.getElementsByClassName("sparkline");
for(var i = 0; i < sparklines.length; i++) {
  var elt = sparklines[i];
  var ymin = parseFloat(elt.getAttribute("data-ymin"));
  var ymin = parseFloat(elt.getAttribute("data-ymax"));
  var points = elt.getAttribute("data-points");
  var data = elt.textContent.split(" ").map(parseFloat);
  drawSparkline(elt, ymin, ymax, data); // Еще не реализована
}

Обратите внимание, что свойство dataset является (или будет, когда будет реализовано) «живым», двунаправленным интерфейсом к атрибутам data- элемента. Изменение или удаление свойства объекта dataset приводит к изменению или удалению соответствующего атрибута data- элемента.

Функция drawSparkline() в приведенном примере является вымышленной, однако в примере 21.13 демонстрируется прием вставки внутристрочных диаграмм (sparklines) подобно тому, как это делается нашем примере, а прорисовка осуществляется с использованием элемента <canvas>.

содержание 15.4.4. Атрибуты как узлы типа Attr

Существует несколько способов работы с атрибутами элементов. Тип Node определяет свойство attributes. Это свойство имеет значение null для всех узлов, не являющихся объектами Element. Свойство attributes объектов Element является объектом, подобным массиву, представляющим все атрибуты элемента и доступным только для чтения. Подобно спискам NodeList, объект attributes не является статической копией. Он может индексироваться числами, что означает возможность перечисления всех атрибутов элемента, а также именами атрибутов:

Значениями, получаемыми в результате индексирования объекта attributes, являются объекты Attr. Объекты Attr – это специализированный подтип Node, но в действительности никогда не используемые в таком качестве. Свойства name и value объектов Attr возвращают имя и значение атрибута.

содержание 15.5. Содержимое элемента содержание

Взгляните еще раз на рис. 15.1 и попробуйте ответить на вопрос: какой объект представляет «содержимое» элемента <p>. На этот вопрос можно дать три ответа:

Все три ответа являются верными, и каждый ответ ценен по-своему. В следующих разделах описывается, как работать с представлением в виде разметки HTML, с представлением в виде простого текста и с представлением в виде дерева объектов.

содержание 15.5.1. Содержимое элемента в виде HTML

При чтении свойства innerHTML объекта Element возвращается содержимое этого элемента в виде строки разметки. Попытка изменить значение этого свойства приводит к вызову синтаксического анализатора веб-браузера и замещению текущего содержимого элемента разобранным представлением новой строки. (Несмотря на свое название, свойство innerHTML может использоваться для работы не только с HTML-, но и с XML-элементами.)

Веб-браузеры прекрасно справляются с синтаксическим анализом разметки HTML, поэтому операция изменения значения свойства innerHTML обычно достаточно эффективна, несмотря на необходимость синтаксического анализа. Обратите внимание, однако, что многократное добавление фрагментов текста в свойство innerHTML с помощью оператора += обычно не эффективно, потому что требует выполнения двух шагов – сериализации и синтаксического анализа.

Впервые свойство innerHTML было реализовано в IE4. Несмотря на то, что оно достаточно давно поддерживается всеми браузерами, это свойство было стандартизовано только с появлением стандарта HTML5. Спецификация HTML5 требует, чтобы свойство innerHTML было реализовано не только в объекте Element, но и в объекте Document, однако этому требованию отвечают пока не все браузеры.

Кроме того, спецификация HTML5 стандартизует свойство с именем outerHTML. При обращении к свойству outerHTML оно возвращает строку разметки HTML или XML, содержащую открывающий и закрывающий теги элемента, которому принадлежит это свойство. При записи нового значения в свойство outerHTML элемента новое содержимое замещает элемент целиком. Свойство outerHTML определено только для узлов типа Element, оно отсутствует в объекте Document. К моменту написания этих строк свойство outerHTML поддерживалось всеми текущими браузерами, кроме Firefox (далее в этой главе в примере 15.5 приводится реализация свойства outerHTML на основе свойства innerHTML).

Еще одной особенностью, впервые появившейся в IE и стандартизованной спецификацией HTML5, является метод insertAdjacentHTML(), дающий возможность вставить строку с произвольной разметкой HTML, «примыкающую» ("adjacent") к указанному элементу. Разметка передается методу во втором аргументе, а точное значение слова «примыкающая» ("adjacent") зависит от значения первого аргумента. Этот первый аргумент должен быть строкой с одним из значений: «beforebegin», «afterbegin», «beforeend» или «afterend». Перечисленные значения определяют позицию вставки так, как изображено на рис. 15.3.

cheme

Рис. 15.3. Позиции вставки в вызове метода insertAdjacentHTML()

Метод insertAdjacentHTML() не поддерживается текущей версией Firefox. Далее в этой главе будет представлен пример 15.6, демонстрирующий, как можно реализовать метод insertAdjacentHTML() с применением свойства innerHTML и как можно написать методы вставки разметки HTML, не требующие указывать позицию вставки с помощью строкового аргумента.

содержание 15.5.2. Содержимое элемента в виде простого текста

Иногда бывает необходимо получить содержимое элемента в виде простого текста или вставить простой текст в документ (без необходимости экранировать угловые скобки и амперсанды, используемые в разметке HTML). Стандартный способ выполнения этих операций основан на использовании свойства textContent объекта Node:

Свойство textContent поддерживается всеми текущими браузерами, кроме IE. В IE вместо него можно использовать свойство innerText. Впервые свойство innerText было реализовано в IE4 и поддерживается всеми текущими браузерами, кроме Firefox.

Свойства textContent и innerText настолько похожи, что обычно могут быть взаимозаменяемы при использовании. Однако будьте внимательны и отличайте пустые элементы (строка "" в языке JavaScript интерпретируется как ложное значение) от неопределенных свойств:

Свойство textContent возвращает результат простой конкатенации всех узлов Text, являющихся потомками указанного элемента. Свойство innerText не обладает четко определенным поведением и имеет несколько отличий от свойства textContent. innerText не возвращает содержимое элементов <script>. Из возвращаемого им текста удаляются лишние пробелы и предпринимается попытка сохранить табличное форматирование. Кроме того, для некоторых элементов таблиц, таких как <table>, <tbody> и <tr>, свойство innerText доступно только для чтения.

Текст в элементах <script>

Встроенные элементы <script> (т. е. без атрибута src) имеют свойство text, которое можно использовать для получения их содержимого в виде текста. Содержимое элементов <script> никогда не отображается в браузерах, а HTML-парсеры игнорируют угловые скобки и амперсанды внутри сценариев. Это делает элемент <script> идеальным средством встраивания произвольных текстовых данных, доступных для использования веб-приложением. Достаточно просто определить в атрибуте type элемента какое-либо значение (такое как «text/x-custom-data»), чтобы сообщить, что этот сценарий не содержит выполняемый программный код на языке JavaScript. В этом случае интерпретатор JavaScript будет игнорировать сценарий, но сам элемент будет включен в дерево документа, а содержащиеся в нем данные можно будет получить с помощью свойства text.

содержание 15.5.3. Содержимое элемента в виде текстовых узлов

Еще одним средством доступа к содержимому элемента является список дочерних узлов, каждый из которых может иметь свое множество дочерних узлов. Когда речь заходит о содержимом элемента, наибольший интерес обычно представляют текстовые узлы. При работе с XML-документами необходимо также быть готовыми встретить узлы CDATASection – подтип класса Text – представляющие содержимое разделов CDATA.

Пример 15.3 демонстрирует функцию textContent(), которая выполняет рекурсивный обход дочерних элементов и объединяет текст, содержащийся во всех текстовых узлах-потомках. Чтобы было более понятно, напомню, что свойство nodeValue (определяемое типом Node) хранит содержимое текстового узла.

Пример 15.3. Поиск всех текстовых узлов, потомков указанного элемента

Свойство nodeValue доступно для чтения и записи, и с его помощью можно изменять содержимое в отображаемых узлах Text и CDATASection. Оба типа, Text и CDATASection, являются подтипами класса CharacterData, описание которого приводится в четвертой части книги. Класс CharacterData определяет свойство data, которое хранит тот же текст, что и свойство nodeValue. Следующая функция преобразует символы текстового содержимого узлов типа Text в верхний регистр, устанавливая значение свойства data:

Класс CharacterData также определяет редко используемые методы добавления в конец, удаления, вставки и замены текста в узлах Text или CDATASection. Кроме изменения содержимого имеющихся текстовых узлов этот класс позволяет также вставлять в элементы Element новые текстовые узлы или замещать существующие текстовые узлы новыми. Создание, вставка и удаление узлов – тема следующего раздела.

содержание 15.6. Создание, вставка и удаление узлов содержание

Мы уже знаем, как получать и изменять содержимое документа, используя строки с разметкой HTML и с простым текстом. Мы также знаем, как выполнять обход документа для исследования отдельных узлов Element и Text, составляющих его содержимое. Однако точно так же существует возможность изменения документа на уровне отдельных узлов. Тип Document определяет методы создания объектов Element и Text, а тип Node определяет методы для вставки, удаления и замены узлов в дереве. Приемы создания и вставки узлов уже были показаны в примере 13.4, который повторяется ниже:

В следующих подразделах более подробно и с примерами рассказывается о создании новых узлов, о вставке и удалении узлов, а также об использовании объектов DocumentFragment, упрощающих работу с множеством узлов.

содержание 15.6.1. Создание узлов

Как показано в приведенном примере, создавать новые узлы Element можно с помощью метода createElement() объекта Document. Этому методу необходимо передать имя тега: это имя не чувствительно к регистру символов при работе с HTML-документами и чувствительно при работе с XML-документами.

Для создания текстовых узлов существует аналогичный метод:

var newnode = document.createTextNode("содержимое текстового узла");

Кроме того, объект Document определяет и другие фабричные методы, такие как редко используемый метод createComment(). Один такой метод, createDocumentFragment(), мы будем использовать в разделе 15.6.4. При работе с документами, в которых используются пространства имен XML, можно использовать метод createElementNS(), позволяющий указывать URI-идентификатор пространства имен и имя тега создаваемого объекта Element.

Еще один способ создания в документе новых узлов заключается в копировании существующих узлов. Каждый узел имеет метод cloneNode(), возвращающий новую копию узла. Если передать ему аргумент со значением true, он рекурсивно создаст копии всех потомков, в противном случае будет создана лишь поверхностная копия. В браузерах, отличных от IE, объект Document дополнительно определяет похожий метод с именем importNode(). Если передать ему узел из другого документа, он вернет копию, пригодную для вставки в текущий документ. Если передать ему значение true во втором аргументе, он рекурсивно импортирует все узлы-потомки.

содержание 15.6.2. Вставка узлов

После создания нового узла его можно вставить в документ с помощью методов типа Node: appendChild() или insertBefore(). Метод appendChild() вызывается относительно узла Element, в который требуется вставить новый узел, и вставляет указанный узел так, что тот становится последним дочерним узлом (значением свойства lastChild).

Метод insertBefore() похож на метод appendChild(), но он принимает два аргумента. В первом аргументе указывается вставляемый узел, а во втором – узел, перед которым должен быть вставлен новый узел. Этот метод вызывается относительно объекта узла, который станет родителем нового узла, а во втором аргументе должен передаваться дочерний узел этого родителя. Если во втором аргументе передать null, метод insertBefore() будет вести себя, как appendChild(), и вставит узел в конец.

Ниже приводится простая функция вставки узла в позицию с указанным числовым индексом. Она демонстрирует применение обоих методов, appendChild() и insertBefore():

Если метод appendChild() insertBefore() используется для вставки узла, который уже находится в составе документа, этот узел автоматически будет удален из текущей позиции и вставлен в новую позицию; нет необходимости явно удалять узел. Пример 15.4 демонстрирует функцию сортировки строк таблицы по значениям ячеек в указанном столбце. Она не создает новые узлы и для изменения порядка следования существующих узлов использует метод appendChild().

Пример 15.4. Сортировка строк таблицы

содержание 15.6.3. Удаление и замена узлов

Метод removeChild() удаляет элемент из дерева документа. Но будьте внимательны: этот метод вызывается не относительно узла, который должен быть удален, а (как следует из фрагмента «child» в его имени) относительно родителя удаляемого узла. Этот метод вызывается относительно родителя и принимает в виде аргумента дочерний узел, который требуется удалить. Чтобы удалить узел n из документа, вызов метода должен осуществляться так:

n.parentNode.removeChild(n);

Метод replaceChild() удаляет один дочерний узел и замещает его другим. Этот метод должен вызываться относительно родительского узла. В первом аргументе он принимает новый узел, а во втором – замещаемый узел. Например, ниже показано, как заменить узел п текстовой строкой:

n.parentNode.replaceChild(document.createTextNode("[ REDACTED ]"), n);

Следующая функция демонстрирует еще один способ применения метода replaceChild():

В разделе 15.5.1 было представлено свойство outerHTML элементов и говорилось, что оно не реализовано в текущей версии Firefox. Пример 15.5 демонстрирует, как можно реализовать это свойство в Firefox (и в любом другом браузере, поддерживающем свойство innerHTML, позволяющем расширять объект-прототип Element, prototype и имеющем методы определения методов доступа к свойствам). Здесь также демонстрируется практическое применение методов removeChild() и сloneNode().

Пример 15.5. Реализация свойства outerHTML с помощью свойства innerHTML

содержание 15.6.4. Использование объектов DocumentFragment

Объекты DocumentFragment – это особая разновидность объектов Node; они служат временным контейнером для других узлов. Создаются объекты DocumentFragment следующим образом:

var frag = document.createDocumentFragment();

Как и узел Document, объекты DocumentFragment являются самостоятельными и не входят в состав какого-либо другого документа. Его свойство parentNode всегда возвращает значение null. Однако, как и узлы Element, объекты DocumentFragment могут иметь любое количество дочерних элементов, которыми можно управлять с помощью методов appendChild(), insertBefore() и т. д.

Одна из особенностей объекта DocumentFragment состоит в том, что он позволяет манипулировать множеством узлов как единственным узлом: если объект DocumentFragment передать методу appendChild(), insertBefore() или replaceChild(), в документ будут вставлены дочерние элементы фрагмента, а не сам фрагмент (дочерние элементы будут перемещены из фрагмента в документ, а сам фрагмент опустеет и будет готов для повторного использования). Следующая функция использует объект DocumentFragment для перестановки в обратном порядке дочерних элементов узла:

В примере 15.6 представлена реализация метода insertAdjacentHTML() (раздел 15.5.1) с применением свойства innerHTML и объекта DocumentFragment. В нем также определяются функции вставки разметки HTML с более логичными именами, чем неочевидное insertAdjacentHTML(). Вложенная вспомогательная функция fragment() является, пожалуй, наиболее интересной частью этого примера: она возвращает объект DocumentFragment, содержащий разобранное представление указанной ей строки с разметкой HTML.

Пример 15.6. Реализация метода insertAdjacentHTML() с использованием свойства innerHTML

содержание 15.7. Пример: создание оглавления содержание

Пример 15.7 показывает, как динамически создавать оглавление документа. Он демонстрирует многие концепции работы с документом, описанные в разделах выше: выбор элементов, навигация по документу, установка атрибутов элементов, установка свойства innerHTML и создание новых узлов и вставку их в документ. Пример снабжен большим количеством комментариев, которые призваны облегчить понимание программного кода.

Пример 15.7. Автоматическое создание оглавления документа

содержание 15.8. Геометрия документа и элементов и прокрутка содержание

До сих пор в этой главе мы рассматривали документы как некие абстрактные деревья элементов и текстовых узлов. Но когда браузер отображает документ в своем окне, он создает визуальное представление документа, в котором каждый элемент имеет определенную позицию и размеры. Часто веб-приложения могут интерпретировать документы как деревья элементов и никак не заботиться о том, как эти элементы отображаются на экране. Однако иногда бывает необходимо определить точные геометрические характеристики элемента. Например, в главе 16 мы увидим, что элемент можно поместить в определенную позицию с помощью CSS. Если вам потребуется использовать CSS для динамического позиционирования элемента (такого как всплывающая подсказка или сноска) рядом с элементом, который позиционируется браузером, вам необходимо иметь возможность определять положение этого элемента.

В этом разделе рассказывается, как можно переходить от абстрактной, древовидной модели документа к геометрическому, основанному на системе координат визуальному представлению документа в окне браузера, и обратно. Свойства и методы, описываемые здесь, реализованы в браузерах достаточно давно (хотя есть некоторые, которые до недавнего времени присутствовали только в IE, а некоторые не были реализованы в IE до появления версии IE9). К моменту написания этих строк они проходили процесс стандартизации консорциумом W3C в виде стандарта «CSSOM-View Module» (https://www.w3.org/TR/cssom-view-1/).

содержание 15.8.1. Координаты документа и видимой области

Позиция элемента измеряется в пикселах. Координата X растет слева направо, а координата Y – сверху вниз. Однако существуют две точки, которые мы можем считать началом координат: координаты X и Y элемента могут отсчитываться относительно верхнего левого угла документа или относительно верхнего левого угла видимой области. Для окон верхнего уровня и вкладок «видимой областью» является часть окна браузера, в которой фактически отображается содержимое документа: в нее не входит обрамление окна (меню, панели инструментов, вкладки). Для документов, отображаемых во фреймах, видимой областью является элемент <iframe>, определяющий фрейм. В любом случае, когда речь заходит о позиции элемента, необходимо знать, какая система координат используется – относительно начала документа или относительно начала видимой области. (Координаты видимой области видимости иногда называют оконными координатами.)

Если документ меньше видимой области или если он еще не прокручивался, верхний левый угол документа находится в верхнем левом углу видимой области, и начала систем координат документа и видимой области совпадают. Однако в общем случае, чтобы перейти от одной системы координат к другой, необходимо добавлять или вычитать смещения прокрутки. Например, если координата Y элемента имеет значение 200 пикселов в системе координат документа и пользователь прокрутил документ вниз на 75, то в системе координат видимой области координата Y элемента будет иметь значение 125 пикселов. Аналогично, если координата X элемента имеет значение 400 в системе координат видимой области и пользователь прокрутил документ по горизонтали на 200 пикселов, то в системе координат документа координата X элемента будет иметь значение 600.

Система координат документа является более фундаментальной, чем система координат видимой области, и на нее не оказывает влияния величина прокрутки. Однако в клиентских сценариях довольно часто используются координаты видимой области. Система координат документа используется при позиционировании элементов с помощью CSS (глава 16). Но проще всего получить координаты элемента в системе координат видимой области (раздел 15.8.2). Аналогично, когда регистрируется функция-обработчик событий от мыши, координаты указателя мыши передаются ей в системе координат видимой области.

Чтобы перейти от одной системы координат к другой, необходимо иметь возможность определять позиции полос прокрутки окна браузера. Во всех браузерах, кроме IE версии 8 и ниже, эти значения можно узнать с помощью свойств pageXOffset и pageYOffset объекта Window. Кроме того, в IE (и во всех современных браузерах) позиции полос прокрутки можно узнать с помощью свойств scrollLeft и scrollTop. Проблема состоит в том, что в обычном случае позиции полос прокрутки следует читать из свойств корневого элемента (document.documentElement) документа, а в режиме совместимости (раздел 13.4.4) необходимо обращаться к элементу <body> (document.body) документа. Пример 15.8 показывает, как определять позиции полос прокрутки переносимым образом.

Пример 15.8. Получение позиций полос прокрутки окна

Иногда бывает удобно иметь возможность определять размеры видимой области, например, чтобы определить, какая часть документа видима в настоящий момент. Как и в случае со смещениями прокрутки, самый простой способ узнать размеры видимой области не работает в IE версии 8 и ниже, а прием, который работает в IE, зависит от режима, в котором отображается документ. Пример 15.9 показывает, как переносимым способом определять размер видимой области. Обратите внимание на сходство программного кода этого примера с программным кодом в примере 15.8.

Пример 15.9. Получение размеров видимой области документа

В двух примерах выше использовались свойства scrollLeft, scrollTop, clientWidth и clientHeight. Мы встретимся с этими свойствами еще раз в разделе 15.8.5.

содержание 15.8.2. Определение геометрии элемента

Самый простой способ определить размеры и координаты элемента – обратиться к его методу getBoundingClientRect(). Этот метод впервые появился в IE5 и в настоящее время реализован во всех текущих браузерах. Он не принимает аргументов и возвращает объект со свойствами left, right, top и bottom. Свойства left и top возвращают координаты X и Y верхнего левого угла элемента, а свойства right и bottom возвращают координаты правого нижнего угла.

Этот метод возвращает позицию элемента в системе координат видимой области. (Слово «Client» в имени метода getBoundingClientRect() косвенно указывает на клиентскую область веб-браузера, т. е. на окно и видимую область в нем.) Чтобы перейти к координатам относительно начала документа, которые не изменяются после прокрутки окна браузера пользователем, нужно добавить смещения прокрутки:

Кроме того, во многих браузерах (и в стандарте W3C) объект, возвращаемый методом getBoundingClientRect(), имеет свойства width и height, но оригинальная реализация в IE не поддерживает их. Для совместимости ширину и высоту элемента можно вычислять, как показано ниже:

В главе 16 вы узнаете, что содержимое элемента окружается необязательной пустой областью, которая называется отступом (padding). Отступы окружаются необязательной рамкой (border), а рамка окружается необязательными полями (margins). Координаты, возвращаемые методом getBoundingClientRect(), включают рамку и отступы элемента, но не включают поля.

Если слово «Client» в имени метода getBoundingClientRect() определяет систему координат возвращаемого прямоугольника, то о чем свидетельствует слово «Bounding» (ограничивающий)? Блочные элементы, такие как изображения, абзацы и элементы <div> , всегда отображаются браузерами в прямоугольных областях. Однако строчные элементы, такие как <span> , <code> и <b> , могут занимать несколько строк и таким образом состоять из нескольких прямоугольных областей. Например, представьте некоторый курсивный текст (отмеченный тегами <i> и </i> ), разбитый на две строки. Область, занимаемая этим текстом, состоит из прямоугольника в правой части первой строки и прямоугольника в левой части второй строки (в предположении, что текст записывается слева направо). Если передать методу getBoundingClientRect() строчный элемент, он вернет геометрию «ограничивающего прямоугольника» (bounding rectangle), содержащего все отдельные прямоугольные области. Для элемента <i> , взятого в качестве приведенного выше примера, ограничивающий прямоугольник будет включать обе строки целиком.

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

Мы уже знаем, что методы модели DOM, такие как getElementsByTagName(), возвращают «живые» результаты, изменяющиеся синхронно с изменением документа. Объекты прямоугольных областей (и списки объектов прямоугольных областей), возвращаемые методами getBoundingClientRect() и getClientRects() не являются «живыми». Они хранят статические сведения о визуальном представлении документа на момент вызова. Они не обновляются, если пользователь прокрутит документ или изменит размеры окна браузера.

содержание 15.8.3. Определение элемента в указанной точке

Метод getBoundingClientRect() позволяет узнать текущую позицию элемента в видимой области. Но иногда бывает необходимо решить обратную задачу – узнать, какой элемент находится в заданной точке внутри видимой области. Сделать это можно с помощью метода elementFromPoint() объекта Document. Он принимает координаты X и Y (относительно начала координат видимой области, а не документа) и возвращает объект Element, находящийся в этой позиции. На момент написания этих строк алгоритм выбора элемента не был строго определен, но суть реализации метода сводится к тому, что он должен возвращать самый внутренний и самый верхний (в смысле CSS-атрибута z-index, который описывается в разделе 16.2.1.1) элемент, находящийся в этой точке. Если передать ему координаты точки, находящейся за пределами видимой области, метод elementFromPoint() вернет null, даже если после преобразования координат в систему координат документа получится вполне допустимая точка.

Метод elementFromPoint() выглядит весьма практичным, и наиболее очевидный случай его использования – определение элемента, находящегося под указателем мыши по его координатам. Однако, как будет показано в главе 17, объект события от мыши уже содержит эту информацию в своем свойстве target. Именно поэтому на практике метод elementFromPoint() почти не используется.

содержание 15.8.4. Прокрутка

В примере 15.8 демонстрировалось, как определять позиции полос прокрутки окна браузера. Чтобы заставить браузер прокрутить документ, можно присваивать значение используемым в этом примере свойствам scrollLeft и scrollTop, но существует более простой путь, поддерживаемый с самых ранних дней развития языка JavaScript. Метод scrollTo() объекта Window (и его синоним scroll()) принимает координаты X и Y точки (относительно начала координат документа) и устанавливает их в качестве величин смещения полос прокрутки. То есть он прокручивает окно так, что точка с указанными координатами оказывается в верхнем левом углу видимой области. Если указать точку, расположенную слишком близко к нижней или к правой границе документа, браузер попытается поместить эту точку как можно ближе к верхнему левому углу видимой области, но не сможет обеспечить точное их совпадение. Следующий пример прокручивает окно браузера так, что видимой оказывается самая нижняя часть документа:

Метод scrollBy() объекта Window похож на методы scroll() и scrollTo(), но их аргументы определяют относительное смещение и добавляются к текущим позициям полос прокрутки. Тем, кто умеет быстро читать, мог бы понравиться следующий букмарклет (раздел 13.2.5.1):

Часто требуется прокрутить документ не до определенных числовых координат, а до элемента в документе, который нужно сделать его видимым. В этом случае можно определить координаты элeмeнтa cпoмoщью мeтoдa getBoundingClientRect(), преобразовать их в координаты относительно начала документа и передать их методу scrollTo(), но гораздо проще воспользоваться методом scrollIntoView() требуемого HTML-элемента. Этот метод гарантирует, что элемент, относительно которого он будет вызван, окажется в видимой области. По умолчанию он старается прокрутить документ так, чтобы верхняя граница элемента оказалась как можно ближе к верхней границе видимой области. Если в единственном аргументе передать методу значение false, он попытается прокрутить документ так, чтобы нижняя граница элемента совпала с нижней границей видимой области. Кроме того, браузер выполнит прокрутку по горизонтали, если это потребуется, чтобы сделать элемент видимым.

Своим поведением метод scrollIntoView() напоминает свойство window.location.hash, когда ему присваивается имя якорного элемента (элемента <a name="">).

содержание 15.8.5. Подробнее о размерах, позициях и переполнении элементов

Метод getBoundingClientRect() поддерживается всеми текущими браузерами, но если требуется обеспечить поддержку браузеров более старых версий, этот метод использовать нельзя и для определения размеров и позиций элементов следует применять более старые приемы. Размеры элементов определяются достаточно просто: доступные только для чтения свойства offsetWidth и offsetHeight любого HTML- элемента возвращают его размеры в пикселах. Возвращаемые размеры включают рамку элемента и отступы, но не включают поля, окружающие рамку снаружи.

Все HTML-элементы имеют свойства offsetLeft и offsetTop, возвращающие их координаты X и Y. Для многих элементов эти координаты откладываются относительно начала документа и непосредственно определяют позицию элемента. Но для потомков позиционируемых элементов и некоторых других элементов, таких как ячейки таблиц, эти свойства возвращают координаты относительно элемента-предка, а не документа. Свойство offsetParent определяет, относительно какого элемента исчисляются значения этих свойств. Если offsetParent имеет значение null, свойства содержат координаты относительно начала документа. Таким образом, в общем случае для определения позиции элемента е с помощью его свойств offsetLeft и offsetTop требуется выполнить цикл:

Выполняя обход цепочки, образуемой ссылками offsetParent, и накапливая смещения, эта функция вычисляет координаты указанного элемента относительно начала документа (напомню, что метод getBoundingClientRect(), напротив, возвращает координаты относительно начала видимой области). Однако на этом тему позиционирования элементов нельзя считать исчерпанной – функция getElementPosition() не всегда вычисляет правильные значения, и ниже будет показано, как исправить эту ошибку.

В дополнение к множеству свойств offset все элементы документа определяют еще две группы свойств; имена свойств в первой группе начинаются с приставки client, а во второй группе – с приставки scroll. To есть каждый HTML-элемент имеет все свойства, перечисленные ниже:

Чтобы понять разницу между группами свойств client и scroll, необходимо уяснить, что объем содержимого HTML-элемента может не умещаться в прямоугольную область, выделенную для этого содержимого, и поэтому отдельные элементы могут иметь собственные полосы прокрутки (смотрите описание CSS-атрибута overflow в разделе 16.2.6). Область отображения содержимого – это видимая область, подобная видимой области в окне браузера, и когда содержимое элемента не умещается в видимой области, необходимо принимать в учет позиции полос прокрутки элемента.

Свойства clientWidth и clientHeight похожи на свойства offsetWidth и offsetHeight, за исключением того, что они включают только область содержимого и отступы и не включают размер рамки. Кроме того, если браузер добавляет между рамкой и отступами полосы прокрутки, то свойства clientWidth и clientHeight не включают ширину полос прокрутки в возвращаемые значения. Обратите внимание, что для строчных элементов, таких как <i> , <code> и <span> , свойства clientWidth и clientHeight всегда возвращают 0.

Свойства clientWidth и clientHeight использовались в методе getViewportSize() из примера 15.9. В том случае, когда эти свойства применяются к корневому элементу документа (или телу элемента в режиме совместимости), они возвращают те же значения, что и свойства innerWidth и innerHeight окна.

Свойства clientLeft и clientTop не имеют большой практической ценности: они возвращают расстояние по горизонтали и вертикали между внешней границей отступов элемента и внешней границей его рамки. Обычно эти значения просто определяют ширину левой и верхней рамки. Однако если элемент имеет полосы прокрутки и если браузер помещает эти полосы прокрутки вдоль левого или верхнего края (что весьма необычно), значения свойств clientLeft и clientTop также будут включать ширину полос прокрутки. Для строчных элементов свойства clientLeft и clientTop всегда возвращают 0.

Свойства scrollWidth и scrollHeight определяют размер области содержимого элемента, плюс его отступы, плюс ширину и высоту области содержимого, выходящую за видимую область. Когда содержимое целиком умещается в видимой области, значения этих свойств совпадают со значениями свойств clientWidth и clientHeight. В противном случае они включают ширину и высоту области содержимого, выходящую за видимую область, и возвращают значения, превосходящие значения свойств clientWidth и clientHeight.

Наконец, свойства scrollLeft и scrollTop определяют позиции полос прокрутки элемента. Мы использовали эти свойства корневого элемента документа в методе getScrollOffsets() (пример 15.8), но они также присутствуют в любом другом элементе. Обратите внимание, что свойства scrollLeft и scrollTop доступны для записи и им можно присваивать значения, чтобы прокрутить содержимое элемента. (HTML-элементы не имеют метода scrollTo(), как объект Window.)

Когда документ содержит прокручиваемые элементы, содержимое которых не умещается в видимой области, объявленный выше метод getElementPosition() дает некорректные результаты из-за того, что он не учитывает позиции полос прокрутки. Ниже приводится исправленная версия, которая вычитает позиции полос прокрутки из накопленных смещений, т. е. преобразует координаты относительно начала документа в координаты относительно видимой области:

В современных браузерах этот метод getElementPos() возвращает те же координаты, что и метод getBoundingClientRect() (но он менее эффективен). Теоретически такая функция, как getElementPos(), могла бы использоваться в браузерах, не поддерживающих метод getBoundingClientRect(). Однако браузеры, не поддерживающие getBoundingClientRect(), имеют массу несовместимостей в механизме позиционирования элементов, и поэтому в них такая простая функция оказывается ненадежной. Клиентские библиотеки, такие как jQuery, включают функции вычисления позиций элементов, которые дополняют этот простой алгоритм множеством проверок, учитывающих несовместимости между браузерами. Если вам потребуется определять позиции элементов в браузерах, не поддерживающих getBoundingClientRect(), то для этих целей лучше использовать библиотеку, например, jQuery.

содержание 15.9. HTML-формы содержание

HTML-элемент <form> и различные элементы ввода, такие как <input> , <select> и <button>, занимают видное место в разработке клиентских сценариев. Эти HTML-элементы появились в самом начале развития Всемирной паутины, еще до появления языка JavaScript. Формы HTML – это механизм веб-приложений первого поколения, не требующий применения JavaScript. Ввод пользователя собирается в элементах форм; затем форма отправляется на сервер; сервер обрабатывает ввод и генерирует новую HTML-страницу (обычно с новыми элементами форм) для отображения на стороне клиента.

Элементы HTML-форм по-прежнему остаются великолепным инструментом получения данных от пользователя, даже когда данные формы целиком обрабатываются JavaScript-сценарием на стороне клиента и не должны отправляться на сервер. С точки зрения программиста, разрабатывающего серверные сценарии, форма оказывается совершенно бесполезной, если в ней отсутствует кнопка отправки формы. Однако с точки зрения программиста, разрабатывающего клиентские сценарии, кнопка отправки вообще не нужна (хотя все еще может использоваться). Серверные программы опираются на механизм отправки форм – они обрабатывают данные порциями, объем которых определяется формой, – и это ограничивает их интерактивные возможности. Клиентские программы опираются на механизм событий – они могут откликаться на события, возникающие в отдельных элементах форм, – и это позволяет обеспечить более высокую степень интерактивности. Например, клиентская программа может проверять ввод пользователя по мере нажатия клавиш. Или откликаться на выбор флажка, разрешая доступ к набору параметров, которые имеют смысл, только когда флажок отмечен.

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

Обратите внимание, что в этом разделе описываются не сам язык разметки HTML, а приемы управления HTML-формами. Здесь предполагается, что вы уже имеете некоторое знакомство с HTML-элементами (<input>, <textarea>, <select>, и т. д), используемыми для создания форм. Тем не менее в табл. 15.1 для справки приводится список наиболее часто используемых элементов форм. Дополнительные сведения о функциях для работы с формами и элементами форм можно узнать в четвертой части книги, в справочных статьях Form, Input, Option, Select и TextArea.

Таблица 15.1. Элементы HTML-форм

HTML-элемент Свойство typeОбработчик событий Описание и события
<input type="button"> или <button type="button">"button"onclickКнопка
<input type="checkbox">"checkbox"onchangeПереключаемый флажок; с иным поведением, чем у радиокнопки
<input type="file">"file"onchangeПоле ввода имени файла для выгрузки на сервер; свойство value доступно только для чтения
<input type="hidden">"hidden"noneДанные, отправляемые вместе с формой, но не видимые пользователю
<option>nonenoneЕдинственный пункт объекта Select; обработчик событий подключается к объекту Select, а не к объектам Option
<input type="password">"password"onchangeПоле ввода пароля – скрывает вводимые символы
<input type="radio">"radio"onchangeПереключатель с поведением радиокнопки – в каждый конкретный момент времени может быть выбран только один переключатель
<input type="reset"> или <button type="reset">"reset"onclickКнопка, сбрасывающая форму в исходное состояние
<select>"select-one"onchangeСписок или раскрывающееся меню, в котором можно выбрать только один пункт (смотрите также <option>)
<select multiple>"select-multiple"onchangeСписок, в котором можно выбрать сразу несколько пунктов (смотрите также <option>)
<input type="submit"> или <button type="submit">"submit"onclickКнопка, инициирующая отправку формы
<input type="text">"text"onchangeОднострочное текстовое поле ввода; по умолчанию в элементе <input> атрибут type отсутствует или не опознается
<textarea>"textarea"onchangeМногострочное текстовое поле ввода

содержание 15.9.1. Выбор форм и элементов форм

Формы и элементы, содержащиеся в них, можно выбрать с помощью стандартных методов, таких как getElementById() и getElementsByTagName()::

var fields = document.getElementById("address").getElementsByTagName("input");

В браузерах, поддерживающих querySelectorAll(), можно выбрать все радиокнопки или все элементы с одинаковыми именами, присутствующие в форме, как показано ниже:

Однако, как описывалось в разделах 14.7, 15.2.2 и 15.2.3, элемент <form> с установленным атрибутом name или id можно также выбрать другими способами. Элемент <form> с атрибутом name="address" можно выбрать любым из следующих способов:

В разделе 15.2.3 говорилось, что свойство document.forms ссылается на объект HTML-Collection, позволяющий выбирать элементы <form> по их порядковым номерам, по значению атрибута id или name. Объекты Form сами по себе действуют подобно объектам HTMLCollection, хранящим элементы форм, и могут индексироваться именами или числами. Если первый элемент формы с атрибутом name="address" имеет атрибут name="street", на него можно сослаться с помощью любого из следующих выражений:

Если необходимо явно указать, что выполняется обращение к элементу формы, вместо объекта формы можно индексировать его свойство elements:

Для выбора конкретных элементов документа предпочтительнее использовать атрибут id. Однако при отправке форм атрибут name играет особую роль и чаще используется с самими формами, чем с элементами форм. Обычно при работе с группами флажков и обязательно при работе с группами радиокнопок для атрибута name используется одно и то же значение. Напомню, что, когда объект HTMLCollection индексируется именем и существует сразу несколько элементов, использующих одно и то же имя, возвращаемым значением является объект, подобный массиву, содержащий все элементы с указанным именем. Взгляните на следующую форму, содержащую радиокнопки для выбора метода доставки товара:

Сослаться на массив радиокнопок в этой форме можно следующим образом:

var methods = document.forms.shipping.elements.method;

Обратите внимание, что элементы <form> имеют HTML-атрибут и соответствующее ему свойство с именем «method», поэтому в данном случае необходимо использовать свойство elements формы вместо прямого обращения к свойству method. Чтобы определить, какой метод доставки выбрал пользователь, необходимо обойти элементы формы в массиве и проверить свойство checked каждого из них:

Co свойствами элементов форм, такими как checked и value, мы поближе познакомимся в следующем разделе.

содержание 15.9.2. Свойства форм и их элементов

Наиболее интересным для нас свойством объекта Form является массив elements[], описанный выше. Остальные свойства объекта Form менее важны. Свойства action, encoding, method и target непосредственно соответствуют атрибутам action, encoding, method и target элемента <form> . Все эти свойства и атрибуты используются для управления отправкой данных формы на веб-сервер и отображением результатов. Клиентский сценарий на языке JavaScript может устанавливать значения этих свойств, но это имеет смысл, только когда форма действительно отправляется серверной программе.

До появления JavaScript отправка форм выполнялась с помощью специальной кнопки Submit, а сброс значений элементов формы в значения по умолчанию с помощью специальной кнопки Reset. В языке JavaScript тем же целям служат два метода, submit() и reset(), объекта Form. Метод submit() объекта Form отправляет форму, а метод reset() сбрасывает элементы формы в исходное состояние.

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

type
Доступная только для чтения строка, идентифицирующая тип элемента формы. Для элементов форм, определяемых с помощью тега <input>, это свойство просто хранит значение атрибута type. Другие элементы форм (такие как <textarea> и <select>) также определяют свойство type, благодаря чему его можно использовать в сценарии для идентификации элементов, подобно тому, как идентифицируются различные типы элементов <input>. Значения этого свойства для каждого типа элементов форм перечислены во втором столбце табл. 15.1.
form
Доступная только для чтения ссылка на объект Form, в котором содержится этот элемент, или null, если элемент не находится внутри элемента <form>.
name
Доступная только для чтения строка, указанная в HTML-атрибуте name.
value
Доступная для чтения и записи строка, определяющая «значение», содержащееся в элементе формы или представляемое им. Эта строка отсылается на веб-сервер при передаче формы и только иногда представляет интерес для JavaScript-программ. Для элементов Text и Textarea это свойство содержит введенный пользователем текст. Для кнопок, создаваемых с помощью тега <input> (но не для кнопок, создаваемых с помощью тега <button>), это свойство определяет отображаемый на кнопке текст. Свойство value для элементов переключателей (радиокнопок) и флажков не редактируется и никак не представляется пользователю. Это просто строка, устанавливаемая HTML-атрибутом value. Эта строка предназначена для отправки веб-серверу, и ее можно использовать для передачи дополнительных данных. Свойство value будет обсуждаться далее в этой главе, когда мы будем рассматривать различные категории элементов формы.

содержание 15.9.3. Обработчики событий форм

Каждый элемент Form имеет обработчик события onsubmit, возникающего в момент отправки формы, и обработчик события onreset, возникающего в момент сброса формы в исходное состояние. Обработчик onsubmit вызывается непосредственно перед отправкой формы. Он может отменить отправку, вернув значение false. Это дает JavaScript-программам возможность проверить ввод пользователя и избежать отправки неполных или ошибочных данных серверной программе. Обратите внимание, что обработчик onsubmit вызывается только в случае щелчка мышью на кнопке Submit. Вызов метода submit() формы не приводит к вызову обработчика onsubmit.

Обработчик событий on reset похож на обработчик onsubmit. Он вызывается непосредственно перед сбросом формы в исходное состояние и может предотвратить сброс элементов формы, вернув значение false. Кнопки Reset редко используются в формах, но если у вас имеется такая кнопка, возможно, у вас появится желание запросить у пользователя подтверждение, прежде чем выполнить сброс:

<form...
  onreset="return confirm('Вы действительно хотите сбросить все и начать сначала?')">
  ...
  <button type="reset">Очистить поля ввода и начать сначала</button>
</form>

Подобно обработчику onsubmit, обработчик on reset вызывается только в случае щелчка мышью на кнопке Reset. Вызов метода reset() формы не приводит к вызову обработчика onreset.

Элементы форм обычно возбуждают событие click или change, когда пользователь взаимодействует с ними, и вы можете реализовать обработку этих событий, определив обработчик onclick или onchange. В третьем столбце таблицы 15.1 для каждого элемента формы указан основной обработчик событий. Вообще говоря, элементы форм, являющиеся кнопками, возбуждают событие click в момент активации (даже когда активация производится посредством нажатия клавиши на клавиатуре, а не щелчком мышью). Другие элементы форм возбуждают событие change, когда пользователь изменяет содержимое, представляемое элементом. Это происходит, когда пользователь вводит текст в текстовое поле или выбирает элемент раскрывающегося списка. Обратите внимание, что это событие возбуждается не каждый раз, когда пользователь нажимает клавишу, находясь в текстовом поле ввода. Оно возбуждается, только когда пользователь изменит значение элемента и перенесет фокус ввода в другой элемент. То есть этот обработчик событий вызывается по завершении ввода. Радиокнопки и флажки являются кнопками, хранящими информацию о своем состоянии, и все они возбуждают события click и change; из них событие change имеет большее практическое значение.

Элементы форм также возбуждают событие focus, когда они получают фокус ввода, и событие blur, когда теряют его.

Важно знать, что внутри обработчика события ключевое слово this всегда ссылается на элемент документа, вызвавший данное событие (подробнее об этом будет рассказываться в главе 17). Во всех элементах форм имеется свойство form, ссылающееся на форму, в которой содержится элемент, поэтому обработчики событий элемента формы всегда могут обратиться к объекту Form, как к this.form. Сделав еще один шаг, мы можем сказать, что обработчик событий для одной формы может ссылаться на соседний элемент формы, имеющий имя x, как this.form.x.

содержание 15.9.4. Кнопки

Кнопки являются одними из наиболее часто используемых элементов форм, т. к. они предоставляют понятный визуальный способ вызова пользователем какого-либо запрограммированного сценарием действия. Элемент кнопки не имеет собственного поведения, предлагаемого по умолчанию, и не представляет никакой пользы без обработчика события onclick. Кнопки, определяемые с помощью элементов <input>, отображают простой текст, содержащийся в их свойстве value.

Кнопки, определяемые с помощью элементов <button>, отображают содержимое элемента.

Обратите внимание, что гиперссылки предоставляют такой же обработчик событий onclick, как и кнопки. Когда действие, выполняемое обработчиком onclick, можно классифицировать как «переход по ссылке», используйте ссылку. В противном случае используйте кнопку.

Элементы Submit и Reset очень похожи на элементы-кнопки, но имеют связанные с ними действия, предлагаемые по умолчанию (передача или очистка формы). Если обработчик событий onclick возвращает false, стандартное действие этих кнопок, предусмотренное по умолчанию, не выполняется. Обработчик onclick элемента Submit можно использовать для проверки введенных значений, но обычно это делается в обработчике onsubmit самой формы.

В четвертой части книги нет описания элемента Button. Описание всех элементов- кнопок, включая описание элементов, создаваемых с помощью тега <button>, вы найдете в разделе, посвященном элементу Input.

содержание 15.9.5. Переключатели и флажки

Флажки и радиокнопки имеют два визуально различимых состояния: они могут быть либо установлены, либо сброшены. Пользователь может изменить состояние такого элемента, щелкнув на нем. Радиокнопки обычно объединяются в группы связанных элементов, имеющих одинаковые значения HTML-атрибута name. При установке одной из радиокнопок предыдущая установленная в группе радиокнопка сбрасывается. Флажки тоже часто объединяются в группы с общим значением атрибута name, и когда вы обращаетесь к ним по имени, необходимо помнить, что вы получаете в ответ объект, подобный массиву, а не отдельный элемент.

И флажки, и переключатели имеют свойство checked. Это доступное для чтения и записи логическое значение определяет, отмечен ли элемент в данный момент. Свойство defaultChecked представляет собой доступное только для чтения логическое значение, содержащее значение HTML-атрибута checked; оно определяет, должен ли элемент отмечаться, когда страница загружается в первый раз.

Флажки и радиокнопки сами не отображают какой-либо текст и обычно выводятся вместе с прилегающим к ним HTML-текстом (или со связанным тегом <label>). Это значит, что установка свойства value элемента флажка или радиокнопки не изменяет внешнего вида элемента. Свойство value можно установить, но это изменит лишь строку, отсылаемую на веб-сервер при передаче данных формы.

Когда пользователь щелкает на флажке или радиокнопке, элемент вызывает свой обработчик onclick. Если состояние флажка или радиокнопки изменяется в результате щелчка мышью, они также вызывают обработчик событий onchange. (При этом радиокнопки, изменяющие состояние в результате щелчка на другой радиокнопке, не вызывают обработчик onchange.)

содержание 15.9.6. Текстовые поля ввода

Однострочные текстовые поля ввода Text применяются в HTML-формах и JavaScript-программах, пожалуй, чаще других. Они позволяют пользователю ввести короткий однострочный текст. Свойство value представляет текст, введенный пользователем. Установив это свойство, можно явно задать выводимый текст.

Определяемый стандартом HTML5 атрибут placeholder позволяет указать строку приглашения к вводу, которая будет отображаться в поле ввода до того момента, пока пользователь не введет какой-нибудь текст:

Дата прибытия: <input type="text" name="arrival" placeholder="yyyy-mm-dd">

Обработчик событий onchange текстового поля ввода вызывается, когда пользователь вводит новый текст или редактирует существующий, а затем указывает, что он завершил редактирование, убрав фокус ввода из текстового поля.

Элемент Textarea (многострочное текстовое поле ввода) очень похож на элемент Text за исключением того, что разрешает пользователю ввести (а JavaScript-программе вывести) многострочный текст. Многострочное текстовое поле создается тегом <textarea>, синтаксис которого существенно отличается от синтаксиса тега <input>, используемого для создания однострочного текстового поля. (Подробнее об этом см. в разделе с описанием элемента Textarea в четвертой части книги.) Тем не менее, эти два типа элементов ведут себя очень похожим образом. Свойство value и обработчик событий onchange элемента Textarea можно использовать точно так же, как в случае с элементом Text.

Элемент <input type="password"> – это модификация однострочного текстового поля ввода, в котором вместо вводимого пользователем текста отображаются символы звездочек. Как можно заключить из имени элемента, его можно использовать, чтобы дать пользователю возможность вводить пароли, не беспокоясь о том, что другие прочитают их через плечо. Следует понимать, что элемент Password защищает введенные пользователем данные от любопытных глаз, но при отправке данных формы эти данные никак не шифруются (если только отправка не выполняется по безопасному HTTPS-соединению) и при передаче по сети могут быть перехвачены.

Наконец, элемент <input type="file"> предназначен для ввода пользователем имени файла, который должен быть выгружен на веб-сервер. По существу, это однострочное текстовое поле, совмещенное со встроенной кнопкой, выводящей диалог выбора файла. У элемента выбора файла, как и у однострочного текстового поля, есть обработчик событий onchange. Однако, в отличие от текстового поля ввода, свойство value элемента выбора файла доступно только для чтения. Это не дает злонамеренным JavaScript-программам обмануть пользователя, выгрузив файл, не предназначенный для отправки на сервер.

Различные текстовые элементы ввода определяют обработчики событий onkeypress, onkeydown и onkeyup. Можно вернуть false из обработчиков событий onkeypress или onkeydown, чтобы запретить обработку нажатой пользователем клавиши. Это может быть полезно, например, когда требуется заставить пользователя вводить только цифры. Этот прием демонстрируется в примере 17.6.

содержание 15.9.7. Элементы Select и Option

Элемент Select представляет собой набор вариантов (представленных элементами Option), которые могут быть выбраны пользователем. Браузеры обычно отображают элементы Select в виде раскрывающихся меню, но, если указать в атрибуте size значение больше чем 1, они будут отображать их в виде списков (возможно, с полосами прокрутки). Элемент Select может работать двумя сильно различающимися способами, а выбор того или иного способа определяется значением свойства type. Если в теге <select> определен атрибут multiple, пользователь сможет выбрать несколько вариантов, а свойство type объекта Select будет иметь значение «select-multiple». В противном случае, если атрибут multiple отсутствует, может быть вы: бран только один вариант и свойство type будет иметь значение «select-one».

В некотором смысле элемент с возможностью множественного выбора похож на набор флажков, а элемент без такой возможности – на набор радиокнопок. Однако варианты выбора, отображаемые элементом Select, не являются кнопками-переключателями: они определяются с помощью тега <option>. Элемент Select определяет свойство options, которое является объектом, подобным массиву, хранящим объекты Option.

Когда пользователь выбирает тот или иной вариант или отменяет выбор, элемент Select вызывает свой обработчик событий onchange. Для элементов Select с возможностью выбора единственного варианта, доступное только для чтения свойство selectedIndex определяет выбранный в данный момент вариант. Для элементов Select с возможностью множественного выбора одного свойства selectedIndex недостаточно для представления полного набора выбранных вариантов. В этом случае для определения выбранных вариантов следует в цикле перебрать элементы массива options[] и проверить значения свойства selected каждого объекта Option.

Кроме свойства selected у каждого объекта Option есть свойство text, задающее строку текста, которая отображается в элементе Select для данного варианта. Используя это свойство, можно изменить видимый пользователем текст. Свойство value представляет доступную для чтения и записи строку текста, который отсылается на веб-сервер при передаче данных формы. Даже если вы пишете исключительно клиентскую программу и ваши формы никуда не отправляются, свойство value (или соответствующий ей HTML-атрибут value) можно использовать для хранения данных, которые потребуются после выбора пользователем определенного варианта. Обратите внимание, что элемент Option не определяет связанных с формой обработчиков событий; используйте вместо этого обработчик onchange соответствующего элемента Select.

Помимо задания свойства text объектов Option имеются способы динамического изменения выводимых в элементе Select вариантов с помощью особых возможностей свойства options, которые ведут свое существование с самого начала появления поддержки клиентских сценариев. Можно обрезать массив элементов Option, установив свойство options.length равным требуемому количеству вариантов, или удалить все объекты Option, установив значение свойства options.length равным нулю. Можно удалять отдельные объекты Option из элемента Select, присваивая элементам массива options[] значение null. В этом случае удаляются соответствующие объекты Option, а все элементы, расположенные в массиве options[] правее, автоматически сдвигаются влево, заполняя опустевшее место.

Чтобы добавить в элемент Select новый вариант, можно создать его с помощью конструктора Option() и добавить в конец массива options[], как показано ниже:

Имейте в виду, что эти специальные возможности элемента Select пришли к нам из прошлых времен. Вставку и удаление вариантов можно реализовать более очевидным способом, воспользовавшись стандартными методами Document.createElement(), Node.insertBefore(), Node.removeChild() и другими.

содержание 15.10. Другие особенности документов содержание

Эта глава начиналась с утверждения, что она является одной из наиболее важных в книге. Она также по необходимости оказалась одной из наиболее длинных. Этот раздел завершает главу описанием некоторых особенностей объекта Document.

содержание 15.10.1. Свойства объекта Document

В этой главе уже были представлены некоторые свойства объекта Document, такие как body, documentElement и forms, ссылающиеся на специальные элементы документа. Кроме них документы определяют еще несколько свойств, представляющих интерес:

cookie
Специальное свойство, позволяющее JavaScript-программам читать и писать cookie-файлы. Это свойство рассматривается в главе 20.
domain
Свойство, которое позволяет доверяющим друг другу веб-серверам, принадлежащим одному домену, ослаблять связанные с политикой общего происхождения ограничения на взаимодействие между их веб-страницами (подробности см. в разделе 13.6.2.1).
lastModified
Строка, содержащая дату последнего изменения документа.
location
Это свойство ссылается на тот же объект Location, что и свойство location объекта Window.
referrer
URL-адрес документа, содержащего ссылку (если таковая существует), которая привела браузер к текущему документу. Это свойство имеет то же значение, что и HTTP-заголовок Referer, но записывается с двумя буквами r.
title
Текст между тегами <title> и </title> данного документа.
URL
Свойство URL документа является строкой, доступной только для чтения, а не объектом Location. Значение этого свойства совпадает с начальным значением свойства location.href, но, в отличие от объекта Location, не является динамическим. Если пользователь выполнит переход, указав новый идентификатор фрагмента внутри документа, то свойство location.href изменится, а свойство document.URL-нет.

Из всех этих свойств наибольший интерес представляет свойство referrer: оно содержит URL-адрес документа, содержащего ссылку, которая привела пользователя к текущему документу. Это свойство можно было бы использовать по примеру следующего кода:

Метод document.write(), использованный в этом примере, является темой следующего раздела.

содержание 15.10.2. Метод document.write()

Метод document.write() был одним из первых методов, реализованных еще в веб-браузере Netscape 2. Он появился до создания модели DOM и представлял единственный способ отображения динамически изменяемого текста в документе. В современных сценариях надобность в этом методе отпала, но вы наверняка встретите его в существующем программном коде.

Метод document.write() объединяет свои строковые аргументы и вставляет получившуюся строку в документ, в точку вызова метода. По завершении выполнения сценария браузер выполнит синтаксический анализ сгенерированного вывода и отобразит его. Например, следующий фрагмент использует метод write() для динамического вывода информации в статический HTML-документ:

Важно понимать, что метод write() можно использовать для вывода разметки HTML только в процессе синтаксического анализа документа. То есть вызывать метод document.write() из программного кода верхнего уровня в теге <script> можно только в случае, если выполнение сценария является частью процесса анализа документа. Если поместить вызов document.write() в определение функции и затем вызвать эту функцию из обработчика события, результат окажется неожиданным – этот вызов уничтожит текущий документ и все содержащиеся в нем сценарии! (Почему это происходит, будет описано чуть ниже.) По тем же причинам нельзя использовать document.write() в сценариях с установленными атрибутами defer или async.

Пример 13.3 в главе 13 использует метод document.write() указанным способом/ чтобы сгенерировать более сложный вывод.

Метод write() можно использовать для создания полностью новых документов в других окнах и фреймах (однако при работе с другими окнами и фреймами необходимо позаботиться о том, чтобы не нарушать политику общего происхождения). Первый вызов метода write() другого документа полностью уничтожит имеющееся содержимое этого документа. Метод write() можно вызывать многократно, тем самым добавляя новое содержимое в документ. Содержимое, передаваемое методу write(), может буферизоваться (и не отображаться), пока не будет выполнен завершающий вызов метода close() объекта документа, сообщающий парсеру, что работа с документом окончена и он может выполнить разбор документа и отобразить его.

Следует отметить, что объект Document поддерживает также метод writeln(), который идентичен методу write() за исключением того, что он добавляет символ перевода строки после вывода своих аргументов. Это может пригодиться, например, при выводе форматированного текста внутри элемента <pre>.

Метод document.write() редко используется в современных сценариях: свойство innerHTML и другие приемы, поддерживаемые моделью DOM, обеспечивают более удачные способы добавления содержимого в документ. С другой стороны, некоторые алгоритмы лучше укладываются в схему потокового ввода/вывода, реализуемую методом write(). Если вы создаете сценарий, который динамически генерирует и выводит текст в процессе своего выполнения, вам, возможно, будет интересно ознакомиться с примером 15.10, в котором свойство innerHTML указанного элемента обертывается простыми методами write() и close().

Пример 15.10. Интерфейс потоков ввода-вывода к свойству innerHTML

содержание 15.10.3. Получение выделенного текста

Иногда удобно иметь возможность определять, какой участок текста документа выделен пользователем. Сделать это можно, как показано ниже:

Стандартный метод window.getSelection() возвращает объект Selection, описывающий текущий выделенный текст, как последовательность одного или более объектов Range. Объекты Selection и Range определяют чрезвычайно сложный прикладной интерфейс, который практически не используется и не описывается в этой книге. Наиболее важной и широко реализованной (везде, кроме IE) особенностью объекта Selection является его метод toString(), который возвращает простое текстовое содержимое выделенной области.

Браузер IE определяет иной прикладной интерфейс, который не описывается в этой книге. Метод document.Selection возвращает объект, представляющий выделенную область. Метод createRange() этого объекта возвращает реализованный только в IE объект TextRange, свойство text которого содержит выделенный

Прием, подобный приведенному в примере выше, может, в частности, пригодиться в букмарклетах (раздел 13.2.5.1) для организации поиска выделенного текста в поисковых системах или на сайте. Так, следующая HTML-ссылка пытается отыскать текущий выделенный фрагмент текста в Википедии. Если поместить в закладку эту ссылку и URL-адрес со спецификатором javascript:, закладка превратится в букмарклет:

В приведенном выше примере, выбирающем выделенный текст, есть одна проблема, связанная с несовместимостью. Метод getSelection() объекта Window не возвращает выделенный текст, если он находится внутри элемента <input> или <textarea>: он возвращает только тот текст, который выделен в теле самого документа. В то же время свойство document.selection, поддерживаемое браузером IE, возвращает текст, выделенный в любом месте в документе.

Чтобы получить текст, выделенный в текстовом поле ввода или в элементе <textarea>, можно использовать следующее решение:

elt.value.substring(elt.selectionStart, elt.selectionEnd);

Свойства selectionStart и selectionEnd не поддерживаются в версиях 1Е8 и ниже.

содержание 15.10.4. Редактируемое содержимое

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

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

<div id="editor" contenteditable>
  Click to edit
</div>

Браузеры могут поддерживать автоматическую проверку орфографии для полей форм и элементов с атрибутом contenteditable. В браузерах, поддерживающих такую проверку, она может быть включена или выключена по умолчанию. Чтобы явно включить ее, следует добавить атрибут spellcheck. А чтобы явно запретить – добавить атрибут spellcheck=false (если, например, в элементе <textarea> предполагается выводить программный код или другой текст с идентификаторами, отсутствующими в словаре).

Точно так же можно сделать редактируемым весь документ, записав в свойство designMode объекта Document строку «on». (Чтобы снова сделать документ доступным только для чтения, достаточно записать в это свойство строку «off».) Свойство designMode не имеет соответствующего ему HTML-атрибута. Можно сделать документ доступным для редактирования, поместив его в элемент <iframe>, как показано ниже (обратите внимание, что здесь используется функция onLoad() из примера 13.5):

Все текущие браузеры поддерживают свойства contenteditable и designMode. Однако они оказываются плохо совместимыми, когда дело доходит до фактического редактирования. Все браузеры позволяют вставлять и удалять текст и перемещать текстовый курсор с помощью клавиатуры и мыши. Во всех браузерах нажатие клавиши Enter выполняет переход на новую строку, но разные браузеры создают в результате разную разметку. Некоторые начинают новый абзац, другие просто вставляют элемент <br/>.

Некоторые браузеры позволяют использовать горячие комбинации клавиш, такие как Ctrl-B, чтобы изменить шрифт выделенного текста на полужирный. В других браузерах (таких как Firefox) стандартные для текстовых процессоров комбинации, такие как Ctrl-B и Ctrl-I, выполняют другие операции, имеющие отношение к самому браузеру, а не к текстовому редактору.

Браузеры определяют множество команд редактирования текста, для большинства из которых не предусмотрены горячие комбинации клавиш. Чтобы выполнить эти команды, необходимо использовать метод execCommand() объекта Document (Обратите внимание, что это метод объекта Document, а не элемента с атрибутом contenteditable. Если документ содержит более одного редактируемого элемента, команда применяется к тому из них, в котором в текущий момент находится текстовый курсор.) Команды, выполняемые методом execCommand(), определяются строками, такими как «bold», «subscript», «justifycenter» или «insertimage». Имя команды передается методу execCommand() в первом аргументе. Некоторые команды требуют дополнительное значение. Например, команда «createlink» требует указать URL для гиперссылки. Теоретически, если во втором аргументе передать методу execCommand() значение true, браузер автоматически запросит у пользователя ввести необходимое значение. Однако для большей совместимости вам необходимо самим запрашивать у пользователя требуемые данные, передавая false во втором аргументе и требуемое значение – в третьем аргументе.

Ниже приводятся две функции, которые реализуют редактирование с помощью метода execCommand():

Команды, выполняемые методом execCommand(), обычно запускаются кнопками на панели инструментов. Качественный пользовательский интерфейс должен запрещать доступ к кнопкам, если вызываемые ими команды недоступны. Чтобы определить, поддерживается ли некоторая команда браузером, можно передать ее имя методу document.queryCommandSupported(). Вызовом метода document.queryCommandEnabled() можно узнать, доступна ли команда в настоящее время. (Команда, которая выполняет некоторые действия с выделенным текстом, например, может быть недоступна, пока не будет выделен фрагмент текста.) Некоторые команды, такие как «bold» и «italic», могут иметь логическое состояние «включено» или «выключено» в зависимости от наличия выделенного фрагмента текста или местоположения текстового курсора. Как правило, эти команды представлены на панели инструментов кнопками-переключателями. Для определения текущего состояния таких команд можно использовать метод document.queryCommandState(). Наконец, некоторые команды, такие как «fontname», ассоциируются с некоторым значением (именем семейства шрифтов). Узнать это значение можно с помощью метода document.queryCommandValue(). Если в текущем выделенном фрагменте используются шрифты двух разных семейств, значение «fontname» будет неопределенным. Для проверки этого случая можно использовать метод document.queryCommandIndeterm().

Различные браузеры реализуют различные наборы команд редактирования. Некоторые команды, такие как «bold», «italic», «createlink», «undo» и «redo», поддерживаются всеми браузерами.

Список поддерживаемых команд можно найти по адресу http://www.quirksmode.org/dom/execCommand.html.

На момент написания этих строк проект стандарта HTML5 определял команды, перечисленные ниже. Однако, поскольку они реализованы пока не во всех браузерах, здесь не будет даваться сколько-нибудь подробное их описание:

bold insertLineBreak selectAll
createLink insertOrderedList subscript
delete insertUnorderedList superscript
formatBlock insertParagraph undo
forwardDelete insertText unlink
insertlmage italic unselect
insertHTML redo

Если вашему веб-приложению потребуется обеспечить возможность редактирования форматированного текста, то вам, вероятно, лучше обратить внимание на уже реализованные решения, учитывающие различия между браузерами. В Интернете можно найти множество готовых компонентов редакторов.

Фреймворки YUI и Dojo включают такие компоненты редакторов. Список других решений можно найти на странице http://en.wikipedia.org/wiki/Online_rich-text_editor

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

После того как пользователь отредактирует содержимое элемента с атрибутом contenteditable, можно воспользоваться свойством innerHTML, чтобы получить разметку HTML отредактированного содержимого. Что дальше делать с полученным отформатированным текстом, полностью зависит от вас. Его можно сохранить в скрытом поле формы и отправить вместе с формой на сервер. Непосредственную отправку отредактированного текста на сервер можно выполнить с помощью приемов, описываемых в главе 18. Можно также сохранить результаты редактирования локально, с помощью механизмов, описываемых в главе 20.