Meteor / Blaze / Mongo / Leaflet - Динамически заполненные всплывающие окна Leaflet не передают данные кнопке для входа в базу данных? - PullRequest
0 голосов
/ 04 июня 2018

Я работаю с картой Leaflet, которая отображает данные маркера на основе запроса MongoDB.Результаты запроса сохраняются в переменную (я знаю, плохая форма для больших объемов информации, но хорошо, если у вас всего ~ 25 штук) в виде массива, а затем я перебрал эту переменную, и она сохранила информацию, используя цикл forсоздать маркеры моей листовки и заполнить их всплывающие окна информацией, относящейся к каждой записи.Эта часть прекрасно работает.

    this.autorun(function(){

    fsqresults = FsqResults.find().fetch({});

    container = $('<div />');

    for (i=0; i < fsqresults.length; i++) {

        marker = L.marker([fsqresults[i].geometry.coordinates[1], fsqresults[i].geometry.coordinates[0]], {icon: violetIcon}).addTo(mymap);

        container.html("<b>" + "Name: " + "</b>" + fsqresults[i].properties.name + "<br>" +
                        "<b>" + "Address: " + "</b>" + fsqresults[i].properties.address + "<br>" +
                        "<b>" + "Checkins: " + "</b>" + fsqresults[i].properties.checkIns + "<br>" +
                        "<b>" + "Users: " + "</b>" + fsqresults[i].properties.usersCount + "<br>" +
                        "<b>" + "Tips: " + "</b>" + fsqresults[i].properties.tips + "<br>");

        marker.bindPopup(container[0]);

    } // end for loop

Для каждого маркера есть кнопка, чтобы зарегистрировать событие «checkin» в другой коллекции Mongo для размещения записей о регистрации.Кнопка успешно запускает событие и создает запись во второй базе данных, но не привязывает динамически заполненные данные к записи, поэтому я могу видеть, на каком маркере нажал пользователь.

    container.append($('<button class="btn btn-sm btn-outline-primary js-checkin">').text("Check In"));

    container.on('click', '.js-checkin', function() {

        var currentVenue = fsqresults[i].properties.name;
        console.log(currentVenue);

        console.log("You clicked the button!");
        if (!Meteor.user()) {
            alert("You need to login first!");
        }

        if (Meteor.user()) {
            console.log("Meteor User Verified");
            Checkins.insert({user: Meteor.user(), name: currentVenue});
        }
    });

}); // end this.autorun

Консольговорит мне, что currentVenue не определено.Я знаю, что это как-то связано с тем, что fsqresults является динамически заполненной переменной.Я пытался найти способы «закрепить» информацию в нем (т.е. создать вторую переменную с пустым массивом, поместить в нее данные из fsqresults, а затем перебрать маркеры по этой переменной), но это не сработалопоскольку результаты запроса MongoDB, несмотря на то, что они сами находятся в формате массива, не будут успешно выдвигать или объединять переменную с пустым массивом.

Я искал ответ на эту проблему, и у меня не получится.Я потерялся;есть ли другое решение, которое могло бы пялиться мне в лицо?

Некоторые замечания: весь этот код находится в функции Template.map.onRendered ().В Leaflet возникают проблемы с областями видимости, если я делегирую код в помощники и события, поэтому я не создал шаблон {markers} и только что сделал {{#each markers}} для его итерации.Поэтому я вынужден использовать кодирование в стиле jQuery для создания элементов DOM и запуска триггеров событий.Приведенный выше код обернут в функцию this.autorun, чтобы гарантировать, что он действительно работает при рендеринге карты.Я не думаю, что это проблема (хотя никто не может исключить это!).

Ответы [ 2 ]

0 голосов
/ 09 июня 2018

Я нашел решение для первой части моей проблемы - сначала проблема с закрытием javascript / областью видимости для внутренней и внешней областей функций.Я потратил около 2 дней, обдумывая этот SO-ответ : концепция использования первого цикла for для создания отдельных экземпляров функции (если бы это была игра, первый цикл for "установил бы сцену"«для показа» и использование второго цикла for для выполнения каждого экземпляра функции («свет, камера, действие!»).

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

this.autorun(function(){

    fsqresults_fetch = FsqResults.find().fetch({});

    // console.log(fsqresults_fetch);

    for (i = 0; i < fsqresults_fetch.length; i++) {

        container = $('<div />');

        const fsq_marker = L.marker([fsqresults_fetch[i].geometry.coordinates[1], fsqresults_fetch[i].geometry.coordinates[0]], {icon: blueIcon}).addTo(mymap);
        const fsq_venueAddress = fsqresults_fetch[i].properties.address;
        const fsq_venueName = fsqresults_fetch[i].properties.name;
        const fsq_geometry = {type: "Point", 
                    coordinates: [fsqresults_fetch[i].geometry.coordinates[0], fsqresults_fetch[i].geometry.coordinates[1]]};

        container.html("<b>" + "Name: " + "</b>" + fsqresults_fetch[i].properties.name + "<br>" +
                    "<b>" + "Address: " + "</b>" + fsqresults_fetch[i].properties.address + "<br>");

        container.append($('<button class="btn btn-sm btn-outline-primary" id="js-checkin">').text("Check In"));

        fsq_marker.bindPopup(container[0]);

        container.on('click', '#js-checkin', function() {

            console.log("You clicked the button!");
                if (!Meteor.user()) {
                    alert("You need to login first!");
                }

                if (Meteor.user()) {
                    console.log("Meteor User Verified");
                    Checkins.insert({type: "Feature", geometry: fsq_geometry, properties: {name: fsq_venueName, address: fsq_venueAddress, user: Meteor.user()}});
                }
            }); //end container.on

    } //end for loop

}); //end this.autorun

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

Теперь мне действительно любопытно попробовать решение, которое разместил @ghybs, поэтому мои события сгруппированы и запущены, поскольку Blaze должен работать!

0 голосов
/ 07 июня 2018

Как отмечено в комментариях к вопросу, у вас есть проблема с областью действия вашего итератора индекса i, и не должно быть никаких технических проблем при интеграции Leaflet с Meteor (хотя с Blaze это может быть не совсем тривиально и не интересно).

1.Проблема области итерации

Консоль сообщает мне, что currentVenue это undefined.

Это потому, что вы пытаетесь получить доступ к fsqresults[i].properties.name в вашем container.on('click' прослушиватель событий / обратный вызов, который будет вызываться по щелчку пользователя, т. Е. после завершения ваш цикл for завершен, следовательно, ваша переменная итератора индекса i будет равна fsqresults.length.

Вы в случае примера 6 приняли ответ: Как работают замыкания JavaScript?

2.Интеграция листовки с Meteor (Blaze)

Поскольку вы упомянули, что пытались использовать помощников, события и {{#each markers}}, я предполагаю, что вы используете Blaze в качестве движка рендеринга Meteor.

Хотя React-Leaflet и Vue2Leaflet действительно предлагают возможность использовать своего рода компонент "<Marker>" (такой же для других типов слоя Leaflet), последний предназначен только для целей объявления шаблона, т.е.он не отображает напрямую DOM / HTML, а только вызывает некоторые методы Leaflet, которые будут отвечать за управление DOM.Как указано в React-Leaflet ограничения :

Представленные компоненты являются абстракциями для слоев Leaflet, а не для элементов DOM.

Примечание: интересно видеть, что angular-leaflet-директива и @ asymmetrik / ngx-leaflet не попали в одно и то же искушение и придерживались JS-декларации слоев Leaflet.

Поэтому попытка создать Template.Marker (используется как {{> Marker}}) в Blaze может быть излишней, так как вы просто вызываете некоторые фабрики Leaflet (например, L.marker) в пределах вашего Template.Marker.onCreated (и нуждаетесь в доступе).каким-то образом родительский объект map для добавления вашего маркера в…), без рендеринга какого-либо узла DOM самостоятельно (т. е. у вас будет HTML-файл с пустым <template name="Marker"></template>).

Хотя мы забудем о шаблоне маркера вBlaze (как вы уже сделали), мы все еще можем использовать управление событиями Blaze для обработки пользовательских кликов в вашем всплывающем окне Leaflet.Для этого нам понадобится несколько функций Blaze, которые, как я признаю, могут быть лучше документированы:

  1. Мы можем присоединить произвольные данные JS к нашему экземпляру шаблона .
  2. События шаблона делегированы , поэтому нам не нужно прикреплять их к каждому <button> перед передачей.
  3. Мы можем легко получить доступ к экземпляру шаблона в обработчиках событий (как второй аргумент слушателя события).

2.1.Присоединение произвольных данных JS к нашему экземпляру шаблона

Как указано в API экземпляров шаблона :

[…], вы можете назначить дополнительные свойства вашегоВыбор объекта.Используйте методы onCreated и onDestroyed для добавления обратных вызовов, выполняющих инициализацию или очистку объекта.

Поэтому вы можете хранить fsqresults в своем экземпляре Template, чтобы вы могли получить доступпозже (обычно в вашем слушателе событий):

Template.myTemplate.onRendered(function () {
  this.autorun(() => { // Using an arrow function to keep the same `this`, but you could do `const self = this` beforehand.
    const fsqresults = this.fsqresults = FsqResults.find().fetch();
  });
});

Но так как мы хотим получить доступ к определенным функциям позже, может быть более интересно преобразовать fsqresults в словарь.Поскольку ваш идентификатор выглядит как feature.properties.name, вы можете сделать:

Template.myTemplate.onCreated(function () {
  this.autorun(() => {
    const fsqresults = this.fsqresults = FsqResults.find().fetch();
    const markersDict = this.markersDict = {};

    L.geoJSON(fsqresults, {
      pointToLayer(feature, latlng) {
        const props = feature.properties;
        const markerName = props.name;

        // Save a direct reference to the Feature data,
        // using the `markerName` as key (ID).
        markersDict[markerName] = feature;

        // Store the `markerName` in the button `dataset`
        // (i.e. as a `data-` attribute),
        // as already suggested in the question comments,
        // so that we can easily retrieve the ID / key
        // of the Marker data associated with the button the user clicked on.
        return L.marker(latlng).bindPopup(`
          <p>${markerName}</p>
          <button role="popupClick" data-marker-name="${markerName}">
            Popup action
          </button>
        `);
      },
    });
  });
});

2.2.Делегирование обработчика событий шаблона

Как указано в Подробные сведения о Blaze :

Механизм DOM […], который имеет […] делегирование события

(извините, в официальном документе нет никаких других упоминаний об этой функции ... дайте мне знать, если вы найдете лучший вариант!)

Therпоэтому, пока мы создаем обработчик события Template с соответствующим селектором, нам не нужно прикреплять прослушиватель событий к каждой кнопке, который в любом случае мы не можем создать как узел, а оставить его как String, переданный Leaflet .bindPopup (как сделано в приведенном выше примере кода).

Например:

Template.myTemplate.events({
  // Even if the `<button role="popupClick">` are not DOM nodes yet
  // (because Leaflet will create them from the HTML String
  // only when the user opens the popup by clicking on the Marker),
  // the "click" event will bubble up to the template instance,
  // which will call this event handler if it matches the selector.
  'click button[role="popupClick"]'() {
    console.log('clicked on a button that has been built in a Leaflet Popup');
  }
});

2.3.Получите доступ к экземпляру шаблона и нашим данным Feature

Обработчик события Blaze вызывается с дополнительным вторым аргументом , который является текущим экземпляром шаблона:

Функция-обработчик получает два аргумента: event, объект с информацией о событии, и template, экземпляр шаблона для шаблона, в котором определен обработчик.

Поэтому в нашемВ этом случае мы можем легко получить переменную markersDict, которую мы определили в onCreated, и использовать ее для получения точных данных функции маркера, связанных с кнопкой, на которую нажал пользователь:

Template.myTemplate.events({
  'click button[role="popupClick"]'(event, templateInstance) {
    const button = event.currentTarget;
    const markerName = button.dataset.markerName;

    const markerFeature = templateInstance.markersDict[markerName];

    // Do something with `markerFeature`…
    console.log(markerFeature);
  }
});

Если вы тольковам нужно свойство name, тогда вы можете даже пропустить шаг 2.1 и напрямую использовать строку markerName, полученную из набора данных <button>.

...