Клуб API Карт

динамическая генерация содержимого балуна

Пост в архиве.

Диспозиция: при открытии страницы браузером, с помощью ajax подгружается массив данных об объектах, метки на карте перерисовываются по событию BoundsChange так, чтобы одновременно отображалось только разумное количество меток.

При построении меток (Placemark) к ним в поле metaDataProperty записываются пользовательские данные - ID объекта в массиве.

Осталось решить подзадачу: как сделать, чтобы балун для метки строился динамически, по щелчку на метке? Суть в том, что содержимое балуна нужно вытащить из массива по ключу, причем кроме данных конкретно этого элемента могут использоваться данные привязанных к нему (ссылающихся на него) элементов. Вешать на каждую создаваемую метку обработчик представляется решением далеким от изящества и разумного использования ресурсов. Вот неспроста же в jQuery придумали метод live(). Есть способ навесить единственный обработчик на всю карту, чтобы он срабатывал при щелчке на метке?

12 комментариев
Sergey Konstantinov
28 января 2016, 07:20
Возможно, Вам следует посмотреть в сторону использования активных областей? Там все Ваши проблемы решаются - динамическая подгрузка меток, пользовательские поля к объектам, единый обработчик открытия балуна на всю карту.
зато раскидать метки на карте с использованием активных областей - получается сложнее. или я чего-то не понимаю...
Кирилл Яковлев
28 января 2016, 07:20
При создании метки в нее можно положить ключ, более того, ей можно сразу балун заполнить.
Подобного рода запросы и генерации сильно тормозят карту и (в меньшей степени) сервер. Тем более что количество объектов, попадающих в обзор может быть больше разумного количества меток.
заполнять балуны сразу для пары сотен меток? можно, конечно, но зачем грузить ресурсы браузера? клиент отображаемую область передвинет, а там снова пара сотен меток - снова балуны генерить? а реально будут вызваны от силы несколько балунов...
вариант 1.
что-то типа


GEvent.addListener(marker, 'click',
            function(){
                thistemplatenode=$("");
                thistemplatenode.html(marker.tiliObj.getBallonHtml());
                setTimeout(function(){marker.tiliObj.jquery(thistemplatenode);},1);
                marker.openInfoWindow(thistemplatenode.get(0));
            });



вешаемся на клик, создаем элемент, заливаем в него данные, на таймауте вешает туда патчинг jquery.
потом передаем ноду балуну и он ее показывает.

Вариант два - создаем балун лайоут, туда будет передан некий content, который есть маркер, в который вы можете дописать свои данные..
получается что-то типа


 // Класс для создания макета для содержимого балуна
        function MyBalloonLayout (context, map, owner) {
            this.balloon = $("");
            this.context = context;
        }

        MyBalloonLayout.prototype = {
           // Вызывается при открытии балуна
            onAddToParent : function (parentNode) {
                this.balloon.appendTo(parentNode);
                this_balloon_template =  this.balloon;
                this.update();
            },

            // Вызывается при закрытии балуна
            onRemoveFromParent : function () {
                this.balloon.remove();
            },

            // Обновление содержимого балуна
            update : function () {
                // Получаем стиль метки
                this.balloon.html(this.context.tiliObj.getBallonHtml());
                var _this=this;
                setTimeout(function(){_this.context.tiliObj.jquery(_this.balloon);},1);
            }
        }
да, гугловский API как раз позволяет навесить единственный обработчик на всю карту, в который будет передаваться указатель на экземпляр метки, по которой был щелчок. но мне-то нужно в API Яндекс.Карт такое реализовать.

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

итак - у нас есть маркер, ему в стили задаем

 s.balloonContentStyle={template :ballon_template}
где
 ballon_template = new YMaps.LayoutTemplate(MyBalloonLayout);   

тогда в фабрике лайаута function MyBalloonLayout (context, map, owner)
context == marker от которого балун открывается
а tiliObj есть this.marker.tiliObj=this;
так как это код tili-testo.ru

получается что

update : function () {
                // Получаем стиль метки
                 this.balloon.html(this.co



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

А насчет одного обработчика и разных тама live
какраз разница что использовать - в любом случае это будет в одном месте.
маркеры - они же в общем случае одинаковые

как привязать к YMaps.Placemark пользовательские данные - разобрался, для этого подходит поле metaDataProperty, наследуемое от YMaps.IGeoObject.

осталась вторая задача. как, в момент щелчка по метке/открытия балуна, обработать это событие, не навешивая обработчик на каждый экземпляр Placemark? приведенные thekashey примеры перевариваю, но пока не догнал
    // ХАК на API карт
    // Т.к. в API карт нет явного стандартного решения наполнения балуна по ajax в момент открытия
    // (т.е. оно есть, но тогда надо дважды отресовывать балун)
    // Переопределяем метод открытия балуна
    YMaps.Placemark.prototype.openBalloon = function myF(v, t) {
        //YMaps.jQuery('#debugArea pre').html(showObj(this));
        // Сам хак, который проверяет наличие описания
        // если его нет делаем ajax запрорс
        if (this.description == null) {
            // вызов ajax
            setAjaxBalloon(this.type, this.id);
            // ставим заглушку, чтобы не было зацикливания,
            // если по какой то причине описание от ajax будет возвращаться null
            if (this.description == null) {
                this.description == ' -- ';
            }
        }
        else {
            // Стандартная обработка болуна
            if (this._map) {
                if (this._balloonVisible) {
                    this.closeBalloon();
                }
                this._hideHintIfVisible();
                var x = this, s = {}, w = this._getComputedBalloonOptions();
                for (var u in w) {
                    s[u] = w[u];
                }
                s.onClose = function () { if (w.onClose) { w.onClose(); } x._onCloseBalloon(); };
                if (this.__hideIcon) {
                    this._setState({ hidden: 1, hover: 0 });
                } else {
                    this._setState({ active: 1 });
                }
                this.__actualHideIcon = this.__hideIcon;
                this._balloonVisible = 1;
                this._map.openBalloon(this._point, this._getBalloonContentLayout(), s);
            }
        }
    }
Это именно хаки. Вы используете скрытые поля и методы. Никто не гарантирует их неизменность при переходе от версии к версии. Такое использование не рекомендуется.

Более того, вы предлагаете переписывать метод класса API без наследования. Пользователи, которые воспользуются вашим решением могут пострадать. Вы ведь этого не хотите, не так ли?

Лучше воспользоваться макетом и использовать его интерфейс для того, чтобы решить вашу задачу.

function MyBalloonContentLayout (context, map, owner) {   
    var element =YMaps.jQuery('');

    this.onAddToParent = function (parentNode) {
        element.appendTo(parentNode);
        this.update();
    };

    this.onRemoveFromParent = function () {
        element.remove();
    };

    this.update = function () {
        var _this = this;

        YMaps.jQuery.getJSON(
            "http://api.flickr.com/services/feeds/photos_public.gne?jsoncallback=?", {
                tags: context.id,
                format: "json"
            },
            function (data) {
                var firstResult = data.items[0];
                element
                    .find(".title")
                        .html(firstResult.title)
                        .end()
                    .find(".content")
                        .html(YMaps.jQuery("").attr("src", firstResult.media.m));
                context.getBalloon().update();
            }
        );
    };

    this.getRootNodes = function () {
        return element;
    };

    this.setContent = function (content) {
        content.onAddToParent(element.find(".content"));
    };
};

Использовать его очень легко.

// Создаем группу с нашим макетом
// Он применится для всех объектов группы автоматически
var group = new YMaps.GeoObjectCollection({
    balloonContentStyle : {
        template : new YMaps.LayoutTemplate(MyBalloonContentLayout)
    }
});
map.addOverlay(group);

group.add(createPlacemark(new YMaps.GeoPoint(37.62, 55.74), "cat"));
group.add(createPlacemark(new YMaps.GeoPoint(37.65, 55.78), "dog"));

// Функция для создания меток (вспомогательная)
function createPlacemark (point, id) {
    var placemark = new YMaps.Placemark(point);
    placemark.id = id;

    return placemark;
}

"Официальное" решение без хаков выглядит вполне симпатично и очень аккуратно ;)
Вы уверены что ваше решение работает?
Я попробовал, и все равно балун сначала появляется пустой, а после того как данные подгрузились, балун так и осталься пустой
И еще почему то после того как ты кликнул по какой-то метке, потом щелкаешь по второй, метод update выполняется два раза подряд!