An advanced template system incorporating dynamic scope compilation

My project requires a unique solution that cannot be achieved with standard data-binding methods.

I have integrated a leaflet map that I need to bind with a vue view-model.

While I was able to display geojson features linked to my view, I am facing challenges in displaying a popup bound with vue.js

The primary question at hand is: "How can I open and bind multiple popups simultaneously to a view property?"

Although I have a working solution currently, it feels cumbersome:

map.html

<div id="view-wrapper">
  <div id="map-container"></div>

  <div v-for="statement in statements" id="map-statement-popup-template-${statement.id}" style="display: none">
    <map-statement-popup v-bind:statement="statement"></map-statement-popup>
  </div>
</div>

<!-- base template for statement map popup -->
<script type="text/template" id="map-statement-popup-template">
  {{ statement.name }}
</script>

map.js

$(document).ready(function() {
  [...]

  //View-model data-bindings
  var vm = new Vue({
    el: '#view-wrapper',
    data: {
      statements: []
    },
    methods: {
      handleStatementFeature: function(feature, layer) {
        var popupTemplateEl = $('<map-statement-popup />');
        var scope = { statement: feature.properties.statement };
        var compiledElement = this.COMPILE?(popupTemplateEl[0], scope);
        layer.bindPopup(compiledElement);
      }
    },
    components: {
      'map-statement-popup': {
        template: '#map-statement-popup-template',
        props: {
          statement: null
        }
      }
    }
  });

  function geoJsonFromStatementsLocations(statements){
    var geoJson = {
      type: "FeatureCollection",
      features: _.map(statements, function(statement) {
        return {
          type: "Feature",
          geometry: {
            type: "LineString",
            coordinates: statement.coordinates
          },
          properties: {
            statement: statement
          }
        };
      });
    };
    return geoJson;
  }
});

Though the current setup works, it involves looping over statements, creating custom elements for each statement, hiding them, and using dynamic IDs to link them with popups.


I wish to simplify the process by implementing something like this :

map.html

<div id="view-wrapper">
  <div id="map-container"></div>
</div>

<!-- base template for statement map popup -->
<script type="text/template" id="map-statement-popup-template">
  {{ statement.name }}
</script>

map.js

$(document).ready(function() {
  [...]

  //View-model data-bindings
  var vm = new Vue({
    el: '#view-wrapper',
    data: {
      statements: []
    },
    methods: {
      handleStatementFeature: function(feature, layer) {
        var popupTemplateEl = $('<map-statement-popup />');
        var scope = { statement: feature.properties.statement };
        var compiledElement = this.COMPILE?(popupTemplateEl[0], scope);
        layer.bindPopup(compiledElement);
      }
    },
    components: {
      'map-statement-popup': {
        template: '#map-statement-popup-template',
        props: {
          statement: null
        }
      }
    }
  });

  function geoJsonFromStatementsLocations(statements){
    var geoJson = {
      type: "FeatureCollection",
      features: _.map(statements, function(statement) {
        return {
          type: "Feature",
          geometry: {
            type: "LineString",
            coordinates: statement.coordinates
          },
          properties: {
            statement: statement
          }
        };
      });
    };
    return geoJson;
  }
});

However, I am unable to find a method to compile based on a defined scope. Essentially, I want to:

  • Create an instance of a custom element
  • Pass it a scope
  • Compile it

EDIT : Upon further research, I did discover the $compile function. However, it is typically used to compile appended child elements in HTML. I aim to compile first and then let leaflet append it automatically.

Answer №1

Does this method suit your needs? Rather than utilizing a component, you can generate a new element to pass to bindPopup, and then initialize a new Vue on that particular element with the necessary data.

new Vue({
  el: 'body',
  data: {
    popups: [1, 2, 3],
    message: "I'm Dad",
    statements: []
  },
  methods: {
    handleFeature: function(id) {
      const newDiv = document.createElement('div');
      const theStatement = {
        name: 'Some name for ' + id
        };
      newDiv.innerHTML = document.getElementById('map-statement-popup-template').innerHTML;
      new Vue({
        el: newDiv,
        data: {
          statement: theStatement
        },
        parent: this
      });

      // Mock call to layer.bindPopup
      const layerEl = document.getElementById(id);
      this.bindPopup(layerEl, newDiv);
    },
    bindPopup: function(layerEl, el) {
      layerEl.appendChild(el);
    }
  }
});
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/1.0.26/vue.min.js"></script>
<div class="leaflet-zone">
  <div v-for="popup in [1,2,3]">
    <button @click="handleFeature('p-' + popup)">Bind</button>
    <div id="p-{{popup}}"></div>
  </div>
</div>

<template id="map-statement-popup-template">
  {{ statement.name }} {{$parent.message}}
</template>

In my opinion, you could achieve a similar result using $compile. However, it is worth noting that $compile lacks proper documentation and is primarily intended for internal usage. It proves beneficial in assuming control of a new DOM element within the current Vue instance and scope. Yet, the scenario you described—having both a new scope and a new DOM element—is precisely what Vue aims to address.

You have the option to establish a parent chain by specifying the parent parameter, as shown in the updated snippet provided above.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Failure to send Websocket connection

Currently working on PHP, here's a snippet: $room_array = array(); $room_array[0] = 'room-list'; $room_array['info'] = array('room_name' => $room['room_name'], 'owner' => $username['usernam ...

Navigating through the features of www.addthis.com is simple and straightforward

I am looking to integrate the addthis.com plugin into my website. Specifically, I want to pass my own data to the plugin when users click on the email link. This data should then be sent to the client. Is there a way to achieve this? ...

Building a multilingual website using AngularJS UI-Router

I am currently working on developing a multilingual website using AngularJS. While providing translations in templates seems straightforward, I am facing challenges when it comes to implementing proper multilingual routes using UI-Router. Unfortunately, I ...

Error encountered with $ref during execution of nextTick() in Vue.js

I have implemented an eventListener to retrieve the scrollHeight of my 2 elements - h1 and wrap. The issue I am facing is that upon mounting, they are displayed as undefined even though I am using nextTick. Only after a user resizes the window do they dis ...

Combining various functions into a single button

I am currently working on creating a full-screen menu that functions like a modal. Everything seems to be working fine, except for the fadeOut animation. Can someone please help me understand what is causing issues with my scripts/codes? I want the content ...

Enhancing File Uploads with Vuetify

For my Vue.js project, I am attempting to upload a file using Vuetify and then store that uploaded file in my data object. This is the HTML code: <input id="file-upload" type="file" @change="onFileChange"> Within my methods section, I have the fol ...

Utilize the data stored in chrome.storage within the Vue.js data

I'm currently developing a Chrome app where I am utilizing Vue.js for the options page. My goal is to retrieve settings from Chrome storage and inject them into the Vue data. However, I am encountering an issue where I am unable to access Vue compone ...

Retrieve information from two observables without the need for separate subscriptions

After my first observable emits user data from Firebase, I need to make a second call to retrieve additional user information from a different collection. While I can handle these operations separately, the second call should only be triggered once the fir ...

Is it possible to achieve partial text stylization in Vue using Nuxt.js?

We're currently developing a Vue application Is there a way to style text partially, similar to this example? Although creating exact constraints from ellipses may not be possible, the functionality could still be achieved procedurally. ...

Generating a JSON file using JavaScript amidst the presence of unconventional characters in JSON keys

In my Node Red Function Node, I'm currently facing a challenge of generating a JSON file from JavaScript code. The specific format I need for the JSON file is as follows: [ { "H-Nr.":"1", "Pos.-Nr.":"1" }, { "H-Nr.":"1", ...

How can Jquery detect when users click on 'ok' in confirm() pop-up dialogs?

Within my application, I have multiple confirmation dialogues that appear when users attempt to delete certain items. Is there a method to determine if a user has clicked the 'ok' button on any of these dialogues globally so that I can execute a ...

Removing leading zero from ng-change value in controller

One example of my input field is shown below: <input id="0900" type="radio" ng-model="formData.appointment_hour" ng-change="change(0900)" name="appointment" value="0900" class="ng-valid ng-dirty"> After inspecting the function in my controller, I n ...

Setting up lint-staged for Vue projects: A step-by-step guide

After setting up a new Vue3 app using the Vue CLI and configuring Prettier as my linter, I decided to implement commitlint, husky, and lint-staged for validating commit messages and linting the code before pushing it. My Approach Following the instructio ...

Exploring Typescript for Efficient Data Fetching

My main objective is to develop an application that can retrieve relevant data from a mySQL database, parse it properly, and display it on the page. To achieve this, I am leveraging Typescript and React. Here is a breakdown of the issue with the code: I h ...

Interacting with the Follow/Unfollow button using jQuery/Ajax, managing repetitive actions efficiently

I'm working on developing a Follow/Unfollow button that can toggle between the two functions without requiring a page refresh. It currently works smoothly - when I click "Follow," it adds the follow data to the database and displays the "Unfollow" but ...

Apply the style when the page loads and remove it when clicked

There is a feature in my code that adds a class when an element with the class .tab is clicked. $(function() { $(".tab").click(function() { $(this).addClass('blueback'); $(".tab").not($(this)).removeClass('bl ...

I would greatly appreciate your assistance in deciphering the JavaScript code provided in the book "Ajax in Action"

While reading through the Ajax in Action book, I came across a code snippet that has left me with a couple of questions. As someone who is new to web programming and still getting to grips with JavaScript, I am hoping for some clarity on the following: ...

Is there a way to trigger the onclick event while dragging the div?

Exploring a Div: <div id="up" onmousedown="handleMouseDown('url(/scanToUserBox/img/cpt_subfunc_005_prev_p.png)', this)" onclick="changePage('up')"> </div> Upon clicking the div, the onmousedown event is trig ...

Customizing the Class of a jQuery UI ui-autocomplete Combobox Container

Is there a way to customize the ui-autocomplete div by adding a custom class? I have several autocomplete widgets on my webpage, and I need to style their drop-downs differently. Since editing the ui-autocomplete class directly is not an option, I am wor ...

Only Chrome causing my JavaScript execution to freeze due to Ajax

When using Ajax, it is supposed to be asynchronous, but for some reason, it seems like it's either stopping or pausing my JavaScript execution and only resuming once the response is received. Here is an example of HTML value: <input value="foo" d ...