JavaScript: проблемы и решения

Как утверждает известная поговорка, "дьявол кроется в мелочах". Эти же мелочи могут украсить ваше душевное равновесие и наградить с любовью созданный сценарий трудно обнаруживаемыми ошибками. Автор - не великий эксперт в языках программирования. Однако я с радостью воспользуюсь предоставленным местом и расскажу о том, с чем пришлось столкнуться и разбираться лично.

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

Второе замечание. Из рассмотренных ниже ошибок и нелепостей считанные единицы повторяются на нескольких броузерах или в нескольких версиях одного броузера. Следует отдать должное разработчикам как NN, так и MSIE: они внимательно следят за откликами пользователей и стараются оперативно устранять недочеты. Поэтому все сказанное далее касается тех, кто хочет создавать сценарии, устойчиво работающие на максимально большом количестве платформ. Соответственно, были отобраны ошибки, которые могут встретиться хотя бы на одном броузере из диапазона: NETSCAPE NAVIGATOR 3.X - NETSCAPE NAVIGATOR 4.X, Internet EXPLORER 3.X - Internet EXPLORER 4.X. А теперь к делу.

Внутренний синтаксис

Названия переменных

Нигде не говорится об ограничениях на комбинации строчных и прописных букв в названии переменной или функции. То есть в теории равно допустимы названия GETCLIENTNAME, GETCLIENTSEX или GETCLIENTAGE. На практике несколько раз приходилось сталкиваться с ситуацией, когда интерпретатор MSIE принимал созданную переменную или функцию за некий внутренний объект. В результате начинали сыпаться жалобы типа "Объект GETCLIENTNAME не поддерживает данный метод". Общей закономерности вывести не удалось, но во всех случаях эти названия напоминали названия методов из расширенного пакета JSCRIPT от MICROSOFT. Если с вами случится подобное, измените название новоявленного "объекта", и все придет в норму.

FALSE, TRUE и операции сравнения

В большинстве языков (программирования, разумеется) пары значений FALSE/TRUE и ноль/не ноль в логических операциях являются взаимозаменяемыми. То есть, например, ответ из окна подтверждения можно было бы обработать так: (напомню, что при нажатии кнопки OK из окна возвращается 1, при нажатии кнопки Отмена - 0)

  VAR AGREE=WINDOW.CONFIRM ("Вы согласны?");  IF (AGREE) ... //Нажата кнопка OK  ELSE ... //Нажата кнопка Отмена

Привычно, изящно... и неработоспособно в некоторых версиях NN, где ложь ложью, а нуль нулем. Зачем это сделали - непонятно. Советую в подобных случаях писать пока напрямую:

  VAR AGREE=WINDOW.CONFIRM ("Вы согласны?");  IF (AGREE==1) ... //Нажата кнопка OK  ELSE ... //Нажата кнопка Отмена

Стандартные методы

Метод WRITE()

В большинстве руководств написано, что команда типа DOCUMENT.WRITE (строка) "записывает строку или несколько строк в окно документа". Это не совсем так. В официальном описании языка сказано, что подобная команда "автоматически открывает поток вывода и..." (остальное - как выше). Житейский опыт подсказывает, что открытую дверь неплохо было бы потом закрыть. Впрочем, если вы пользуетесь командой лишь единожды, при первоначальной загрузке страницы, то ничего страшного вам не грозит. Другое дело, если вы рискнете использовать метод WRITE() для создания какой-либо анимации. Например, на рисунке ниже приведена сделанная автором программа подбора фона и тона страницы. Окно броузера разбито на три фрейма, и результаты настроек немедленно отражаются в верхнем левом фрейме с образцом текста.

Если выводить текст - по одной команде WRITE(), то через некоторое время начнутся разные чудеса. Какого рода чудеса, будет зависеть от броузера, платформы и мощности компьютера. Автор в качестве эксперимента запускал программу, которая безостановочно "щелкала" кнопку "Применить". Побочный эффект обычно возникал через 50-100 итераций, при этом в 50% случаев поступала жалоба на переполнение стека, а в остальных броузер радовал самыми неожиданными и замысловатыми поступками.

Поэтому если вы решите использовать WRITE() для динамического обновления фрейма или всей страницы, лучше создать специальную функцию. Будем оригинальны, назовем ее PRINT. В приведенном примере она имеет следующий вид:

  FUNCTION PRINT (STRING)  {   WITH (WINDOW.TOP.LEFT.DOCUMENT)    {     CLEAR(); // Явным образом очищаем фрейм     WRITE ("     ");     // Броузеры очень не любят, когда в них        загружают HTML-файлы,не имеющие этих        стандартных тегов, и ведут себя неустойчиво.        Так как при выполнении команд CLEAR и WRITE        область вывода очищается "до нуля", не        забывайте расставлять эти теги        самостоятельно.     BGCOLOR=BGR; // Устанавливаем цвет фона     IF (ISBOLD)       WRITE (SAMPLE.FONTCOLOR(FRG).       FONTSIZE(FSIZE).BOLD());          ELSE             WRITE (SAMPLE.FONTCOLOR(FRG).             FONTSIZE(FSIZE));     // Выводим форматированный текст-образец     WRITE ("
");// Выводим закрывающие теги CLOSE(); // Закрываем поток вывода } }

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

К сожалению, при выполнении WRITE() целевое окно или фрейм предварительно насильственно очищаются. Поэтому вы не можете что-то дописывать/перезаписывать на уже загруженной странице.

Метод SETTIMEOUT()

Этот метод является в JAVASCRIPT единственным явным способом управлять событиями в реальном времени. Как было сказано в предыдущей статье, JAVASCRIPT изначально не предназначался для такого применения. Создатели явно рассчитывали, что через этот метод будут в назначенное время загружаться страницы и выводиться сообщения: тихо, мирно, с паузами, измеряемыми минутами и секундами. А нехорошие пользователи сразу стали выжимать из него все возможное и невозможное: бегущие строки, динамические игры типа "Тетриса", мультипликацию. В результате из метода так и посыпались сбои, перечисление и описание которых заняло бы остаток статьи. К сожалению, что-либо универсальное посоветовать здесь невозможно, слишком велики изначальные "родимые пятна".

Коротко говоря, можно ли получать с помощью SETTIMEOUT динамические эффекты? Да, можно. В примере ниже приведена работающая программа для вывода текущего времени (подразумевается, что на странице имеется форма CLOCK, в ней текстовое поле DISPLAY, а при загрузке выполняется функция STARTCLOCK() ).

  VAR TIMERID = NULL;  VAR TIMERRUNNING = FALSE;    FUNCTION STOPCLOCK()  {    IF (TIMERRUNNING) CLEARTIMEOUT (TIMERID);    TIMERRUNNING = FALSE;  }  FUNCTION STARTCLOCK()  {    STOPCLOCK();    SHOWTIME();  }  FUNCTION SHOWTIME()  {    VAR NOW = NEW DATE();    DOCUMENT.CLOCK.DISPLAY.VALUE = NOW;    TIMERID = SETTIMEOUT("STARTCLOCK()",1000);    TIMERRUNNING = TRUE;  }

Другой вопрос - есть ли гарантия, что эта и любая подобная программа будет работать (запустится, не вызовет переполнения стека, не подвесит броузер и так далее) у всех без исключения посетителей страницы? Увы, нет, и в ближайшее время такой гарантии не предвидится. Если вам нужно через определенные промежутки времени (измеряемые секундами и более) загружать в окно или фрейм новые страницы, не забывайте о простой и удобной разновидности тега META. Например, если страница имеет шапку как на примере ниже, то без всякого JAVASCRIPT через 10 секунд ее место займет страница SECOND.HTM.

<HTML>  <HEAD>  <TITLE>Первая страница</TITLE>  <META HTTP-EQUIV="REFRESH" CONTENT="10;URL=  SECOND.HTM">  </HEAD>

Если же вам обязательно хочется создать нечто яркое и переливающееся, лучше не рисковать и не мучиться, а обратиться к апплетам JAVA, к VRML и иже с ними.

Методы объекта HISTORY

Как известно, этот объект служит для навигации по уже просмотренным страницам из программы на JAVASCRIPT.

Его проблема в том, что два из трех предлагаемых методов, BACK() и FORWARD(), порой наотрез отказываются работать в третьих версиях броузеров. Поэтому если вам потребуется перейти к предыдущей или следующей странице, лучше пока пользоваться более надежным методом GO():

  DOCUMENT.HISTORY.GO(1) // Переход к следующей         просмотренной странице  DOCUMENT.HISTORY.GO(-1) // Переход к предыдущей         странице

Объекты и методы в формах

О формах довольно много говорилось в июльском номере журнала. Подводя итог, можно сказать, что в этой области JAVASCRIPT является непревзойденным средством и позволяет создавать формы, реагирующие буквально на каждый вздох пользователя. Форма может подсказывать, поправлять, дополнять - все зависит только от умения и фантазии создателя.

Методы, предусмотренные в формах, в основном работают безупречно и соответствуют своим названиям и описаниям. Забавный (а порой и вовсе нет) дефект имеет раскрывающийся список (тег SELECT). По команде FORMNAME.LISTNAME.SELECTEDINDEX легко узнать номер выбранного элемента в списке. А вот попробуйте получить сам выбранный элемент! Очевидный вариант FORMNAME.LISTNAME.VALUE ничего не дает - возвращается значение NULL. Все прочие ухищрения ни к чему не привели. Самое обидное, что броузер все знает, а вот сказать никому не хочет: только серверу, уже при отправке формы. Упоминаю этот изъян в надежде: может быть, кто-то все же нашел решение?

Адресация между фреймами и окнами

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

Прежде чем перейти к примерам, немного теории. Верхнюю строчку в списке объектов JAVASCRIPT занимает объект WINDOW. В учебниках для начинающих с этого места обычно начинаются рассуждения о холодильниках, обладающих свойством "еда", тарелках и кастрюлях, относящихся к классу "посуда", и т. д. и т. п. Лично мне кажется, что сравнение окон или форм с тарелками и кастрюлями помогает весьма слабо. В нашем случае проще представить себе дорогу к любому элементу формы в виде привычного пути к файлу. В таком случае любой путь будет начинаться с WINDOW (корневой каталог). Затем, если речь идет о разбитом на фреймы окне, следует каталог TOP (в нем всегда хранится главное окно, в котором помещены фреймы). Затем следует название фрейма, которое было ему дано при создании. Помните?

Если этого не было сделано, придется сделать: без названий в JAVASCRIPT "и ни туды, и ни сюды".

Затем следует название формы - спускаемся еще на уровень вниз. И, наконец, следует название элемента формы (низший уровень), а через точку - метод, ради которого все это затевалось.

Пусть, например, ваше окно разбито на два фрейма: левый и правый. Пусть левый фрейм вы назвали LEFT, а правый - RIGHT. Предположим, скрипт у вас работает в левом фрейме, а в правом имеется форма ANKETA, а в ней - поле TIME. И вот вам очень хочется, чтобы значение поля TIME стало равно D. Тянемся из левого фрейма в правый:

WINDOW.TOP.RIGHT.ANKETA.TIME.VALUE=D // Уф!

Соответственно, из правого фрейма в левый мы придем так:

WINDOW.TOP.LEFT.FORMNAME.ELEMENT.NAME

Если вы захотите приказать из левого фрейма загрузить новый документ в правый фрейм, путь будет несколько иной:

WINDOW.TOP.RIGHT.DOCUMENT.OPEN()

Вот, например, функция, которая, выполняясь в левом фрейме, загружает в правый фрейм страницу с анкетой и заранее устанавливает курсор ввода в поле NAME:

  FUNCTION LOADPAGE()  {     WINDOW.TOP.RIGHT.DOCUMENT.OPEN("ANKETA.HTM");     // Загружаем страницу     WINDOW.TOP.RIGHT.ANKETA.NAME.FOCUS();     // Помещаем курсор в поле NAME  }

Перейдем к адресации между окнами. На рисунке ниже показан узел одного из обучающих центров. Основная страница с внутренним именем CENTER содержит левый фрейм с основным меню (название MENU) и правый, куда загружаются текущие документы (название MAIN). Посетитель щелкнул на ссылке QUICK COURSE SELECTION WIZARD в правом фрейме, в результате была выполнена команда

  W=WINDOW.OPEN("WIZARD.HTM", "WIZARD", "WIDTH=  400,HEIGHT=344,RESIZABLE=YES");

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

  WINDOW.CENTER.MAIN... // Далее могут     следовать названия формы и элемента

К сожалению, JAVASCRIPT не позволяет создавать модальные окна, то есть окна, которые всегда располагаются поверх остальных.