Кросдоменная передача данных между html-страницами

Автор: Вячеслав Гринин | веб-программирование, веб-разработка | 29 Апр 2010 3:39 пп

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

Справочник по JavaScript и объектной документной модели DOM говорит нам, что для открытия нового окна нужно использовать метод window.open(), а для доступа из “дочернего” окна в “родительское” (то есть то, которое и породило новое окно) нужно использовать указатель opener. Рассмотрим этот факт на примере:

index.htm

<html xmlns="http://www.w3.org/1999/xhtml">
<head>
 <title>Главное окно</title>
 <script>
 function openWindow() {
 window.open("http://easy4web.ru/samples/transfer/popup.htm",
    "contents", "toolbar=no", "status=no");
 }
 </script>
 </head>
 <body>
 <input type="button" onclick="openWindow()"
    value="Показать диалог"/><br />
 <input type="text" id="data" />
 </body>
</html>

popup.htm

<html xmlns="http://www.w3.org/1999/xhtml">
 <head>
 <title>Диалоговое</title>
 <script>
 function transferData() {
 opener.document.getElementById("data").value =
    document.getElementById("inp").value;
 window.close();
 }
 </script>
 </head>
 <body>
 <input type="text" id="inp" name="inp"/>
 <input type="button" value="OK" onclick="transferData()"/>
 </body>
</html>

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

А вот и диалоговое окно, в нем мы видим TEXTBOX и кнопку по нажатию на которую текст введенный в текстовое поле присваивается текстовому полю data из главного окна. А доступ к нему мы и получаем при помощи указателя opener.

Вот здесь (Передача данных между окнами в рамках одного домена) вы можете протестировать работу этого алгоритма.

Здесь все просто, и я не стал бы писать эту статью, если бы хотел рассказать только про это.

А хочу я теперь рассказать о том, как быть если окна эти расположены в разных доменах. Когда такое может произойти? Предположим, есть Ваш форум, а есть специализированный сервис для загрузки и хранения фотографий и вот теперь владелец форума хочет договориться с владельцем фото-сервиса, что на форму он разместит кнопку “Добавить изображение”, которая будет открывать окно созданное в рамках фото-сервиса, в этом окне пользователь загрузит фотографии, а затем по нажатию кнопки “ОК” ссылки на фотографии скопируются в окно редактирования сообщения на форуме. Как видим, здесь есть два окна в разных доменах и нам нужно передать текст из одного окна в другое.
“Что тут сложного?” – спросите вы. А давайте попробуем.

Невозможность передачи данных между окнами в разных доменах

При попытке передачи данных между окнами получим ошибку Error: Access is denied.. Происходит это потому что прежде чем получить доступ к любому методу или свойству объекта opener браузер сравнит домен, в котором существует этот объект и домен, из которого происходит вызов собственно метода или свойства объекта opener. И если доменные имена не совпадают, будет возбуждено исключение “Доступ запрещен”.

Решить эту проблему можно. И мы решим ее без использования каких-либо серверных технологий, только силами JavaScript. Это становится возможным, если мы узнаем еще вот какую тонкость. При изменении свойства location.href в порожденном окне в него загрузится страница, заданная ссылкой, но значение указателя opener не изменится, он так и будет продолжать указывать на породившее его главное окно. А теперь представим себе, что мы в диалоговое окно загрузили все ту же главную страницу, или любую другую но с того же домена, где лежит главная страница, а после этого обратились к объекту opener, теперь домены диалогового окна и объекта opener снова совпадают, а значит исключение возбуждено не будет.

А теперь – время для исходников, иллюстрирующих пример:

index.htm

<html xmlns="http://www.w3.org/1999/xhtml">
 <head>
 <title>Главное окно</title>
 <script>
 function openWindow() {
 var par = "?loc=" + location.href + "&id=data";
 window.open("http://easyapi.ru/easy4web/transfer/popup2.htm" + par,
    "contents", "toolbar=no", "status=no");
 }
 function getDataFromUrl() {
 var txt_id, data;
 var url = location.href;
 var query = url.split("?")[1];
 if(query) {
 var params = query.split("&");
 for(var i = 0; i < params.length; i++) {
 var keyval = params[i].split("=");
 if(keyval[0] == "data") {
 data = keyval[1];
 }
 if(keyval[0] == "id") {
 txt_id = keyval[1];
 }
 }
 }
 if(txt_id && data) {
 opener.document.getElementById(txt_id).value = data;
 close();
 }
 }
 getDataFromUrl();
 </script>
 </head>
 <body>
 <input type="button" onclick="openWindow()"
    value="Показать диалог"/><br/>
 <input type="text" id="data" />
 <script>
 getDataFromUrl();
 </script>
 </body>
</html>

Итак, здесь мы видим, что процедура openWindow() все также открывает диалоговое окно, однако теперь она передает ему параметры: loc – указывающий на URL самой порождающей страницы; id – айдишник текстбокса, в который будет вставлен текст из дочернего окна.
А еще мы видим, что после загрузки контента главной страницы вызывается функция getDataFromUrl(), которая проверяет, есть ли GET-параметры id и data (айдишник текстбокса и текст, который мы в него будем вставлять). Параметры эти при изначальной загрузке страницы не заданы, а сначит при первой загрузке страницы код обновляющий содержимое текстбокса не выполнится. А когда он выполнится мы узнаем после того, как рассмотрим исходники диалогового окна.

popup.htm

<html xmlns="http://www.w3.org/1999/xhtml">
 <head>
 <title>Диалог</title>
 <script>
 function transferData() {
 var url = location.href;
 var query = url.split("?")[1];
 if(query) {
 var params = query.split("&");
 for(var i = 0; i < params.length; i++) {
 var keyval = params[i].split("=");
 if(keyval[0] == "loc") {
 var loc = keyval[1];
 }
 if(keyval[0] == "id") {
 var txt_id = keyval[1];
 }
 }
 }
 if(loc && txt_id) {
 location.href = loc + "?id=" + txt_id +
    "&data=" + document.getElementById("inp").value;
 }
 }
 </script>
 </head>
 <body>
 <input type="text" id="inp" name="inp"/>
 <input type="button" value="OK" onclick="transferData()"/>
 </body>
</html>

Здесь при клике по кнопке выполнится функция transferData(), которая сначала извлечет из адресной строки параметры loc и id, те самые, которые мы передали окну при его порождении. И если эти параметры есть – присвоим location.href адрес страницы loc(главной страницы), а в GET-параметры ей передадим id и data(айдишник текстбокса и данные, которые мы в него запишем). При этом в текущее окно (диалоговое) загрузится содержимое главнйо страницы и выполнится та самая функция getDataFromUrl() которая на этот раз извлечет все необходимые параметры из GET-строки и выполнит метод getElementById() объекта opener.document.

Вот и весь секрет кросдоменной передачи данных между окнами.

Вот здесь (Передача данных между окнами в разных доменах) можно протестировать работу вышеприведенного алгоритма.

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

Прочтите еще:

DOM: вытаскиваем текст вне тегов

Автор: Вячеслав Гринин | веб-программирование, кратко | 23 Мар 2010 6:12 пп

Данная публикация открывает новую рубрику в блоге. Рубрика называется “Кратко”. В ней я буду публиковать совсем кратенькие статьи – рецепты, темы для которых чаще всего буду брать из статистики поискового трафика. То есть, если я вижу, что человек пришел на мой сайт по некоему запросу, и ответа на вопрос на моем сайте нет, то я буду писать на них ответы и может когда-нибудь мой ответ пригодится.

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

В нашем случае документ представляет собой два дива с заданными строго идентификаторами, между которыми расположен искомый текстовый элемент.  При помощи getElementById мы на ходим элемент div1, а затем при помощи nextSibling добираемся до текстового узла, ведь он стоит следующим в DOM-структуре. Получить его значение можно при помощи свойства nodeValue.

Вот и весь рецепт.

<html>
<head>
 <title>Hello!</title>
 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
 <script type="text/javascript">
 function getText()
 {
 var div1 = document.getElementById("div1");
 div1.nextSibling.nodeValue = "Поменял текст";
 }
 </script>
</head>
<body>
<div id="div1">Предшествующий блок</div>
Это текст я хочу получить
<div id="div2">Замыкающий блок</div>
<input type="button" value="Start!" onclick="getText()">
</body>
</html>

Прочтите еще:

Реализация чата на ASP.NET с использованием Long Polling

Автор: Вячеслав Гринин | веб-программирование | 13 Мар 2010 2:06 пп

Бродя по просторам интернета, на одном из сайтов я увидел простой чат. Просмотрев страницу в FireBug я понял, что никакими апплетами там и не пахнет, а значит чат реализован на простом JavaScript.

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

И встал передо мной вопрос – как они это сделали? Как они обеспечили такую высокую скорость отдачи свежих данных? Ведь веб-сервер на то и сервер, что может лишь отвечать на запросы пользователя, но уж никак не самостоятельно инициировать запросы.

Непродолжительный поиск по интернетам дал мне ответ. И ответ этот “Long Polling“, а встречается еще и другое название технологии – Comet.

Итак, как же это работает?

По сути, это все тот же старый добрый AJAX, и использование объекта XmlHttpRequest. С одной большой разницей, заключенной на серверной стороне. Разница состоит вот в чем. Клиент посылает XHR-запрос и длительное время ожидает ответа сервера. А “длительное время” отклика обеспечивает сам сервер, который не сразу отдает данные клиенту, а лишь только тогда, когда у него в очереди появляются свежие данные, то есть когда ему действительно есть что сказать.

Клиент, дождавшись ответа, обрабатывает его (например, отображает сообщение на странице), а затем, без промедления, снова запрашивает сервер. И снова ждет.

Если же подав запрос клиент так и не дождался ответа из-за таймаута, то он снова запрашивает сервер. А откуда этот таймаут? А просто серверу нам нечего ответить, вот он и не отдает клиенту результат запроса. Ждет. Но не дождавшись, разрывает соединение по таймауту. Такая вот несложная схема.

Это и называется Long Polling, то есть – длительный опрос.

Но от слов к делу. Сейчас мы изготовим серверную и клиентскую часть простого интернет-чата, построенного на описанной технологии. Мы подробно рассмотрим работу всего серверного и клиентского кода и даже в конце статьи получим ссылку на вполне работоспособный продукт. Сразу скажу, у него есть некоторые ограничения. Вы, например, не сможете его использовать в режиме сильной нагрузки, то есть наличии одновременно большого количества клиентов. В нем нет схемы очистки “потерянных” соединений. Это лишь работоспособный учебный пример. Все недостающие аспекты кода предлагаю вам на самостоятельную доработку.

Итак… Без особых размышлений приведу здесь листинг кода и объясню принцип его действия.

using System;
using System.Collections.Generic;

// Класс описывающий одно сообщение от клиента и
// метод его сериализации
public class CometMessage
{
 public string UserName;
 public string Message;
 public string Serialize()
 {
 return "{'user': '" + UserName + "', 'message': '" + Message + "'}";
 }
}

// Собственно, серверная часть
public static class CometServer
{
 // вспомогательный объект для блокировки ресурсов
 // многопоточного приложения
 private static Object _lock = new Object();

 // Список, хранящий состояние всех подключенных клиентов
 private static List<CometAsyncState> _clientStateList =
   new List<CometAsyncState>();

 // Возвращаем сообщение каждому подключенному клиенту
 public static void PushMessage(CometMessage message)
 {
 lock (_lock)
 {
 // Пробегаем по списку всех подключенных клиентов
 foreach (CometAsyncState clientState in _clientStateList)
 {
 if (clientState.CurrentContext.Session != null)
 {
 // И пишем в выходной поток текущее сообщение
 clientState.CurrentContext.Response.Write(message.Serialize());
 // После чего завершаем запрос - вот именно после этого результаты
 // запроса пойдут ко всем подключенным клиентам
 clientState.CompleteRequest();
 }
 }
 }
 }

 // Срабатывает кажды раз при запуске клиентом запроса Long poll
 // так как при этом HttpContext клиента изменяется, то надо обновить
 // все изменившиеся данные клиента в списке, идентифицируемом по
 // гуиду, который у клиента в течение работы остается постоянным
 public static void UpdateClient(CometAsyncState state, String guid)
 {
 lock (_lock)
 {
 // ищем клиента в списке по его гуиду
 CometAsyncState clientState = _clientStateList.Find(s => s.ClientGuid
   == guid);
 if (clientState != null)
 {
 // и если он нашелся, то обновляем все его параметры
 clientState.CurrentContext = state.CurrentContext;
 clientState.ExtraData = state.ExtraData;
 clientState.AsyncCallback = state.AsyncCallback;
 }
 }
 }

 // Регистрация клиента
 public static void RegicterClient(CometAsyncState state)
 {
 lock (_lock)
 {
 // Присваиваем гуид и добавляем в список
 state.ClientGuid = Guid.NewGuid().ToString("N");
 _clientStateList.Add(state);
 }
 }

 // Разрегистрация клиента
 public static void UnregisterClient(CometAsyncState state)
 {
 lock (_lock)
 {
 // Просто удаляем его из списка
 _clientStateList.Remove(state);
 }
 }
}

Итак:

6-14 строки – инкапсулируют класс сообщения от пользователя, как видим, там еще есть метод, преобразующий объект в JSON-строку. Вообще, по-хорошему, здесь надо пользоваться нормальным JSON-сериализатором, а не городить строку вручную.

Далее идет описание класса CometServer в котором и живет вся серверная логика LongPolling-технологии.

Заметим, что в переменной _clientStateList (строка 24) хранятся все подключенные на данный момент к серверу клиенты.

Метод PushMessage (строка 24) пробегает по всему списку клиентов и пишет в выходной поток каждого переданное ему в качестве аргумента сообщение, естественно, предварительно сериализовав его. После чего для каждого клиента в обязательном порядке вызывается метод CompleteRequest(), который завершает асинхронный запрос, передавая его результаты клиенту.

Метод UpdateClient (строка 51) по полученному им гуиду клиента, находит клиента в списке clientStateList и обновляет все его параметры, такие как HttpContext-например. Этим мы обеспечиваем всегда актуальное состояние списка клиентов, после каждого их реконнекта.

Метод RegicterClient (строка 69) /опечатка вышла в его имени, сами исправите при необходимости:)/ добавляет нового клиента в очередь.

Метод UnregisterClient (строка 80) соответственно – удаляет клиента из очереди. Что вполне законно. Зачем уведомлять клиента о новых сообщениях в чате, если он давно покинул чат?

Дальше идет описание класса CometAsyncState. Зачем мы его создали? Да чтобы хранить такие параметры клиента, как CurrentContext, AsyncCallback и ClientGuid. Ну то есть, чтобы отдельный поток, порожденный в пуле потоков и отвечающий за одного клиента, мог собственно функционировать и взаимодействовать с клиентом. Этот класс, по большому счету, всего лишь набор заглушек для методов интерфейса IAsyncResult плюс метод CompleteRequest(), который вызывает callback-функцию при завершении потока.

using System;
using System.Threading;
using System.Web;

public class CometAsyncState : IAsyncResult
{
    // Чтобы было где хранить все это и был создан
    // класс-наследник от IAsyncResult
    public HttpContext CurrentContext;
    public AsyncCallback AsyncCallback;
    public object ExtraData;
    public string ClientGuid;
    private Boolean _isCompleted;

    // Конструктор
    public CometAsyncState(HttpContext context,
      AsyncCallback callback, object data)
    {
        CurrentContext = context;
        AsyncCallback = callback;
        ExtraData = data;
        _isCompleted = false;
    }

    // Завершим запрос
    public void CompleteRequest()
    {
        // При завершении запроса просто выставим флаг
        // что он завершен
        // и вызовем callback
        _isCompleted = true;
        if (AsyncCallback != null)
        {
            AsyncCallback(this);
        }
    }

    #region IAsyncResult Members
    // И снова видим набор заглушек для интерфейса IAsyncResult

    public Boolean CompletedSynchronously
    {
        get
        {
            return false;
        }
    }

    public bool IsCompleted
    {
        get
        {
            return _isCompleted;
        }
    }

    public object AsyncState
    {
        get
        {
            return ExtraData;
        }
    }

    public WaitHandle AsyncWaitHandle
    {
        get
        {
            return new ManualResetEvent(false);
        }
    }
    #endregion
}

Осталась малость – клиентский JavaScript. Ниже я приведу его с моими подробными комментариями в самом коде и не буду его так детально разжевывать. Потому что весь он разжеван в начале статьи, где я описывал принцип работы LongPolling.

var clientGuid

$(document).ready(function() {
 // Подключаемся после загрузки страницы,
 // запускаем первый long polling
 Connect();
});

$(window).unload(function() {
 // При выгрузке страницы - запрашиваем сервер об отключении
 // клиента для экономии ресурсов
 Disconnect();
});

// Посылает lonp poll - запрос серверу
function SendRequest() {
 var url = './comet.ashx?guid=' + clientGuid;
 $.ajax({
 type: "POST",
 url: url,
 // Если запрос завершился успехом, значит сервер сообщил
 // о новых событиях - обрабатываем их
 success: ProcessResponse,
 // При ошибке (например таймауте), снова рекурсивно
 // посылаем запрос обеспечивая тем самым непрерывный
 // процесс прослушки серверных событий
 error: SendRequest
 });
}

// Регистрируемся на сервере
function Connect() {
 var url = './comet.ashx?cmd=register';
 $.ajax({
 type: "POST",
 url: url,
 success: OnConnected,
 error: ConnectionRefused
 });
}

// Разрегистрируемся на сервере
function Disconnect() {
 var url = './comet.ashx?cmd=unregister';
 $.ajax({
 type: "POST",
 url: url
 });
}

// Обработка сообщений, принятых с сервера
function ProcessResponse(transport) {
 eval('var d=' + transport + ';');
 document.getElementById("content").innerHTML +=
    ' <strong>' + d.user + '</strong> : "' + d.message + '"<br/>';
 // После отображения результатов запроса -
 // снова циклично делаем запрос.
 SendRequest();
}

// После регистрации на сервере сохраняем наш guid и
// посылаем первый long poll запрос на сервер
function OnConnected(transport) {
 clientGuid = transport;
 SendRequest();
}

// Если подключиться не удалось, то ждем три мекунды
// и опять пробуем подключиться
function ConnectionRefused() {
 $("#content").html("не удалось подключиться к серверу.
    Попробуем через 3 секунды...");
 setTimeout(Connect(), 3000);
}

// Отправка сообщения на сервер
function clickSendMessage() {
 var userName = document.getElementById("userName").value;
 var message = document.getElementById("message").value;
 var url = './comet.ashx?cmd=send&message=' + message + '&user=' + userName;
 $.ajax({
 type: "POST",
 url: url
 });
}

Ну и разумеется, код серверного хэндлера comet.ashx, который обрабатывает команды клиентского скрипта. Скажу сразу, хэндлер этот асинхронный, а потому унаследован не от IHttpHandler, а от IHttpAsyncHandler

< %@ WebHandler Language="C#" Class="CometAsyncHandler" %>

using System;
using System.Web;
using System.Threading;

public class CometAsyncHandler : IHttpAsyncHandler,
  System.Web.SessionState.IRequiresSessionState
{
    #region IHttpAsyncHandler Members

    public IAsyncResult BeginProcessRequest(HttpContext ctx,
      AsyncCallback cb, Object obj)
    {
        // Готовим объект для передачи его в QueueUserWorkItem
        CometAsyncState currentAsyncState =
          new CometAsyncState(ctx, cb, obj);

        // Добавляем в тредпул новый ждущий поток
        ThreadPool.QueueUserWorkItem(new WaitCallback(RequestWorker),
           currentAsyncState);

        return currentAsyncState;
    }

    public void EndProcessRequest(IAsyncResult ar)
    {
    }

    #endregion

    #region IHttpHandler Members
    // IHttpHandler Members - просто пустые заглушки,
    // так как нам не требуется реализация синхронных методов

    public bool IsReusable
    {
        get
        {
            return true;
        }
    }

    public void ProcessRequest(HttpContext context)
    {
    }

    #endregion

    // Основная функция рабочего потока
    private void RequestWorker(Object obj)
    {
        // obj - второй параметр
        // при вызове ThreadPool.QueueUserWorkItem()
        CometAsyncState state = obj as CometAsyncState;

        string command =
          state.CurrentContext.Request.QueryString["cmd"];
        string guid =
          state.CurrentContext.Request.QueryString["guid"];

        switch (command)
        {
            case "register":
                // Регистрируем клиента в очереди сообщений
                CometServer.RegicterClient(state);
                state.CurrentContext.Response.Write(
                  state.ClientGuid.ToString());
                state.CompleteRequest();
                break;
            case "unregister":
                // Удаляем клиента из очереди сообщений
                CometServer.UnregisterClient(state);
                state.CompleteRequest();
                break;
            case "send":
                // Отсылка сообщения
                string message =
                  state.CurrentContext.Request.QueryString["message"];
                string userName =
                  state.CurrentContext.Request.QueryString["user"];
                CometServer.PushMessage(new CometMessage() {
                  Message = message, UserName = userName });
                state.CompleteRequest();
                break;
            default:
                // При реконнекте клиента
                if (guid != null)
                {
                    CometServer.UpdateClient(state, guid);
                }
                break;

        }
    }
}

Основная логика программы кроется в методе RequestWorker (строки 51-95), который в зависимости от полученной с клиента команды, выполняет либо регистрацию/разрегистрацию клиента, либо отправку сообщения. Либо обновление данных о клиенте при его реконнекте.

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

Вот и все. Надеюсь, что статья оказалось полезной для вас.

Прочтите еще:

AJAX. Загрузка файлов.

Автор: Вячеслав Гринин | веб-программирование | 20 Фев 2010 7:39 пп

Немного странное название статьи… Мы помним, что AJAX это асинхронный JavaScript, построенный на основе объекта XmlHttpRequest. Отличная технология! С некоторыми ограничениями. Одно из них – отсутствие кросдоменности – мы уже научились обходить в статье Кросдоменный JavaScript (JSONP). Но есть у этой замечательной технологии еще один недостаток – она не позволяет асинхронно загружать файлы. Ну вот не умеет объект XmlHttpRequest загружать файлы!

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

Для загрузки файлов мы используем технологию, которая называется Remote Scripting посредством IFRAME. А в общем, технология проста. Для загрузки файла мы все также будем использовать обычную форму FORM, в которой разместим самый обычный INPUT TYPE=’FILE’. Все как и прежде в случае синхронной загрузки файла. И вот здесь и начинаются отличия. А отличие состоит в том, что у формы target указывает на скрытый на странице IFRAME, который по сути и перезагрузится во время операции. Мало того, в IFRAME мы вернем результат операции в виде вызова простого JavaScript-коллбэка, который завершит операцию загрузки.

Итак, от слов к делу. Далее я приведу пример кода, который и реализует вышеописанную технологию. Код этот очень простой.

<html>
<head>
<script type="text/javascript">
 function onResponse(d) {
 eval('var obj = ' + d + ';');
 alert('Файл ' + obj.filename + (obj.success ? " " : " НЕ ") +
    "загружен.");
 }
 </script>
</head>

<iframe id="rFrame" name="rFrame" style="display: none">
</iframe>

<form action="handler.php" target="rFrame" method="POST"
   enctype="multipart/form-data">
<input type="file" name="loadfile">
<input type='submit' value='Загрузить'>
</form>

</html>

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

У формы action=”handler.php” указывает на серверный обработчик, и не забудьте указать enctype=”multipart/form-data” иначе форма не сможет загружать файлы.

А дальше я приведу серверный код, содержимое файла handler.php.

<?php
 function jsOnResponse($obj)
 {
 echo '
 <script type="text/javascript">
 window.parent.onResponse("'.$obj.'");
 </script>
 ';
 }

 $dir = '/home/path/path/path';
 $name = basename($_FILES['loadfile']['name']);
 $file = $dir . $name;

 $success = move_uploaded_file($_FILES['loadfile']['tmp_name'], $file);
 jsOnResponse("{'filename':'" . $name . "', 'success':'" . $success . "'}");

?>

Этот код просто сохраняет файл в папку на сервере, объяснять саму загрузку нет смысла – она стандартна. Обращу лишь внимание, что после загрузки файла вызывается функция jsOnResponse которая просто пишет в выходной поток вызов JavaScript-функции window.parent.onResponse($obj). Клиентский IFRAME, приняв этот код, выполнит его, вызвав функцию onResponse у родительской страницы фрейма.

Вот и все. Пользуйтесь. Здесь можно скачать работающий код приведенного в статье примера.

Используя уникальную систему поиска Price-AZ можно заказать любые запчасти Фольксваген и Ауди на сайте ООО “Польга”. Теперь стало гораздо удобнее найти нужную запчасть не только по номеру, но и по ее названию. При магазине есть также удобный автосервис. Кстати, система Price-AZ позволяет найти запчасть на автомобиль любой марки.

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

Прочтите еще:

2. easyAPI. Удаленная загрузка XML.

Автор: Вячеслав Гринин | веб-мастеру, веб-программирование | 30 Янв 2010 1:00 пп

И вот, наконец, долгожданная, вторая статья из раздела easyAPI.

Сегодня мы узнаем, как загрузить XML-данные с удаленного домена. Подобная задача возникает, например, когда владелец сайта на бесплатном (а значит – без серверного кода) хостинге хочет вставить в страницу погодный XML-информер. Именно на этом примере мы и рассмотрим сегодня удаленную загрузку XML-файлов.

Итак, для фоновой загрузки данных мы привыкли использовать так называемый AJAX, то есть объект XMLHttpRequest, про эту технологию я уже не раз писал в статьях блога. Однако, эта технология в данном случае никуда не годится. Почему? А потому что объект XMLHttpRequest не позволяет делать запросы к доменам, иным, чем тот, в котором работает скрипт, создающий этот объект.

И здесь к нам на помощь приходит технология, получившая название JSONP, что значит JavaScript Object Notation with Padding. Про сам JSON можео почитать вот здесь JSON, а JSONP представляет собой объект в нотации JSON обернутый в вызов функции, что-то вроде этого:

onSuccess('JSON-object')

А теперь объясню, зачем это нужно. Хоть XMLHttpRequest и не позволяет делать запросы к удаленным доменам, однако все браузеры позволяют нам загружать JavaScript’ы с любых доменов, то есть страница, загруженная с домена localdomain.ru, позволяет выполнить в ее рамках следующую инструкцию:

<script type='text/javascript' src='http://remotedomain.ru'>
</script>

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

Осталось только одно – завладеть таким remotedomain.ru, который вернет нам то, что нам нужно. Скажу, что домен этот – http://easyapi.ru. Да-да, именно на этом, предусмотрительно приобретенном не так давно домене, и будут жить все наши полезные, жизненно необходимые почти каждому веб-разработчику, функции. :) Скромненько и со вкусом…

Итак, в рамках домена easyapi.ru я уже создал ответную часть для удаленной загрузки XML-документов. Живет она вот здесь: http://easyapi.ru/xml/get.php и принимает на входе два GET-параметра: url и callback. Первый из них – адрес, с которого будет загружаться XML-файл, а второй – имя JavaScript-функции, которая будет вызвана после загрузки, и которая должна будет обработать полученный XML-документ.

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

Вот исходный код страницы:

<html>
<head>
<script type="text/javascript">
function getXMLFromString(s) {
    if(window.ActiveXObject)
    {
        var xml;
        xml=new ActiveXObject("Microsoft.XMLDOM");
        xml.async=false;
        xml.loadXML(s);
        return xml;
    }
    else if(window.DOMParser)
    {
      var parser = new DOMParser();
      return parser.parseFromString(s,'text/xml');
    }
    else
    {
        alert("Загрузка XML не поддерживается браузером");
        return null;
    }
}

function getXMLDocument(url)
{
    var xml;
    if(window.XMLHttpRequest)
    {
        xml=new window.XMLHttpRequest();
        xml.open("GET", url, false);
        xml.send("");
        return xml.responseXML;
    }
    else
        if(window.ActiveXObject)
        {
            xml=new ActiveXObject("Microsoft.XMLDOM");
            xml.async=false;
            xml.load(url);
            return xml;
        }
        else
        {
            alert("XML loading not supported");
            return null;
        }
}

function transformXslt(source,style) {
    if(window.ActiveXObject)
    {
        return source.transformNode(style);
    }
    else if(window.XSLTProcessor)
    {
        var xsltProcessor=new XSLTProcessor();
        xsltProcessor.importStylesheet(style);
        var resultDocument = xsltProcessor.transformToDocument(source);
        var xmls = new XMLSerializer();
        return xmls.serializeToString(resultDocument);
    }
    else
    {
        alert("Преобразование XML не поддерживается браузером");
        return null;
    }
}

</script>

</head>
<body>
<div id="res"></div>
<script type="text/javascript">
function onSuccess(res)
{
    var xslt = getXMLDocument("http://easy4web.ru/samples/easyxml/gis.xsl");
    var xml = getXMLFromString(res.result);
    var res = transformXslt(xml, xslt);
    document.getElementById("res").innerHTML=res;
}
</script>
<script src="http://easyapi.ru/xml/get.php?url=http://informer.gismeteo.ru/xml/27612_1.xml&callback=onSuccess" type="text/javascript"></script>
</body>
</html>

В первой части страницы мы видим три функции, все они – кроссбарузерные:

  • getXMLFromString(s) – преобразует валидную строку s в XML-документ
  • getXMLDocument(url) – загружает XML-документ с адреса url
  • transformXslt(source,style) – преобразует XML-документ source при помощи XSLT-шаблона style

В нижней части исходного кода мы видим функцию onSuccess – ока как раз и выполнится после загрузки xml-документа с удаленного домена. Она отрисует загруженный и полученный в переменной res.result XML-Документ при помощи XSLT-файла, хранящегося по адресу http://easy4web.ru/samples/easyxml/gis.xsl
И в самой-самой нижней части исходного кода мы видим вот такую конструкцию:

<script src="http://easyapi.ru/xml/get.php?url=http://informer.gismeteo.ru/xml/27612_1.xml&callback=onSuccess" type="text/javascript"></script>

Вот эта часть и представляет собой загружалку XML-документов с удаленных доменов. Как видим в атрибуте src тега SCRIPT мы указали, кто будет заниматься загрузкой (http://easyapi.ru/xml/get.php), что он будет загружать (url=http://informer.gismeteo.ru/xml/27612_1.xml) и какая функция займется обработкой документа (callback=onSuccess)

Здесь вы можете скачать исходные коды приведенного примера. А вот здесь – посмотреть, как это работает.

Прочтите еще:

Позже »