Быстрый старт
Создание карты с информацией о текущем местоположении
Для отображения карты мы используем веб-элемент управления карт Bing, экземпляр которого создан на странице map.html, которая загружена в элемент iframe главной страницы. Страница загружает скрипт элемента управления карт Bing из удалённого источника и исполняется в веб-контексте. Обратите внимание на то, что мы, кроме того, можем воспользоваться SDK для карт Bing (http://msdn.microsoft.com/library/hh846481.aspx), который предоставит нам скрипт, подходящий для загрузки в локальный контекст. Сейчас я хочу использовать подход с удаленным скриптом, так как это даёт нам возможность работать с веб-содержимым, и веб-контекст, в его основных проявлениях, это то, что вы, я уверен, захотите понять для использования в собственных приложениях. Мы переключимся на локальные элементы управления в "Быстрый старт" курса "Пользовательский интерфейс приложений для Windows 8, созданных с использованием HTML, CSS и JavaScript".
Таким образом, разместим файл map.html в папке html. Для этого щёлкните правой кнопкой мыши по проекту, выберите команду Добавить > Создать папку (Add > New Folder), введите для неё имя html. Затем щёлкните правой кнопкой по этой папке, выберите команду Добавить > Создать элемент (Add > New Item) и выберите HTML-страницу (HTML Page). Когда новая страница отобразится на экране, замените её содержимое на следующее6Обратите внимание на то, что вы должны заменить значение параметра credentials в функции init на собственный ключ, полученный на https://www.bingmapsportal.com/ :
<!DOCTYPE html> <html> <head> <title>Map</title> <script type="text/javascript" src="http://ecn.dev.virtualearth.net/mapcontrol/mapcontrol.ashx?v=7.0"></script> <script type="text/javascript"> //Глобальные переменные var map = null; document.addEventListener("DOMContentLoaded", init); window.addEventListener("message", processMessage); //Функция для преобразования строки к синтаксису { functionName: ..., args: [...] } //в вызове именованной функции с такими аргументами. Основана на стандартном //диспетчере, который позволяет коду в iframe быть вызванным посредством //postMessage. function processMessage(msg) { //Проверяет данные и источник (в данном случае - страницу, работающую в локальном //контексте) if (!msg.data || msg.origin !== "ms-appx://" + document.location.host) { return; } var call = JSON.parse(msg.data); if (!call.functionName) { throw "Message does not contain a valid function name."; } var target = this[call.functionName]; if (typeof target != 'function') { throw "The function name does not resolve to an actual function"; } return target.apply(this, call.args); } function notifyParent(event, args) { //Добавляет имя события к аргументам обекта и преобразует в строку в //качестве сообщения. args["event"] = event; window.parent.postMessage(JSON.stringify(args), "ms-appx://" + document.location.host); } //Создает карту (хотя пространство имен не может быть определено без возможности связи) function init() { if (typeof Microsoft == "undefined") { return; } map = new Microsoft.Maps.Map(document.getElementById("mapDiv"), { //Замените эти учетные данные на свои, полученные на //http://msdn.microsoft.com/en-us/library/ff428642.aspx credentials: "...", //zoom: 12, mapTypeId: Microsoft.Maps.MapTypeId.road }); } function pinLocation(lat, long) { if (map === null) { throw "No map has been created"; } var location = new Microsoft.Maps.Location(lat, long); var pushpin = new Microsoft.Maps.Pushpin(location, { draggable: true }); Microsoft.Maps.Events.addHandler(pushpin, "dragend", function (e) { var location = e.entity.getLocation(); notifyParent("locationChanged", { latitude: location.latitude, longitude: location.longitude }); }); map.entities.push(pushpin); map.setView({ center: location, zoom: 12, }); return; } function setZoom(zoom) { if (map === null) { throw "No map has been created"; } map.setView({ zoom: zoom }); } </script> </head> <body> <div id="mapDiv"></div> </body> </html>
Обратите внимание на то, что JavaScript-код отсюда может быть без проблем размещен в разных файлах, на которые можно ссылаться, используя относительный путь. Для простоты, я оставил весь код в одном месте.
В верхней части страницы вы можете видеть ссылку на удалённый скрипт элемента управления карт Bing. Мы можем здесь ссылаться на удалённые скрипты, так как страница загружается в веб-контексте внутри элемента iframe (ms-appx-web:// в default.html). Затем вы можете видеть, как функция init вызывается DOMContentLoader и создаёт элемент управления карты. Далее, у нас есть еще пара методов, pinLocation и setZoom, которые, если понадобятся, могут быть вызваны из основного приложения.
Конечно, так как эта страница загружена в iframe в веб-контексте, мы не можем просто вызвать эти функции напрямую из кода нашего приложения. Вместо этого мы используем функцию HTML5 postMessage, которая вызывает событие message в iframe. Это важный момент: локальный и веб-контексты разделены, поэтому произвольный веб-контент не может управлять приложением или получать доступ к API WinRT. Эти два контекста разделены границей между приложением и веб'ом, которую может пересечь лишь postMessage.
В вышеприведенном коде вы можете видеть, что мы берем подобные сообщения и передаем их функции processMessage, небольшой стандартной подпрограмме, которая преобразует JSON-строку в вызов локальной функции с аргументами.
Для того, чтобы посмотреть на то, как это работает, взглянем на то, как мы вызываем pinLocation из default.js. Для того, чтобы выполнить этот вызов, нам нужны некоторые координаты, которые мы можем получить из API для определения местоположения (Geolocation) WinRT. Мы делаем это в обработчике события onactivated, поэтому местоположение пользователя задаётся при старте приложения (и сохраняется в переменной lastPosition позже для организации общего доступа):
//Поместите это после строки: var activation = Windows.ApplicationModel.Activation; var lastPosition = null; //Поместите это после args.setPromise(WinJS.UI.processAll()); var gl = new Windows.Devices.Geolocation.Geolocator(); gl.getGeopositionAsync().done(function (position) { //Сохраним для общего доступа lastPosition = { latitude: position.coordinate.latitude, longitude: position.coordinate.longitude }; callFrameScript(document.frames["map"], "pinLocation", [position.coordinate.latitude, position.coordinate.longitude]); });
Здесь callFrameScript - это просто небольшая вспомогательная функция, которая превращает целевой элемент, имя функции и аргументы в соответствующий вызов posMessage:
//Поместите это перед app.start(); function callFrameScript(frame, targetFunction, args) { var message = { functionName: targetFunction, args: args }; frame.postMessage(JSON.stringify(message), "ms-appx-web://" + document.location.host); }
Некоторые замечания по этому коду. Для того чтобы получить координаты, вы можете использовать API WinRT для определения местоположения или API HTML5 для определения местоположения. Они почти одинаковые, с небольшими различиями, описанными в "Анатомия приложения и навигация по страницам" курса "Пользовательский интерфейс приложений для Windows 8, созданных с использованием HTML, CSS и JavaScript", "Ввод данных и сенсоры", во врезке: "Определение местоположения в HTML5". Это API присутствует в WinRT, так как другие поддерживаемые языки (C# и C++) не имеют доступ к API определения местоположения HTML5. Мы, в этом курсе, сосредоточены на API WinRT, поэтому мы просто используем функции в пространстве имен Windows.Devices.Geolocation.
Далее, в качестве второго параметра postMessage, вы можете видеть комбинацию из ms-appx[-web]:// и document.location.host. Это означает: "текущее приложение из локального [или веб] контекста", которое является подходящим источником сообщения. Обратите внимание на то, что мы используем то же самое значение для того, чтобы проверить источник сообщения при его получении: код в map.html проверяет, пришло ли сообщение из локального контекста приложения, тогда как код в default.js проверяет, что сообщение пришло из веб-контекста приложения. всегда будьте уверены в том, что соответствующим образом проверяете источники сообщения; посмотрите материал "Разработка безопасных приложений" (http://msdn.microsoft.com/library/windows/apps/hh849625.aspx).
Наконец, вызов getGeoPositionAsync содержит интересную конструкцию, где мы делаем вызов и используем внутри него цепочку из функции done, аргумент которой - еще одна функция. Это - весьма распространённый подход, который мы можем видеть, работая с API WinRT, для любого API, которому может потребоваться более 50 мс для завершения асинхронной операции. Подобное решение было принято так, что использование API ведет к созданию быстрых и динамичных приложений по умолчанию.
В JavaScript подобные API возвращают то, что называется promise-объектом, он представляет результаты, которые будут получены когда-нибудь в будущем. Каждый такой объект имеет метод done, первый аргумент которого - функция, которая будет вызвана по завершении операции, обработчик завершения (completed handler). В учебном курсе мы еще коснёмся данной темы, в частности, поговорим о функции then, которая похожа на done, но поддерживает дальнейшее объединение функций в цепочку ( "Анатомия приложения и навигация по страницам" ) и о том, как promise-объекты в более общем виде используются в асинхронных операциях ( "Коллекции и элементы управления для вывода коллекций" курса "Программная логика приложений для Windows 8, созданных с использованием HTML, CSS и JavaScript и их взаимодействие с системой").
Аргумент, который передается обработчику завершения содержит результат асинхронного вызова, в нашем случае это объект Windows.Geolocation.Geoposition, содержащий последние данные, прочитанные с сенсора. (Читая документацию по асинхронным функциям, вы можете увидеть, что возвращаемый тип имеет вид, похожий на IAsyncOperation<Geoposition> . Имя внутри <> показывает реальный тип данных результата, в итоге, для того, чтобы узнать подробности, вы можете перейти к материалу по этому типу.) Координаты, прочитанные с сенсора, это то, что мы передаем функции pinLocation внутри iframe, которая, в свою очередь, создаёт на карте маркер в указанных координатах и центрирует карту по данному местоположению7Маркер местоположения можно перемещать, однако сейчас это не окажет никакого влияния на наше приложение. Смотрите раздел "Исключительное доверие: Принимаем сообщения из iframe" ниже для того, чтобы узнать, как мы можем получить сведения об изменении местоположения с карты.
Еще одно, последнее, примечание по асинхронным API. Внутри API WinRT все асинхронные функции имеют "Async" в своих именах. Так как подобная практика не принята в среде JavaScript или DOM API, асинхронные функции внутри WinJS не используют данный суффикс. Другими словами, WinRT создана нейтральной по отношению к языкам программирования, а WinJS разработана с учетом языковых соглашений, характерных для JavaScript.