Итак, посмотрим на код поближе.

import

Перейдем в папку pset8/ bin и откроем import. Пока в этом файле только шебанг (то есть «#!«) и TODO. Именно в этом файле вы, в конечном счёте, напишете PHP-скрипт, который будет выполнять итерацию по строкам US.txt, дописывая информацию из каждой строки в places, нашу таблицу MySQL. Но об этом позже.

index.php

Перейдем к pset8/public и откроем index.html. В шапке страницы перечислены библиотеки CSS и JavaScript, которые мы будем использовать. В HTML-комментариях содержатся URL-ссылки на документацию по каждой библиотеке.
Далее посмотрим на body страницы, внутри него содержится div с уникальным id для map-canvas. В этот div мы будем помещать карты. Ниже находится form, который содержит input типа text с единственным id для q (его мы будем использовать для получения входных данных пользователей).

styles.css

Перейдем в public/css и откроем styles.css. Здесь содержится часть CSS, которая имплементирует для нашей задачи интерфейс пользователя по умолчанию. Чтобы лучше увидеть, как всё работает, внесите изменения, сохраните файл и перезагрузите страницу в Chrome. Однако, прежде чем мы пойдем дальше, лучше вернуть файл в его первоначальный вид.

scripts.js

Перейдем в public/js и откроем scripts.js. Наконец-то что-то интересное! Этот файл задает внешний интерфейс для нашей задачи на основе Google Maps и некоторого внутреннего PHP-скрипта для данных. Посмотрим на файл подробнее.

В начале файла объявлены глобальные переменные:

  • Переменная map содержит ссылку на карту, которую мы скоро создадим;
  • Массив markers содержит ссылки на маркеры, которые мы будем использовать в нашей карте;
  • info — ссылка на «информационное окно», в котором будут отображаться ссылки на статьи.

Ниже находится анонимная функция, которую автоматически вызывает jQuery, когда DOM (document object model) полностью загружен (например, когда index.html и все его включения, особенно CSS и JavaScript, были загружены в память).

Перед этой функцией находится определения для styles, массива из двух объектов, который мы будем использовать для конфигурирования нашей карты в соответствии с https://developers.google.com/maps/documentation/javascript/styling. Напомним, квадратные скобки [ ] используются для обозначения массива, тогда как фигурные скобки { } используются для объектов. Выделение части кода абзацем — это лишь стилистическая прихоть, тем не менее, лучше её придерживаться.

Ниже под styles находится options — дополнительный набор ключей и значений, которые мы будем использовать при настройке карты в соответствии с https://developers.google.com/maps/documentation/javascript/reference#MapOptions.

Далее мы определяем canvas, используя jQuery для получения узла DOM, для которого единственным id является map-canvas. Тогда как $ ("# map-canvas") возвращает объект jQuery (который содержит ряд встроенных функций), $("#map-canvas").get(0) возвращает фактический базовый узел DOM.

Вероятно, самой важной является следующая строка, где мы предоставляем глобальной переменной map значение. С помощью строки
new google.maps.Map (canvas, options)

мы говорим браузеру установить новую карту, помещая ее в узел DOM (который определен с помощью canvas), настраивая ее, как описано в options.
Следующая ссылка приказывает браузеру вызвать configure (еще одну функцию, которую мы написали), как только карта будет загружена.
addMarker

Здесь у нас есть TODO. Эта функция добавляет маркер (например, иконку) на карту по заданным place (например, почтовый индекс и др.).

configure

Сразу после нашей анонимной функции в работу вступает функция configure. Напомним, configure вызывается, как только карта загружена. С помощью этой функции мы задаем количество «слушателей» ( «listeners»), определяя, что будет происходить, когда мы «слышим» определенное событие. К примеру,

google.maps.event.addListener(map, "dragend", function() {
    update();
});

означает, что мы хотим «слышать» на карте событие dragend, вызывая нашу анонимную функцию, при условии, что мы ее «слышим». Эта анонимная функция, тем временем, вызывает еще одну функцию (с которой мы познакомимся позже) update. Согласно https://developers.google.com/maps/documentation/javascript/reference#Map, dragend передается, «когда пользователь перестает перетаскивать карту».

Аналогично мы слушаем событие zoom_changed, которое передается, «когда пользователь изменяет масштаб карты (увеличивает или уменьшает)».

С другой стороны, как только мы слышим dragstart, сразу вызывается функция removeMarker. Благодаря ей все маркеры временно исчезают, пока пользователь перетаскивает карту. Это помогает избежать мерцания, которое может появиться, когда маркеры удаляются, а затем снова добавляются, во время изменения границ (т.е. углов) карт.

Ниже обработчиков событий — наша конфигурация для плагина typeahead. Посмотрите еще раз https://github.com/twitter/typeahead.js/blob/master/doc/jquery_typeahead.md, чтобы вспомнить, как работают autoselect, highlight и minLength.

Самое главное — знать, что source возвращает функцию (например, search), которую будет вызывать плагин, как только пользователь начнет печатать, чтобы функция могла ответить массивом результатов поиска на основе ввода пользователя.

Таким образом, функция возвращает массив значений-результатов поиска в соответствии со значениями, введенных пользователем. Например, если пользователь в текстовом поле введет foo, функция должна в конечном итоге вернуть массив всех мест в вашей базе данных, которые каким-то образом соответствуют foo. Как обеспечить эти совпадения — нужно решить вам!

Тем временем, templates возвращает объект с двумя ключами: empty, значением которого является HTML, который должен отображаться, когда search возвращает пустой результат (например, массив длиной 0), и suggestion, значением которого является «шаблон», который будет использоваться для форматирования значений из раскрывающегося списка плагина. На данный момент, шаблон содержит только

TODO . Это значит, что значение из раскрывающегося списка условно является TODO. Однако если вы захотите изменить эти значения, например на

<p> <% - place_name%>, <% - admin_name1%> </ p>

то плагин динамически внесет эти значения (place_name и admin_name1) в список. В отличие от <%=, который Undercore также поддерживает, <%- гарантирует, что значение будет пропущено, как и htmlspecialchars для PHP (см. http://underscorejs.org/#template).

Теперь посмотрим на следующие строки, которые, на первый взгляд, могут показаться весьма загадочными:

$("#q").on("typeahead:selected", function(eventObject, suggestion, name) {
    map.setCenter({lat: parseFloat(suggestion.latitude), lng: parseFloat(suggestion.longitude)});
    update();
});

Эти строки говорят о том, что, если HTML-элемент, уникальным id которого является q, вызывает событие typeahead: selected (это может случиться, когда пользователь выбирает значение из раскрывающегося списка плагина), то мы хотим, чтобы jQuery вызвал анонимную функцию, второй аргумент которой (suggestion) будет объектом, отражающим выбранное значение. Этот объект должен содержать по меньшей мере два параметра: latitude и longitude. Тогда мы вызовем setCenter, чтобы переместить центр карты в эти координаты, а также update, чтобы обновить маркеры на карте.

Между тем, ниже есть следующие строки:

$("#q").focus(function(eventData) {
    hideInfo();
});

Они станут понятнее, если вы ознакомитесь с http://api.jquery.com/focus/.

Еще ниже — следующее:

document.addEventListener("contextmenu", function(event) {
    event.returnValue = true;
    event.stopPropagation && event.stopPropagation();
    event.cancelBubble && event.cancelBubble();
}, true);

Эти строки активируют функции ctrl- и правого клика на карте, которые в Google Maps, к сожалению, отключены, поскольку они конфликтуют с гугловской (невероятно полезной) фичей Inspect Element.

Последняя часть configure вызывает функции update (которую мы скоро рассмотрим подробнее) и focus, на этот раз без аргументов (см. http://api.jquery.com/focus/, чтобы понять, почему).

hideInfo

Ура, короткая функция! Она только вызывает close относительно нашего глобального информационного окна.

removeMarkers

Что ж, TODO. В конце концов, эта функция таки будет «убирать» избранные либо все маркеры с карты!

Search

Эта функция вызывается каждый раз, когда пользователь изменяет текстовое поле, внося или удаляя символы. Значение текстового поля (то есть то, что пользователь в конечном итоге введет), передается search как query. Плагин также передает в search свой второй аргумент cb «обратная связь», является функцией, которую search вызывает как только поиск совпадений закончен.

Иными словами, эта передача cb позволяет search быть «асинхронным». Таким образом, search вызывает cb как только он готов, и при этом не блокирует другие функции. Соответственно, search использует метод из jQuery getJSON чтобы асинхронно связаться search.php, передавая ему параметр geo, значением которого является query. Как только search.php ответит (пройдет несколько секунд), будет вызвана анонимная функция, которая была передана в done, и она передаст data, значением которого будет JSON, запускающий search.php (в случае ошибки вместо этого будет вызвана функция fail).

Последняя функция, которая вызывается, — cb, которой search также передает data. Таким образом, плагин мог перебирать места в нём (при условии, что search.php обнаружил совпадения) с целью обновления раскрывающегося списка плагина.

Заметьте, что согласно http://api.jquery.com/jquery.getjson/, мы используем интерфейс getJSON. Вместо того, чтобы сразу передавать анонимную функцию в getJSON, мы связываем вызовы getJSON, done (чей аргумент, анонимная функция, будет вызвана в случае успеха) и fail (аргумент которой, другая анонимная функция, будет вызвана в случае неудачи). Для получения дополнительной информации можно перейти по ссылке http://api.jquery.com/jquery.ajax/, а для объяснения интерфейсов — http://davidwalsh.name/write-javascript-promises.

Также заметьте, что мы можем использовать console.log по аналогии с функцией printf в C для отображения лога ошибок для отладки. Просто помните, что console.log будет отображать сообщения в консоли браузера (то есть во вкладке Console), а не в вашем окне (подробности: https://developer.mozilla.org/en-US/docs/Web/API/Console.log).

showInfo

Эта функция открывает информационное окно для отдельного маркера в определенном формате (например, HTML). Таким образом, если получен только один аргумент (marker), showInfo будет просто отображать крутящуюся картинку (обычный анимированный GIF-файл). Обратите внимание, как эта функция создает строку в HTML динамически, а затем передает эту строку в setContent. Этот прием стоит запомнить, он может быть полезным в дальнейшем.

update

Эта функция сначала определяет существующие границы карты и координаты верхнего правого и нижнего левого углов. Затем она передает эти кординаты с помощью GET-запроса (в пределах getJSON) в update.php в стиле:

GET /update.php?ne=37.45215513235332%2C-122.03830380859375&q=&sw=37.39503397352173%2C-122.28549619140625 HTTP/1.1

Здесь %2C — запятые, которые были «закодированы» в URL. Использование запятых здесь является произвольным; мы ожидаем, что update.php самостоятельно будет анализировать и извлекать широту и долготу из этих параметров. Мы могли просто передать этой функции четыре отдельных параметра, однако, наверняка будет понятнее с семантической точки зрения задать по одному параметру для каждого угла.

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

Как только update.php пришлет ответ, вызывается анонимная функция, которая была передана в done, и передает data со значением JSON, который выпустил update.php. В случае ошибки вызывается fail. Эта неизвестная функция сначала убирает все маркеры с карты, а затем итеративно добавляет маркеры JSON, по одному для каждого места (например, город).

update.php

Перейдем в pset8/public и откроем update.php. Здесь находится скрытый от пользователя «внутренний» скрипт. Он выдает массив JSON, содержащий до 10 элементов (например, городов), которые попадают в определенные рамки (например, в прямоугольник, который определяется упомянутыми выше углами). Вам не нужно менять этот файл, однако внимательно перечитайте его код, подспудно «гугля» каждую функцию, которая вам не понятна.

Обратите особое внимание на функцию preg_match, которая позволяет сравнить строки с «обычными выражениями». Вызов этой функции дважды в update.php просто гарантирует, что sw и ne являются координатами по широте и долготе, которые разделены запятой. Также, SQL-запросы к этому файлу типично предполагают, что земля плоская (для простоты

search.php

Затем откройте search.php. Ага, теперь тут совсем немного данных. Просто возможный TODO!

articles.php

Теперь откройте articles.php. Внимательно его прочитайте, используя Google, если нужно. Заметьте, каким образом здесь используется параметр geo, который передается для локальных новостей в Google News, получая в результате массив объектов JSON, каждый из которых состоит из двух ключей: link и title.

В конце концов, вы можете увидеть этот файл в действии. Просто перейдите по этим URL:

  • http: //pset8/articles.php?geo = Cambridge, + Massachusetts & ned = us
  • http: //pset8/articles.php?geo = 02138 & ned = us

используя Chrome с виртуальной лаборатории CS50.

Или, если пользуетесь CS50 IDE:

или

где username — ваше имя пользователя. Вы увидите массив статей/заметок JSON!

config.php

Теперь вкратце ознакомимся с файлом, на который ссылаются все PHP-файлы. Перейдите в pset8/includes и откройте config.php. Этот файл очень похож (хотя попроще будет) на файл config.php из предыдущего задания (№7).

helpers.php

В этом файле мы определили только одну функцию — lookup. В отличие от поиска в практическом задании с прошлой недели, эта версия поиска запрашивает новости Google для статей для конкретной географии. Необязательно понимать все строки этого файла, но просмотрите его комментарии!

config.json

Затем откройте config.json. Знакомо выглядит, не так ли? Только теперь он нужен для базы данных pset8.