Пишем Web-приложения, использующие HTML 5

Создаем Web-приложения завтрашнего дня сегодня

Уже много лет Web-разработчики ожидают появления в новом поколении Web-браузеров поддержки новой функциональности, описанной в спецификации HTML 5. Возможно, вы приятно удивитесь, узнав, как много этих возможностей поддерживается в браузерах уже сегодня. В этой статье вы научитесь определять, какую новую функциональность поддерживает браузер, и узнаете, как ее использовать в своем приложении. Исследуйте мощную функциональность HTML 5, такую как многопоточность, геолокация, встроенные базы данных и встроенное видео.

Майкл Галпин, инженер по программному обеспечению, Vitria Technology

Майкл Галпин (Michael Galpin) имеет учёную степень по математике в Калифорнийском Технологическом институте. Он является Java-разработчиком с конца 90-х гг. и работает инженером по программному обеспечению в Vitria Technology, в Саннивейл, Калифорния.



18.12.2012

Введение

Уже много лет Web-разработчики ожидают появления в новом поколении Web-браузеров поддержки новой функциональности, описанной в спецификации HTML 5. Возможно, вы приятно удивитесь, узнав, как много этих возможностей поддерживается в браузерах уже сегодня. В этой статье вы научитесь определять, какую новую функциональность поддерживает браузер, и узнаете, как ее использовать в своем приложении. Исследуйте мощную функциональность HTML 5, такую как многопоточность, геолокация, встроенные базы данных и встроенное видео.

Начинаем

Самое важное, что нужно иметь для выполнения примеров из данной статьи - это несколько браузеров для тестирования. Я настоятельно рекомендую использовать для тестов последние версии Mozilla Firefox, Apple Safari и Google Chrome. При работе над этой статьей я использовал Mozilla Firefox 3.6, Apple Safari 4.04 и Google Chrome 5.0.322. Возможно, вы пожелаете проверить работу приложения в браузерах мобильных устройств. Я тестировал свое приложение на браузерах, запущенных на эмуляторах мобильных устройств, с помощью последних версий SDK для Android и iPhone.

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

Примеры включают в себя очень маленький серверный компонент, который запускался на Java™. Я использовал JDK 1.6.0_17 и Apache Tomcat 6.0.14. Эти инструменты можно загрузить по ссылкам в разделе Ресурсы.


Определяем поддерживаемую функциональность

Есть старая шутка о том, что Web-разработчики тратят 20% процентов своего времени на написание кода, а остальные 80% на то, чтобы он одинаково работал во всех браузерах. Сказать, что разработчики свыклись с тем, что разные браузеры работают по-разному – это не сказать ничего. В связи с новой волной инноваций в браузерах эта пессимистичная точка зрения вполне обоснованна. Функциональность, поддерживаемая последними версиями браузеров, постоянно меняется.

Однако положительный момент состоит в том, что новые возможности появляются на основе Web-стандартов, что позволяет начать их использовать уже сегодня. Можно использовать прием постепенного расширения (progressive enhancement) – предоставить базовую функциональность, проверить поддержку дополнительных возможностей и, если они поддерживаются, расширить свое приложение новой функциональностью. Сначала давайте посмотрим, как определить, поддерживает ли браузер какие-либо из новых возможностей. В листинге 1 представлен простой сценарий обнаружения.

Листинг 1. Сценарий обнаружения
function detectBrowserCapabilities(){
    $("userAgent").innerHTML = navigator.userAgent;    
    var hasWebWorkers = !!window.Worker;
    $("workersFlag").innerHTML = "" + hasWebWorkers;
    var hasGeolocation = !!navigator.geolocation;
    $("geoFlag").innerHTML = "" + hasGeolocation;
    if (hasGeolocation){
        document.styleSheets[0].cssRules[1].style.display = "block";
        navigator.geolocation.getCurrentPosition(function(location) {
            $("geoLat").innerHTML = location.coords.latitude;
            $("geoLong").innerHTML = location.coords.longitude;
        });
    }
    var hasDb = !!window.openDatabase;
    $("dbFlag").innerHTML = "" + hasDb;
    var videoElement = document.createElement("video");
    var hasVideo = !!videoElement["canPlayType"];
    var ogg = false;
    var h264 = false;
    if (hasVideo) {
        ogg = videoElement.canPlayType('video/ogg; codecs="theora, vorbis"') || "no";
           h264 = videoElement.canPlayType('video/mp4; 
codecs="avc1.42E01E, mp4a.40.2"') || "no";
    }
    $("videoFlag").innerHTML = "" + hasVideo;
    if (hasVideo){
        var vStyle = document.styleSheets[0].cssRules[0].style;
        vStyle.display = "block";
    }
    $("h264Flag").innerHTML = "" + h264;
    $("oggFlag").innerHTML = "" + ogg;
}

В рамках HTML 5 появилось огромное количество новых возможностей и стандартов. Мы в этой статье сосредоточимся на нескольких довольно полезных возможностях. Сценарий, представленный в листинге 1, определяет поддержку браузером четырех возможностей:

  • Рабочие потоки (многопоточность)
  • Геолокация
  • Локальная база данных
  • Аппаратная поддержка проигрывания видео

Сценарий начинается с вывода идентификатора User-Agent браузера пользователя. Эта строка (обычно) однозначно идентифицирует браузер, однако ее легко подделать. В нашем приложении мы просто выведем эту строку. Следующий шаг – это определение поддерживаемых возможностей. Сначала мы проверим поддержку рабочих потоков, для чего попытаемся найти в глобальной области видимости (окне) функцию Worker. Здесь мы используем естественную для JavaScript конструкцию двойного отрицания. Если функция Worker не существует, то window.Worker будет иметь неопределенное значение (undefined), которое считается ложным в JavaScript. Одно отрицание приведет выражение к значению true, а второе отрицание – к значению false. Проверив значение, сценарий выводит его на экран, изменяя DOM-структуру, показанную в листинге 2.

Листинг 2. DOM-структура для вывода результатов работы сценария обнаружения
<input type="button" value="Begin detection" 
    onclick="detectBrowserCapabilities()"/>
<div>Your browser's user-agent: <span id="userAgent">
   </span></div>
<div>Web Workers? <span id="workersFlag"></span></div>

<div>Database? <span id="dbFlag"></span></div>
<div>Video? <span id="videoFlag"></span></div>
<div class="videoTypes">Can play H.264? <span id="h264Flag">
   </span></div>
<div class="videoTypes">Can play OGG? <span id="oggFlag">
   </span></div>
<div>Geolocation? <span id="geoFlag"></span></div>
<div class="location">
    <div>Latitude: <span id="geoLat"></span></div>
    <div>Longitude: <span id="geoLong"></span></div>
</div>

В листинге 2 представлена простая HTML-структура, используемая для отображения диагностической информации, собираемой сценарием. Далее в листинге 1 мы проверяем поддержку геолокации. Здесь опять используется прием двойного отрицания, однако здесь проверяется объект geolocation, который должен быть свойством объекта navigator. Если объект geolocation существует, то мы с помощью его метода getCurrentPosition получаем текущее местоположение. Получение местоположения может быть медленным действием, так как оно обычно включает в себя сканирование Wi-Fi сетей. На мобильных устройствах эта процедура также может включать сканирование вышек сотовой связи и опрос GPS-спутников. Так как это может занять длительное время, getCurrentPosition является асинхронным методом, который принимает в качестве параметра функцию обратного вызова. В данном случае в качестве функции обратного вызова мы используем замыкание, которое просто отображает поля с информацией о местонахождении (посредством соответствующего стиля CSS), а затем записывает значения координат в DOM-структуру.

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

В конце сценария проверяется наличие аппаратной поддержки проигрывания видео. С помощью DOM API мы создаем элемент video. Такой элемент может создать каждый современный браузер. В более старых браузерах это будет корректный DOM-элемент, который, однако, не будет иметь специального значения. Для таких браузеров это равносильно созданию элемента foo. В современных браузерах это будет специальный элемент, такой же, который создается при создании элемента div или table. У него будет функция canPlayType, поэтому мы просто проверяем ее наличие.

Даже если в браузере имеется аппаратная поддержка проигрывания видео, типы видео и поддерживаемые кодеки не стандартизованы. Возможно, вы захотите проверить, какие кодеки поддерживает браузер. Стандартного списка кодеков не существует, однако два наиболее популярных из них – это H.264 и Ogg Vorbis. Чтобы проверить поддержку определенного кодека, можно передать в функцию canPlayType строку, идентифицирующую данный кодек. Если браузер поддерживает этот кодек, то функция вернет значение probably (т.е. «возможно», - это не шутка). Если кодек не поддерживается, функция вернет null. В коде сценария мы проверяем возвращаемое этой функцией значение и выводим результат в DOM-структуру. В листинге 3 представлены сводные результаты тестирования этого кода в нескольких популярных браузерах.

Листинг 3. Возможности различных браузеров
#Firefox 3.6
Your browser's user-agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.6; 
en-US; rv:1.9.2) Gecko/20100115 Firefox/3.6
Web Workers? true
Database? false
Video? true
Can play H.264? no
Can play OGG? probably
Geolocation? true
Latitude: 37.2502812
Longitude: -121.9059866

#Safari 4.0.4
Your browser's user-agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_2; 
en-us) AppleWebKit/531.21.8 (KHTML, like Gecko) Version/4.0.4 Safari/531.21.10
Web Workers? true
Database? true
Video? true
Can play H.264? probably
Can play OGG? no
Geolocation? false

#Chrome 5.0.322
Your browser's user-agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_2; 
en-US) AppleWebKit/533.1 (KHTML, like Gecko) Chrome/5.0.322.2 Safari/533.1
Web Workers? true
Database? true
Video? true
Can play H.264? no
Can play OGG? no
Geolocation? false

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

  • Firefox поддерживает все, кроме баз данных. Из видеокодеков он поддерживает только Ogg.
  • Safari поддерживает все, за исключением геолокации.
  • Chrome поддерживает все, за исключением геолокации. Также браузер утверждает, что он не поддерживает ни H.264, ни Ogg. Возможно, это ошибка либо сборки Chrome, использованной для тестирования, либо кода теста. На самом деле Chrome поддерживает кодек H.264.

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

Листинг 4. Браузеры мобильных устройств
#iPhone 3.1.3 Simulator
Your browser's user-agent: Mozilla/5.0 (iPhone Simulator; U; CPU iPhone OS 3.1.3 
like Mac OS X; en-us) AppleWebKit/528.18 (KHTML, like Gecko) 
Version/4.0 Mobile/7E18 Safari/528.16
Web Workers? false
Database? true
Video? true
Can play H.264? maybe
Can play OGG? no
Geolocation? true
Latitude: 37.331689
Longitude: -122.030731

#Android 1.6 Emulator
Your browser's user-agent: Mozilla/5.0 (Linux; Android 1.6; en-us; 
sdk Build/Donut) AppleWebKit/528.5+ (KHTML, like Gecko) Version/3.1.2 
Mobile Safari/525.20.1
Web Workers? false
Database? false
Video? false
Geolocation? false

#Android 2.1 Emulator
Your browser's user-agent: Mozilla/5.0 (Linux; U; Android 2.1; en-us;
sdk Build/ERD79) AppleWebKit/530.17 (KHTML, like Gecko) Version/4.0
Mobile Safari/530.17 
Web Workers? true 
Database? true 
Video? true 
Can play H.264? no 
Can play OGG? no 
Geolocation? true 
Latitude: 
Longitude:

В листинге выше приведены результаты для одного из последних эмуляторов iPhone и двух разновидностей эмуляторов Android. Android 1.6 не поддерживает ничего из того, что проверялось в тестах. На самом деле он поддерживает все возможности, за исключением аппаратного проигрывания видео, однако он для этого использует Google Gears. Google Gears предоставляет эквивалентные интерфейсы API (в терминах функций), однако они не соответствуют Web-стандартам, поэтому мы получили результаты, показанные в листинге 4. Сравните их с результатами Android 2.1, в котором поддерживается вся функциональность.

Обратите внимание, что iPhone поддерживает все, за исключением рабочих потоков. В листинге 3 показано, что настольная версия браузера Safari поддерживает рабочие потоки, поэтому логично ожидать, что эта функциональность скоро появится и в iPhone.

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


Создаем приложение завтрашнего дня

В данном примере мы сосредоточимся на использовании геолокации на мобильных устройствах, однако не будем забывать, что Firefox 3.5+ также поддерживает геолокацию. Приложение начинает свою работу с поиска мест (в терминах Foursquare – venues), расположенных рядом с текущим местонахождением пользователя. Местами может быть все, что угодно, однако обычно - это рестораны, бары, магазины и т.д. Так как наш пример является Web-приложением, на него действует политика ограничения домена. Браузер не может напрямую обращаться к API, предоставляемым Foursquare. Поэтому мы создадим Java-сервлет, который фактически будет проксировать вызовы браузера. Не обязательно для этого использовать Java; подобный прокси можно легко написать на PHP, Python, Ruby или на другом языке. В листинге 5 показан используемый в данном примере прокси-сервлет.

Листинг 5. Прокси-сервлет, работающий с Foursquare
public class FutureWebServlet extends HttpServlet {
    protected void doGet(HttpServletRequest request, 
            HttpServletResponse response) throws ServletException, IOException {
        String operation = request.getParameter("operation");
        if (operation != null && operation.equalsIgnoreCase("getDetails")){

            getDetails(request,response);
        }
        String geoLat = request.getParameter("geoLat");
        String geoLong = request.getParameter("geoLong");
        String baseUrl = "http://api.foursquare.com/v1/venues.json?";
        String urlStr = baseUrl + "geolat=" + geoLat + "&geolong=" + geoLong;
        PrintWriter out = response.getWriter();
        proxyRequest(urlStr, out);        
    }

    private void proxyRequest(String urlStr, PrintWriter out) throws IOException{
        try {
            URL url = new URL(urlStr);
            InputStream stream = url.openStream();
            BufferedReader reader = new BufferedReader( new InputStreamReader(stream));
            String line = "";
            while (line != null){
                line = reader.readLine();
                if (line != null){
                    out.append(line);
                }
            }
            out.flush();
            stream.close();
        } catch (MalformedURLException e) {
            e.printStackTrace();
        }
    }
    
    private void getDetails(HttpServletRequest request, HttpServletResponse response)
throws IOException{
        String venueId = request.getParameter("venueId");
        String urlStr = "http://api.foursquare.com/v1/venue.json?vid="+venueId;
        proxyRequest(urlStr, response.getWriter());
    }
}

Обратите внимание, что здесь мы проксируем вызовы к двум API Foursquare. Один из них служит для поиска места, а другой – для получения детальной информации о месте. Для указания места второй API принимает дополнительный параметр. Также мы указываем, что хотим получать данные в формате JSON, который можно легко разбирать с помощью JavaScript. Итак, мы знаем, какие вызовы будет делать наше приложение, теперь давайте посмотрим, как именно оно их будет делать и, как оно будет использовать полученные от Foursquare данные.

Используем геолокацию

Сначала приложение делает вызов для поиска мест. В листинге 5 показано, что нам нужно передать два параметра: geoLat и geoLong, обозначающие широту и долготу.

Листинг 6. Делаем вызов для поиска мест рядом с данным местонахождением
if (!!navigator.geolocation){
    navigator.geolocation.getCurrentPosition(function(location) {
        venueSearch(location.coords.latitude, location.coords.longitude);
    });
}
var allVenues = [];
function venueSearch(geoLat, geoLong){
    var xhr = new XMLHttpRequest();
    xhr.onreadystatechange = function(){
        if (this.readyState == 4 && this.status == 200){
            var responseObj = eval('(' + this.responseText + ')');
            var venues = responseObj.groups[0].venues;
            allVenues = venues;
            buildVenuesTable(venues);
        }
    }
    xhr.open("GET", "api?geoLat=" + geoLat + "&geoLong="+geoLong);
    xhr.send(null);
}

Приведенный выше код проверяет поддержку геолокации в браузере. Если геолокация поддерживается, приложение получает информацию о местонахождении и вызывает функцию venueSearch, передавая в нее широту и долготу. В этой функции используется Ajax (объект XMLHttpRequest для вызова сервлета, показанного в листинге 5). В качестве функции обратного вызова в коде используется замыкание, в котором разбираются данные JSON, полученные от Foursquare, и вызывается показанная ниже функция buildVenuesTable.

Листинг 7. Отображаем информацию о местах
function buildVenuesTable(venues){
    var rows = venues.map(function (venue) {
        var row = document.createElement("tr");
        var nameTd = document.createElement("td");
        nameTd.appendChild(document.createTextNode(venue.name));
        row.appendChild(nameTd);
        var addrTd = document.createElement("td");
        var addrStr = venue.address + " " + venue.city + "," + venue.state;
        addrTd.appendChild(document.createTextNode(addrStr));
        row.appendChild(addrTd);
        var distTd = document.createElement("td");
        distTd.appendChild(document.createTextNode("" + venue.distance));
        row.appendChild(distTd);
        return row;
    });
    var vTable = document.createElement("table");
    vTable.border = 1;
    var header = document.createElement("thead");
    var nameLabel = document.createElement("td");
    nameLabel.appendChild(document.createTextNode("Venue Name"));
    header.appendChild(nameLabel);
    var addrLabel = document.createElement("td");
    addrLabel.appendChild(document.createTextNode("Address"));
    header.appendChild(addrLabel);
    var distLabel = document.createElement("td");
    distLabel.appendChild(document.createTextNode("Distance (m)"));
    header.appendChild(distLabel);
    vTable.appendChild(header);
    var body = document.createElement("tbody");
    rows.forEach(function(row) {
        body.appendChild(row);
    });
    vTable.appendChild(body);
    $("searchResults").appendChild(vTable);
    if (!!window.openDatabase){
        $("saveBtn").style.display = "block";
    }
}

Код в листинге 7 преимущественно представляет собой DOM-код для создания таблицы с данными о полученных объектах. Тем не менее стоит отметить несколько интересных моментов. Обратите внимание на использование такой продвинутой функциональности JavaScript, как словарь массивов и функция forEach. Эта функциональность доступна во всех браузерах, поддерживающих геолокацию. Также для нас представляют интерес последние две строки. Здесь определяется, поддерживает ли браузер базу данных. Если база данных поддерживается, то становится активной кнопка Save, нажав на которую пользователь может сохранить всю информацию о местах в локальную базу данных. В следующем разделе мы рассмотрим, как это делается.

Структурированное хранилище данных

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

Листинг 8. Сохраняем информацию в базе данных
var db = {};
function saveAll(){
    db = window.openDatabase("venueDb", "1.0", "Venue Database",1000000);
    db.transaction(function(txn){
        txn.executeSql("CREATE TABLE venue (id INTEGER NOT NULL PRIMARY KEY, "+
                "name NVARCHAR(200) NOT NULL, address NVARCHAR(100), 
cross_street NVARCHAR(100), "+
                "city NVARCHAR(100), state NVARCHAR(20), geolat TEXT NOT NULL, "+
                "geolong TEXT NOT NULL);");
    });
    allVenues.forEach(saveVenue);
    countVenues();
}
function saveVenue(venue){
    // check if we already have the venue
    db.transaction(function(txn){
        txn.executeSql("SELECT name FROM venue WHERE id = ?", [venue.id],
            function(t, results){
                if (results.rows.length == 1 && results.rows.item(0)['name']){
                    console.log("Already have venue id=" + venue.id);
                } else {
                    insertVenue(venue);
                }
            })
    });
}
function insertVenue(venue){
    db.transaction(function(txn){
        txn.executeSql("INSERT INTO venue (id, name, address, cross_street, "+
                "city, state, geolat, geolong) VALUES (?, ?, ?, ?, "+
                "?, ?, ?, ?);", [venue.id, venue.name, 
                 venue.address, venue.crossstreet, venue.city, venue.state,
                 venue.geolat, venue.geolong], null, errHandler);
    });        
}
function countVenues(){
    db.transaction(function(txn){
        txn.executeSql("SELECT COUNT(*) FROM venue;",[], function(transaction, results){
            var numRows = results.rows.length;
            var row = results.rows.item(0);
            var cnt = row["COUNT(*)"];
            alert(cnt + " venues saved locally");
        }, errHandler);
    });
}

Чтобы сохранить информацию о местах в базе данных, мы сначала создадим таблицу, в которой эта информация будет храниться. Это довольно стандартный синтаксис SQL для создания таблицы. (Все браузеры, поддерживающие базы данных, используют SQLite. Обратитесь к документации SQLite, чтобы узнать о поддерживаемых типах данных, ограничениях и т.д.) SQL-код выполняется асинхронно. Вызывается функция transaction, в которую передается функция обратного вызова. Функция обратного вызова принимает в качестве параметра объект транзакции, с помощью которого можно выполнять SQL-запросы. Функция executeSQL принимает в качестве параметра строку кода SQL, необязательный список параметров, а также обработчики для успешного выполнения запроса и для случая возникновения ошибки. Если функция-обработчик для случая ошибки отсутствует, то ошибка "проглатывается". Для инструкции create table именно так и следует делать. При первом выполнении сценария таблица будет успешно создана. При последующих вызовах сценарий должен будет выдавать ошибку, так как страница уже существует. Поэтому в данном коде мы просто гарантируем, что таблица, в которую мы собираемся добавлять строки, существует.

Создав таблицу, мы в цикле forEach вызываем функцию saveVenue для каждого места, полученного от Foursquare. В этой функции сначала проверяется, существует ли уже этот объект в локальной базе данных. Здесь мы видим в действии обработчик успешного выполнения запроса. В этот обработчик передается результат выполнения запроса. Если результатов нет или данное место еще не было сохранено локально, вызывается функция insertVenue, в которой выполняется инструкция insert для добавления этого места в таблицу.

После сохранения в базе данных всех объектов в функции saveAll вызывается функция countVenues. В ней запрашивается количество мест, которые были добавлены в таблицу. Здесь используется синтаксис row["COUNT(*)"], с помощью которого определяется количество строк, возвращенных в результате выполнения запроса.

Мы узнали, как пользоваться встроенной в браузер базой данных, если она имеется. В следующем разделе мы рассмотрим использование рабочих потоков.

Фоновая обработка с помощью рабочих потоков (Web workers)

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

Листинг 9. Модифицированный поиск мест
function venueSearch(geoLat, geoLong){
    var xhr = new XMLHttpRequest();
    xhr.onreadystatechange = function(){
        if (this.readyState == 4 && this.status == 200){
            var responseObj = eval('(' + this.responseText + ')');
            var venues = responseObj.groups[0].venues;
            allVenues = venues;
            buildVenuesTable(venues);
            if (!!window.Worker){
                var worker = new Worker("details.js");
                worker.onmessage = function(message){
                    var tips = message.data;
                    displayTips(tips);
                };
                worker.postMessage(allVenues);
            }
        }
    }
    xhr.open("GET", "api?geoLat=" + geoLat + "&geoLong="+geoLong);
    xhr.send(null);
}

В приведенном выше коде используется тот же механизм обнаружения, который мы использовали раньше. Если рабочие потоки поддерживаются, мы создаем новый поток. При создании потока ему нужно передать URL-адрес сценария, который он должен выполнить. В данном случае это файл details.js. По окончании работы рабочий поток посылает сообщение основному потоку. Это сообщение получает функция-обработчик onmessage. Здесь в качестве обработчика мы используем простое замыкание. И, наконец, чтобы запустить поток, мы вызываем функцию postMessage, в которую мы передаем данные, которые следует обработать. В данном случае мы передаем список всех мест, полученных от Foursquare. В листинге 10 показано содержимое сценария details.js, который выполняется рабочим потоком.

Листинг 10. Сценарий, выполняемый рабочим потоком, details.js
var tips = [];
onmessage = function(message){
    var venues = message.data;
    venues.foreach(function(venue){
        var xhr = new XMLHttpRequest();
        xhr.onreadystatechange = function(){
            if (this.readyState == 4 && this.status == 200){
                var venueDetails = eval('(' + this.responseText + ')');
                venueDetails.tips.forEach(function(tip){
                    tip.venueId = venue.id;
                    tips.push(tip);
                });
            }
        };
        xhr.open("GET", "api?operation=getDetails&venueId=" + venueId, true);
        xhr.send(null);
    });
    postMessage(tips);
}

В этом сценарии выполняется обход всех мест. Для каждого места сценарий делает вызов к прокси Foursquare, чтобы получить подробную информацию о данном месте. Как обычно, для этого используется объект XMLHttpRequest. Однако, обратите внимание что в вызове функции open этого объекта, открывающей соединение, мы передаем третий параметр (true). Это делает вызов синхронным (а не асинхронным, как обычно). Так можно делать, потому что это действие будет выполнять рабочий поток, а не основной поток пользовательского интерфейса, а, значит, приложение не будет зависать на этих вызовах. Делая вызовы синхронными, мы гарантируем, что к началу каждого следующего вызова предыдущий вызов уже завершен. Обработчик просто извлекает все ассоциированные с данным местом подсказки и собирает их для передачи в основной поток пользовательского интерфейса. Для возвращения данных используется функция postMessage, которая вызывает в рабочем потоке функцию обратного вызова onmessage, как было показано в листинге 9.

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


Заключение

В этой статье мы рассмотрели некоторые новые возможности HTML 5, поддерживаемые современными браузерами. Мы узнали, как проверять поддержку этих возможностей и постепенно расширять за счет них функциональность своего приложения. Большинство этих возможностей уже широко поддерживаются популярными браузерами, особенно на мобильных устройствах. Теперь вы можете создавать инновационные Web-приложения, используя в них преимущества таких технологий, как геолокация и рабочие потоки.


Загрузка

ОписаниеИмяРазмер
Исходный код примераFutureWeb.zip9КБ

Ресурсы

Научиться

Получить продукты и технологии

  • Загрузите Mozilla Firefox 3.6.
  • Загрузите Safari 4.04.
  • Загрузите Google Chrome 5.0.322.
  • Загрузите Android SDK, используйте справочное руководство по API и узнавайте последние новости об Android.
  • Загрузите последнюю версию iPhone SDK. В этой статье мы использовали версию 3.1.3.
  • Загрузите исходный код Android с сайта Android Open Source Project.
  • Загрузите Java SDK. В этой статье использовалась версия JDK 1.6.0_17.

Комментарии

developerWorks: Войти

Обязательные поля отмечены звездочкой (*).


Нужен IBM ID?
Забыли Ваш IBM ID?


Забыли Ваш пароль?
Изменить пароль

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Профиль создается, когда вы первый раз заходите в developerWorks. Информация в вашем профиле (имя, страна / регион, название компании) отображается для всех пользователей и будет сопровождать любой опубликованный вами контент пока вы специально не укажите скрыть название вашей компании. Вы можете обновить ваш IBM аккаунт в любое время.

Вся введенная информация защищена.

Выберите имя, которое будет отображаться на экране



При первом входе в developerWorks для Вас будет создан профиль и Вам нужно будет выбрать Отображаемое имя. Оно будет выводиться рядом с контентом, опубликованным Вами в developerWorks.

Отображаемое имя должно иметь длину от 3 символов до 31 символа. Ваше Имя в системе должно быть уникальным. В качестве имени по соображениям приватности нельзя использовать контактный e-mail.

Обязательные поля отмечены звездочкой (*).

(Отображаемое имя должно иметь длину от 3 символов до 31 символа.)

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Вся введенная информация защищена.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Web-архитектура
ArticleID=852627
ArticleTitle=Пишем Web-приложения, использующие HTML 5
publish-date=12182012