Заметки в категории «JavaScript» (страница 12)

Внедряем карты Google Maps на сайт

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

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

var APP = {};

Собственно, код модуля:


(function () {

    // TODO!!! Нужно будет получить свой уникальный ключ
    var GOOGLE_MAP_KEY = "abcdefg";

    /**
     * Карта Google
     * @param root  корневой DOMElement, в котором будет рисоваться карта
     * @param model массив маркеров
     */
    APP.Map = function (root, model) {

        var map;

        function drawMap (map, data) {

            var i, bounds = new google.maps.LatLngBounds();

            for (i = 0; i < data.length; i++) {
                bounds.extend(new google.maps.LatLng(data[i].lat, data[i].lng));
            }

            map.setCenter(bounds.getCenter(), map.getBoundsZoomLevel(bounds));

            // создаем базовый объект маркета
            var baseIcon = new google.maps.Icon(google.maps.DEFAULT_ICON);

            for (i = 0; i < data.length; i++) {
                (function (s) {
                    var icon, marker;
                    icon = new google.maps.Icon(baseIcon);
                    icon.image = s.icon || icon.image;
                    marker = new google.maps.Marker(new google.maps.LatLng(s.lat, s.lng), icon);
                    map.addOverlay(marker);

                    google.maps.Event.addListener(marker, "click", function() {
                        // обрабатываем клики по маркеру
                    });
                })(data[i]);
            }
        };

        // загружаем карту, если это необходимо, и рисуем маркеры
        try {
            if (typeof google === "undefined") {
                $(document).one("mapLoaded", function () {
                    map = new google.maps.Map2(root);
                    map.addControl(new google.maps.SmallZoomControl());
                    $("body").bind("unload", google.maps.Unload);
                    drawMap(map, model);
                });
                $.getScript("http://www.google.com/jsapi?key=" + GOOGLE_MAP_KEY + "&callback=APP.Map.callback");
            } else {
                drawMap(map, model);
            }
        } catch (exc) {};
    };

    APP.Map.callback = function () {
        google.load("maps", "2", {
            "callback": function () {
                $(document).trigger("mapLoaded");
            },
            // опциональный параметр, задающий язык карты
            "language": "ru"
        });
    };
})();

Для нормальной работы модуля нужно получить ключ для доступа к API Google Maps и указать его в соответствующей переменной.

А теперь пример использования модуля.


// инициализируем карту тестовыми данными
$(function () {
    // центр в Челябинске
    var center = {lat: 55.16, lng: 61.4},
        mapHolder = $("#map-object").get(0),
        markers = [], i;

    if (mapHolder) {
        // случайно несколько маркеров около заданного центра
        for (i = 0; i < 10; i++) {
            markers.push({
                lat: center.lat + (Math.random() - 0.5) / 30,
                lng: center.lng + (Math.random() - 0.5) / 15
            });
        }
        markers.push(center);
        APP.Map(mapHolder, markers);
    }
});

Оставте свой комментарий

Закомментировать код

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

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

/*

alert("Error!!!");

// */

Этот блок сейчас закомментирован. Но добавив всего-лишь один слеш в первой строке комментария мы раскомментируем весь блок целиком.


//*

alert("Error!!!");

// */

Этот трюк сильно упрощает включение и выключение целых блоков кода.

Комментарии к заметке: 3

Инверсия управления

Инверсия управления (Inversion of Control) — принцип программирования, который уменьшает связанность между программными компонентами. Разрыв зависимости достигается благодаря некому абстрактному интерфейсу, через который один компонент взаимодействует с другим. Отличной реализацией такого абстрактного интерфейса может быть система событий. Один компонент генерирует события, а другой отслеживает их появление.

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

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

Есть задача сделать список объектов и панель инструментов для работы с этими объектами.

Ни один объект в списке не выбран

Когда в списке выбран один объект, то его можно отредактировать или удалить.

В списке выбран один объект

Если выбрано несколько объектов, то их можно только удалить.

В списке выбрано несколько объектов

В списке есть дополнительная «галочка» для выбора всех объектов в списке.

В списке выбраны все объекты

То, что объект выбран, отражается не только «галочкой» рядом с его названием, но и подсветкой всей строки другим цветом.

Для связи компонент списка и панели инструментов важно отслеживать 3 состояния списка:

  • ни один объект не выбран (selected-zero)
  • выбран один объект (selected-one)
  • выбрано несколько объектов (selected-many)

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


var btnEdit = $("#toolbar input.btn-edit"),
    btnDelete = $("#toolbar input.btn-delete");

$("#list")
    .bind("selected-zero", function () {
        btnEdit.attr("disabled", true);
        btnDelete.attr("disabled", true);
    })
    .bind("selected-one", function () {
        btnEdit.attr("disabled", false);
        btnDelete.attr("disabled", false);
    })
    .bind("selected-many", function () {
        btnEdit.attr("disabled", true);
        btnDelete.attr("disabled", false);
    });

В списке перехватываем изменение состояние «галочек».


var list = $("#list"),
    checkboxes = list.find("tbody input:checkbox");

checkboxes.bind("change click", function (e) {
    $(this).closest("tr").toggleClass("sel", this.checked);
    // блокируем всплытие события click, так как уже отработали его
    e.stopPropagation();
    // оповещаем подписчиков, что изменилось состояние списка
    notify();
}).trigger("change");

«Галочка» в шапке списка передает свое состояние всем остальным


list.find("thead input:checkbox").bind("change click", function () {
    checkboxes.attr("checked", this.checked).trigger("change");
});

Далее внутри самого списка чтобы выбрать объект или отменить выбор можно пользоваться не только «галочками», но и кликнуть по строке.


list.find("tbody tr").bind("click", function () {
    var chb = $("input:checkbox", this);
    chb.attr("checked", !chb.get(0).checked).trigger("change");
});

Диспетчер событий.


function notify() {
    var count = checkboxes.filter(":checked").length,
        type = count === 0 ? "selected-zero" : count === 1 ? "selected-one" : "selected-many";
    list.trigger(type);
}

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

Подведем итог:

  1. Связываем несколько программных компонент не прямыми вызовами их методов, а через абстрактный интерфейс событий.
  2. Генерируем столько событий, сколько нам требуется для описания состояния компонента.
  3. Исключаем дублирование кода с помощью синтетических DOM-событий.
Оставте свой комментарий

Навигация с помощью клавиатуры

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

Любой элемент в документе может получить фокус, если у него указан атрибут tabindex . Он не является частью спецификации HTML 4.01, но фактически реализован во всех современных браузерах.

Значение атрибута tabindex

Атрибут может принимать любые числовые значения. Явное указание атрибута позволяет получить фокус с помощью указателя мыши или программно через метод focus(). Отсутствие атрибута устанавливает обычную реакцию элемента. В этом случае фокус могут получить только элементы формы, ссылки и некоторые другие.

Поведение при навигации с помощью клавиши «Tab»

  • Отрицательное

    Элемент игнорируется

  • Нулевое

    Последовательность задается положением элемента в документе

  • Положительное

    Значение явно задает порядок элементов при перемещении фокуса

Отслеживание сфокусированного элемента

События focus и blur могут быть использованы для любого элемента. Но так как нет стандартного интерфейса, для определения получил элемент фокус или нет, то нужно будет самостоятельно обрабатывать и запоминать это состояние.

Не следует предполагать, что фокус может меняться только с помощью клавиатуры или мышки, потому что вспомогательные технологии, такие как «чтение с экрана» могут самостоятельно устанавливать фокус на элементы.

Динамическое изменение атрибута tabIndex

Изменение атрибута может использоваться в случае, когда элемент управления может отключаться, и его следует пропускать при перемещении между элементам.

Отслеживание фокуса у элемента document

Что касается событий focus и blur , то их может генерировать не только конкретный элемент, но и весь документ в целом. В jQuery 1.4 разработчики добавили синтетические события focusin и focusout для корректной делегации родительским элементам DOM-событий focus и blur соответственно. Прикрепив обработчик этих событий к документу, можно отслеживать активность окна браузера.

Оставте свой комментарий

Инфраструктура виджетов в YUI3

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

Пример минимального виджета:


YUI.add("mywidget", function (Y) {

    var App = Y.namespace("App");

    /* MyWidget конструктор */
    function MyWidget(config) {
        MyWidget.superclass.constructor.apply(this, arguments);
    }

    /* Имя виджета, которое будет использоваться для генерации названий классов и т.п. */
    MyWidget.NAME = "mywidget";

    Y.extend(MyWidget, Y.Widget, {
        initializer: function (config) {},
        destructor: function () {},
        renderUI: function () {},
        bindUI: function () {},
        syncUI: function () {}
    });

    App.MyWidget = Y.Base.build(MyWidget.NAME, MyWidget, []);

}, "0.1", {use: ["widget"]});

Важными методами жизненного цикла виджета являются:

  • initializer

    Метод автоматически вызывается конструктором и может быть использован для настойки экземпляра. Параметры, с которыми был вызван конструктор, передаются без изменений. Зачастую метод может оставаться пустым.

  • destructor

    Метод вызывается во время удаления виджета и используется для очистки экземпляра от созданных DOM-элементов и обработчиков событий. Если все DOM-элементы создавались внутри «bounding box», то нужно будет только «занулить» ссылки на них. Сами элементы и обработчики событий будут удалены автоматически.

  • renderUI

    Часть реализации «renderer», ответственная за создание представления виджета. Метод добавляет DOM-элементы, которые необходимы виджету, или изменяет существующие.

  • bindUI

    Часть реализации «renderer», ответственная за подключение обработчиков событий изменения атрибутов или DOM-событий.

  • syncUI

    Часть реализации «renderer», ответственная за обновление интерфейса виджета на основании его текущего состояния.

Получение данных о состоянии виджета в методах renderUI, bindUI, syncUI выполняется с помощью метода get, наследованного у класса Attribute . Следует отметить, что если используется статическое поле ATTRS у виджета, которое определяет начальное состояние, то в нем следует описать все используемые атрибуты. Если какой-то из них не будет описан, то получить доступ к нему не будет возможности.

Наиболее часто используемыми атрибутами, пожалуй, являются boundingBox и contentBox . Первый содержит ссылку на объект типа Node, который служит контейнером для виджета, а второй — ссылку на контейнер содержимого. Задать разметку для этих контейнеров можно через конфигурационные свойства BOUNDING_TEMPLATE и CONTENT_TEMPLATE соответственно. По умолчанию для них используется просто элемент DIV.

Подключается в проект этот виджет как и любой другой модуль YUI. Вызвать его из приложения можно, например, следующим образом:


YUI().use("mywidget", function (Y) {
    var widget = new Y.App.MyWidget({
        a: 10,
        title: "Sample widget"
    });
    widget.render();
});

В конструкторе указываются, если это необходимо, конфигурационные параметры виджета. Метод render может быть вызван с параметром, определяющий DOM-элемент, где будет отображен виджет. Если он вызван без параметров, как в примере, то виджет отображается в элементе BODY.

Оставте свой комментарий