I have a very specific need that cannot realy be solved with standard data-binding.
I've got a leaflet map that I want to bind with a vue view-model.
I succeeded to display geojson features kinda bounds to my view, but I'm struggling at displaying a popup bound with vue.js
The main question is : "How to open a popup (possibly multiple popups at the same time) and bind it to a view property "
For now I've come to a working solution, but this is aweful :
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;
}
});
This seems pretty aweful to me, because I have to loop over statements with a v-for, render a div for my custom element for every statement, hide it, then use it in the popup, grabbing it with a dynamic id technique.
I would like to do 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;
}
});
... but I couldn't find a function to "COMPILE?" based on a defined scope. Basically I want to :
Create a custom element instance
Pass it a scope
Compile it
EDIT : Actually, I could find $compile function. But it's often used to compile appended child to html. I don't want to append it THEN compile it. I'd like to compile it then let leaflet append it for me.
Would this work for you? Instead of using a component, you create a new element to be passed to bindPopup, and you new Vue on that element, with your data set appropriately.
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>
I think you could do the same thing with $compile, but $compile is poorly (really un-) documented and intended for internal use. It is useful for bringing a new DOM element under control of the current Vue in the current scope, but you had a new scope as well as a new DOM element, and as you noted, that binding is exactly what Vue is intended to do.
You can establish a parent chain by specifying the parent option as I have updated my snippet to do.
Related
I am creating project with vue.js and plot.ly javascript graph library.
How can I bind in "pts" to vue's data's "TestSentences"?
Here is my code ,
thank you to everyone who contributed
My goal is to create an interactive dashboard using this variable. In this way, I can change the data by clicking anywhere on the chart.
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
<div id="app">
<div id="grafik"></div>
</div>
<!-- Vue-->
<script>
var app = new Vue({
el: '#app',
data: {
TestSentences: "",
},
methods: {
grafikCiz() {
var trace1 = {
x: [1, 2, 3],
y: ["book", "pencil", "bag"],
mode: 'markers',
marker: {
color: ['#6886c5', '#f40552', '#1b1b2f'],
size: [10, 20, 30]
}
};
var data = [trace1];
var layout = {
height: 400,
width: 400,
};
Plotly.newPlot('grafik', data, layout);
},
},
mounted: function () {
this.grafikCiz();
},
});
</script>
<!-- Vue -->
<script>
var my_graph = document.getElementById('grafik');
my_graph.on('plotly_click', function (data) {
for (var i = 0; i < data.points.length; i++) {
pts = 'x = ' + data.points[i].x + '\ny = ' + data.points[i].y + '\n\n';
};
alert('Closest point clicked:\n\n' + pts);
});
</script>
Use plolty wrapper for vue.js https://github.com/David-Desmaisons/vue-plotly
You can add ref to the component
<vue-plotly v-show="display" :data="graphData" :layout="calculatedLayoutSizes" id="3dPlot"
:display-mode-bar="false" ref="crazyPlotly"></vue-plotly>
then use the ref within your mount point or similar method
this.$refs.crazyPlotly.$on('click', d => {
console.log(d);
});
"d" is an obj with values like x and y datapoint, index...etc
source: https://github.com/statnett/vue-plotly/issues/23
As Alagappan A already pointed out, https://github.com/David-Desmaisons/vue-plotly can make working with plotly in javascript much easier. For me it was sufficient to just:
<vue-plotly :data="data" :layout="layout" #click="temp"> </vue-plotly>
which can directly be utilized in a method:
methods: {
temp (value) {
console.log(value)
}
}
I need insert script like this
<div data-player-id="912d05c">
<script src="//cdn.flowplayer.com/players/7/flowplayer.async.js">
{
"src": "https://s3.amazonaws.com/69693f173770c49cbb5.mp4"
}
</script>
</div>
to inside of html under the vue.
So I found that I need to generate script tag by js but I'm not sure how to add
{
"src": "https://s3.amazonaws.com/69693f173770c49cbb5.mp4"
}
to this script tag
Code what I have (simplified):
<div id="app">
<div id="videocontent"></div>
</div>
el: "#app",
data: {},
created: function() {
let playerContainer = document.createElement('div');
playerContainer.setAttribute('data-player-id','912d05c');
let flowplayerScript = document.createElement('script');
flowplayerScript.setAttribute('src', '//cdn.flowplayer.com/players/7/flowplayer.async.js');
flowplayerScript.innerText = {"src": "https://s3.amazonaws.com/productionadgate_video/eceae5886caaf69693f173770c49cbb5.mp4"};
playerContainer.append(flowplayerScript);
let container = document.getElementById('videocontent');
container.append(playerContainer);
}
and flowplayerScript.innerText = {"src": "https://s3.amazonaws.com/productionadgate_video/eceae5886caaf69693f173770c49cbb5.mp4"}; is not correclty injected and player is always loading but not showing videos. Also I was tried tu use:
flowplayerScript.onload = function(){
return {
"src": "https://s3.amazonaws.com/productionadgate_video/eceae5886caaf69693f173770c49cbb5.mp4"
}
};
but still not working :( and I'm getting the error like:
SyntaxError: Unexpected token $ in JSON at position 0 flowplayer.async.js:2
You can use pure JavaScript installation, then init flowplayer in 'mounted' method.
new Vue({
el: "#app",
mounted: function() {
this.$nextTick(function() {
// select the above element as player container
let containerEl = document.getElementById("videocontent")
// install flowplayer into selected container
flowplayer(containerEl, {
clip: {
sources: [
{ type: "application/x-mpegurl",
src: "//mydomain.com/video.m3u8" },
{ type: "video/mp4",
src: "//mydomain.com/video.mp4" }
]
}
})
})
}
})
jsfiddle
I try to dynamic notify when I wrote some messages.
That's my vue.js code.
<script>
Vue.http.options.emulateJSON = true; // Send as
new Vue({
el: '#app',
data: {
name : "",
postResult : ""
},
methods: {
click: function() {
this.$http.post('/api/test', {name:this.name}).then(function(response){
var result = response.data;
//this.postResults.push(result.name);
if (result.name == "1234")
{
this.postResult = "<div> Success </div>";
}
else
{
this.postResult = "<div> Fail </div>";
}
}, function(response){
// Error Handling
});
}
}
});
</script>
When I use jQuery's Ajax, I used this method. But my vue.js script is not working. Should I study more about Vue JS? or I forget some syntax in this vue.js?
<template>
<div v-if='requestCompleted'>
<div v-if='!postResult'> Fail </div>
<div v-else-if='postResult'> Success </div>
</div>
</template>
<script>
Vue.http.options.emulateJSON = true; // Send as
new Vue({
el: '#app',
data: {
name : "",
postResult : null,
requestCompleted: false
},
methods: {
click: function() {
this.$http.post('/api/test', {name:this.name}).then((response)=>{
var result = response.data;
this.requestCompleted=true;
if (result.name == "1234")
{
this.postResult = true;
}
else
{
this.postResult = false;
}
}, function(response){
// Error Handling
});
}
}
});
</script>
Use arrow functions for getting access to 'this' inside your callback function.
For HTTP requests, it's better to use Axios. Also, you can use vuex store and manage your requests with actions
You don't have "this" inside your response callback. Do var me = this at the top level of your click function, then do me.postResult = ... in the callback.
In general terms, try and keep all your markup in the template element, no ?
I'm converting an established site over to VueJS but hit a stumbling block on the best way to achieve this.
It's using D3-Funnel (https://github.com/jakezatecky/d3-funnel) to draw a funnel chart but how do I pass VueJS data variables to the charts constructor?
<script>
const data = [
{ label: 'Step 1', value: this.step1 },
{ label: 'Step 2', value: this.step2 },
.......
];
const options = {
block: {
dynamicHeight: true,
minHeight: 15,
},
};
const chart = new D3Funnel('#funnel');
chart.draw(data, options);
</script>
So I need to pass vue data variables into the values. My first thought is to move this into it's own function in the VueJS methods object and use the variables there using this.
Is there a better way of achieving this?
---------- Edit -------------
As per comments people wanted to see how I achieved this currently in vue. As already mentioned above I just created a function in the vue methods object and then call it.
methods : {
drawChart(){
const data = [
{ label: 'Step 1', value: 99999 },
{ label: 'Step 2', value: 9999 },
.......
];
const options = {
block: {
dynamicHeight: true,
minHeight: 15,
},
};
const chart = new D3Funnel('#funnel');
chart.draw(data, options);
}
},
mounted(){
this.drawChart();
}
Data is coming from an API and put into the vue data object.
data:{
step1: 0,
step2: 0,
....
},
methods:{
getData(){
axois.post......
response{
this.step1 = response.data.step1
this.step2 = response.data.step2
....
}
}
}
As I understand it you are trying to pass information down to a component and use it. If you are using single file components and webpack you can do something like this which is put together with examples listed on the vue website.
You can also take a look at this guys approach
App.vue
...
<my-d3-component :d3data="d3Data"></my-d3-component>
...
<script>
import d3Component from 'path/to/component'
var app = new Vue({
el: '#app',
data: {
d3Data: {}
},
components: {
'my-d3-component': d3Component
}
})
</script>
d3Component.vue
<template>
d3 html stuff goes here
</template>
<script>
export default {
props: ['d3Data'],
data() {
return {}
},
mounted: {
const options = {
block: {
dynamicHeight: true,
minHeight: 15,
},
};
const chart = new D3Funnel('#funnel');
chart.draw(this.d3Data, options);
}
}
</script>
Im grabbing this JSON object and passing it on to this Vue. However, It is not updating on my page, but the object is there since window.alert(jobj.Name) works fine. Here is my vue and my view.
var app2 = new Vue({
el: '#menuPage',
data: {
HeaderTitle: 'NOT CHANGED',
content_body: 'test body',
},
methods: {
loadMENU: function (jobj) {
app2 = this;
window.location.href = "tools/menu.html"; //relative to domain
window.alert(jobj.Name);
this.HeaderTitle = jobj.Name;
}
} });
<div id="menuPage">{{HeaderTitle}}</div>
It is only showing "NOT CHANGED" Instead of the object Name.
You didn't call the method. You should use a button to trigger the method.
html
<div id="menuPage">{{HeaderTitle}}
<button v-on:click="loadMENU">button</button>
</div>
javascript
var app2 = new Vue({
el: '#menuPage',
data: {
HeaderTitle: 'NOT CHANGED',
content_body: 'test body',
},
methods: {
loadMENU: function () {
app2 = this;
const herf = window.location.href;
window.alert(herf);
this.HeaderTitle = herf;
}
} });