Image not showing on loop using :ref
I am using FileReader() to read the field.
Vue Component
<div v-for="(image, index) in imgFile" :key="index">
<img :ref="'image'+parseInt( index )">
{{image.name}}
</div>
<input type="file" class="file-upload-default" #change="onFileChange" multiple>
<span style="cursor:pointer" class="file-upload-browse">
<img src="/addmore.png" height="50" width="50">
</span>
export default{
onFileChange(e){
var selectedFiles = e.target.files;
for (var i=0; i < selectedFiles.length; i++){
this.imgFile.push(selectedFiles[i]);
}
if (selectedFiles) {
for (var i=0; i < this.imgFile.length; i++){
let reader = new FileReader();
reader.addEventListener('load', function(){
this.$ref["image"+parseInt( i )][0].src = reader.result;
}.bind(this), false);
reader.readAsDataURL(this.imgFile[i]);
}
}
}
}
I want to show the images which is selected from eventListener.
Improve your architecture by splitting your logic into two components. You can do this like this:
<template>
<div class="parent-component">
<image-component :image="image" v-for="(image, index) in images" :key="index" />
</div>
</template>
<script>
export default {
name: 'ParentComponent',
data () {
return {
images: []
};
}
};
</script>
<template>
<div class="image-component">
<img ref="imgElement">
{{ image.name }}
</div>
</template>
<script>
export default {
name: 'ChildComponent',
props: {
image: {
type: Object,
required: true
}
},
methods: {
...
},
mounted () {
// you don't have to loop here, because in the scope of the image-component,
// you only have one `<img>`-tag. the loop is in the parent component
console.log(this.$refs.imgElement);
}
};
</script>
Related
I have a child component that has an input field that is hidden behind a slotted element. The parent will provide the slotted click event element, but also show a preview of the file(s), with the ability to delete them above.
I'm not sure how to work this select and preview functionality when working between a child/parent relationship.
What I have below is as far as I got, but I'm just confused at this point as to where to go.
The slot works to trigger the event in the child, but I get a "TypeError: Failed to execute 'readAsDataURL' on 'FileReader': parameter 1 is not of type 'Blob'." when trying to actually get things to render as currently written.
Where am I going wrong with this?
If you need anymore information please let me know! Cheer!
NOTE: I also need to make this compatitible with V-model, but I don't know how to do that currently.
UploadMediaFiles (Child Component)
<template>
<div class="upload-media-files">
<input
id="input-file"
type="file"
accept="*"
multiple
#change="addMedia"
class="_add-media-input"
ref="input"
/>
<label for="input-file">
<slot :openFileDialog="openFileDialog">
<img
src="https://www.clipartmax.com/png/middle/142-1422132_png-file-svg-upload-file-icon-png.png"
alt=""
/>
</slot>
</label>
</div>
</template>
<style lang="sass" scoped>
input
display: none
</style>
<script>
export default {
name: 'UploadMediaFiles',
props: {
multiple: { type: Boolean },
accept: { type: String },
},
data() {
return {
files: [],
}
},
computed: {},
methods: {
async addMedia(event) {
const files = event.target.files || event.dataTransfer.files
if (!files.length) return
console.log(`files → `, files)
this.files.push(files)
this.$emit('selected', this.files)
},
openFileDialog() {
this.$refs.input.click()
},
},
}
</script>
SelectAndPreviewFiles (Parent Component)
<template>
<div class="select-and-preview-files">
<div v-if="selectedFiles">
<div :key="index" v-for="(selectedFile, index) in selectedFiles">
<img :src="selectedFile" alt="" />
<button #click="deleteFile(index)">Delete</button>
</div>
</div>
<!-- <img />
//OR
<video /> -->
<!-- <img :src="selectedFile" alt="" />-->
<UploadMediaFiles #selected="(files) => selectFiles(files)" v-slot="{ openFileDialog }">
<button #click="openFileDialog">
<img
src="https://upload.wikimedia.org/wikipedia/commons/thumb/a/a1/Circle-icons-upload.svg/1200px-Circle-icons-upload.svg.png"
alt=""
/>
</button>
</UploadMediaFiles>
</div>
</template>
<style lang="sass" scoped>
img
width: 20%
margin: auto
display: block
margin-bottom: 10px
</style>
<script>
import UploadMediaFiles from '../atoms/UploadMediaFiles.vue'
export default {
name: 'SelectAndPreviewFiles',
components: {
UploadMediaFiles,
},
props: {},
data() {
return {
selectedFiles: [],
}
},
computed: {},
methods: {
selectFiles(files) {
this.selectedFiles.push(files)
this.previewImage(files)
},
previewImage(files) {
var vm = this
for (var index = 0; index < files.length; index++) {
var reader = new FileReader()
reader.onload = function (event) {
const imageUrl = event.target.result
vm.files.push(imageUrl)
}
reader.readAsDataURL(files[index])
}
},
deleteFile(index) {
this.selectedFiles.splice(index, 1)
},
},
}
</script>
CodePen without the parent-child relationship
https://codepen.io/LovelyAndy/pen/gOmYGKO?editors=0001
The problem is the input value for multiple selected files is an array of FileLists, which itself is a list of File objects. However, previewImage() seems to assume that the value is an array of File objects.
files[index] is actually a FileList, which is not an acceptable argument to reader.readAsDataURL(), leading to the error.
To resolve the issue, iterate each FileList in the array:
export default {
methods: {
selectFiles(files) {
this.selectedFiles.push(files);
this.previewImage(files);
},
previewImage(files) {
var vm = this
for (var index = 0; index < files.length; index++) {
const fileList = files[index]
fileList.forEach(file => {
var reader = new FileReader()
reader.onload = function (event) {
const imageUrl = event.target.result
vm.selectedFiles.push(imageUrl)
}
reader.readAsDataURL(file)
})
}
},
}
}
demo
I pass the value from parent template to child template under this scheme:
parentModel -> parentTemplate -> prop -> childModel -> childTemplate.
That is, when getting in a child model, I need to handle value before installing in template... but it doesn't work!
My method is similar to a kludge =(
Parent:
<template>
<section class="login-wrapper border border-light">
<form id="add-form" class="form-signin" enctype="multipart/form-data" #submit.prevent="send">
<label>
<span>Images</span>
<input type="file" id="files" ref="files" multiple #change="addFile()"/>
</label>
<button type="submit">Submit</button>
</form>
<div id="files-container">
<div v-for="(file, index) in files" class="file-listing" v-bind:key="index">
<Preview :msg="file"></Preview><!-- here I send data to the child with :msg property -->
</div>
</div>
</section>
</template>
<script>
import Preview from "../Partial/ImagePreview.vue"
export default {
name: "ProductAdd",
components: {
Preview
},
data() {
return {
files: []
}
},
methods: {
addFile() {
for (let i = 0; i < this.$refs.files.files.length; i++) {
const file = this.$refs.files.files[i]
this.files.push( file );
}
},
async send() {
/// Sending data to API
}
}
}
</script>
Child:
<template>
<section>
<span>{{ setImage(msg) }}</span><!-- This I would like to avoid -->
<img :src="image_url" alt=""/>
</section>
</template>
<script>
export default {
name: 'ImagePreview',
data: () => {
return {
image_url: ""
}
},
props: [ "msg" ],
methods: {
setImage(data) {
const reader = new FileReader();
reader.onload = (event) => {
this.image_url = event.target.result;
};
reader.readAsDataURL(data);
return null;
}
}
}
</script>
I'm so sorry for a stupid question (perhaps), but I rarely work with frontend.
Now there is such a need =)
PS: I tried using "watch" methods, it doesn't work in this case. When changing an array in the parent component, these changes are not passed to child
But its work.. I see selected image preview
const Preview = Vue.component('ImagePreview', {
data: () => {
return {
image_url: ""
}
},
template: `
<section>
<span>{{ setImage(msg) }}</span><!-- This I would like to avoid -->
<img :src="image_url" alt=""/>
</section>
`,
props: [ "msg" ],
methods: {
setImage(data) {
const reader = new FileReader();
reader.onload = (event) => {
this.image_url = event.target.result;
};
reader.readAsDataURL(data);
return null;
}
}
});
new Vue({
name: "ProductAdd",
components: {Preview},
data() {
return {
files: []
}
},
methods: {
addFile() {
for (let i = 0; i < this.$refs.files.files.length; i++) {
const file = this.$refs.files.files[i]
this.files.push( file );
}
},
async send() {
/// Sending data to API
}
}
}).$mount('#container');
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id='container'>
<section class="login-wrapper border border-light">
<form id="add-form" class="form-signin" enctype="multipart/form-data" #submit.prevent="send">
<label>
<span>Images</span>
<input type="file" id="files" ref="files" multiple #change="addFile()"/>
</label>
<button type="submit">Submit</button>
</form>
<div id="files-container">
<div v-for="(file, index) in files" class="file-listing" v-bind:key="index">
<Preview :msg="file"></Preview><!-- here I send data to the child with :msg property -->
</div>
</div>
</section>
</div>
i want import image file from specific link into my vue variable can someone help me
i tried with required but it doesn't work this.imagefile = require(linkofimage)
does anyone know how to solve it
I suggest you to import first the images as constants and assign them to your vue data properties in hooks or methods:
<template>
<div>
<img :src="imageDirect" alt="alert">
<img :src="imageOnHook" alt="alert">
<img :src="imageOnMethod" alt="alert">
<img :src="imageRequire" alt="alert">
</div>
</template>
<script>
const image = require('#/assets/alert_logo_card.png')
export default {
data: () => ({
imageDirect: image,
imageOnHook: null,
imageOnMethod: null,
imageRequire: null,
}),
mounted() {
this.imageOnHook = image
this.imageRequire = require('#/assets/alert_logo_card.png')
this.assignImage()
},
methods: {
assignImage() {
this.imageOnMethod = this.imageDirect
}
}
}
</script>
I'm using the same image just for example purpose.
Something like this also will work:
methods: {
assignImage() {
this.imageOnMethod = this.imageDirect
}
}
Showing an image from the network:
<template>
<div>
<img :src="imageFromUrl" alt="alert" width="200" height="200">
</div>
</template>
<script>
export default {
data: () => ({
imageFromUrl: null
}),
mounted() {
setTimeout(() => {
this.requestImage()
}, 2000);
},
methods: {
requestImage() {
const responseFromNetwork = 'https://upload.wikimedia.org/wikipedia/commons/thumb/1/12/Google_Photos_icon_%282020%29.svg/1024px-Google_Photos_icon_%282020%29.svg.png'
this.imageFromUrl = responseFromNetwork
}
}
}
</script>
I am trying to return the sum of an object property from an array.
I managed to get this done in another component, but fail to redo it in another.
I get the following error: this.cartitems.forEach is not a function"
Below is the a working example:
<template>
<div id="shopcartpreview" v-if="carthover">
<div class="cartitem" v-for="item in cartitems">
<div class="cartitempic"><img class="productImg" width="80px"
height="80px" v-bind:src="'assets/products/' + item.image"></div>
<div class="cartitemdetails">
<div class="cartitemname">{{item.name}}</div>
<div class="cartitemqty">{{item.qty}} X </div>
<div class="cartitemprice">€{{item.unit_price}}</div>
</div>
<div class="cartitemdelete">
<img src="assets/images/icon-bin.png" width="15px"
height="15px">
</div>
</div>
<div class="carttotal">
<div class="carttotaltext">TOTAL:</div>
<div class="carttotalprice">€{{Total}}</div>
</div>
<div class="cartcheckouttbn">PROCEED TO CHECKOUT</div>
<div class="viewcart">VIEW CART</div>
</div>
</template>
<script>
module.exports = {
data: function () {
return{
cartitems: 0,
carthover: false
}
},
created(){
EventBus.$on('addToCart', (payload) =>{
this.cartitems = payload
}),
EventBus.$on('mouseover', (carthover) =>{
this.carthover = carthover
$('#shopcartpreview').css('display','block');
})
},
computed: {
Total: function() {
var total = 0;
this.cartitems.forEach(item => {
total += (item.unit_price * item.qty);
});
return total;
}
}
}
below is the code that I can't get to work properly:
<template>
<div>
<div id="headerLogo">{{carthover}}<span v-if="carthover"> | {{cartitems[0].name}}</span></div>
<div id="headerAction">
<div class="headerActionItem">LOGIN/REGISTER</div>
<div class="headerActionItem"><img src="assets/images/icon-search.png" width="20px" height="20px"></div>
<div class="headerActionItem"><img src="assets/images/icon-settings.png" width="20px" height="20px"></div>
<div class="headerActionItem"><img src="assets/images/icon-love.png" width="20px" height="20px"><a class="floating ui red circular label">0</a></div>
<div class="headerActionItem" #mouseover="mouseOver"><img src="assets/images/icon-cart.png" width="26px" height="20px"><a class="floating ui red circular label elProduct" id="cartLabel">{{Totall}}</a></div>
</div>
<shopcart-preview></shopcart-preview>
</div>
</template>
<script>
module.exports = {
data: function () {
return{
cartitems: 0,
carthover: false
}
},
components: {
'shopcart-preview': httpVueLoader('components/shopcart-preview.vue')
},
created(){
EventBus.$on('addToCart', (payload) =>{
this.cartitems = payload
})
},
methods: {
mouseOver(){
this.carthover = true
carthover = true
EventBus.$emit('mouseover',carthover);
}
},
computed: {
Totall: function() {
var totall = 0;
this.cartitems.forEach(item => {
totall += (item.unit_price * item.qty);
});
return totall;
}
}
}
The part that isnt working is the following:
computed: {
Totall: function() {
var totall = 0;
this.cartitems.forEach(item => {
totall += (item.unit_price * item.qty);
});
return totall;
}
}
There is a lot of code, but that is to give a full picture of what is happening in code. Perhaps the problem is the result of another part of the code.
this.cartitems is not an Array, and therefore doesnt have the Array.forEach() method. This might be because you have set the initial state of cartItems to 0 here
data: function () {
return{
cartitems: 0,
carthover: false
}
},
Keep in mind that even if this.cartItems is later modified to be an array when you assign payload to it, if your forEach call runs before the mutation is done, your script will crash.
I'm using Vue.js and Dragula to make a drag and drop tile page. Each tile contains its own set of data, so each tile is a Vue component.
The problem is that once I drag and drop one of the tiles, the DOM elements and the data array in the Vue instance fall out of sync and start to cause problems. Just dragging and dropping doesn't create a problem, but once I drag and drop something and then try to delete it, everything goes wrong.
Here's a fiddle: https://jsfiddle.net/wfjawvnL/13/
Here's my HTML, with the component template:
<body>
<div id="app">
<div id="draggable" class="wrapper">
<template v-for="(index, item) in list">
<block :id="index" :index="index" :item="item" :name="item.name">
</block>
</template>
</div>
<div class="footer">
<pre>{{ $data | json }}</pre>
</div>
</div>
</body>
<template id="block-template">
<div :id="index" :class="[name, 'block']">
<div class="text name">{{ name }}</div>
<div>Index: {{ index }}</div>
<span class="delete" v-on:click="removeItem(item)">✗</span>
</div>
</template>
Here's my Vue instance:
var vm = new Vue({
el: '#app',
data: {
list: [
{name: 'item1'},
{name: 'item2'},
{name: 'item3'},
{name: 'item4'},
{name: 'item5'}
]
},
methods: {
reorder: function (element, sibling) {
var children = element.parentNode.querySelectorAll(".block");
var length = this.list.length;
var ids = [];
for (var i = 0; i < length; i++) {
if (children[i].id) {
ids.push(children[i].id);
}
children[i].id = i;
}
var vm = this;
var newArr = ids.map(function (id) {
return vm.list[id];
});
this.list = newArr;
}
}
});
Here's the component:
Vue.component('block', {
template: '#block-template',
props: ['index', 'name', 'item'],
methods: {
removeItem: function (item) {
vm.list.$remove(item);
}
}
});
And I'm calling Dragula like this:
dragula([document.getElementById('draggable')]).on('drop', function (el, target, source, sibling) {
vm.reorder(el, sibling);
});
This fiddle works fine but doesn't use components: https://jsfiddle.net/z2s83yfL/1/
I had two problems. First, I was binding the array index and using it as an id. The index was being changed, which was also causing the id to change. Second, using v-for on a template tag was causing some issues. I changed the tags to divs and it now works.
Working fiddle: https://jsfiddle.net/wfjawvnL/18/
Vue stuff:
Vue.component('block', {
template: '#block-template',
props: ['name', 'item'],
methods: {
removeItem: function (item) {
vm.list.$remove(item);
}
}
});
var vm = new Vue({
el: '#app',
data: {
list: [
{name: 'item1'},
{name: 'item2'},
{name: 'item3'},
{name: 'item4'},
{name: 'item5'}
]
},
ready: function() {
var self = this;
var from = null;
var drake = dragula([document.querySelector('#draggable')]);
drake.on('drag', function(element, source) {
var index = [].indexOf.call(element.parentNode.children, element);
from = index;
});
drake.on('drop', function(element, target, source, sibling) {
var index = [].indexOf.call(element.parentNode.children, element);
self.list.splice(index, 0, self.list.splice(from, 1)[0]);
});
}
});
HTML stuff:
<body>
<div id="app">
<div id="draggable" class="wrapper">
<div class="wrapper" v-for="item in list">
<block :item="item" :name="item.name">
</block>
</div>
</div>
<pre>{{ $data | json }}</pre>
</div>
</body>
<template id="block-template">
<div :class="[name, 'block']">
<div class="text name" v-on:click="removeItem(item)">{{ name }}</div>
<span class="delete" v-on:click="removeItem(item)">✗</span>
</div>
</template>