JavaScript в веб-броузерах До обсуждения JavaScript давайте поближе познакомимся с веб-страницами, ото- бражаемыми в вебброузерах. Некоторые страницы представляют статическую информацию; их можно называть документами. (Представление такой статиче- ской информации может быть дополнено динамическим поведением с помощью JavaScript, но сама по себе информация остается статической.) Другие страницы больше похожи на приложения, чем на документы. Эти страницы способны динамически загружать новую информацию по мере необходимости, они могут со- стоять преимущественно из графических изображений, могут действовать без подключения к серверу, сохранять данные локально и, как следствие, способны восстанавливать свое состояние при повторном посещении. Имеются также веб- страницы, которые занимают промежуточное положение и объединяют в себе особенности документов и приложений. Объект Window объект Window является глобальным объектом для клиентских JavaScript-программ. 14.1. Адрес документа и навигация по нему Свойство location объекта Window ссылается на объект Location, представляющий текущий URL-адрес документа, отображаемого в окне и определяющий методы, инициирующие загрузку нового документа в окно. Свойство location объекта Document также ссылается на объект Location: window.location === document.location // всегда верно Кроме того, объект Document имеет свойство URL, хранящее статическую строку с адресом URL документа. При перемещении по документу с использованием идентификаторов фрагментов (таких как «#table-of-contents») внутри документа объект Location будет обновляться, отражая факт перемещения, но свойство docu- ment.URL останется неизменным. 14.1.1. Анализ URL Свойство location окна является ссылкой на объект Location и представляет URL- адрес документа, отображаемого в данный момент в текущем окне. Свойство href объекта Location – это строка, содержащая полный текст URL-адреса. Другие свойства этого объекта, такие как protocol, host, hostname, port, pathname, search и hash, определяют отдельные части URL-адреса. Свойство hash возвращает «идентификатор фрагмента» из адреса URL, если он имеется: символ решетки (#) со следующим за ним идентификатором. Свойство search содержит часть URL-адреса, следующую за вопросительным знаком, если таковая имеется, включая сам знак вопроса. 14.1.2. Загрузка нового документа Метод assign() объекта Location заставляет окно загрузить и отобразить документ по указанному URLадресу. Метод replace() выполняет похожую операцию, но пе- ред открытием нового документа он удаляет текущий документ из списка посе- щавшихся страниц. Когда сценарию просто требуется загрузить новый документ, часто предпочтительнее использовать метод replace(), а не assign(). В противном случае кнопка Back (Назад) броузера вернет оригинальный документ и тот же са- мый сценарий снова загрузит новый документ. метод reload(), который заставляет броузер перезагрузить документ. Однако более традиционный способ заставить броузер перейти к новой странице заключается в том, чтобы просто присвоить новый URL-адрес свойству location: location = "http://www.oreilly.com"; // Перейти, чтобы купить несколько книг! location = "page2.html"; // Загрузить следующую страницу Идентификатор фрагмента – это особый вид относительного URL-адреса, кото- рый заставляет броузер просто прокрутить страницу, чтобы отобразить новый раздел, а не загружать новый документ. Идентификатор #top имеет специальное назначение: если в документе отсутствует элемент с идентификатором «top», он вынудит броузер перейти в начало документа: location = "#top"; // Перейти в начало документа 14.2. История посещений Свойство history объекта Window ссылается на объект History данного окна. Объект History хранит историю просмотра страниц в окне в виде списка документов и све- дений о них. Свойство length объекта History позволяет узнать количество элемен- тов в списке, но по причинам, связанным с безопасностью, сценарии не имеют возможности получить хранящиеся в нем URL-адреса. (Иначе любой сценарий смог бы исследовать историю посещения веб-сайтов.) Методы back() и forward() действуют подобно кнопкам Back (Назад) и Forward (Вперед) броузера: они заставляют броузер перемещаться на один шаг назад и вперед по истории просмотра данного окна. Третий метод, go(), принимает целочисленный аргумент и пропускает заданное число страниц, двигаясь вперед (если аргумент положительный) или назад (если аргумент отрицательный) в списке истории. history.go(-2); // Переход назад на 2 элемента, как если бы пользователь // дважды щелкнул на кнопке Back (Назад) 14.3. Информация о броузере и об экране 14.3.1. Объект Navigator Свойство navigator объекта Window ссылается на объект Navigator, содержащий об- щую информацию о номере версии и о производителе броузера. Объект Navigator имеет четыре свойства, предоставляющих информацию о версии работающего броузера, и вы можете использовать их для определения типа броузера: appName Название веб-броузера. В IE это строка «Microsoft Internet Explorer». В Firefox значением этого свойства является строка «Netscape». Для совместимости с су- ществующими реализациями определения типа броузера значением этого свойства в других броузерах часто является строка «Netscape». appVersion Обычно значение этого свойства начинается с номера версии, за которым сле- дует другая информация о версии броузера и его производителе. Обычно в на- чале строки указывается номер 4.0 или 5.0, свидетельствующий о совмести- мости с четвертым или пятым поколением броузеров. Формат строки в свойст- ве appVersion не определяется стандартом, поэтому невозможно организовать разбор этой строки способом, не зависящим от типа броузера. userAgent Строка, которую броузер посылает в http-заголовке USER-AGENT. Это свойство обычно содержит ту же информацию, что содержится в свойстве appVersion, а также может включать дополнительные сведения. Как и в случае со свойст- вом appVersion, формат представления этой информации не стандартизован. Поскольку это свойство содержит больше информации, именно оно обычно используется для определения типа броузера. platform Строка, идентифицирующая операционную систему (и, возможно, аппарат- ную платформу), в которой работает броузер. В дополнение к свойствам с информацией о версии и производителе броузера, объект Navigator имеет еще несколько свойств и методов. В число стандартных и часто реализуемых нестандартных свойств входят: onLine Свойство navigator.onLine (если существует) определяет, подключен ли броузер к сети. Приложениям может потребоваться сохранять информацию о состоя- нии локально (с использованием приемов, описываемых в главе 20), если бро- узер не подключен к сети. geolocation Объект Geolocation, определяющий API для выяснения географического поло- жения пользователя. Подробнее об этом рассказывается в разделе 22.1. javaEnabled() Нестандартный метод, который должен возвращать true, если броузер спосо- бен выполнять Javaапплеты. cookiesEnabled() Нестандартный метод, который должен возвращать true, если броузер спосо- бен сохранять cookies. Если броузер настроен на сохранение cookies только для определенных сайтов, этот метод может возвращать некорректное значение. 14.3.2. Объект Screen Свойство screen объекта Window ссылается на объект Screen, предоставляющий ин- формацию о размере экрана на стороне пользователя и доступном количестве цветов. Свойства width и height возвращают размер экрана в пикселах. Свойства availWidth и availHeight возвращают фактически доступный размер экрана; из них исключается пространство, требуемое для таких графических элементов, как панель задач. Свойство colorDepth возвращает количество битов на пиксел, определяющих цвет. Типичными значениями являются 16, 24 и 32. 14.5. Обработка ошибок Свойство onerror объекта Window – это обработчик событий, который вызывается во всех случаях, когда необработанное исключение достигло вершины стека вы- зовов и когда броузер готов отобразить сообщение об ошибке в консоли Java- Script. Если присвоить этому свойству функцию, функция будет вызываться вся- кий раз, когда в окне будет возникать ошибка выполнения программного кода JavaScript: присваиваемая функция станет обработчиком ошибок для окна. Исторически сложилось так, что обработчику события onerror объекта Window передается три строковых аргумента, а не единственный объект события, как в других обработчиках. (Другие объекты в клиентском JavaScript также имеют обработчики onerror, обрабатывающие различные ошибочные ситуации, но все они являются обычными обработчиками событий, которым передается единст- венный объект события.) Первый аргумент обработчика window.onerror – это сооб- щение, описывающее произошедшую ошибку. Второй аргумент – это строка, со- держащая URL-адрес документа с JavaScript-кодом, приведшим к ошибке. Тре- тий аргумент – это номер строки в документе, где произошла ошибка. 14.5.1. Открытие и закрытие окон Открыть новое окно веб-броузера (или вкладку, что обычно зависит от настроек броузера) можно с помощью метода open() объекта Window. Метод Window.open() за- гружает документ по указанному URLадресу в новое или в существующее окно и возвращает объект Window, представляющий это окно. Он принимает четыре не- обязательных аргумента: Первый аргумент open() – это URL-адрес документа, отображаемого в новом окне. Если этот аргумент отсутствует (либо является пустой строкой), будет открыт специальный URL пустой страницы about:blank. Второй аргумент open() – это строка с именем окна. Если окно с указанным име- нем уже существует (и сценарию разрешено просматривать содержимое этого ок- на), используется это существующее окно. Иначе создается новое окно и ему при- сваивается указанное имя. Если этот аргумент опущен, будет использовано спе- циальное имя «_blank», т. е. будет открыто новое неименованное окно. Третий необязательный аргумент open() – это список параметров, определяющих размер и видимые элементы графического пользовательского интерфейса нового окна. Если опустить этот аргумент, окно получает размер по умолчанию и пол- ный набор графических элементов: строку меню, строку состояния, панель инст- рументов и т. д. В броузерах, поддерживающих вкладки, это обычно приводит к созданию новой вкладки. Указав этот аргумент, можно явно определить размер окна и набор имеющихся в нем элементов управления. (Если явно указать раз- мер, в большинстве случаев это приведет к созданию нового окна, а не вкладки.) Например, маленькое окно с изменяемым размером, имеющее строку состояния, но не содержащее меню, панели инструментов и адресную строку, можно от- крыть посредством следующим образом: var w = window.open("smallwin.html", "smallwin", "width=400,height=350,status=yes,resizable=yes"); Этот третий аргумент является нестандартным, и спецификация HTML5 требует, чтобы броузеры игнорировали его. Указывать четвертый аргумент open() имеет смысл, только если второй аргумент определяет имя существующего окна. Этот аргумент – логическое значение, опре- деляющее, должен ли URL-адрес, указанный в первом аргументе, заменить теку- щую запись в истории просмотра окна (true) или требуется создать новую запись (false). Если этот аргумент опущен, используется значение по умолчанию false. Значение, возвращаемое методом open(), является объектом Window, представляю- щим вновь созданное окно. Этот объект позволяет сослаться в JavaScript-коде на новое окно так же, как исходный объект Window ссылается на окно, в котором вы- полняется сценарий: var w = window.open(); // Открыть новое пустое окно w.alert("Будет открыт сайт http://example.com"); // Вызвать его метод alert() w.location = "http://example.com"; // Установить св-во location 14.5.1.1. Закрытие окон Новое окно открывается при помощи метода open() и закрывается при помощи метода close(). Если объект Window был создан сценарием, то этот же сценарий сможет закрыть его следующей инструкцией: w.close(); JavaScript-код, выполняющийся внутри данного окна, может закрыть его так: window.close(); Работа с документами Клиентский JavaScript предназначен для того, чтобы превращать статические HTML-документы в интерактивные веб-приложения. Работа с содержимым веб- страниц – главное предназначение JavaScript. Данная глава является одной из наиболее важных в этой книге – здесь рассказывается о том, как это делается. 15.1. Обзор модели DOM Объектная модель документа (Document Object Model, DOM) – это фундамен- тальный прикладной программный интерфейс, обеспечивающий возможность работы с содержимым HTML- и XML-документов. Прежде всего, следует понимать, что вложенные элементы 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. Document <html> <head> <body> <title> "Sample Document" <h1> <p> "An HTML Document" "This is a" <i> "document" "simple" Каждый прямоугольник на рис. 15.1 является узлом документа, который пред- ставлен объектом Node. Выбор элементов документа Работа большинства клиентских программ на языке JavaScript так или иначе связана с манипулированием элементами документа. В ходе выполнения эти про- граммы могут использовать глобальную переменную document, ссылающуюся на объект Document. Однако, чтобы выполнить какиелибо манипуляции с элемента- ми документа, программа должна каким-то образом получить, или выбрать, объекты Element, ссылающиеся на эти элементы документа. Модель DOM опреде- ляет несколько способов выборки элементов. Выбрать элемент или элементы до- кумента можно: • по значению атрибута id; • по значению атрибута name; • по имени тега; • по имени класса или классов CSS; или • по совпадению с определенным селектором CSS. Все эти приемы выборки элементов описываются в следующих подразделах. 15.1.1. Выбор элементов по значению атрибута id Все HTML-элементы имеют атрибуты id. Значение этого атрибута должно быть уникальным в пределах документа – никакие два элемента в одном и том же до- кументе не должны иметь одинаковые значения атрибута id. Выбрать элемент по уникальному значению атрибута id можно с помощью метода getElementById() объекта Document. var section1 = document.getElementById("section1"); 15.1.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, а классом HTMLDocu- ment, поэтому он доступен только в HTML-документах и не доступен в XML-доку- ментах. Он возвращает объект NodeList, который ведет себя, как доступный толь- ко для чтения массив объектов Element. В IE метод getElementsByName() возвращает также элементы, значения атрибутов id которых совпадает с указанным значени- ем. 15.1.3. Выбор элементов по типу Метод getElementsByTagName() объекта Document позволяет выбрать все HTML- или XML-элементы указанного типа (или по имени тега). Например, получить подоб- ный массиву объект, доступный только для чтения, содержащий объекты Element всех элементов <span> в документе, можно следующим образом: var spans = document.getElementsByTagName("span"); Подобно методу getElementsByName(), getElementsByTagName() возвращает объект No- deList. (Подробнее класс NodeList описывается во врезке, в этом же разделе.) Эле- менты документа включаются в массив NodeList в том же порядке, в каком они следуют в документе, т. е. первый элемент <p> в документе можно выбрать так: var firstpara = document.getElementsByTagName("p")[0]; Имена HTML-тегов не чувствительны к регистру символов, и когда getElements- ByTagName() применяется к HTML-документу, он выполняет сравнение с именем тега без учета регистра символов. Переменная spans, созданная выше, например, будет включать также все элементы <span>, которые записаны как <SPAN>. Можно получить NodeList, содержащий getElementsByTagName() шаблонный символ «*». все элементы документа, если передать методу Кроме того, классом Element также определяет метод getElementsByTagName(). Он действует точно так же, как и версия метода в классе Document, но выбирает толь- ко элементы, являющиеся потомками для элемента, относительно которого вызывается метод. То есть отыскать все элементы <span> внутри первого элемента <p> можно следующим образом: var firstpara = document.getElementsByTagName("p")[0]; var firstParaSpans = firstpara.getElementsByTagName("span"); 15.1.4. Выбор элементов по классу CSS Значением HTML-атрибута class является список из нуля или более идентифика- торов, разделенных пробелами. Он дает возможность определять множества свя- занных элементов документа: любые элементы, имеющие в атрибуте class один и тот же идентификатор, являются частью одного множества. Однако кроме этого, стандарт HTML5 определяет метод getElementsByClassName(), позволяющий выбирать множества элементов до- кумента на основе идентификаторов в их атрибутах class. Подобно методу getElementsByTagName(), метод getElementsByClassName() может вы- зываться и для HTML-документов, и для HTML-элементов, и возвращает «жи- вой» объект NodeList, содержащий все потомки документа или элемента, соответствующие критерию поиска. Метод getElementsByClassName() принимает единст- венный строковый аргумент, но в самой строке может быть указано несколько идентификаторов, разделенных пробелами. Соответствующими будут считаться все элементы, атрибуты class которых содержат все указанные идентификаторы. Порядок следования идентификаторов не имеет значения. Обратите внимание, что и в атрибуте class, и в аргументе метода getElementsByClassName() идентифика- торы классов разделяются пробелами, а не запятыми. 15.1.5. Выбор элементов с использованием селекторов CSS Каскадные таблицы стилей CSS имеют очень мощные синтаксические конструк- ции, известные как селекторы, позволяющие описывать элементы или множест- ва элементов документа. Полное описание синтаксиса селекторов CSS выходит далеко за рамки этой книги1, однако несколько примеров помогут прояснить их основы. Элементы можно описать с помощью имени тега и атрибутов id и class: #nav div .warning // Элемент с атрибутом id="nav" // Любой элемент <div> // Любой элемент с идентификатором "warning" в атрибуте class В более общем случае элементы можно выбирать, опираясь на значения атрибутов: p[lang="fr"] // Абзац с текстом на французском языке: <p lang="fr"> *[name="x"] // Любой элемент с атрибутом name="x" Эти простейшие селекторы можно комбинировать: span.fatal.error // Любой элемент <span> с классами "fatal" и "error" span[lang="fr"].warning // Любое предупреждение на французском языке С помощью селекторов можно также определять взаимоотношения между эле- ментами: #log span // Любой элемент <span>, являющийся потомком элемента с id="log" #log>span // Любой элемент <span>, дочерний по отношению к элементу с id="log" body>h1:first-child // Первый элемент <h1>, дочерний по отношению к <body> Селекторы можно комбинировать для выбора нескольких элементов или мно- жеств элементов: div, #log // Все элементы <div> плюс элемент с id="log" Как видите, селекторы CSS позволяют выбирать элементы всеми способами, опи- санными выше: по значению атрибута id и name, по имени тега и по имени класса. Наряду со стандартизацией селекторов CSS3, другой стандарт консорциума W3C, известный как «Selectors API» (API селекторов), определяет методы JavaScript для получения элементов, соответствующих указанному селектору.1 Ключевым в этом API является метод querySelectorAll() объекта Document. Он принимает един- ственный строковый аргумент с селектором CSS и возвращает объект NodeList, представляющий все элементы документа, соответствующие селектору. В отли- чие от ранее описанных методов выбора элементов, объект NodeList, возвращае- мый методом querySelectorAll(), не является «живым»: он хранит элементы, которые соответствовали селектору на момент вызова метода, и не отражает после- дующие изменения в документе. В случае отсутствия элементов, соответствую- щих селектору, метод querySelectorAll() вернет пустой NodeList. Если методу querySelectorAll() передать недопустимую строку, он возбудит исключение. В дополнение к методу querySelectorAll() объект документа также определяет ме- тод querySelector(), подобный методу querySelectorAll(), – с тем отличием, что он возвращает только первый (в порядке следования в документе) соответствующий элемент или null, в случае отсутствия соответствующих элементов. 15.3. Структура документа и навигация по документу После выбора элемента документа иногда бывает необходимо отыскать структур- но связанные части документа (родитель, братья, дочерний элемент). Объект Docu- ment можно представить как дерево объектов 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 15.3.2. Документы как деревья элементов Первой частью этого прикладного интерфейса является свойство children объек- тов Element. Подобно свойству childNodes, его значением является объект NodeList. Однако, в отличие от свойства childNodes, список children содержит только объек- ты Element. Обратите внимание, что узлы Text и Comment не имеют дочерних узлов. Это означает, что описанное выше свойство Node.parentNode никогда не возвращает узлы типа Text или Comment. Значением свойства parentNode любого объекта Element всегда будет другой объект Element или корень дерева – объект Document или DocumentFragment. Второй частью прикладного интерфейса навигации по элементам документа яв- ляются свойства объекта Element, аналогичные свойствам доступа к дочерним и братским узлам объекта Node: firstElementChild, lastElementChild Похожи на свойства firstChild и lastChild, но возвращают дочерние элементы. nextElementSibling, previousElementSibling Похожи на свойства nextSibling и previousSibling, но возвращают братские эле- менты. childElementCount Количество дочерних элементов. Возвращает то же значение, что и свойство children.length. 15.4. Атрибуты 15.4.1. HTML-атрибуты как свойства объектов Element Объекты HTMLElement, представляющие элементы HTML-документа, определяют свойства, доступные для чтения/записи, соответствующие HTML-атрибутам эле- ментов. Например, уз- нать URL-адрес изображения можно, обратившись к свойству src объекта HTML- Element, представляющего элемент <img>: var image = document.getElementById("myimage"); var imgurl = image.src; // Атрибут src определяет URL-адрес изображения image.id === "myimage" // Потому что поиск элемента выполнялся по id Аналогично можно устанавливать атрибуты элемента <form>, определяющие по- рядок отправки формы: var f = document.forms[0]; // Первый элемент <form> в документе f.action = "http://www.example.com/submit.php"; // Установить URL отправки. f.method = "POST"; // Тип HTTP-запроса Имена атрибутов в разметке HTML не чувствительны к регистру символов, в от- личие от имен свойств в языке JavaScript. Чтобы преобразовать имя атрибута в имя свойства в языке JavaScript, его нужно записать символами в нижнем реги- стре. Однако, если имя атрибута состоит из более чем одного слова, первый сим- вол каждого слова, кроме первого, записывается в верхнем регистре, например: defaultChecked и tabIndex. 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"); Класс Element также определяет два родственных метода, hasAttribute() и remove- Attribute(). Первый из них проверяет присутствие атрибута с указанным именем, а второй удаляет атрибут. Эти методы особенно удобны при работе с логическими атрибутами: для этих атрибутов (таких как атрибут disabled HTML-форм) важно их наличие или отсутствие в элементе, а не их значения. 15.5. Содержимое элемента 15.5.1. Содержимое элемента в виде HTML При чтении свойства innerHTML объекта Element возвращается содержимое этого элемента в виде строки разметки. Попытка изменить значение этого свойства приводит к вызову синтаксического анализатора веб-броузера и замещению те- кущего содержимого элемента разобранным представлением новой строки. (Не- смотря на свое название, свойство innerHTML может использоваться для работы не только с HTML-, но и с XML-элементами.) Кроме того, спецификация HTML5 стандартизует свойство с именем outerHTML. При обращении к свойству outerHTML оно возвращает строку разметки HTML или XML, содержащую открывающий и закрывающий теги элемента, которому при- надлежит это свойство. При записи нового значения в свойство outerHTML элемента новое содержимое замещает элемент целиком. Свойство outerHTML определено только для узлов типа Element, оно отсутствует в объекте Document. К моменту на- писания этих строк свойство outerHTML поддерживалось всеми текущими броузе- рами, кроме Firefox. Еще одной особенностью, впервые появившейся в IE и стандартизованной спе- цификацией HTML5, является метод insertAdjacentHTML(), дающий возможность вставить строку с произвольной разметкой HTML, прилегающую («adjacent») к указанному элементу. Разметка передается методу во втором аргументе, а точ- ное значение слова «прилегающая» («adjacent») зависит от значения первого аргумента. Этот первый аргумент должен быть строкой с одним из значений: «before- begin», «afterbegin», «beforeend» или «afterend». Эти значения определяют пози- цию вставки, как изображено на рис. 15.3. <div id="target"> This is the element content </div> beforebegin afterbegin beforeend afterend Рис. 15.3. Позиции вставки в вызове метода insertAdjacentHTML() Метод insertAdjacentHTML() не поддерживается текущей версией Firefox. 15.5.2. Содержимое элемента в виде простого текста Иногда бывает необходимо получить содержимое элемента в виде простого текста или вставить простой текст в документ (без необходимости экранировать угловые скобки и амперсанды, используемые в разметке HTML). Стандартный способ вы- полнения этих операций основан на использовании свойства textContent объекта Node: var para = document.getElementsByTagName("p")[0]; // Первый <p> в документе var text = para.textContent; // Текст "This is a simple document." para.textContent = "Hello World!"; // Изменит содержимое абзаца Свойство textContent поддерживается всеми текущими броузерами, кроме IE. В IE вместо него можно использовать свойство innerText. 15.6. Создание, вставка и удаление узлов 15.6.1. Создание узлов Как было показано в примере выше, создавать новые узлы Element можно с помо- щью метода createElement() объекта Document. Этому методу необходимо передать имя тега: это имя не чувствительно к регистру символов при работе с HTML-доку- ментами и чувствительно при работе с XML-документами. Для создания текстовых узлов существует аналогичный метод: var newnode = document.createTextNode("содержимое текстового узла"); 15.6.2. Вставка узлов После создания нового узла его можно вставить в документ с помощью методов типа Node: appendChild() или insertBefore(). Метод appendChild() вызывается отно- сительно узла Element, в который требуется вставить новый узел, и вставляет ука- занный узел так, что тот становится последним дочерним узлом (значением свой- ства lastChild). Метод insertBefore() похож на метод appendChild(), но он принимает два аргумен- та. В первом аргументе указывается вставляемый узел, а во втором – узел, перед которым должен быть вставлен новый узел. Этот метод вызывается относительно объекта узла, который станет родителем нового узла, а во втором аргументе дол- жен передаваться дочерний узел этого родителя. Если во втором аргументе пере- дать null, метод insertBefore() будет вести себя, как appendChild(), и вставит узел в конец. 15.6.3. Удаление и замена узлов Метод removeChild() удаляет элемент из дерева документа. Но будьте вниматель- ны: этот метод вызывается не относительно узла, который должен быть удален, а (как следует из фрагмента «child» в его имени) относительно родителя удаляе- мого узла. Этот метод вызывается относительно родителя и принимает в виде ар- гумента дочерний узел, который требуется удалить. Чтобы удалить узел n из документа, вызов метода должен осуществляться так: n.parentNode.removeChild(n); Метод replaceChild() удаляет один дочерний узел и замещает его другим. Этот ме- тод должен вызываться относительно родительского узла. В первом аргументе он принимает новый узел, а во втором – замещаемый узел. Например, ниже показа- но, как заменить узел n текстовой строкой: n.parentNode.replaceChild(document.createTextNode("[ ИСПРАВЛЕНО ]"), n); 15.7. Геометрия документа и элементов и прокрутка 15.7.1. Координаты документа и видимой области Позиция элемента измеряется в пикселах. Координата X растет слева направо, а координата Y – сверху вниз. Однако существуют две точки, которые мы можем считать началом координат: координаты X и Y элемента могут отсчитываться от- носительно верхнего левого угла документа или относительно верхнего левого уг- ла видимой области. Для окон верхнего уровня и вкладок «видимой областью» является часть окна броузера, в которой фактически отображается содержимое документа: в нее не входит обрамление окна (меню, панели инструментов, вклад- ки). Чтобы перейти от одной системы координат к другой, необходимо иметь возмож- ность определять позиции полос прокрутки окна броузера. Во всех броузерах, кро- ме IE версии 8 и ниже, эти значения можно узнать с помощью свойств pageXOffset и pageYOffset объекта Window. Кроме того, в IE (и во всех современных броузерах) позиции полос прокрутки можно узнать с помощью свойств scrollLeft и scrollTop. 15.7.2. Определение геометрии элемента Самый простой способ определить размеры и координаты элемента – обратиться к его методу getBoundingClientRect(). Этот метод впервые появился в IE5 и в на- стоящее время реализован во всех текущих броузерах. Он не принимает аргумен- тов и возвращает объект со свойствами left, right, top и bottom. Свойства left и top возвращают координаты X и Y верхнего левого угла элемента, а свойства right и bottom возвращают координаты правого нижнего угла. Этот метод возвращает позицию элемента в системе координат видимой области. (Слово «Client» в имени метода getBoundingClientRect() косвенно указывает на кли- ентскую область веб-броузера, т. е. на окно и видимую область в нем.) Чтобы пе- рейти к координатам относительно начала документа, которые не изменяются после прокрутки окна броузера пользователем, нужно добавить смещения про- крутки: var box = e.getBoundingClientRect(); // Координаты в видимой области var offsets = getScrollOffsets(); // Вспомогат. функция, объявленная выше var x = box.left + offsets.x; // Перейти к координатам документа var y = box.top + offsets.y; Кроме того, во многих броузерах (и в стандарте W3C) объект, возвращаемый ме- тодом getBoundingClientRect(), имеет свойства width и height, но оригинальная реа- лизация в IE не поддерживает их. Для совместимости ширину и высоту элемента можно вычислять, как показано ниже: var box = e.getBoundingClientRect(); var w = box.width || (box.right - box.left);var h = box.height || (box.bottom - box.top); Если передать методу getBoundingClientRect() строчный элемент, он вернет геометрию «ограничивающего прямоугольника» (bounding rectangle), содержащего все от- дельные прямоугольные области. Для элемента <i>, взятого в качестве примера выше, ограничивающий прямоугольник будет включать обе строки целиком. Для определения координат и размеров отдельных прямоугольников, занимае- мых строчными элементами, можно воспользоваться методом getClientRects(), который возвращает объект, подобный массиву, доступный только для чтения, чьи элементы представляют объекты прямоугольных областей, подобные тем, что возвращаются методом getBoundingClientRect(). 15.7.3. Прокрутка Чтобы заставить броузер прокрутить документ, можно присваивать значение используемым в этом примере свойствам scrollLeft и scrollTop, но суще- ствует более простой путь, поддерживаемый с самых ранних дней развития язы- ка JavaScript. Метод scrollTo() объекта Window (и его синоним scroll()) принимает координаты X и Y точки (относительно начала координат документа) и устанавливает их в качестве величин смещения полос прокрутки. То есть он прокручива- ет окно так, что точка с указанными координатами оказывается в верхнем левом углу видимой области. Если указать точку, расположенную слишком близко к нижней или к правой границе документа, броузер попытается поместить эту точку как можно ближе к верхнему левому углу видимой области, но не сможет обеспечить точное их совпадение. Следующий пример прокручивает окно броузе- ра так, что видимой оказывается самая нижняя часть документа: // Получить высоту документа и видимой области. Метод offsetHeight описывается ниже. var documentHeight = document.documentElement.offsetHeight; var viewportHeight = window.innerHeight; // Или использовать getViewportSize(), // описанный выше // И прокрутить окно так, чтобы переместить последнюю "страницу" в видимую область window.scrollTo(0, documentHeight - viewportHeight); Метод scrollBy() объекта Window похож на методы scroll() и scrollTo(), но их аргу- менты определяют относительное смещение и добавляются к текущим позициям полос прокрутки. Тем, кто умеет быстро читать, мог бы понравиться следующий букмарклет (раздел 13.2.5.1): // Прокручивает документ на 10 пикселов каждые 200 мсек. // Учтите, что этот букмарклет нельзя отключить после запуска! JavaScript:void setInterval(function() {scrollBy(0,10)}, 200); Часто требуется прокрутить документ не до определенных числовых координат, а до элемента в документе, который нужно сделать его видимым. В этом случае можно определить координаты элемента с помощью метода getBoundingClientRect(), преобразовать их в координаты относительно начала документа и передать их ме- тоду scrollTo(), но гораздо проще воспользоваться методом scrollIntoView() тре- буемого HTML-элемента. Этот метод гарантирует, что элемент, относительно ко- торого он будет вызван, окажется в видимой области. По умолчанию он старается прокрутить документ так, чтобы верхняя граница элемента оказалась как можно ближе к верхней границе видимой области. Если в единственном аргументе пере- дать методу значение false, он попытается прокрутить документ так, чтобы ниж- няя граница элемента совпала с нижней границей видимой области. Кроме того, броузер выполнит прокрутку по горизонтали, если это потребуется, чтобы сде- лать элемент видимым. Своим поведением метод scrollIntoView() напоминает свойство window.location. hash, когда ему присваивается имя якорного элемента (элемента <a name="">). 15.9. Другие особенности документов Эта глава начиналась с утверждения, что она является одной из наиболее важ- ных в книге. Она также по необходимости оказалась одной из наиболее длинных. Этот раздел завершает главу описанием некоторых особенностей объекта Document. 15.9.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-адрес документа, содержащего ссылку, которая привела пользова- теля к текущему документу. Это свойство можно было бы использовать, как по- казано ниже: if (document.referrer.indexOf("http://www.google.com/search?") == 0) { var args = document.referrer.substring(ref.indexOf("?")+1).split("&"); for(var i = 0; i < args.length; i++) { if (args[i].substring(0,2) == "q=") { document.write("<p>Добро пожаловать, пользователь Google. "); document.write("Вы искали: " + unescape(args[i].substring(2)).replace('+', ' '); break; } } } Метод document.write(), использованный в этом примере, является темой следую- щего раздела. 15.9.2. Получение выделенного текста Иногда удобно иметь возможность определять, какой участок текста документа выделен пользователем. Сделать это можно, как показано ниже: function getSelectedText() { if (window.getSelection) // Функция, определяемая стандартом HTML5 return window.getSelection().toString(); else if (document.selection) // Прием, характерный для IE. return document.selection.createRange().text; } Стандартный метод window.getSelection() возвращает объект Selection, описываю- щий текущий выделенный текст, как последовательность одного или более объек- тов Range. Объекты Selection и Range определяют чрезвычайно сложный приклад- ной интерфейс, который практически не используется и не описывается в этой книге. Наиболее важной и широко реализованной (везде, кроме IE) особенностью объекта Selection является его метод toString(), который возвращает простое текстовое содержимое выделенной области. 15.9.3. Редактируемое содержимое Существует два способа включения поддержки возможности редактирования. Можно установить HTML-атрибут contenteditable любого тега или установить Ja- vaScript-свойство contenteditable соответствующего элемента Element, содержи- мое которого разрешается редактировать. Когда пользователь щелкнет на содер- жимом внутри этого элемента, появится текстовый курсор и пользователь смо- жет вводить текст с клавиатуры. Ниже представлен HTML-элемент, создающий область редактирования: <div id="editor" contenteditable> Щелкните здесь, чтобы отредактировать </div> Броузеры могут поддерживать автоматическую проверку орфографии для полей форм и элементов с атрибутом contenteditable. В броузерах, поддерживающих та- кую проверку, она может быть включена или выключена по умолчанию. Чтобы явно включить ее, следует добавить атрибут spellcheck. А чтобы явно запретить – добавить атрибут spellcheck=false (если, например, в элементе <textarea> предпо- лагается выводить программный код или другой текст с идентификаторами, отсутствующими в словаре). Точно так же можно сделать редактируемым весь документ, записав в свойство designMode объекта Document строку «on». (Чтобы снова сделать документ доступ- ным только для чтения, достаточно записать в это свойство строку «off».) Свойст- во designMode не имеет соответствующего ему HTMLатрибута. Можно сделать до- кумент доступным для редактирования, поместив его в элемент <iframe>, как по- казано ниже (обратите внимание, что здесь используется функция onLoad() изпримера 13.5): <iframe id="editor" src="about:blank"></iframe> <script> onLoad(function() { // Пустой фрейм // После загрузки var editor = document.getElementById("editor"); // найти фрейм документа editor.contentDocument.designMode = "on"; // и включить режим }); // редактирования. </script> Все текущие броузеры поддерживают свойства 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(): function bold() { document.execCommand("bold", false, null); } function link() { var url = prompt("Введите адрес гиперссылки"); if (url) document.execCommand("createlink", false, url); } Команды, выполняемые методом execCommand(), обычно запускаются кнопками на панели инструментов. Качественный пользовательский интерфейс должен за- прещать доступ к кнопкам, если вызываемые ими команды недоступны. Чтобы определить, поддерживается ли некоторая команда броузером, можно передать ее имя методу document.queryCommandSupported(). Вызовом метода document.queryCom- mandEnabled() можно узнать, доступна ли команда в настоящее время. (Команда, которая выполняет некоторые действия с выделенным текстом, например, может быть недоступна, пока не будет выделен фрагмент текста.) Некоторые команды, такие как «bold» и «italic», могут иметь логическое состояние «включено» или «выключено» в зависимости от наличия выделенного фрагмента текста или ме- стоположения текстового курсора. Как правило, эти команды представлены на панели инструментов кнопкамипереключателями. Для определения текущего состояния таких команд можно использовать метод document.queryCommandState(). Наконец, некоторые команды, такие как «fontname», ассоциируются с некото- рым значением (именем семейства шрифтов). Узнать это значение можно с помо- щью метода document.queryCommandValue(). Если в текущем выделенном фрагменте используются шрифты двух разных семейств, команда «fontname» будет иметь неопределенное значение. Для проверки этого случая можно использовать метод document.queryCommandIndeterm(). Различные броузеры реализуют различные наборы команд редактирования. Не- которые команды, такие как «bold», «italic», «createlink», «undo» и «redo», под- держиваются всеми броузерами.1 На момент написания этих строк проект стан- дарта HTML5 определял команды, перечисленные ниже. Однако, поскольку они реализованы пока не во всех броузерах, здесь не будет даваться скольконибудь подробное их описание: bold createLink delete formatBlock forwardDelete insertImage insertHTML insertLineBreak insertOrderedList insertUnorderedList insertParagraph insertText italic redo selectAll subscript superscript undo unlink unselect