Фреймы. Согласование состояния
Третьяков М.Ю., Тимофеев И.Ю.
Компания Ключ.

Резюме

В статье описывается способ согласования данных в разных фреймах. В качестве задач такого согласования можно представить: 

Данные задачи и будут решены в статье в качестве демонстрации предлагаемого подхода. Решения описаны для реализации как на стороне клиента, так и на стороне сервера.

Содержание

Введение
Решаемые задачи
Управление фреймовой структурой
Синхронизация оглавления и основной страницы
Выделение текущего элемента в меню
Заключение

Введение

Данная статья описывает решение еще одного аспекта взаимодействия фреймов, возникающего при создании веб-сайтов, а именно, синхронизации состояния разных фреймов.

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

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

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

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

 

Решаемые задачи

Пример учебного сайта

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

menu
toc main

Структура окна состоит из трех фреймов: menu, toc (table of content - оглавление) и main. Фрейм menu является статичным и всегда содержит страницу menu.htm, в которой описывается меню сайта. Фрейм toc содержит оглавление основной, информационной страницы, которая отображается во фрейме main.

Для описания такой структуры сайта используется следующий код:

<frameset rows="40,*">
  <frame name="menu" src="menu.htm">
  <frameset cols="40,*">
    <frame name="toc" src="toc.htm">
    <frame name="main" src="index.htm">
  </frameset>   
</frameset>

Задача синхронизации оглавления и основной страницы

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

Задача выделения текущего раздела в меню

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

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

Управление фреймовой структурой

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

Те, кто хорошо помнят реализацию этого подхода, могут пропустить данный раздел.

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

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

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

Реализация на стороне клиента

Функция CheckFrames

Функция CheckFrames занимается проверкой и созданием, при необходимости, фреймовой структуры, и, т.к. эта функция используется во всех страницах, то мы ее вынесем в отдельный файл frames.js.

function CheckFrames{
  if (window.name != "main"){
    var PageURL = document.URL;
    window.name="root";
    document.write("<frameset rows='40,*'>");
    document.write("<frame name='menu' src='menu.htm'>");
    document.write("<frameset cols='40,*'>");
    document.write("<frame name='toc' src='toc.htm'>");
    document.write("<frame name='main' src='" + PageURL + "?embedded=yes'>");
    document.write("</frameset>");
    document.write("</frameset>");
  }
}

Изменения основных страниц

В каждую страницу сайта между заголовком и телом необходимо добавить следующий код:

  <script language="JavaScript" src="frames.js">
  </script>
  <script language="JavaScript">
    CheckFrames();
  </script>

Реализация на стороне сервера

Функция CheckFrames

Как и в случае с реализацией на стороне клиента, реализация функции проверки и создания вынесена в отдельный файл - frames.inc, который затем будет подключаться ко всем страницам сайта.

<%
function CheckFrames()
  if (Request.QueryString("embedded").Count = 0) then
    CheckFrames = False
    PageURL = "http://" & Request.ServerVariables("server_name") & Request.ServerVariables("script_name")
    Response.write("<frameset rows='40,*'>")
    Response.write("<frame name='menu' src='menu.asp'>")
    Response.write("<frameset cols='40,*'>")
    Response.write("<frame name='toc' src='toc.asp'>")
    Response.write("<frame name='main' src='" + PageURL + "?embedded=yes'>")
    Response.write("</frameset>")
    Response.write("</frameset>")
  else
    CheckFrames = True
  end if
end function
%>

Изменения основных страниц

В начале каждой страницы между заголовком и телом необходимо добавить следующий код:

  <!--#include virtual="frames.inc"-->
  <% if not CheckFrames() then Response.End()%>

Синхронизация оглавления и основной страницы

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

Реализация на стороне клиента

Функция CheckFrames

Во-первых, мы изменим сигнатуру метода, и теперь прототип метода будет выглядеть следующим образом:

function CheckFrames(TOC_URL){
}

Во-вторых, мы изменим способ определения страницы для  фрейма toc.

function CheckFrames(TOC_URL){
  if (window.name != "main"){
    var PageURL = document.URL;
    window.name="root";
    document.write("<frameset rows='40,*'>");
    document.write("<frame name='menu' src='menu.htm'>");
    document.write("<frameset cols='40,*'>");
    document.write("<frame name='toc' src='" + TOC_URL + "'>");
    document.write("<frame name='main' src='" + PageURL + "?embedded=yes'>");
    document.write("</frameset>");
    document.write("</frameset>");
  }
}

Как видно, изменения сделанные нами, абсолютно незначительны.

Изменения основных страниц

Теперь изменим вызов функции CheckFrames:

  <script language="JavaScript">
    var TOC_URL = "toc.htm";
    CheckFrames(TOC_URL);
  </script>

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

Предложенное здесь решение является наиболее простым с точки зрения программирования, но требует изменения значения переменной TOC_URL в каждой информационной странице. Чтобы этого избежать, можно принять определенную дисциплину именования страниц-оглавлений, например, добавлением префикса toc_  к названию информационной страницы, к которой относится оглавление. В этом случае можно так изменить процесс получения адреса страницы-оглавления на основании адреса самой информационной страницы, что этот скрипт можно будет вставлять на все страницы без малейших изменений и даже воспользоваться для этих целей механизмом SSI (Server Side Includes - вставки на стороне сервера).

Реализация на стороне сервера

Функция CheckFrames

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

<%
function CheckFrames(TOC_URL)
  if (Request.QueryString("embedded").Count = 0) then
    CheckFrames = False
    PageURL = "http://" & Request.ServerVariables("server_name") & Request.ServerVariables("script_name")
    Response.write("<frameset rows='40,*'>")
    Response.write("<frame name='menu' src='menu.asp'>")
    Response.write("<frameset cols='40,*'>")
    Response.write("<frame name='toc' src='"+TOC_URL+"'>")
    Response.write("<frame name='main' src='" + PageURL + "?embedded=yes'>")
    Response.write("</frameset>")
    Response.write("</frameset>")
  else
    CheckFrames = True
  end if
end function
%>

Изменения основных страниц

Теперь необходимо изменить основные страницы, добавив на каждую адрес ее страницы-оглавления:

  <!--#include virtual="frames.inc"-->
  <% TOC_URL = "toc.asp" %>
  <% if not CheckFrames(TOC_URL) then Response.End()%>

Выделение текущего элемента в меню

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

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

Каждый элемент в меню будет представлен в виде блока следующей структуры:

  <span id="topic_id"><a href="topic.htm">Topic</a></span>

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

  {background-color: #FF0000; font-weight: bold}

Для того, чтобы выделение подействовало только на нужный нам элемент, мы будем генерировать таблицу стилей при загрузки страницы меню, а для задания элемента, к которому мы будем применять этот стиль, воспользуемся ID-селекторами таблиц стилей. Именно поэтому, для каждого элемента меню в теге span задан атрибут id, являющийся его идентификатором.

Таким образом, мы получим следующий код:

  <style>
    #topic_id {background-color: #FF0000; font-weight: bold}
  </style>

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

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

Реализация на стороне клиента

Функция CheckFrames

Для обеспечения передачи идентификатора текущего раздела в меню мы изменим функцию CheckFrames следующим образом:

  function CheckFrames(TOC_URL, topic){
    if (window.name != "main"){
      var PageURL = document.URL;
      window.name="root";
      document.write("<frameset rows='40,*'>");
      document.write("<frame name='menu' src='menu.htm?topic='" + topic + "'>");
      document.write("<frameset cols='40,*'>");
      document.write("<frame name='toc' src='" + TOC_URL + "'>");
      document.write("<frame name='main' src='" + PageURL + "?embedded=yes'>");
      document.write("</frameset>");
      document.write("</frameset>");
    }
  }

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

Изменения основных страниц

Изменения основных страниц, аналогичны тем, что мы проделали в случае синхронизации оглавления с основной страницей:

  <script language="JavaScript">
    var TOC_URL = "toc.htm";
    var topic = "topic1";
    CheckFrames(TOC_URL, topic);
  </script>

Страница меню

Новая реализация функции CheckFrames обеспечивает получение текущего раздела в виде параметра в адресной строке. Поэтому нам необходимо получить значение текущего раздела и сформировать таблицу стилей.

Разместим скрипт, по формированию таблицы стилей, между заголовком и телом страницы.

  <html>
    <head>
      ...
    </head>
    <script language="JavaScript">
    </script>
    <body>
      ...
    </body>
  </html>

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

    <script language="JavaScript">
      var re = /\?.*topic=([^&]*)/i;
      var arr = re.exec(document.location);
      if ((arr != null) && (arr.length>=2)){
        var topic = arr[1]; 
      }
    </script>

После выполнения этого кода, в переменной topic будет находится значение текущего раздела. И теперь нам осталось только сформировать таблицу стилей с помощью метода document.write.

    <script language="JavaScript">
      var re = /\?.*topic=([^&]*)/i;
      var arr = re.exec(document.location);
      if ((arr != null) && (arr.length>=2)){
        var topic_id = arr[1]; 
        document.write("<style>");
        document.write("#" + topic + "{background-color: #FF0000; font-weight: bold}");
        document.write("</style>");
      }
    </script>

Все. Реализация на стороне клиента выполнена.

Реализация на стороне сервера

Функция CheckFrames

Изменения в функции CheckFrames полностью аналогичны тем, что мы сделали при реализации на клиенте.

  <%
  function CheckFrames(TOC_URL, topic)
    if (Request.QueryString("embedded").Count = 0) then
      CheckFrames = False
      PageURL = "http://" & Request.ServerVariables("server_name") & Request.ServerVariables("script_name")
      Response.write("<frameset rows='40,*'>")
      Response.write("<frame name='menu' src='menu.asp?topic='" + topic + "'>")
      Response.write("<frameset cols='40,*'>")
      Response.write("<frame name='toc' src='"+TOC_URL+"'>")
      Response.write("<frame name='main' src='" + PageURL + "?embedded=yes'>")
      Response.write("</frameset>")
      Response.write("</frameset>")
    else
      CheckFrames = True
    end if
  end function
  %>

Изменения основных страниц

Повторим, ставшую уже стандартной, процедуру изменения основных страниц:

  <!--#include virtual="frames.inc"-->
  <% TOC_URL = "toc.asp" %>
  <% topic = "topic" %>
  <% if not CheckFrames(TOC_URL, topic) then Response.End()%>

Страница меню

Реализация на стороне сервера страницы меню сильно отличается от ее реализации на стороне клиента, благодаря сервису, предоставляемому ASP. Представленный ниже код необходимо разместить между заголовком и телом страницы menu.asp:

  <% if not (Request.QueryString("topic").Count = 0) then %>
  <style>
    <% topic = Request.QueryString("topic")(1) %>
    <% Response.write("#" + topic + " {background-color: #FF0000; font-weight: bold}") %>
  </style>
  <% end if %>

Коллекция Reques.QueryString предоставляет доступ к параметрам, переданным в адресной строке. Благодаря этому получение значения текущего раздела существенно упрощается по сравнению с реализацией на стороне клиента. Затем с помощью метода Response.write мы записываем таблицу стилей.

Все. Реализация на стороне сервера полностью выполнена.

Заключение

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

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

--<< Возврат к содержанию >>-- Назад