【问题标题】:Dynamic template with dynamic scope compilation具有动态范围编译的动态模板
【发布时间】:2016-09-19 11:30:04
【问题描述】:

我有一个非常具体的需求,但标准数据绑定无法真正解决。

我有一张传单地图,我想与 vue 视图模型绑定。

我成功地在我的视图中显示了 geojson 功能,但我在显示与 vue.js 绑定的弹出窗口时遇到了困难

主要问题是:“如何打开一个弹窗(可能同时有多个弹窗)并将其绑定到一个视图属性”

现在我已经找到了一个可行的解决方案,但这太棒了:

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() {
  var map = new L.Map('map-container');
  map.setView(new L.LatLng(GLOBALS.MAP.STARTCOORDINATES.lng, GLOBALS.MAP.STARTCOORDINATES.lat), GLOBALS.MAP.STARTZOOM);

  var osm = new L.TileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png');
  osm.addTo(map);

  //Initialize map dynamic layers
  var mapLayers = {};

  //View-model data-bindings
  var vm = new Vue({
    el: '#view-wrapper',
    data: {
      statements: []
    },
    methods: {
      getStatements: function() {
        return $.get('api/statements');
      },
      updateStatements: function() {
        var that = this;
        return that.getStatements().then(
          function(res) {
            that.statements = res.data;
          }
        );
      },
      refreshStatements: function() {
        mapLayers.statements.layer.clearLayers();

        if(this.statements && this.statements.length){
          var geoJsonStatements = geoJsonFromStatements(this.statements);
          mapLayers.statements.layer.addData(geoJsonStatements);
        }
      },
      handleStatementFeature: function(feature, layer) {
        var popupTemplateEl = $('#map-statement-popup-template-' + feature.properties.statement.id);
        layer.bindPopup(popupTemplateEl.html());

        var statementIndex = _.findIndex(this.statements, {statement:{id: feature.properties.statement.id}});

        if(feature.geometry.type === 'LineString') {
          this.statements[statementIndex].layer = {
            id: L.stamp(layer)
          };
        }
      },
      openStatementPopup: function(statement) {
        if(statement.layer) {
          var featureLayer = mapLayers.statements.layer.getLayer(statement.layer.id);
          featureLayer.openPopup();
        }
      }
    },
    created: function() {
      var that = this;

      //Set dynamic map layers
      var statementsLayer = L.geoJson(null, {
        onEachFeature: this.handleStatementFeature
      });

      mapLayers.statements = {
        layer: statementsLayer
      };

      map.addLayer(mapLayers.statements.layer);

      this.updateStatements().then(this.refreshStatements);

      this.$watch('statements', this.refreshStatements);
    },
    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;
  }
});

这对我来说似乎很可怕,因为我必须使用 v-for 循环遍历 语句,为每个语句的自定义元素呈现一个 div,隐藏它,然后在弹出窗口中使用它,用动态 id 技术抓取它。


我想做这样的事情:

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;
  }
});

...但我找不到“编译”的功能?基于定义的范围。基本上我想:

  • 创建自定义元素实例
  • 传递一个范围
  • 编译它

编辑:实际上,我可以找到 $compile 函数。但它通常用于将 appended 子元素编译到 html。我不想附加它然后编译它。我想编译它,然后让传单为我附加它。

【问题讨论】:

  • 我不完全理解您的问题,但我的直觉是您应该使用自定义指令。
  • 我的问题是我不知道如何编译带有作为参数传递的范围的自定义元素。阅读handleStatementFeature,里面有几行我不知道怎么编码。
  • 我不明白为什么你需要这个功能。看起来您只需要插入一个 selectedStatement 变量即可。
  • @RoyJ,感谢您的建议。这是我的第二个想法,但我需要能够拥有我的自定义组件的多个并行实例。这就是为什么我将它实例化为每个可能的数据(语句)一次,但我觉得这很难看。我更喜欢 on demand 实例化它,从而准确地从 js 运行时编译它。如果您需要更多详细信息,请告诉我。

标签: javascript data-binding leaflet vue.js


【解决方案1】:

这对你有用吗?您无需使用组件,而是创建一个要传递给 bindPopup 的新元素,然后在该元素上使用 new Vue,并适当设置 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>

认为你可以用$compile 做同样的事情,但是$compile 的文档记录很差(真的没有)并且打算供内部使用。在当前范围内将新的 DOM 元素置于当前 Vue 的控制之下很有用,但是您有一个新的范围和一个新的 DOM 元素,并且正如您所指出的,该绑定正是 Vue 的意图做。

您可以通过指定parent option 来建立父链,因为我已经更新了我的sn-p。

【讨论】:

  • 感谢您的回答。虽然它没有回答我的问题。这可能是我的错误,代码没有很好地集中并且有尾随错误。我将对我的帖子进行大量编辑。基本上我会忘记清单内容以专注于我的需求。很抱歉将您的答案与我的问题脱钩。
  • 你成就了我的一天。那行得通,我接受了。不过,我听说 vue is 实际上是一个元素,而实例化 vue 就像将范围绑定到元素一样。我相信有一种更清洁的方法可以做到这一点(我希望我的子视图成为第一个的子视图)。虽然,你让我大吃一惊,并提供了一个可行的解决方案。
猜你喜欢
  • 2023-04-05
  • 1970-01-01
  • 1970-01-01
  • 2012-12-19
  • 1970-01-01
  • 1970-01-01
  • 2020-06-11
  • 1970-01-01
  • 2014-04-21
相关资源
最近更新 更多