I have the following code
<marker-popup v-for="point in pointsArray" :position="point.latlng" :title="point.name" > </marker-popup>
with marker-popup defined here:
<template>
<l-marker :position="position" :title="title" :draggable="false">
<l-popup :content="text"></l-popup>
</l-marker>
</template>
<script>
export default {
name: 'MarkerPopup',
props: ['position','title'],
computed: {
text: function(){
return "<b>" + this.title + "</b><br>"
+ this.position[0] + ", " + this.position[1];
}
}
}
</script>
<style lang="scss">
</style>
pointsArray is updated here:
addPoint: function(data) {
let alreadyExists = false;
if(this.pointsDictionary[data.uid]!=undefined){
alreadyExists = true;
}
this.pointsDictionary[data.uid] = {};
this.$set(this.pointsDictionary,data.uid,{
'name': data.name,
'latlng': data.latlng,
'uid': data.uid
});
// this.pointsDictionary[data.uid]['name'] = data.name;
// this.pointsDictionary[data.uid]['latlng'] = data.latlng;
// this.points[data.uid]["marker"] = null;
if(alreadyExists){
console.log("exists");
var index = this.pointsArray.find(function(point){
return point.uid == data.uid;
});
//this.$set(this.pointsArray,index,this.pointsDictionary[data.uid]);
this.pointsArray.splice(index,1,this.pointsDictionary[data.uid]);
}
else {
this.pointsArray.push(this.pointsDictionary[data.uid]);
}
console.log(JSON.stringify(this.pointsDictionary));
console.log(JSON.stringify(this.pointsArray2()));
}
However, it does nothing to affect the v-for statement. Whenever the addPoint() method runs, it alters pointsArray in one of two ways
It pushes to the array - this works fine, v-for is perfect
It changes an element in the array according to what the Vue.js docs recommend here. This does not work at all. My console.log statements tell me that the change occurs in pointsArray - Vue does not react to that change despite me trying their recommended approach for changing arrays.
I could, I suppose, remove the element and then just push it but that approach seems clumsy and this should work according to their docs.
Turns out that I was doing everything right but the vue-leaflet package doesn't play nicely with Vue 2.0 and refuses to properly react. Switching over to this one solved all the problems.
Related
The content of my Vue app is fetched from Prismic (an API CMS). I have a rich text block, some parts of which are wrapped inside span tags with a specific class. I want to get those span nodes with Vue and add to them an event listener.
With JS, this code would work:
var selectedSpanElements = document.querySelectorAll('.className');
selectedSpanElements[0].style.color = "red"
But when I use this code in Vue, I can see that it works just a fraction of a second before Vue updates the DOM. I've tried using this code on mounted, beforeupdate, updated, ready hooks... Nothing has worked.
Update: Some hours later, I found that with the HTMLSerializer I can add HTML code to the span tag. But this is regular HTML, I cannot access to Vue methods.
#Bruja
I was able to find a solution using a closure. The folks at Prismic reminded/showed me.
Of note, per Phil Snow's comment above: If you are using Nuxt you won't have access to Vue's functionality and will have to go old-school JS.
Here is an example where you can pass in component-level props, data, methods, etc... to the prismic htmlSerializer:
<template>
<div>
<prismic-rich-text
:field="data"
:htmlSerializer="anotherHtmlSerializer((startNumber = list.start_number))"
/>
</div>
</template>
import prismicDOM from 'prismic-dom';
export default {
methods: {
anotherHtmlSerializer(startNumber = 1) {
const Elements = prismicDOM.RichText.Elements;
const that = this;
return function(type, element, content, children) {
// To add more elements and customizations use this as a reference:
// https://prismic.io/docs/vuejs/beyond-the-api/html-serializer
that.testMethod(startNumber);
switch (type) {
case Elements.oList:
return `<ol start=${startNumber}>${children.join('')}</ol>`;
}
// Return null to stick with the default behavior for everything else
return null;
};
},
testMethod(startNumber) {
console.log('test method here');
console.log(startNumber);
}
}
};
I believe you are on the right track looking into the HTML Serializer. If you want all your .specialClass <span> elements to trigger a click event that calls specialmethod() this should work for you:
import prismicDOM from 'prismic-dom';
const Elements = prismicDOM.RichText.Elements;
export default function (type, element, content, children) {
// I'm not 100% sure if element.className is correct, investigate with your devTools if it doesn't work
if (type === Elements.span && element.className === "specialClass") {
return `<span #click="specialMethod">${content}</span>`;
}
// Return null to stick with the default behavior for everything else
return null;
};
I'm new to Vue.js (with a background in Computer Science and programming, including interactive Javascript webpages) and as I'm a teacher, I have a quiz site I use to give homework to my students.
My codebase is messy, so I decided to migrate the whole thing to Vue, with the idea that I could use a component for each individual type of question -- separation of concerns, and all that.
However, I can't seem to find a way to generate appropriate components on the fly and include them in my page.
Here's a simplified version of my framework, with two question types. If I include the components directly in the HTML, they work fine.
Vue.component("Freetext",{
props: ["prompt","solution"],
data : function() {return {
response:""
}},
methods : {
check : function () {
if (this.solution == this.response) {
alert ("Correct!");
app.nextQuestion();
} else {
alert ("Try again!");
}
}
},
template:'<span><h1>{{prompt}}</h1> <p><input type="text" v-model="response"></input></p> <p><button class="LG_checkbutton" #click="check()">Check</button></p></span>'
})
Vue.component("multi",{
props : { prompt: String,
options : Array,
key_index : Number // index of correct answer
},
data : function() {return {
response:""
}},
methods : {
check : function (k) {
if (k == this.key_index) {
alert ("Correct!");
app.nextQuestion();
} else {
alert ("Try again!");
}
}
},
template:'<span><h1>{{prompt}}</h1><button v-for="(v,k) in options" #click="check(k)">{{v}}</button></span>'
})
</script>
<div id="app">
<Freetext prompt="Type 'correct'." solution="correct"></freetext>
<multi prompt="Click the right answer." :options='["right","wrong","very wrong"]' :key_index=0></multi>
</div>
<script>
var app = new Vue({
el: "#app",
data : {
questions:[ {type:"Multi",
prompt: "Click the right answer.",
options:["right","wrong","very wrong"],
key:0},
{type:"Freetext",
prompt:"Type 'correct'.",
solution:"correct"}
],
question_number:0
},
methods : {
nextQuestion : function () {
this.question_number ++;
}
}
})
</script>
But what I want to do is generate the contents of the div app on the fly, based on using the data member app.question_number as an index to app.questions, and the .type member of the question indicated (i.e. app.questions[app.question_number].type)
If I try to make the app of the form:
{{question}}
</div>
<script>
//...
computed : {
question : function () {
var typ = this.questions[this.question_number].type;
return "<"+typ+"></"+typ+">";
}
...I just get as plain text, and it isn't parsed as HTML.
If I try document.getElementById("app").innerHTML = "<multi prompt='sdf'></multi>"; from the console, the tag shows up in the DOM inspector, and isn't processed by Vue, even if I call app.$forceUpdate().
Is there any way round this?
While Keith's answer works for most of what I need to do, there's another way to handle this that I've just found out about, which I thought I'd share in case anyone else is looking for it: giving a block level HTML element a v-html property.
For me, this is handy as a short term fix as I'm migrating a codebase that generates dynamic HTML as strings, and I can quickly integrate some of my existing code without reworking it completely.
For example, I have a function makeTimetable that takes a custom datastructure representing a week's actively and turns it into a table with days across the top and times down the left-hand side, setting appropriate rowspans for all the activities. (It's a bit of a convoluted function, but it does what I need and isn't really worth refactoring at this point.)
So I can use this as follows:
<script type="text/x-template" id="freetext-template">
<span>
<div v-html="tabulated_timetable"></div>
<p>{{prompt}}</p>
<p><input type="text" v-model="response"></input></p>
<p><button class="LG_checkbutton" #click="check()">Check</button></p>
</span>
</script>
<script>
var freetext = Vue.component("Freetext",{
props: {"prompt":String,
"timetable":Object,
"solution":String,
data : function() {return {
response:""
}},
computed : {
tabulated_timetable : function () {
return makeTimetable (this.timetable);
}},
methods : {
check : function () {
if (this.solution == this.response) {
alert ("Correct!");
app.nextQuestion();
} else {
alert ("Try again!");
}
}
},
template:'#freetext-template'
})
</script>
(I suppose I could put `tabulated_timetable` in `methods` rather than `computed`, as it's set once and never changed, but I don't know if there would be any performance benefit to doing it that way.)
I think maybe a slightly different approach, Vue supports the concept of "dynamic components"
see https://v2.vuejs.org/v2/guide/components-dynamic-async.html
this will let you define what component to use on each question which would look something like
<component v-bind:is="question.component" :question="question"></component>
Long story short: I'm trying to add a front-end app to my portfolio site that uses React. I would like to integrate the app into the component as it renders. What I have setup right now is:
React component:
class Giphy extends Component {
constructor(props) {
super(props);
this.state = {src: 1}
this.handleClick = this.handleClick.bind(this);
}
handleClick(event) {
this.setState({src: event.target.value})
}
componentDidMount() {
const script = document.createElement("script");
script.type = "text/javascript";
script.src = "/scripts/giphyLogic.js";
document.body.appendChild(script);
}
...and a bunch of stuff in the render() method that doesn't matter
the script that I want to load involves a bunch of jQuery and simple JS stuff.
function displayButtons() {
$("#buttons").empty();
for (i=0; i<buttonArray.length; i++){
var a = $("<button type='button' class='btn btn-info'>");
var btnID = buttonArray[i].replace(/\s+/g, "+")
a.attr("id", btnID);
a.text(buttonArray[i]);
$("#buttons").append(a);
}
}
$("#addButton").on("click", function() {
var newButton = $(".form-control").val();
buttonArray.push(newButton);
displayButtons();
})
function displayGIFs() {
$(".btn-info").on("click", function() {
$("#resultsContainer").empty();
var subject = $(this).attr("id");
var giphyURL = "http://api.giphy.com/v1/gifs/search?q=" + subject + "&api_key=dc6zaTOxFJmzC";
$.ajax({ url: giphyURL, method: "GET"}).done(function(res) {
for (t=0; t<25; t++) {
var rating = res.data[t].rating;
var image = $("<img>");
var imgURLmoving = res.data[t].images.fixed_height.url;
var imgURLstill = res.data[t].images.fixed_height_still.url;
image.attr("src", imgURLstill);
image.attr("data-still", imgURLstill);
image.attr("data-moving", imgURLmoving);
image.attr("data-state", "still")
image.addClass("gif");
$("#resultsContainer").append("<p>" + rating + "</p");
$("#resultsContainer").append(image);
}
})
$(document.body).on("click", ".gif", function() {
var state = $(this).attr("data-state");
if (state === "still") {
$(this).attr("src", $(this).data("moving"));
$(this).attr("data-state", "moving");
} else {
$(this).attr("src", $(this).data("still"));
$(this).attr("data-state", "still");
}
})
})
}
displayButtons();
displayGIFs();
This all works on a standalone HTML document, but I can't seem to get the script to work properly. When the component loads and I inspect the page,
<script type="text/javascript" src="/scripts/giphyLogic.js"></script>
is there under the bundle.js script tag, but nothing from the script happens.
I get an "Uncaught SyntaxError: Unexpected token <" error that is attributed to giphyLogic.js:1 even though in the actual .js file, that line is blank. I've looked around, and this apparently happens when a file is included that doesn't exist, but the file is definitely there. I've double checked the path (by including an image in the same folder and loading the image successfully on the page) and it's correct.
Is there a way to resolve this, or am I going to have to create methods within the React component that I'm creating?
Do not mix jQuery and react. Learn how to use react properly by reading the well-written documentation. They can guide you through the many examples to get a simple app up and running.
Once again, do NOT use jQuery and react. jQuery wants to manually manipulate the DOM, and react manages a virtual DOM. The two will conflict more often than not, and you're going to have a bad time. If you have a very deep understanding of react, there are very few scenarios in which you could maybe use some jQuery, but nearly all of the time, it is to be avoided at all costs.
Obviously things like $.ajax() are fine, but for anything dealing with DOM manipulation, stay away. And if you only end up using jQuery for $.ajax() calls... you should switch to a leaner library like axios or use the native fetch API.
I have multiple download buttons on a page, with a progress bar for each download:
<progress-bar *ngIf="progressbar" [progress]="loadProgress_id1"></progress-bar>
<progress-bar *ngIf="progressbar" [progress]="loadProgress_id2"></progress-bar>
...
I have a function that sets the progress:
setpercentage(perc,id) {
this.loadProgress_+id = Math.round(perc); // --> how could I do this?
this.ref.detectChanges();
}
What I've tried (the above) doesn't work. How could I achieve this? Or should I use a different approach?
try this
setpercentage(perc,id) {
this['loadProgress_' + id] = ....
}
or
setpercentage(perc,id) {
const prop = 'loadProgress_' + id;
this[prop] = ...
}
Is there any reason an array wouldn't work for this?
// Html
<progress-bar
*ngFor="let progress of progressBars"
[progress]="progress"
></progress-bar>
// Ts
progressBars: number[] = [0,0];
setpercentage(perc,id) {
this.progressBars[id] = Math.round(perc);
this.ref.detectChanges();
}
I'm trying to build a reusable Image Loader component in Vue.js, which should:
Manage its own thumbnail data
Take src from parent as prop
Display different thumbnails based on prop, using same instance without being destroyed
So it may take data from two places (own thumbnail state || src prop), and I have a very difficult time wrapping my head around how to manage this. Not too sure if this is the right approach to the problem either.
At this point I am getting an infinite update loop warning in the console.
[Vue warn]: You may have an infinite update loop in a component render function.
Here is my code. Any help whatsoever would be greatly appreciated.
<template>
<div>
<label class="fileContainer">
<span class="icon is-large">
<i class="fa fa-camera"></i>
</span>
<input type="file" :index="index" #change="updateThumbnail"/>
</label>
<object
:data="pdfURL"
type="application/pdf"
:class="{visible: pdfURL != ''}">
</object>
<img :src="getSrc()" />
</div>
</template>
<script>
export default {
props: ["index", "srcProp"],
data() {
return {
imageSrc: '',
imageDataURI: '',
pdfURL: '',
}
},
methods: {
getSrc() {
if (typeof this.srcProp !== "undefined") {
this.imageSrc = ''
if (this.srcProp !== '') {
this.imageSrc = this.srcProp
} else {
this.imageSrc = this.imageDataURI
}
} else {
this.imageSrc = this.imageDataURI
}
return this.imageSrc
},
updateThumbnail(event) {
this.$emit('change')
const fileTypes = ['jpg', 'jpeg', 'png']
const imgFile = event.target.files[0] || event.srcElement.files[0]
const extension = imgFile.name.split('.').pop().toLowerCase()
const isImg = fileTypes.indexOf(extension) > -1
if (extension === 'pdf') {
const pdfURL = URL.createObjectURL(imgFile);
this.pdfURL = pdfURL
this.height = 200
return
} else if (isImg) {
var reader = new FileReader();
reader.readAsDataURL(imgFile);
reader.onload = () => {
this.imageDataURI = reader.result
return
}
} else {
alert("Please submit images or PDFs only.")
}
},
}
}
</script>
I was facing the same problem. Let me explain to you what I know.
When :src="anyFunction()" is used it re-renders upto infinite time even after it gets the result.
At this point we get same array for infinite times. Try displaying console.log('this.imgSrc'), you will get infinite number of array.
Here we cannot use slice or splice to get the first array. I haven't found solution but I managed to keep variables in a src rather than rendering whole function and getting url.
<img :src="'https://maps.googleapis.com/maps/api/staticmap?zoom=15&size=500x250&markers=color:red%7Clabel:L%7C'+this.leadIpinfo.loc.split(',').slice(0,1)+','+this.Ipinfo.loc.split(',').slice(1,2) alt="loc">
Here I have fetched the array and splited and sliced into 2 values.
Hope it could help in some ways.