Выполнить javascript во фрейме

IFRAME для AJAX и COMET

Материал на этой странице устарел, поэтому скрыт из оглавления сайта.

Эта глава посвящена IFRAME – самому древнему и кросс-браузерному способу AJAX-запросов.

Сейчас он используется, разве что, для поддержки кросс-доменных запросов в IE7- и, что чуть более актуально, для реализации COMET в IE9-.

Для общения с сервером создаётся невидимый IFRAME . В него отправляются данные, и в него же сервер пишет ответ.

Введение

Сначала – немного вспомогательных функций и особенности работы с IFRAME .

Двуличность IFRAME: окно+документ

Что такое IFRAME? На этот вопрос у браузера два ответа

  1. IFRAME – это HTML-тег: со стандартным набором свойств.
    • Тег можно создавать в JavaScript
    • У тега есть стили, можно менять.
    • К тегу можно обратиться через document.getElementById и другие методы.
  2. IFRAME – это окно браузера, вложенное в основное
    • IFRAME – такое же по функциональности окно браузера, как и основное, с адресом и т.п.
      • Если документ в IFRAME и внешнее окно находятся на разных доменах, то прямой вызов методов друг друга невозможен.
    • Ссылку на это окно можно получить через window.frames[‘имя фрейма’] .

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

В теге свойство contentWindow хранит ссылку на окно.

Окна также содержатся в коллекции window.frames .

// Окно из ифрейма var iframeWin = iframe.contentWindow; // Можно получить и через frames, если мы знаем имя ифрейма (и оно у него есть) var iframeWin = window.frames[iframe.name]; iframeWin.parent == window; // parent из iframe указывает на родительское окно // Документ не будет доступен, если iframe с другого домена var iframeDoc = iframe.contentWindow.document;

Больше информации об ифреймах вы можете получить в главе Общение между окнами и фреймами.

IFRAME и история посещений

IFRAME – полноценное окно, поэтому навигация в нём попадает в историю посещений.

Это означает, что при нажатии кнопки «Назад» браузер вернёт посетителя назад не в основном окне, а в ифрейме. В лучшем случае – браузер возьмёт предыдущее состояние ифрейма из кэша и посетитель просто подумает, что кнопка не сработала. В худшем – в ифрейм будет сделан предыдущий запрос, а это уже точно ни к чему.

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

  • Ифрейм нужно создавать динамически, через JavaScript.
  • Когда ифрейм уже создан, то единственный способ поменять его src без попадания запроса в историю посещений:
// newSrc - новый адрес iframeDoc.location.replace(newSrc);

Таким образом, общий принцип использования IFRAME : динамически создать, сделать запрос, удалить.

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

Функция createIframe

Приведённая ниже функция createIframe(name, src, debug) кросс-браузерно создаёт ифрейм с данным именем и src .

name Имя и id ифрейма src Исходный адрес ифрейма. Необязательный параметр. debug Если параметр задан, то ифрейм после создания не прячется.

function createIframe(name, src, debug) < src = src || 'javascript:false'; // пустой src var tmpElem = document.createElement('div'); // в старых IE нельзя присвоить name после создания iframe // поэтому создаём через innerHTML tmpElem.innerHTML = ''; var iframe = tmpElem.firstChild; if (!debug) < iframe.style.display = 'none'; >document.body.appendChild(iframe); return iframe; >

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

Кстати, при вставке, если не указан src , тут же произойдёт событие iframe.onload . Пока обработчиков нет, поэтому оно будет проигнорировано.

Функция postToIframe

Функция postToIframe(url, data, target) отправляет POST-запрос в ифрейм с именем target , на адрес url с данными data .

url URL, на который отправлять запрос. data Объект содержит пары ключ:значение для полей формы. Значение будет приведено к строке. target Имя ифрейма, в который отправлять данные.

// Например: postToIframe('/vote', , 'frame1') function postToIframe(url, data, target) < var phonyForm = document.getElementById('phonyForm'); if (!phonyForm) < // временную форму создаём, если нет phonyForm = document.createElement("form"); phonyForm.id = 'phonyForm'; phonyForm.style.display = "none"; phonyForm.method = "POST"; document.body.appendChild(phonyForm); >phonyForm.action = url; phonyForm.target = target; // заполнить форму данными из объекта var html = []; for (var key in data) < var value = String(dataВыполнить javascript во фрейме).replace(/"/g, """); // в старых IE нельзя указать name после создания input // поэтому используем innerHTML вместо DOM-методов html.push(""); > phonyForm.innerHTML = html.join(''); phonyForm.submit(); >

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

В IFRAME можно отправлять и существующую форму, включающую файловые и другие поля.

Запросы GET и POST

Общий алгоритм обращения к серверу через ифрейм:

  1. Создаём iframe со случайным именем iframeName .
  2. Создаём в основном окне объект CallbackRegistry , в котором в CallbackRegistry[iframeName] сохраняем функцию, которая будет обрабатывать результат.
  3. Отправляем GET или POST-запрос в него.
  4. Сервер отвечает как-то так:
  

Подробнее можно понять процесс, взглянув на код.

Мы будем использовать в нём две функции – одну для GET, другую – для POST:

  • iframeGet(url, onSuccess, onError) – для GET-запросов на url . При успешном запросе вызывается onSuccess(result) , при неуспешном: onError() .
  • iframePost(url, data, onSuccess, onError) – для POST-запросов на url . Значением data должен быть объект ключ:значение для пересылаемых данных, он конвертируется в поля формы.

Пример в действии, возвращающий дату сервера при GET и разницу между датами клиента и сервера при POST:

function createIframe(name, src, debug) < src = src || 'javascript:false'; // пустой src var tmpElem = document.createElement('div'); // в старых IE нельзя присвоить name после создания iframe, поэтому создаём через innerHTML tmpElem.innerHTML = ''; var iframe = tmpElem.firstChild; if (!debug) < iframe.style.display = 'none'; >document.body.appendChild(iframe); return iframe; > // функция постит объект-хэш content в виде формы с нужным url , target // напр. postToIframe('/count.php', , 'frame1') function postToIframe(url, data, target) < var phonyForm = document.getElementById('phonyForm'); if (!phonyForm) < // временную форму создаем, если нет phonyForm = document.createElement("form"); phonyForm.id = 'phonyForm'; phonyForm.style.display = "none"; phonyForm.method = "POST"; phonyForm.enctype = "multipart/form-data"; document.body.appendChild(phonyForm); >phonyForm.action = url; phonyForm.target = target; // заполнить форму данными из объекта var html = []; for (var key in data) < var value = String(dataВыполнить javascript во фрейме).replace(/"/g, """); html.push(""); > phonyForm.innerHTML = html.join(''); phonyForm.submit(); > var CallbackRegistry = <>; // реестр function iframeGet(url, onSuccess, onError) < var iframeOk = false; // флаг успешного ответа сервера var iframeName = Math.random(); // случайное имя для ифрейма var iframe = createIframe(iframeName, url); CallbackRegistry[iframeName] = function(data) < iframeOk = true; // сервер ответил успешно onSuccess(data); >iframe.onload = function() < iframe.parentNode.removeChild(iframe); // очистка delete CallbackRegistry[iframeName]; if (!iframeOk) onError(); // если сервер не ответил как надо - что-то не так >> // аналогично iframeGet, но в postToIframe передаются данные data function iframePost(url, data, onSuccess, onError) < var iframeOk = false; var iframeName = Math.random(); var iframe = createIframe(iframeName); CallbackRegistry[iframeName] = function(data) < iframeOk = true; onSuccess(data); >iframe.onload = function() < iframe.parentNode.removeChild(iframe); // очистка delete CallbackRegistry[iframeName]; if (!iframeOk) onError(); // если коллбэк не вызвался - что-то не так >postToIframe(url, data, iframeName); >
var http = require('http'); var url = require('url'); var static = require('node-static'); var file = new static.Server('.', < cache: 0 >); var multiparty = require('multiparty'); function accept(req, res) < var urlParsed = url.parse(req.url, true); res.setHeader('Cache-Control', 'no-cache'); if (urlParsed.pathname == '/server') < res.end(wrap(new Date())); return; >else if (urlParsed.pathname == '/diff') < var form = new multiparty.Form(); form.parse(req, function(err, fields, files) < var diff = new Date() - fields.clientDate[0]; res.end(wrap(diff)); >); > else < file.serve(req, res); >> function wrap(data) < return ''; > // ------ запустить сервер ------- if (!module.parent) < http.createServer(accept).listen(8080); >else
    function ok(result) < alert('result: ' + result); >function fail()    

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

  • В IE8+ есть интерфейс postMessage для общения между окнами с разных доменов.
  • В любых, даже самых старых IE, можно обмениваться данными через window.name . Эта переменная хранит «имя» окна или фрейма, которое не меняется при перезагрузке страницы. Поэтому если мы сделали POST в на другой домен и он поставил window.name = «Вася» , а затем сделал редирект на основной домен, то эти данные станут доступны внешней странице.
  • Также в совсем старых IE можно обмениваться данными через хеш, то есть фрагмент URL после # . Его изменение доступно между ифреймами с разных доменов и не приводит к перезагрузке страницы. Таким образом они могут передавать данные друг другу. Есть готовые библиотеки, которые реализуют этот подход, например Porthole.

IFRAME для COMET

Бесконечный IFRAME – самый старый способ организации COMET. Когда-то он был основой AJAX-приложений, а сейчас – используется лишь в случаях, когда браузер не поддерживает современный стандарт WebSocket, то есть для IE9-.

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

Классическая реализация – это когда клиент создаёт невидимый IFRAME, ведущий на служебный URL. Сервер, получив соединение на этот URL, не закрывает его, а время от времени присылает блоки сообщений . Появившийся в IFRAME’е javascript тут же выполняется браузером, передавая информацию на основную страницу.

Таким образом, для передачи данных используется «бесконечный» ифрейм, через который сервер присылает все новые данные.

  1. Создаётся , по адресу COMET_URL расположен сервер.
  2. Сервер выдаёт начало («шапку») документа и останавливается, оставляя соединение активным.
  3. Когда сервер хочет что-то отправить – он пишет в соединение Браузер тут же выполняет этот скрипт – так сообщение приходит на клиент.
  4. Ифрейм, в теории, грузится бесконечно. Его завершение означает обрыв канала связи. Его можно поймать по iframe.onload и заново открыть соединение (создать новый iframe ).

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

Ифрейм при этом работает только на получение данных с сервера, как альтернатива Server Sent Events. Для запросов используется обычный XMLHttpRequest .

Обход проблем с IE

Такое использование ифреймов является хаком. Поэтому есть ряд проблем:

  1. Показывается индикатор загрузки, «курсор-часики».
  2. При POST в раздаётся звук «клика».
  3. Браузер буферизует начало страницы.

Мы должны эти проблемы решить, прежде всего, в IE, поскольку в других браузерах есть WebSocket и Server Sent Events .

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

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

     . 

Решение – забить начало ифрейма чем-нибудь, поставить, например, килобайт пробелов в начале:

   ******* 1 килобайт пробелов, а потом уже сообщения ******   . 

Для решения проблемы с индикацией загрузки и клика мы можем использовать безопасный ActiveX-объект htmlfile . IE не требует разрешений на его создание. Фактически, это независимый HTML-документ.

Оказывается, если iframe создать в нём, то никакой анимации и звуков не будет.

  1. Основное окно main создаёт вспомогательный объект: new ActiveXObject(«htmlfile») . Это HTML-документ со своим window , похоже на встроенный iframe .
  2. В htmlfile записывается iframe .
  3. Цепочка общения: основное окно – htmlfile – ифрейм.

iframeActiveXGet

На самом деле всё ещё проще, если посмотреть на код:

Метод iframeActiveXGet по существу идентичен обычному iframeGet , которое мы рассмотрели. Единственное отличие – вместо createIframe используется особый метод createActiveXFrame :

function iframeActiveXGet(url, onSuccess, onError) < var iframeOk = false; var iframeName = Math.random(); var iframe = createActiveXFrame(iframeName, url); CallbackRegistry[iframeName] = function(data) < iframeOk = true; onSuccess(data); >iframe.onload = function() < iframe.parentNode.removeChild(iframe); // очистка delete CallbackRegistry[iframeName]; if (!iframeOk) onError(); // если колбэк не вызвался - что-то не так >>

createActiveXFrame

В этой функции творится вся IE-магия:

function createActiveXFrame(name, src) < // (1) var htmlfile = window.htmlfile; if (!htmlfile) < htmlfile = window.htmlfile = new ActiveXObject("htmlfile"); htmlfile.open(); // (2) htmlfile.write(" "); htmlfile.close(); // (3) htmlfile.parentWindow.CallbackRegistry = CallbackRegistry; >// (4) src = src || 'javascript:false'; htmlfile.body.insertAdjacentHTML('beforeEnd', " "); return htmlfile.body.lastChild; >
  1. Вспомогательный объект htmlfile будет один и он будет глобальным. Можно и спрятать переменную в замыкании. Смысл в том, что в один htmlfile можно записать много ифреймов, так что не будем множить сущности и занимать ими лишнюю память.
  2. В htmlfile можно записать любой текст и, при необходимости, через document.write(‘. ) . Здесь мы делаем пустой документ.
  3. Когда загрузится iframe , он сделает вызов:
  

Пример в действии (только IE):

Источник

Читайте также:  Ubuntu apache php not working
Оцените статью