diff --git a/1-js/4-data-structures/11-datetime/article.md b/1-js/4-data-structures/11-datetime/article.md
index bfd6539c..148c00a8 100644
--- a/1-js/4-data-structures/11-datetime/article.md
+++ b/1-js/4-data-structures/11-datetime/article.md
@@ -414,7 +414,7 @@ alert( d.toString() ); // вывод, похожий на 'Wed Jan 26 2011 16:40
То же самое, что `toString()`, но дата в зоне UTC.
`toISOString()`
-
Возвращает дату в формате ISO Детали формата будут далее. Поддерживается современными браузерами, не поддерживается IE<9.
+
Возвращает дату в формате ISO Детали формата будут далее. Поддерживается современными браузерами, не поддерживается IE8-.
```js
//+ run
diff --git a/1-js/5-functions-closures/2-closures/article.md b/1-js/5-functions-closures/2-closures/article.md
index a87125fb..07d6781a 100644
--- a/1-js/5-functions-closures/2-closures/article.md
+++ b/1-js/5-functions-closures/2-closures/article.md
@@ -102,7 +102,7 @@ sayHi.[[Scope]] = window
При запуске функции её объект переменных `LexicalEnvironment` получает ссылку на "внешнее лексическое окружение" со значением из `[[Scope]]`.
-Если переменная не найдена в функции -- она будет искаться в снаружи.
+Если переменная не найдена в функции -- она будет искаться снаружи.
Именно благодаря этой механике в примере выше `alert(userName)` выводит внешнюю переменную. На уровне кода это выглядит как поиск во внешней области видимости, вне функции.
diff --git a/3-more/2-ajax/10-ajax-jsonp/article.md b/3-more/2-ajax/10-ajax-jsonp/article.md
index fde6abb0..9fdf1880 100644
--- a/3-more/2-ajax/10-ajax-jsonp/article.md
+++ b/3-more/2-ajax/10-ajax-jsonp/article.md
@@ -1,97 +1,100 @@
-# JSONP, получение данных через SCRIPT
+# Протокол JSONP
-Если создать узел `SCRIPT` со ссылкой на внешний исходник, то при добавлении в документ запустится процесс загрузки. В ответ сервер может прислать скрипт, содержащий нужные данные.
+Если создать тег `
+
```
-Браузер тут же обработает его: запросит `/user.php?id=123` с заданного URL и выполнит.
+Браузер тут же обработает его: запросит `/user?id=123` с заданного URL и выполнит.
## Обработка ответа, JSONP
-В примере выше рассмотрено создание запроса, но как получить ответ? Самый простой способ -- это присвоить его в переменную, т.е. сервер ответит как-то так:
+В примере выше рассмотрено создание запроса, но как получить ответ? Допустим, сервер хочет прислать объект с данными.
+
+Конечно, он может присвоить её в переменную, например так:
+
```js
-var response = "....";
+// ответ сервера
+var user = {name: "Вася", age: 25 };
```
-...А браузер по `script.onload` отловит окончание загрузки и получит значение `response`.
+...А браузер по `script.onload` отловит окончание загрузки и прочитает значение `user`.
-Но что, если одновременно делается несколько запросов? Получается, нужно присваивать в разные переменные.
+Но что, если одновременно делается несколько запросов? Получается, нужно присваивать в разные переменные.
-Для большей гибкости при ответе используется протокол `JSONP`.
+Протокол JSONP как раз и призван облегчить эту задачу.
-**При этом название функции браузер может передать вместе с запросом.**
+Он очень простой:
-Запрос:
+
+
Вместе с запросом клиент в специальном, заранее оговорённом, параметре передаёт название функции.
+
+Обычно такой параметр называется `callback`. Например :
```js
-attachScript('/user.php?id=123&*!*callback=onUserData*/!*');
+addScript('user?id=123&*!*callback=onUserData*/!*');
```
-
-Cервер кодирует данные как JavaScript-объект и оборачивает их в функцию, название которой получает из параметра `callback`:
+
+
Cервер кодирует данные в JSON и оборачивает их в функцию, название которой получает из параметра `callback`:
```js
+// ответ сервера
onUserData({
name: "Вася",
- age: 25,
- isAdmin: false
+ age: 25
});
```
-
-**Формат запроса, когда JSON заворачивается в функцию, называется JSONP ("JSON with Padding").**
-
-[warn header="Аспекты безопасности"]
-
-
При кросс-доменном получении скрипта JavaScript не имеет доступа к его тексту.
-
Клиентский код должен доверять серверу при таком запросе. Ведь серверу ничего не стоит добавить в скрипт любые команды.
+
+
+Это и называется JSONP ("JSON with Padding").
+
+
+[warn header="Аспект безопасности"]
+Клиентский код должен доверять серверу при таком запросе. Ведь серверу ничего не стоит добавить в скрипт любые команды.
[/warn]
## Реестр CallbackRegistry
-В примере выше функция `onUserData` должна быть глобальной, иначе скрипт её не увидит.
+В примере выше функция `onUserData` должна быть глобальной, ведь `
+
```
Сервер обернёт ответ в функцию `CallbackRegistry.func12345`, она вызывает нужный обработчик и очищает память, удаляя себя.
-Далее мы посмотрим код всего этого, но перед этим -- важный момент! Нужно предусмотреть обработку ошибок.
+Далее мы посмотрим более полный код всего этого, но перед этим -- важный момент! Нужно предусмотреть обработку ошибок.
## Обнаружение ошибок
@@ -106,39 +109,23 @@ scriptRequest("/user.php?id=123", function(data) {
Чтобы отловить их все "одним махом", используем следующий алгоритм:
-
Создаётся `SCRIPT`.
-
На `SCRIPT` ставятся обработчики `onreadystatechange` (для старых IE) и `onload/onerror` (для остальных браузеров).
+
Создаётся `
-[/head]
\ No newline at end of file
diff --git a/3-more/2-ajax/10-ajax-jsonp/jsonp.view/index.html b/3-more/2-ajax/10-ajax-jsonp/jsonp.view/index.html
new file mode 100644
index 00000000..d4cbb4c2
--- /dev/null
+++ b/3-more/2-ajax/10-ajax-jsonp/jsonp.view/index.html
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/3-more/2-ajax/10-ajax-jsonp/scriptRequest.js b/3-more/2-ajax/10-ajax-jsonp/jsonp.view/scriptRequest.js
similarity index 97%
rename from 3-more/2-ajax/10-ajax-jsonp/scriptRequest.js
rename to 3-more/2-ajax/10-ajax-jsonp/jsonp.view/scriptRequest.js
index 601bc41d..acca02eb 100644
--- a/3-more/2-ajax/10-ajax-jsonp/scriptRequest.js
+++ b/3-more/2-ajax/10-ajax-jsonp/jsonp.view/scriptRequest.js
@@ -6,7 +6,7 @@ function scriptRequest(url, onSuccess, onError) {
var scriptOk = false; // флаг, что вызов прошел успешно
// сгенерировать имя JSONP-функции для запроса
- var callbackName = 'f'+String(Math.random()).slice(2);
+ var callbackName = 'cb' + String(Math.random()).slice(-6);
// укажем это имя в URL запроса
url += ~url.indexOf('?') ? '&' : '?';
diff --git a/3-more/2-ajax/10-ajax-jsonp/jsonp.view/server.js b/3-more/2-ajax/10-ajax-jsonp/jsonp.view/server.js
new file mode 100644
index 00000000..1f2103df
--- /dev/null
+++ b/3-more/2-ajax/10-ajax-jsonp/jsonp.view/server.js
@@ -0,0 +1,38 @@
+var http = require('http');
+var url = require('url');
+var static = require('node-static');
+var file = new static.Server('.', { cache: 0 });
+
+
+function accept(req, res) {
+
+ var urlParsed = url.parse(req.url, true);
+
+ if (urlParsed.pathname == '/user') {
+ var id = urlParsed.query.id;
+ var callback = urlParsed.query.callback;
+
+ res.setHeader('Content-Type', 'application/javascript; charset=utf-8');
+
+ var user = {
+ name: "Вася",
+ id: id
+ };
+
+ res.end(callback + '(' + JSON.stringify(user) + ')');
+
+ } else {
+ file.serve(req, res);
+ }
+
+}
+
+
+// ------ запустить сервер -------
+
+if (!module.parent) {
+ http.createServer(accept).listen(8080);
+} else {
+ exports.accept = accept;
+}
+
diff --git a/3-more/2-ajax/11-server-sent-events/article.md b/3-more/2-ajax/11-server-sent-events/article.md
index 0c3a050d..232e5ea1 100644
--- a/3-more/2-ajax/11-server-sent-events/article.md
+++ b/3-more/2-ajax/11-server-sent-events/article.md
@@ -1,6 +1,15 @@
-# EventSource - события с сервера
+# Server Side Events -- события с сервера
+
+Сразу заметим, что на текущий момент этот способ поддерживают все современные браузеры, кроме IE.
+
+Современный стандарт [Server-Sent Events](https://html.spec.whatwg.org/multipage/comms.html#the-eventsource-interface) позволяет браузеру создавать специальный объект `EventSource`, который сам обеспечивает соединение с сервером, делает пересоединение в случае обрыва и генерирует события при поступлении данных.
+
+Он, по дизайну, может меньше, чем WebSocket'ы.
+
+С другой стороны, Server Side Events проще в реализации, работают по обычному протоколу HTTP и сразу поддерживают ряд возможностей, которые для WebSocket ещё надо реализовать.
+
+Поэтому в тех случаях, когда нужна преимущественно односторонняя передача данных от сервера к браузеру, они могут быть удачным выбором.
-Современный стандарт [Server-Sent Events](http://dev.w3.org/html5/eventsource/) позволяет браузеру создавать специальный объект `EventSource`, который сам обеспечивает соединение с сервером, делает пересоединение в случае обрыва и генерирует события при поступлении данных.
[cut]
## Получение сообщений
@@ -12,7 +21,7 @@ var eventSource = new EventSource("/events/subscribe");
eventSource.onmessage = function(e) {
console.log("Пришло сообщение: " + e.data);
-}
+};
```
Чтобы соединение успешно открылось, сервер должен ответить с заголовком `Content-Type: text/event-stream`, а затем оставить соединение висящим и писать в него сообщения в специальном формате:
@@ -35,13 +44,15 @@ data: из двух строк
-Здесь все очень просто и удобно, кроме разделения сообщения при переводе строки. Но, если подумать -- это не так уж страшно: на практике сложные сообщения обычно передаются в формате JSON. А перевод строки в нём кодируется как `\n`. Соответственно, что данные будут пересылаться так:
+Здесь все очень просто и удобно, кроме разделения сообщения при переводе строки. Но, если подумать -- это не так уж страшно: на практике сложные сообщения обычно передаются в формате JSON. А перевод строки в нём кодируется как `\n`.
+
+Соответственно, многострочные данные будут пересылаться так:
```
data: {"user":"Вася","message":"Сообщение 3\n из двух строк"}
```
-..То есть, строка `data:` будет одна.
+...То есть, строка `data:` будет одна, и накаких проблем с разделением сообщения нет.
## Восстановление соединения
@@ -49,7 +60,7 @@ data: {"user":"Вася","message":"Сообщение 3\n из двух стр
Это очень удобно, никакой другой транспорт не обладает такой встроенной способностью.
-[smart header="А что, если сервер "чисто" закрыл соединение?"]
+[smart header="Как серверу полностью закрыть соединение?"]
При любом закрытии соединения, в том числе если сервер ответит на запрос и закроет соединение сам -- браузер через короткое время повторит свой запрос.
Есть лишь два способа, которыми сервер может "отшить" надоедливый `EventSource`:
@@ -145,7 +156,7 @@ eventSource.onmessage = function(e) {
};
```
-### Своё имя события: event
+## Своё имя события: event
По умолчанию на события срабатывает обработчик `onmessage`, но можно сделать и свои события. Для этого сервер должен указать перед событием его имя после `event:`.
@@ -170,34 +181,22 @@ data: Вася
```js
eventSource.addEventListener('join', function(e) {
alert('Пришёл ' + e.data);
-}
+});
eventSource.addEventListener('message', function(e) {
alert('Сообщение ' + e.data);
-}
+});
eventSource.addEventListener('leave', function(e) {
alert('Ушёл ' + e.data);
-}
+});
```
-На момент написания этой статьи (конец 2012) браузер Chrome не поддерживал свои события.
-
## Демо
-В примере ниже сервер посылает в соединение числа от 1 до 5, а затем -- событие `bye` и закрывает соединение. Браузер автоматически откроет его заново.
+В примере ниже сервер посылает в соединение числа от 1 до 3, а затем -- событие `bye` и закрывает соединение. Браузер автоматически откроет его заново.
-```js
-//+ src="browser.js"
-```
-
-
-
-```js
-//+ src="index.js" hide="Открыть код для сервера"
-```
-
-[iframe src="eventsource" border="1" zip link height=200]
+[codetabs src="eventsource"]
## Кросс-доменность
@@ -209,21 +208,46 @@ var source = new EventSource("http://pupkin.ru/stream", { withCredentials: true
Второй аргумент сделан объектом с расчётом на будущее. Пока что никаких других свойств там не поддерживается, только `withCredentials`.
-При обработки события у `event` также появится свойство `origin`, содержащее адрес источника, откуда пришёл ответ.
+Сервер при этом получит заголовок `Origin` с доменом запроса и должен ответить с заголовком `Access-Control-Allow-Origin` (и `Access-Control-Allow-Credentials`, если стоит `withCredentials`), в точности как в главе [](/xhr-crossdomain).
+
+При кросс-доменных запросах у событий `event` также появится дополнительное свойство `origin`, содержащее адрес источника, откуда пришли данные. Его можно использовать для дополнительной проверки со стороны браузера:
+
+```js
+eventSource.addEventListener('message', function(e) {
+ if (e.origin != 'http://javascript.ru') return;
+ alert('Сообщение ' + e.data);
+});
+```
## Итого
-Объект `EventSource` создаётся так:
+Объект `EventSource` предназначен для передачи текстовых сообщений с сервера, используя обычный протокол HTTP.
+Он предлагает не только передачу сообщений, но и встроенную поддержку важных вспомогательных функций:
+
+
+
События `event`.
+
Автоматическое пересоединение, с настраиваемой задержкой `retry`.
+
Проверка текущего состояния подключения по `readyState`.
+
Идентификаторы сообщений `id` для точного возобновления потока данных, последний полученный идентификатор передаётся в заголовке `Last-Event-ID`.
+
Кросс-доменность CORS.
+
+
+Этот набор функций делает EventSource достойной альтернативой WebSocket, которые хоть и потенциально мощнее, но требуют реализации всех этих функций на клиенте и сервере, поверх протокола.
+
+Поддержка -- все браузеры, кроме IE.
+
+
+
Синтаксис:
```js
-var source = new EventSource(src); // src - адрес с любого домена
+var source = new EventSource(src[, credentials]); // src - адрес с любого домена
```
Второй необязательный аргумент, если указан в виде `{ withCredentials: true }`, инициирует отправку Cookie и данных авторизации при кросс-доменных запросах.
Безопасность при кросс-доменных запросах обеспечивается аналогично `XMLHttpRequest`.
-
-**Свойства объекта:**
+
+
Свойства объекта:
`readyState`
Текущее состояние соединения, одно из `EventSource.CLOSING (=0)`, `EventSource.OPEN (=1)` или `EventSource.CLOSED (=2)`.
@@ -233,15 +257,15 @@ var source = new EventSource(src); // src - адрес с любого доме
Параметры, переданные при создании объекта. Менять их нельзя.
-
-**Методы:**
+
+
Методы:
`close()`
Закрывает соединение.
-
-**События:**
+
+
События:
`onmessage`
@@ -255,33 +279,17 @@ var source = new EventSource(src); // src - адрес с любого доме
Эти события можно ставить напрямую через свойство: `source.onmessage = ...`.
Если сервер присылает имя события в `event:`, то такие события нужно обрабатывать через `addEventListener`.
-
-**Формат ответа сервера:**
+
`date:` -- сообщение, несколько таких строк подряд склеиваются и образуют одно сообщение.
`id:` -- обновляет `lastEventId`.
`retry:` -- указывает паузу между пересоединениями, в миллисекундах. JavaScript не может указать это значение, только сервер.
`event:` -- имя события, должен быть перед `date:`.
-
-Сообщения разделяются двойным переводом строки.
-
-Основным преимуществом `EventSource` является поддержка пересоединения и событий "из коробки".
-
-Поддержка -- все браузеры, кроме IE.
+
+
-
-
-
-
-
-
-
-
-
-[head]
-
-[/head]
\ No newline at end of file
diff --git a/3-more/2-ajax/11-server-sent-events/browser.js b/3-more/2-ajax/11-server-sent-events/browser.js
deleted file mode 100644
index b64a2d39..00000000
--- a/3-more/2-ajax/11-server-sent-events/browser.js
+++ /dev/null
@@ -1,36 +0,0 @@
-
-if (!window.EventSource) {
- document.write('В этом браузере нет поддержки EventSource.');
-}
-
-var eventSource;
-
-function start() { // при нажатии на Старт
- eventSource = new EventSource('digits');
-
- var log = document.getElementById('log');
-
- eventSource.onopen = function(e) {
- log.innerHTML += "Соединение открыто ";
- };
-
- eventSource.onerror = function(e) {
- if (this.readyState == EventSource.CONNECTING) {
- log.innerHTML += "Соединение порвалось, пересоединяемся... ";
- } else {
- log.innerHTML += "Ошибка, состояние: " + this.readyState;
- }
- };
-
- eventSource.addEventListener('bye', function(e) {
- log.innerHTML += "Bye ";
- }, false);
-
- eventSource.onmessage = function(e) {
- log.innerHTML += e.data + ' ';
- };
-}
-
-function stop() { // при нажатии на Стоп
- eventSource.close();
-}
\ No newline at end of file
diff --git a/3-more/2-ajax/11-server-sent-events/eventsource.view/index.html b/3-more/2-ajax/11-server-sent-events/eventsource.view/index.html
new file mode 100644
index 00000000..fb82827f
--- /dev/null
+++ b/3-more/2-ajax/11-server-sent-events/eventsource.view/index.html
@@ -0,0 +1,61 @@
+
+
+
+
+
+
+
+
+
+
+
+Нажмите "Старт" для начала.
+
+
+
+
+
diff --git a/3-more/2-ajax/11-server-sent-events/index.js b/3-more/2-ajax/11-server-sent-events/eventsource.view/server.js
similarity index 78%
rename from 3-more/2-ajax/11-server-sent-events/index.js
rename to 3-more/2-ajax/11-server-sent-events/eventsource.view/server.js
index 760cca7b..d078282a 100644
--- a/3-more/2-ajax/11-server-sent-events/index.js
+++ b/3-more/2-ajax/11-server-sent-events/eventsource.view/server.js
@@ -6,7 +6,7 @@ var fileServer = new (require('node-static')).Server('.');
function onDigits(req, res) {
res.writeHead(200, {
- 'Content-Type': 'text/event-stream',
+ 'Content-Type': 'text/event-stream; charset=utf-8',
'Cache-Control': 'no-cache'
});
@@ -16,12 +16,16 @@ function onDigits(req, res) {
write();
function write() {
- res.write('data: ' + ++i + '\n\n');
- if (i == 5) {
- clearInterval(timer);
+ i++;
- res.end('event: bye\ndata: до свидания');
+ if (i == 4) {
+ res.write('event: bye\ndata: до свидания\n\n');
+ clearInterval(timer);
+ res.end();
+ return;
}
+
+ res.write('data: ' + i + '\n\n');
}
}
diff --git a/3-more/2-ajax/11-server-sent-events/eventsource/browser.js b/3-more/2-ajax/11-server-sent-events/eventsource/browser.js
deleted file mode 100644
index b64a2d39..00000000
--- a/3-more/2-ajax/11-server-sent-events/eventsource/browser.js
+++ /dev/null
@@ -1,36 +0,0 @@
-
-if (!window.EventSource) {
- document.write('В этом браузере нет поддержки EventSource.');
-}
-
-var eventSource;
-
-function start() { // при нажатии на Старт
- eventSource = new EventSource('digits');
-
- var log = document.getElementById('log');
-
- eventSource.onopen = function(e) {
- log.innerHTML += "Соединение открыто ";
- };
-
- eventSource.onerror = function(e) {
- if (this.readyState == EventSource.CONNECTING) {
- log.innerHTML += "Соединение порвалось, пересоединяемся... ";
- } else {
- log.innerHTML += "Ошибка, состояние: " + this.readyState;
- }
- };
-
- eventSource.addEventListener('bye', function(e) {
- log.innerHTML += "Bye ";
- }, false);
-
- eventSource.onmessage = function(e) {
- log.innerHTML += e.data + ' ';
- };
-}
-
-function stop() { // при нажатии на Стоп
- eventSource.close();
-}
\ No newline at end of file
diff --git a/3-more/2-ajax/11-server-sent-events/eventsource/index.html b/3-more/2-ajax/11-server-sent-events/eventsource/index.html
deleted file mode 100644
index bf8d4634..00000000
--- a/3-more/2-ajax/11-server-sent-events/eventsource/index.html
+++ /dev/null
@@ -1,17 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-Нажмите "Старт" для начала.
-
-
-
-
-
diff --git a/3-more/2-ajax/11-server-sent-events/eventsource/index.js b/3-more/2-ajax/11-server-sent-events/eventsource/index.js
deleted file mode 100644
index 760cca7b..00000000
--- a/3-more/2-ajax/11-server-sent-events/eventsource/index.js
+++ /dev/null
@@ -1,50 +0,0 @@
-var http = require('http');
-var url = require('url');
-var querystring = require('querystring');
-
-var fileServer = new (require('node-static')).Server('.');
-
-function onDigits(req, res) {
- res.writeHead(200, {
- 'Content-Type': 'text/event-stream',
- 'Cache-Control': 'no-cache'
- });
-
- var i=0;
-
- var timer = setInterval(write, 1000);
- write();
-
- function write() {
- res.write('data: ' + ++i + '\n\n');
- if (i == 5) {
- clearInterval(timer);
-
- res.end('event: bye\ndata: до свидания');
- }
-
- }
-}
-
-function accept(req, res) {
-
- if (req.url == '/digits') {
- onDigits(req, res);
- return;
- }
-
- // всё остальное -- статика
- fileServer.serve(req, res);
-
-
-}
-
-
-
-// ----- запуск accept как сервера из консоли или как модуля ------
-
-if (!module.parent) {
- http.createServer(accept).listen(8080);
-} else {
- exports.accept = accept;
-}
diff --git a/3-more/2-ajax/12-ajax-iframe/article.md b/3-more/2-ajax/12-ajax-iframe/article.md
index 169156ce..77be4652 100644
--- a/3-more/2-ajax/12-ajax-iframe/article.md
+++ b/3-more/2-ajax/12-ajax-iframe/article.md
@@ -1,6 +1,8 @@
-# IFRAME для AJAX-запросов
+# IFRAME для AJAX и COMET
-Транспорт `iframe` -- самый кросс-браузерный, это его основное преимущество. Он работает везде: от IE6 до Opera Mobile.
+Эта глава посвящена `IFRAME` -- самому древнему и кросс-браузерному способу AJAX-запросов.
+
+Сейчас он используется, разве что, для поддержки кросс-доменных запросов в IE7- и для COMET в IE9-.
Для общения с сервером создается невидимый `IFRAME`. В него отправляются данные, и в него же сервер пишет ответ.
@@ -12,18 +14,18 @@
### Двуличность IFRAME: окно+документ
-Что такое `IFRAME`? На этот вопрос у браузера два ответа
+Что такое IFRAME? На этот вопрос у браузера два ответа
-
`IFRAME` -- это HTML-тег: <iframe> со стандартным набором свойств.
+
IFRAME -- это HTML-тег: <iframe> со стандартным набором свойств.
Тег можно создавать в JavaScript
У тега есть стили, можно менять.
К тегу можно обратиться через `document.getElementById` и другие методы.
-
`IFRAME` -- это окно браузера, вложенное в основное
+
IFRAME -- это окно браузера, вложенное в основное
-
`IFRAME` -- такое же по функционалу окно браузера, как и основное, с адресом и т.п.
+
IFRAME -- такое же по функционалу окно браузера, как и основное, с адресом и т.п.
Если документ в `IFRAME` и внешнее окно находятся на разных доменах, то прямой вызов методов друг друга невозможен.
Ссылку на это окно можно получить через `window.frames['имя фрейма']`.
@@ -32,7 +34,7 @@
Для достижения цели мы будем работать как с тегом, так и с окном. Они, конечно же, взаимосвязаны.
-**В теге `IFRAME` свойство `contentWindow` хранит ссылку на окно.**
+**В теге `
+...То есть, вызывает из основного окна функцию обработки (`window.name` в ифрейме -- его имя).
Дополнительно нужен обработчик `iframe.onload` -- он сработает и проверит, выполнилась ли функция `CallbackRegistry[window.name]`. Если нет, значит какая-то ошибка. Сервер при нормальном потоке выполнения всегда отвечает её вызовом.
@@ -196,30 +198,194 @@ function postToIframe(url, data, target){
`iframePost(url, data, onSuccess, onError)` -- для POST-запросов на `url`. Значением `data` должен быть объект `ключ:значение` для пересылаемых данных, он конвертируется в поля формы.
-```js
-//+ src="samedomain.js"
+Пример в действии, возвращающий дату сервера при GET и разницу между датами клиента и сервера при POST:
+
+
+[codetabs src="date"]
+
+
+Прямой вызов функции внешнего окна из ифрейма отлично работает, потому что они с одного домена. Если с разных, то нужны дополнительные действия, например:
+
+
+
В IE8+ есть интерфейс [postMessage](https://developer.mozilla.org/en-US/docs/DOM/window.postMessage) для общения между окнами с разных доменов.
+
В любых, даже самых старых IE, можно обмениваться данными через `window.name`. Эта переменная хранит "имя" окна или фрейма, которое не меняется при перезагрузке страницы.
+
+Поэтому если мы сделали `POST` в `
+
Также в совсем старых IE можно обмениваться данными через хеш, то есть фрагмент URL после `#`. Его изменение доступно между ифреймами с разных доменов и не переводит к перезагрузке страницы. Таким образом они могут передавать данные друг другу. Есть готовые библиотеки, которые реализуют этот подход, например [Porthole](http://ternarylabs.github.io/porthole/).
+
+
+
+## IFRAME для COMET
+
+Бесконечный IFRAME -- самый старый способ организации COMET. Когда-то он был основой AJAX-приложений, а сейчас -- используется лишь в случаях, когда браузер не поддерживает современный стандарт WebSocket, то есть для IE9-.
+
+Этот способ основан на том, что браузер читает страницу последовательно и обрабатывает все новые теги по мере того, как сервер их присылает.
+
+Классическая реализация -- это когда клиент создает невидимый IFRAME, ведущий на служебный URL. Сервер, получив соединение на этот URL, не закрывает его, а
+время от времени присылает блоки сообщений <script>...javascript...</script>. Появившийся в IFRAME'е javascript тут же выполняется браузером, передавая информацию на основную страницу.
+
+Таким образом, для передачи данных используется "бесконечный" ифрейм, через который сервер присылает все новые данные.
+
+Схема работы:
+
+
Создаётся `
+
Сервер выдаёт начало ("шапку") документа и останавливается, оставляя соединение активным.
+
Когда сервер хочет что-то отправить -- он пишет в соединение <script>parent.onMessage(сообщение)</script> Браузер тут же выполняет этот скрипт -- так сообщение приходит на клиент.
+
Ифрейм, в теории, грузится бесконечно. Его завершение означает обрыв канала связи. Его можно поймать по `iframe.onload` и заново открыть соединение (создать новый `iframe`).
+
+
+Также ифрейм можно пересоздавать время от времени, для очистки памяти от старых сообщений.
+
+
+
+Ифрейм при этом работает только на получение данных с сервера, как альтернатива [Server Sent Events](/server-sent-events). Для запросов используется обычный `XMLHttpRequest`.
+
+## Обход проблем с IE
+
+Такое использование ифреймов является хаком. Поэтому есть ряд проблем:
+
+
+
Показывается индикатор загрузки, "курсор-часики".
+
При POST в `
+
Браузер буферизует начало страницы.
+
+
+Мы должны эти проблемы решить, прежде всего, в IE, поскольку в других браузерах есть [WebSocket](/websockets) и [Server Sent Events](/server-sent-events) .
+
+Проще всего решить последнюю -- IE не начинает обработку страницы, пока она не загрузится до определенного размера.
+
+Поэтому в таком `IFRAME` первые несколько сообщений задержатся:
+
+```html
+
+
+
+
+
+ ...
```
-Код с серверной стороны (важна лишь выделенная часть):
+Решение -- забить начало ифрейма чем-нибудь, поставить, например, килобайт пробелов в начале:
+
+```html
+
+
+
+ ******* 1 килобайт пробелов, а потом уже сообщения ******
+
+
+ ...
+```
+
+Для решения проблемы с индикацией загрузки и клика в мы можем использовать безопасный ActiveX-объект `htmlfile`. IE не требует разрешений на его создание. Фактически, это независимый HTML-документ.
+
+Оказывается, если `iframe` создать в нём, то никакой анимации и звуков не будет.
+
+Итак, схема:
+
+
Основное окно `main` создёт вспомогательный объект: `new ActiveXObject("htmlfile")`. Это HTML-документ со своим `window`, похоже на встроенный `iframe`.
+
В `htmlfile` записывается `iframe`.
+
Цепочка общения: основное окно - `htmlfile` - ифрейм.
+
+
+### iframeActiveXGet
+
+На самом деле всё еще проще, если посмотреть на код:
+
+Метод `iframeActiveXGet` по существу идентичен обычному `iframeGet`, которое мы рассмотрели. Единственное отличие -- вместо `createIframe` используется особый метод `createActiveXFrame`:
```js
-res.write(200, {'Cache-Control': 'no-cache'});
+function iframeActiveXGet(url, onSuccess, onError) {
-var data = JSON.stringify(data из POST или дата сервера);
+ var iframeOk = false;
+ var iframeName = Math.random();
*!*
-res.end('');
+ 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(); // если коллбэк не вызвался - что-то не так
+ }
+
+}
```
-Пример в действии, возвращающий дату сервера при GET и клиента при POST:
+### createActiveXFrame
-[iframe src="samedomain" border="1" link]
+В этой функции творится вся IE-магия:
+
+```js
+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;
+}
+```
+
+
+
Вспомогательный объект `htmlfile` будет один и он будет глобальным. Можно и спрятать переменную в замыкании. Смысл в том, что в один `htmlfile` можно записать много ифреймов, так что не будем множить сущности и занимать ими лишнюю память.
+
В `htmlfile` можно записать любой текст и, при необходимости, через `document.write('
+```
+
+Здесь `parent'ом` для `iframe'а` будет `htmlfile`, т.е. `CallbackRegistry` будет искаться среди переменных соответствующего ему окна, а вовсе не верхнего `window`.
+
+Окно для `htmlfile` доступно как `htmlfile.parentWindow`, копируем в него ссылку на реестр коллбэков `CallbackRegistry`. Теперь ифрейм его найдёт.
+
Далее вставляем ифрейм в документ. В старых `IE` нельзя поменять `name` ифрейму через DOM, поэтому вставляем строкой через `insertAdjacentHTML`.
+
+
+Пример в действии (только IE):
+
+[codetabs src="date-activex"]
+
+Запрос, который происходит, полностью незаметен.
+
+Метод POST делается аналогично, только форму ужно добавлять не в основное окно, а в `htmlfile`, через вызов `htmlfile.appendChild`. В остальном -- всё так же, как и при обычной отправке через ифрейм.
+
+Впрочем, для COMET нужен именно GET.
+
+Можно и сочетать эти способы: если есть ActiveX: `if ("ActiveXObject" in window)` -- используем методы для IE, описанные выше, а иначе -- обычные методы.
+
+Вот мини-приложение с сервером на Node.JS, непрерывно получающее текущее время с сервера через `