I'm working with BootstrapVue and I've tried to have a photo preview in my v-for loop.
Everything works fine, except of :src="'fileRequestSrc'+countTitle" - In my developer tools I get following information:
This is how it looked before when I wrote it manually without the loop:
What is the mistake in my code respectively in this code line :src="'fileRequestSrc'+countTitle" - thanks in advance!
<template>
<div>
<div v-for="countTitle in 3" :key="countTitle">
<b-button v-b-toggle="'request'+countTitle" variant="danger btn-block mb-2">Upload {{countTitle}}</b-button>
<b-collapse :id="'request'+countTitle" class="mt-2">
<div class="m-2 mt-3">
<table class="table table-striped mt-2">
<tbody>
<div class="mt-3 mb-2 ml-1">Upload</div>
<b-form-file :v-model="'fileRequest'+countTitle" placeholder="Upload ..." class="mb-2"></b-form-file>
<b-img v-if="'hasfileRequest'+countTitle" :src="'fileRequestSrc'+countTitle" class="mb-3" fluid block rounded></b-img>
</tbody>
</table>
</div>
</b-collapse>
</div>
</div>
</template>
<script>
const base64Encode = data =>
new Promise((resolve, reject) => {
const reader = new FileReader();
reader.readAsDataURL(data);
reader.onload = () => resolve(reader.result);
reader.onerror = error => reject(error);
});
export default {
data() {
return {
fileRequest1: null,
fileRequestSrc1: null,
fileRequest2: null,
fileRequestSrc2: null,
fileRequest3: null,
fileRequestSrc3: null,
}
},
computed: {
hasfileRequest1() {
return !!this.fileRequest1;
},
hasfileRequest2() {
return !!this.fileRequest2;
},
hasfileRequest3() {
return !!this.fileRequest3;
}
},
watch: {
fileRequest1(newValue, oldValue) {
if (newValue !== oldValue) {
if (newValue) {
base64Encode(newValue)
.then(value => {
this.fileRequestSrc1 = value;
})
.catch(() => {
this.fileRequestSrc1 = null;
});
} else {
this.fileRequestSrc1 = null;
}
}
},
fileRequest2(newValue, oldValue) {
if (newValue !== oldValue) {
if (newValue) {
base64Encode(newValue)
.then(value => {
this.fileRequestSrc2 = value;
})
.catch(() => {
this.fileRequestSrc2 = null;
});
} else {
this.fileRequestSrc2 = null;
}
}
},
fileRequest3(newValue, oldValue) {
if (newValue !== oldValue) {
if (newValue) {
base64Encode(newValue)
.then(value => {
this.fileRequestSrc3 = value;
})
.catch(() => {
this.fileRequestSrc3 = null;
});
} else {
this.fileRequestSrc3 = null;
}
}
},
},
}
</script>
<style scoped>
</style>
The issue that you are encountering is a difference between what you are expecting vue to do and what vue is actually doing.
With this statement
:src="'fileRequestSrc'+countTitle"
You are expecting vue to concatenate the string and then get the contents of the variable fileRequestSrc1, fileRequestSrc1, etc. However, what Vue is actually doing is concatenating the string together and assigning that to the value of src.
Currently, the way your logic is structured there are a few ways to accomplish what you want to do. Personally, the method I would take is to create an array of your variables and only access the one you want within your template.
However, none of these methods are really great. I would suggest restructuring your component to iterate over an array of objects, that contain the source for your image, as well as the file request. For example
data(){
return{
files:[
{id:0, src:null, request:null}
]
}
}
then iterate over the files in the array instead of counting to 3.
Therefore your template would change to
<template>
<div>
<div v-for="item in files" :key="item.id">
<b-button v-b-toggle="item.request" variant="danger btn-block mb-2">Upload {{item.id}}</b-button>
<b-collapse :id="'request'+item" class="mt-2">
<div class="m-2 mt-3">
<table class="table table-striped mt-2">
<tbody>
<div class="mt-3 mb-2 ml-1">Upload</div>
<b-form-file :v-model="item.request" placeholder="Upload ..." class="mb-2"></b-form-file>
<b-img v-if="item.request" :src="item.src" class="mb-3" fluid block rounded></b-img>
</tbody>
</table>
</div>
</b-collapse>
</div>
</div>
</template>
Then you would need to add a watcher to encode the image as base 64. For example
watch: {
files: {
deep: true, //used to watch the object's properties
handler(newValue, oldValue){
if (newValue !== oldValue) {
if (newValue) {
base64Encode(newValue.src)
.then(value => {
newValue.src = value;
})
.catch(() => {
newValue.src = null;
});
} else {
newValue.src = null;
}
}
}
}
}
Related
I have 2 buttons. One adds a movie to local storage, the other removes it from there. I made a function that basically switches the button. If the movie is added it shows "remove", if the movie's not been added it shows the button "add".
The function works but it doesn't know when the boolean changes so the button doesn't change. Someone explained that i should use watch property, but how am I supposed to watch an output of a function?
here is the code
<template>
<div>
<div class="card" v-for="movie in movies"
:key="movie.id">
{{movie.title}}
{{movie.release_date}}
<button v-show="!showButton(movie.id)" type="submit" #click="storeMovie(movie.id)" >
Aggiungi
</button>
<button v-show="showButton(movie.id)" type="submit" #click="removeMovie(movie.id)">
Rimuovi
</button>
</div>
<div class="card" v-for="favourite in watchlist"
:key="favourite.id">
{{favourite.title}}
</div>
</div>
</template>
<script>
import axios from 'axios'
export default {
name: 'HomeComp',
data () {
return {
movies: [],
watchlist: [],
movie: null,
}
},
mounted () {
axios
.get('https://api.themoviedb.org/3/movie/popular?api_key=###&language=it-IT&page=1&include_adult=false®ion=IT')
.then(response => {
this.movies = response.data.results
// console.log(response.data.results)
})
.catch(error => {
console.log(error)
this.errored = true
})
.finally(() => this.loading = false)
},
watch: {
switchButton(oldValue, newValue) {
if (oldValue != newValue) {
this.showButton(id) = true;
} //made an attempt here
}
},
methods: {
storeMovie(id) {
const favouriteMovie = this.movies.find(movie => movie.id === id )
this.watchlist.push(favouriteMovie);
localStorage.setItem("watchlist", JSON.stringify(this.watchlist));
},
removeMovie(id) {
const removedMovie = this.watchlist.find(movie => movie.id === id )
const indexMovie = this.watchlist.indexOf(removedMovie);
if (indexMovie > -1) {
this.watchlist.splice(indexMovie, 1);
}
localStorage.setItem("watchlist", JSON.stringify(this.watchlist));
},
showButton(id) {
const favouriteMovie = this.watchlist.find(movie => movie.id === id )
if (favouriteMovie && favouriteMovie.length > 0) {
return true
} else{
return false
}
}
},
}
</script>
<style scoped lang="scss">
</style>
A better approach would be to store the state of a movie being stored or not in the watchlist directly on the movie object.
Then use a computed to get the watchlist from the movie list instead of using two different arrays.
<template>
<div>
<div class="card" v-for="movie in movies" :key="movie.id">
{{movie.title}}
{{movie.release_date}}
<button v-show="!movie.toWatch" type="submit" #click="storeMovie(movie.id)">
{{ movie.toWatch ? 'Rimuovi' : 'Aggiungi' }}
</button>
</div>
<div class="card" v-for="favourite in watchList" :key="favourite.id">
{{favourite.title}}
</div>
</div>
</template>
<script>
export default {
name: 'HomeComp',
data() {
return {
movies: [],
}
},
computed: {
// Get the watchList from the movies list
watchList() {
return this.movies.filter(movie => movie.toWatch)
}
},
watch: {
watchList(newWatchList) {
// Update the localStorage whenever the list changes
localStorage.setItem("watchlist", JSON.stringify(newWatchList));
}
},
mounted() {
// your axios call
},
methods: {
storeMovie(id) {
const favouriteMovie = this.movies.find(movie => movie.id === id)
if (favouriteMovie) {
// just reverse the boolean
favouriteMovie.toWatch = !favouriteMovie.toWatch
}
},
},
}
</script>
i'm new to Vue js in this code below , i wanted to hide button "Clear Filter" when nothing selected and show the button only when function " selectedAnswer(index)" called so that it will show only when filter applied otherwise it should be hided , is there a way to do it in my code?
and thanks in advance
<template>
<div class="container" width=800px>
<b-row>
<b-col cols="8">
<h1> Recently Asked </h1>
<ul class="container-question" v-for="(question1,index) in questions" :key="index">
<li>
{{question1.question}}
</li>
</ul>
</b-col>
<b-button class="outline-primaryy" style="margin:auto;" #click="ClearFilter" >Clear Filter</b-button>
</div>
<router-view />
</div>
</template>
<script>
export default {
data() {
return {
questions: [],
answered: null,
index: 0,
selectedIndex: null,
}
},
methods: {
selectedAnswer(index) {
this.selectedIndex = index;
this.questions = this.questions.filter((question) => question.incorrect_answers.includes(index))
console.log(index)
},
ClearFilter() {
this.questions = this.unmutated
},
watch: {
question1: {
handler() {
this.selectedIndex = null;
this.answered = false;
},
},
},
},
mounted: function() {
fetch('https://opentdb.com/api.php?amount=10&category=9&difficulty=medium&type=multiple', {
method: 'get'
})
.then((response) => {
return response.json()
})
.then((jsonData) => {
this.questions = jsonData.results
this.unmutated = jsonData.results;
})
}
}
</script>
You just need to add a v-if="selectedIndex" to your btn element.
ie
<b-button v-if="selectedIndex" class="outline-primaryy" style="margin:auto;" #click="ClearFilter" >Clear Filter</b-button
In my Vue.js app, I have an search input, at the moment if the search is empty it shows an error message which is fine, but I want to hide it when the user has started to add input into the search field. So far my component code is as follows, which shows the template and script areas.
<template>
<div class="jumbotron">
<h1 class="display-4">{{title}}</h1>
<p class="lead">{{intro}}</p>
<hr class="my-4">
<p v-if="errors.length">
<b>Please correct the following error(s):</b>
</p>
<p v-for="(error, index ) in errors" :key="index">{{ error }}</p>
<input
class="form-control form-control-lg mb-3"
type="search"
placeholder="Search"
aria-label="Search"
v-model="search"
required
>
<div class="loading" v-if="loading"></div>
<table class="table table-sm table-light table-bordered" v-if="result.length">
<thead class="thead-dark">
<tr class="col-8">
<th scope="col">Name</th>
<th scope="col">Artist</th>
</tr>
</thead>
<tbody>
<tr v-for="(result, index) in result" :key="index">
<td>{{result.collectionName}}</td>
<td>{{result.artistName}}</td>
</tr>
</tbody>
</table>
<button
class="btn btn-success btn-lg btn-block mb-3"
type="submit"
v-on:click="getData"
v-if="result.length < 1"
>Get data</button>
</div>
</template>
<script>
export default {
name: "Hero",
props: {
navLink: String
},
data: function() {
return {
title: "Simple Search",
intro: "This is a simple hero unit, a simple jumbotron-style.",
subintro:
"It uses utility classes for typography and spacing to space content out.",
result: [],
errors: [],
search: "",
loading: ""
};
},
watch: {
search: function(val) {
if (!val) {
this.result = [];
}
}
},
methods: {
getData: function() {
this.loading = true;
fetch(`https://itunes.apple.com/search?term=${this.search}&entity=album`)
.then(response => response.json())
.then(data => {
this.result = data.results;
this.loading = false;
console.log(data);
});
if (this.search) return true;
this.errors = [];
if (!this.search) this.errors.push("Enter search field.");
}
}
};
</script>
Any idea's would be great, do I need to add it to the v-if statement or in the script tag?
You should watch search attribute, then update errors array if search is not entered:
methods: {
getData: function() {
this.loading = true;
fetch(`https://itunes.apple.com/search?term=${this.search}&entity=album`)
.then(response => response.json())
.then(data => {
this.result = data.results;
this.loading = false;
console.log(data);
});
}
},
watch: {
search: function(val) {
if (!val) {
this.result = [];
this.errors = [];
this.errors.push("Enter search field.");
}
}
}
I have this 3 components in VueJS. The problem i want to solve is: When i click at vehicle component, it needs to be selected (selected = true) and other vehicles unselected.
What i need to do for two-way data binding? Because i'm changing this selected property in VehiclesList.vue component and it also need to be changed in Monit.vue (which is a parent) and 'Vehicle.vue' need to watch this property for change class.
Also problem is with updating vehicles. In Monit.vue i do not update full object like this.vehicles = response.vehicles, but i do each by each one, and changing only monit property.
Maybe easier would be use a store for this. But i want to do this in components.
EDITED:Data sctructure
{
"m":[
{
"id":"v19",
"regno":"ATECH DOBLO",
"dt":"2017-10-09 13:19:01",
"lon":17.96442604,
"lat":50.66988373,
"v":0,
"th":0,
"r":0,
"g":28,
"s":"3",
"pow":1
},
{
"id":"v20",
"regno":"ATECH DUCATO_2",
"dt":"2017-10-10 01:00:03",
"lon":17.96442604,
"lat":50.6698494,
"v":0,
"th":0,
"r":0,
"g":20,
"s":"3"
},
]
}
Monit.vue
<template>
<div class="module-container">
<div class="module-container-widgets">
<vehicles-list :vehicles="vehicles"></vehicles-list>
</div>
</div>
</template>
<script>
import VehiclesList from '#/components/modules/monit/VehiclesList.vue';
export default {
name: "Monit",
data (){
return {
vehicles: null
}
},
components: {
VehiclesList
},
methods: {
getMonitData(opt){
let self = this;
if (this.getMonitDataTimer) clearTimeout(this.getMonitDataTimer);
this.axios({
url:'/monit',
})
.then(res => {
let data = res.data;
console.log(data);
if (!data.err){
self.updateVehicles(data.m);
}
self.getMonitDataTimer = setTimeout(()=>{
self.getMonitData();
}, self.getMonitDataDelay);
})
.catch(error => {
})
},
updateVehicles(data){
let self = this;
if (!this.vehicles){
this.vehicles = {};
data.forEach((v,id) => {
self.vehicles[v.id] = {
monit: v,
no: Object.keys(self.vehicles).length + 1
}
});
} else {
data.forEach((v,id) => {
if (self.vehicles[v.id]) {
self.vehicles[v.id].monit = v;
} else {
self.vehicles[v.id] = {
monit: v,
no: Object.keys(self.vehicles).length + 1
}
}
});
}
},
},
mounted: function(){
this.getMonitData();
}
};
</script>
VehiclesList.vue
<template>
<div class="vehicles-list" :class="{'vehicles-list--short': isShort}">
<ul>
<vehicle
v-for="v in vehicles"
:key="v.id"
:data="v"
#click.native="select(v)"
></vehicle>
</ul>
</div>
</template>
<script>
import Vehicle from '#/components/modules/monit/VehiclesListItem.vue';
export default {
data: function(){
return {
isShort: true
}
},
props:{
vehicles: {}
},
methods:{
select(vehicle){
let id = vehicle.monit.id;
console.log("Select vehicle: " + id);
_.forEach((v, id) => {
v.selected = false;
});
this.vehicles[id].selected = true;
}
},
components:{
Vehicle
}
}
</script>
Vehicle.vue
<template>
<li class="vehicle" :id="data.id" :class="classes">
<div class="vehicle-info">
<div class="vehicle-info--regno font-weight-bold"><span class="vehicle-info--no">{{data.no}}.</span> {{ data.monit.regno }}</div>
</div>
<div class="vehicle-stats">
<div v-if="data.monit.v !== 'undefined'" class="vehicle-stat--speed" data-name="speed"><i class="mdi mdi-speedometer"></i>{{ data.monit.v }} km/h</div>
</div>
</li>
</template>
<script>
export default {
props:{
data: Object
},
computed:{
classes (){
return {
'vehicle--selected': this.data.selected
}
}
}
}
</script>
Two-way component data binding was deprecated in VueJS 2.0 for a more event-driven model: https://v2.vuejs.org/v2/guide/components.html#One-Way-Data-Flow
This means, that changes made in the parent are still propagated to the child component (one-way). Changes you make inside the child component need to be explicitly send back to the parent via custom events: https://v2.vuejs.org/v2/guide/components.html#Custom-Events or in 2.3.0+ the sync keyword: https://v2.vuejs.org/v2/guide/components.html#sync-Modifier
EDIT Alternative (maybe better) approach:
Monit.vue:
<template>
<div class="module-container">
<div class="module-container-widgets">
<vehicles-list :vehicles="vehicles" v-on:vehicleSelected="onVehicleSelected"></vehicles-list>
</div>
</div>
</template>
<script>
import VehiclesList from '#/components/modules/monit/VehiclesList.vue';
export default {
name: "Monit",
data (){
return {
vehicles: null
}
},
components: {
VehiclesList
},
methods: {
onVehicleSelected: function (id) {
_.forEach((v, id) => {
v.selected = false;
});
this.vehicles[id].selected = true;
}
...other methods
},
mounted: function(){
this.getMonitData();
}
};
</script>
VehicleList.vue:
methods:{
select(vehicle){
this.$emit('vehicleSelected', vehicle.monit.id)
}
},
Original post:
For your example this would probably mean that you need to emit changes inside the select method and you need to use some sort of mutable object inside the VehicleList.vue:
export default {
data: function(){
return {
isShort: true,
mutableVehicles: {}
}
},
props:{
vehicles: {}
},
methods:{
select(vehicle){
let id = vehicle.monit.id;
console.log("Select vehicle: " + id);
_.forEach((v, id) => {
v.selected = false;
});
this.mutableVehicles[id].selected = true;
this.$emit('update:vehicles', this.mutableVehicles);
},
vehilcesLoaded () {
// Call this function from the parent once the data was loaded from the api.
// This ensures that we don't overwrite the child data with data from the parent when something changes.
// But still have the up-to-date data from the api
this.mutableVehilces = this.vehicles
}
},
components:{
Vehicle
}
}
Monit.vue
<template>
<div class="module-container">
<div class="module-container-widgets">
<vehicles-list :vehicles.sync="vehicles"></vehicles-list>
</div>
</div>
</template>
<script>
You still should maybe think more about responsibilities. Shouldn't the VehicleList.vue component be responsible for loading and managing the vehicles? This probably would make thinks a bit easier.
EDIT 2:
Try to $set the inner object and see if this helps:
self.$set(self.vehicles, v.id, {
monit: v,
no: Object.keys(self.vehicles).length + 1,
selected: false
});
I am trying to get familiar with Vue.js by making this tasks app. When I try to update the v-if="!task.deleted" value to show some transition on delete, although the task is successfully deleted from the database, it is not removed from the page.
I tried retrieving the tasks again once a task is deleted, but I don't think that's the right way to update the list (maybe I am wrong). I can't get the transition effects to work with this method either.
Here's the code so far in my single file component Tasks.vue:
<template>
...
<table class="table">
<tbody>
<tr :tasks="tasks" v-for="task in tasks" :key="task.id">
<transition name="fade">
<task-item v-if="!task.deleted" v-on:singleTaskDeleted="taskDeleted(task)" :task="task"></task-item>
</transition>
</tr>
</tbody>
</table>
...
</template>
<script>
import TaskInput from './TaskInput.vue';
import TaskItem from './TaskItem.vue';
export default {
data : function (){
return {
dbTasks : {}
}
},
computed : {
tasks : function (){
return this.dbTasks;
}
},
components: {
TaskItem, TaskInput
},
methods: {
getTasks(){
axios.get('tasks')
.then( response => {
this.dbTasks = response.data;
})
.catch(function (error) {
console.log(error);
});
},
/* Is this the right way to set `deleted`?? */
taskDeleted(task){
task.deleted = 1;
}
},
created() {
this.getTasks();
},
mounted() {
console.log('Component mounted.')
}
}
</script>
The TaskItem.vue component is set up like this:
<template>
<td class="task-item" v-if="!task.deleted"
v-on:task-deleted="taskDeleted(task)" >
{{singleTask.id}} - {{singleTask.text}}
<button type="button" class="close" aria-label="Close"
v-on:click="deleteTaskItem(singleTask.id)">
<span aria-hidden="true">×</span>
</button>
</td>
</template>
<script>
export default {
props: ['task'],
data : function () {
return {
singleTask : this.task,
deleted : false,
};
},
mounted() {
console.log('Component TaskItem mounted.')
},
methods: {
deleteTaskItem : function (tid) {
axios.delete('tasks/'+tid, {
csrf : this.csrf,
id : this.singleTask.id
})
.then( response => {
this.$emit('singleTaskDeleted');
console.log('Delete event emitted');
console.log(response);
})
.catch(function (error) {
console.log(error);
});
}
}
}
</script>