Vue.js how to show image preview before update - javascript

Hi i have my custom component where i am loading image. I need to show that image after i choose image and before i upload it on server. How to do this please? I tried use v-on:change effect on img tag but it is no working. My plan was use that test() function to doing that image preview.
<template>
<div id="insert-component">
<div id="insert-new" >
<h2 class="text-md-left">New Category</h2>
<div class="mt-2 text-left">
<a href="#" id="img-button" class=" d-flex flex-wrap" v-on:click.stop="loadImage()">
<img src="/storage/images/no_image.png" alt="logo" v:on-change="test()">
<input type="file" class="d-none" id="load-category-image" v-on:input="handleFileSelected">
<button class="btn btn-dark btn-block" >Pridať obrázok</button>
</a>
<small class="text-danger d-none error">Súbor musí byť png, jpg alebo jpeg</small>
</div>
</div>
<button class="btn btn-success btn-block my-2" v-on:click="submit($event)">Pridať kategóriu</button>
</div>
</template>
<script>
export default {
name: "InsertComponent",
props: [ 'updateTableData' ],
data: function () {
return {
category_name: "",
category_description: "",
category_img:"/storage/images/no_image.png",
file:null
}
},
methods: {
test(){
alert("test");
},
loadImage(){
document.getElementById('load-category-image').click();
},
handleFileSelected(event) {
const acceptedImageTypes = ['image/jpg', 'image/jpeg', 'image/png'];
let loadedFile = document.getElementById('load-category-image').files;
if(acceptedImageTypes.includes(loadedFile[0]['type']))
{
this.category_img="/storage/images/"+loadedFile.name;
this.file=loadedFile;
}
else{
let $errorsElements = document.getElementsByClassName('error');
for (let item of $errorsElements) {
item.classList.remove('d-none');
};
category_img="/storage/images/no_image.png";
}
},
},
}
</script>

You can use URL.createObjectURL(files[0]) generating url.
codepen - https://codepen.io/anon/pen/XGYoyr

Related

Uploaded image in angular 7 not displayed just after upload

I have written the below code which is working, there is no compilation error. I am successfully getting the uploaded images printed in the console. But just after uploading the image, its not visible in the page. I need to click anywhere on the screen ,then the images get previewed in the screen.
onFileChange(event) {
if (event.target.files && event.target.files[0]) {
var filesAmount = event.target.files.length;
for (let i = 0; i < filesAmount; i++) {
var reader = new FileReader();
reader.readAsDataURL(event.target.files[i]);
reader.onload = (event: any) => {
this.images.push(event.target.result);
};
}
}
}
<form [formGroup]="formGroup" (ngSubmit)="submit()">
<section>
<h3 class="fs-20 fw-600 text-color9 mb-30">
Step 4: Sample information
</h3>
<div class="samples d-flex flex-wrap mb-5">
<div class="sample-item mb-3 mb-lg-0">
<div
class="upload-box rounded-4 bg-white position-relative d-flex justify-content-center align-items-center flex-column overflow-hidden"
>
<input
id="file"
type="file"
class="form-control"
accept="image/*"
(change)="onFileChange($event)"
/>
<i class="icon-plus fs-36 text-color4 mb-2"></i>
<h6 class="fs-18 fw-500 text-color4 mb-0">Upload image</h6>
</div>
</div>
<div class="sample-item mb-3 mb-lg-0">
<div class="image-box">
<img class="rounded-4" *ngFor="let url of images" [src]="url" />
<br />
</div>
</div>
</div>
//other formgoup inputs will be written here
<div class="action-button pb-3">
<hr />
<div class="text-end">
<button
type="button"
class="btn btn-sm btn-primary-outline me-3"
routerLink="/order/create-order"
[queryParams]="{ id: orderObject?.orderID }"
>
Back
</button>
<button type="submit" class="btn btn-sm btn-primary">
Continue
</button>
</div>
</div>
</section>
</form>
Every time I click choose file in the this.image the images get inserted. If I try to runconsole.log(this.images) it also gets printed . But its not immediately rendered in the screen by this array this.images via *ngFor
When this ts code is run in https://stackblitz.com/edit/angular-file-upload-preview?file=app%2Fapp.component.ts its running fine. Just after choosing file the image get visible in the screen.
Please help me to know what is the reason behind such behavior.
You might need to run the angular cycle again. try
constructor(
private cdr: ChangeDetectorRef
) {}
reader.onload = (event: any) => {
this.images.push(event.target.result);
this.cdr.detectChanges();
};

Updating module on instant change when list is updated

Sorry for the long post, but I tried explaining things in as much detail as possible.
So as I dive deeper into JavaScript and start learning more and more about AJAX requests and other components, I've stumbled across something that I can't seem to figure out.
So below, I will explain what I'm doing and what I would like to do, and see if someone has some guidance for me.
So here is my Vue.js app:
new Vue({
name: 'o365-edit-modal',
el: '#o365-modal-edit',
data: function() {
return {
list: {},
}
},
created() {
this.fetchApplicationsMenu();
},
methods: {
fetchApplicationsMenu() {
var self = this;
wp.apiRequest( {
path: 'fh/v1/menus/applications',
method: 'GET',
}).then(menu => self.list = menu.data);
},
changed() {
const selected = this.$data.list.selected;
function get_ids(list, field) {
const output = [];
for (let i=0; i < list.length ; ++i)
output.push(list[i][field]);
return output;
}
const result = get_ids(selected, "id");
wp.apiRequest( {
path: 'fh/v1/menus/applications',
method: 'PUT',
data: {
ids: result,
},
}).then((post) => {
return post;
},
(error) => {
console.log(error);
});
},
add(x) {
this.$data.list.selected.push(...this.$data.list.available.splice(x, 1));
this.changed();
},
remove(x) {
this.$data.list.available.push(...this.$data.list.selected.splice(x, 1));
this.changed();
},
},
});
Then here is the HTML portion that I'm using to render the two columns:
<div class="column is-half-desktop is-full-mobile buttons">
<nav class="level is-mobile mb-0">
<div class="level-left">
<div class="level-item is-size-5 has-text-left">Selected</div>
</div>
<div class="level-right">
<div class="level-item">
<i class="fas fa-sort-alpha-up is-clickable"></i>
</div>
</div>
</nav>
<hr class="mt-1 mb-3">
<draggable class="list-group"
v-model="list.selected"
v-bind="dragOptions"
:list="list.selected"
:move="onMove"
#change="changed">
<button class="button is-fullwidth is-flex list-group-item o365_app_handle level is-mobile" v-for="(app, index) in list.selected" :key="app.id">
<div class="level-left">
<span class="icon" aria-hidden="true">
<img :src="app.icon_url" />
</span>
<span>{{app.name}}</span>
</div>
<div class="level-right">
<span class="icon has-text-danger is-clickable" #click="remove(index)">
<i class="fas fa-times"></i>
</span>
</div>
</button>
</draggable>
</div>
<div class="column is-half-desktop is-full-mobile buttons">
<div class="is-size-5 has-text-left">Available</div>
<hr class="mt-1 mb-3">
<draggable class="list-group"
v-model="list.available"
v-bind="dragOptions"
:list="list.available"
:move="onMove">
<button class="button is-fullwidth is-flex list-group-item o365_app_handle level is-mobile" v-for="(app, index) in list.available" :key="app.id">
<div class="level-left">
<span class="icon" aria-hidden="true">
<img :src="app.icon_url" />
</span>
<span>{{app.name}}</span>
</div>
<div class="level-right">
<span class="icon has-text-primary is-clickable" #click="add(index)">
<i class="fas fa-plus"></i>
</span>
</div>
</button>
</draggable>
</div>
That outputs the following items, and all works great. See the video display below of each component working as needed. This all works great! I'm calling the changed() method on add and remove which grabs all the IDs and stores them in the DB via an endpoint.
The Problem:
Now I have the following dropdown menu, which depends on the fh/v1/menus/applications endpoint to pull in all the items as shown below:
As you can see below, when I open the dropdown, it has three apps, when I open the cog wheel and remove one of the apps and it saves it but the dropdown doesn't get automatically updated, I have to refresh the page and then I will see the updates.
Does anyone know how to fetch the new items without a refresh?
Here is the HTML and the JS for the dropdown piece:
HTML: As you can see in there, I have data-source="applications" which pulls in the items inside the init_menu as shown in the JS.
<div class="dropdown-menu" id="dropdown-o365" role="menu">
<div class="dropdown-content">
<div class="container is-fluid px-4 pb-4">
<?php if ($application = Applications::init()): ?>
<div class="columns">
<div class="dropdown-item column is-full has-text-centered is-size-6">
<div class="level is-mobile">
<div class="level-left">
<?= $application->get_name() ?>
</div>
<div class="level-right">
<a class="navbar-item modal-element icon" id="o365-apps-cogwheel" data-target="o365-modal-edit" aria-haspopup="true">
<i class="fa fa-cog"></i>
</a>
</div>
</div>
</div>
</div>
<div class="columns is-multiline" data-source="applications"></div>
<?php else: ?>
<div class="columns">
<div class="column is-full">
No applications present.
</div>
</div>
<?php endif; ?>
</div>
</div>
</div>
Then here is the JavaScript. I initilize the method inside DOMContentLoaded using init_menu('applications');:
function init_menu(paths)
{
paths.forEach(path => {
const target = document.querySelector('[data-source=' + path + ']');
if (target) {
wp.api.loadPromise.done(function () {
const Menus = wp.api.models.Post.extend({
url: wpApiSettings.root + 'fh/v1/menus/' + path,
});
const menus = new Menus();
menus.fetch().then(posts => {
// This returns the data object.
const data = posts.data;
let post_list;
// Check if it's an array and see if selected is empty otherwise show available.
if (Array.isArray(data.selected) && data.selected.length !== 0) {
post_list = data.selected;
} else {
post_list = data.available;
}
post_list.forEach(function (post) {
switch(path) {
case 'applications':
target.appendChild(create_apps_dom_tree(post));
break;
default:
console.log('Path route is invalid.');
break;
}
})
})
})
}
});
}
function create_apps_dom_tree(post) {
const {
icon_url,
url,
name,
} = post
const container = document.createElement('div');
container.className = 'column is-one-third is-flex py-0';
const anchor = document.createElement('a');
anchor.href = url;
anchor.className = 'dropdown-item px-2 is-flex is-align-items-center';
const figure = document.createElement('figure');
figure.className = 'image is-32x32 is-flex';
const img = document.createElement('img');
img.src = icon_url;
const span = document.createElement('span');
span.className = 'pl-2';
span.textContent = name;
figure.appendChild(img);
anchor.append(figure, span);
container.appendChild(anchor);
return container;
}
If anyone has some guidance or an answer on how to pull in live data from the database on the fly, that would be amazing.
Basically, I need my data-source: to automatically grab the items when my vue/db request is sent so I don't have to refresh the page.
Inside my Vue app, I have the following method:
fetchApplicationsMenu() {
var self = this;
wp.apiRequest( {
path: 'fh/v1/menus/applications',
method: 'GET',
}).then(menu => self.list = menu.data);
},
which calls a GET request and then stores the data inside the return { list: {} }.
A quick fix might be to just invoke init_menu() from the component's beforeDestroy() hook, called when the dialog closes. You might choose to do it from changed() instead if the dropdown is still accessible with this dialog open.
new Vue({
// option 1:
beforeDestroy() {
init_menu('applications');
},
// option 2:
methods: {
changed() {
init_menu('applications');
}
}
})
Alternative: You already know what the final application list is in changed(), so you could update the dropdown with the new list from that method.
function update_menu(path, post_list) {
const target = document.querySelector('[data-source=' + path + ']');
// remove all existing children
Array.from(target.childNodes).forEach(x => x.remove());
post_list.forEach(post => target.appendChild(create_apps_dom_tree(post)))
}
new Vue({
methods: {
changed() {
update_menu('applications', this.$data.available);
}
}
})

Vue js send image blob between child components and show preview of image

I have Three Component with this structure
1.ParrentComponent(
-1.ChildComponent
-2.Child
)
1.Child component is loading image via file input and making preview in that controller. after i click submit button i need to send this blob image to second child and show that preview there.
My problem is when i sent image to second child url in image is the same but image not show just alt is showing
1.Child:
<template>
<div id="insert-component">
<div id="insert-new" >
<h2 class="text-md-left">Nová kategória</h2>
<div class="mt-2 text-left">
<a href="#" id="img-button" class=" d-flex flex-wrap" v-on:click.stop="loadImage()">
<img v-bind:src="category_img" alt="logo" id="preview">
<input type="file" class="d-none" id="load-category-image" v-on:input="handleFileSelected">
<button class="btn btn-dark btn-block" >Pridať obrázok</button>
</a>
<small class="text-danger d-none error" id="img-error">Súbor musí byť png, jpg alebo jpeg</small>
</div>
<div class="form-group mt-2 text-left">
<div>
<label for="category_name">Názov kategórie:</label>
<input type="text" required name="name" class="form-control" v-model="category_name" id="category_name">
<small class="text-danger d-none error" id="name-error">*Názov je povinný</small>
</div>
<label for="category_description" class="mt-2">Popis kategórie:</label>
<textarea name="description" class="form-control" rows="4" v-model="category_description" id="category_description">
</textarea>
</div>
</div>
<button class="btn btn-success btn-block my-2" v-on:click.prevent="submit()">Pridať kategóriu</button>
</div>
</template>
<script>
export default {
name: "InsertComponent",
props: [ 'updateTableData' ],
data: function () {
return {
category_name: "",
category_description: "",
category_img:"/storage/images/no_image.png",
file:null
}
},
methods: {
loadImage(){
document.getElementById('load-category-image').click();
},
submit(){
if(this.checkIfEmptyById('category_name')){
this.showErrors('name-error');
return
}
let item = this.createNewItem();
this.updateTableData(item);
this.clearInputs();
},
createNewItem(){
return {
category_img: this.category_img,
category_name: this.category_name,
category_description: this.category_description,
created_at: null,
updated_at: null,
id: null,
file:this.file
};
},
clearInputs(){
this.category_name="";
this.category_description="";
this.category_img="/storage/images/no_image.png";
},
handleFileSelected() {
let loadedFile = document.getElementById('load-category-image').files[0];
if(this.checkIfFileIsImage(loadedFile))
{
this.file = loadedFile;
//this.category_img="/storage/images/"+loadedFile.name;
this.changeImagePreview();
}
else{
//show image error
let imgError = document.getElementById('img-error');
imgError.classList.remove('d-none');
}
},
checkIfFileIsImage(file){
const acceptedImageTypes = ['image/jpg', 'image/jpeg', 'image/png'];
return acceptedImageTypes.includes(file['type']);
},
changeImagePreview(){
let loadedFile = document.getElementById('load-category-image').files;
this.category_img = URL.createObjectURL(loadedFile[0]);
},
},
}
</script>
Second child:
<template>
<div class="text-center">
<b-table striped hover :items="items" :fields="fields">
<template v-slot:cell(category_img)="data">
<img v-bind:src="'/storage/images/'+data.item.category_img" alt="img" width="50px" height="50px" class="rounded-circle">
</template>
<template v-slot:cell(category_name)="data">
{{data.item.category_name | capitalize}}
</template>
<template v-slot:cell(category_description)="data">
{{data.item.category_description | capitalize}}
</template>
<template v-slot:cell(actions)="data">
<div class="d-flex justify-content-center">
<i class="fas fa-edit"></i>
<i class="fas fa-times-circle"></i>
</div>
</template>
</b-table>
</div>
</template>
<script>
export default {
name:"TableComponent",
props: ['tableData'],
data() {
return {
fields: [
{
key: 'category_img',
label:'Img',
sortable: false
},
{
key: 'category_name',
label:'Name',
tdClass: 'capitalize-first-letter',
sortable: true,
variant: 'dark'
},
{
key: 'category_description',
thClass: 'd-none d-md-block',
tdClass: 'd-none d-md-block text-left',
label:'Description',
sortable: false,
},
{
key: 'actions',
sortable: false,
}
],
items: this.tableData
}
},
Parrent component just passing data between these component it is not important data are passing good. Problem is just that image. Thx for help
This is looks like: (right side child 1, left side child 2)
Correct if Im wrong but in your Child you are passing a function as a prop which in vue is an anti pattern. You should always follow the props down event up design pattern.
Anyway, continue to your problem. On your Child 2 you have the following line
items: this.tableData
This line will assign the value of this.tableData to items. This is only assigned on the created hook part of the component. If this table data changes (which I'm fairly sure it does) the item variable won't be updated. You should have a watch watching the prop and re-assign to item
watch: {
tableData: (value) => {
this.item = value;
}
}

Axios making DELETE and GET in the wrong order

I'm working on a small project in vue.js connected to a lumen API (working).
I currently have a list of students ('Etudiants') in which I can click in the list to select one, and delete it via a button in a toolbar.
When a student is deleted I'm reloading the student list (since it's not up to date anymore), therefore I'm doing 2 api calls via axios.
DELETE http://www.url.com/etudiants (param: idEtudiant)
GET http://www.url.com/etudiants (param: page)
The problem is that my API calls are not done in the right order, as shown here on a screenshot of the calls (with watterfall):
This problem involves 3 vue files.
'Etudiants.vue' and its 2 childs: 'ListeEtudiants.vue' (student list) and 'BarreOutilsEtudiant.vue' (toolbar)
This simple image shows the hierarchy of the 3 files and the order of which everything should execute.
In my case (when it's not working) the action number 3, the axios DELETE, happens in last.
Here are the contents of my files:
Etudiants.vue:
<template>
<div id="etudiants" class="container-fluid h-100">
<div class="row">
<div class="col-3 borderR">
<ListeEtudiants ref="list" #idEtudiantChanged="updateIdEtudiant"/>
</div>
<div class="col-9 bg-light">
<BarreOutilsEtudiant v-if="idEtudiant != null" :idEtudiant="idEtudiant" #delEtudiant="delEtudiant"/>
<InfosEtudiant v-if="idEtudiant != null" :idEtudiant="idEtudiant"/>
</div>
</div>
</div>
</template>
<script>
import axios from 'axios'
import ListeEtudiants from '#/components/ListeEtudiants.vue'
import InfosEtudiant from '#/components/InfosEtudiant.vue'
import BarreOutilsEtudiant from '#/components/BarreOutilsEtudiant.vue'
export default {
components: {
ListeEtudiants,
InfosEtudiant,
BarreOutilsEtudiant
},
data: function(){
return {
idEtudiant: null
}
},
methods:{
updateIdEtudiant(idEtudiant){
this.idEtudiant=idEtudiant;
},
delEtudiant(){
axios
.delete('http://82ab2617.ngrok.io/etudiants', {params: {"idEtudiant" :this.idEtudiant}})
.then(this.$refs.list.loadList())
.catch(error => console.log(error));
}
}
}
</script>
ListeEtudiants.vue:
<template>
<div id="ListeEtudiants">
<div class="row bg-light">
<!-- Trigger Modal Ajout Etudiant -->
<button class="btn btn-light w-100" data-toggle="modal" data-target="#addModal">
<font-awesome-icon icon="plus" size="1x"/>
<span> Ajouter un Etudiant</span>
</button>
</div>
<ul v-if="etudiants != null" id="list" class="row flex-nowrap list-group list-group-flush pr-0">
<button v-for="etudiant in etudiants.data" v-on:click='$emit("idEtudiantChanged",etudiant.idEtudiant)' class="btn btn-light text-left list-group-item pl-5 py-1">{{ etudiant.nom }} {{ etudiant.prenom }}</button>
</ul>
<ul v-else class="row flex-nowrap list-group list-group-flush pr-0">
</ul>
<div class="row bg-light">
<button class="btn btn-light col-3" v-on:click="page -= 1" :disabled="page === 1 || disabled"><font-awesome-icon icon="chevron-left" size="1x"/></button>
<div class="align-middle col-6 my-auto">{{ page }} / {{ maxPage }}</div>
<button class="btn btn-light col-3" v-on:click="page += 1" :disabled="page === maxPage || disabled"><font-awesome-icon icon="chevron-right" size="1x"/></button>
</div>
</div>
</template>
<script>
import axios from 'axios'
export default {
name: "ListeEtudiants",
data: function(){
return {
etudiants: null,
maxPage:1,
disabled:false,
page:1,
}
},
methods: {
parseAndDisplay: function(data){
this.etudiants = data;
this.maxPage = data.last_page;
this.page = data.current_page;
this.disabled = false;
},
loadList: function(){
this.disabled = true;
this.etudiants = null;
axios
.get('http://82ab2617.ngrok.io/etudiants', {params: {page:this.page}})
.then(response =>this.parseAndDisplay(response.data))
.catch(error => console.log(error));
}
},
watch: {
'page': function(newVal, oldVal){
if((newVal === 0 && oldVal === 1) || (newVal === this.maxPage+1 && oldVal === this.maxPage)){
this.page = oldVal;
}else{
if(oldVal !== 0 && oldVal !== this.maxPage+1) {
this.loadList();
}
}
}
}
,
mounted() {
this.loadList();
}
}
</script>
BarreOutilsEtudiant.vue:
<template>
<div class="row p-2 navbar-expand navbar-info bg-info">
<button class="btn btn-info mr-5" type="button"><font-awesome-icon icon="download" size="1x"/> Télécharger le Bulletin</button>
<button class="btn btn-info ml-auto" type="button"><font-awesome-icon icon="user-edit" size="1x"/></button>
<button class="btn btn-danger ml-4" v-on:click="$emit('delEtudiant')" type="button"><font-awesome-icon icon="trash-alt" size="1x"/></button>
</div>
</template>
<script>
export default {
name: "BarreOutilsEtudiant"
}
</script>
<style scoped>
</style>
Thank you very much for helping me.
I believe the problem is here:
.then(this.$refs.list.loadList())
That will call loadList immediately and pass the value it returns to then, which isn't what you want.
Instead it should be something like this, wrapping it in a function:
.then(() => this.$refs.list.loadList())

Error on saving to local storage for Vue.js

In my Vue JS app I am trying to save data I get back from a rest api into a list component but I am getting the following error:
Error in mounted hook: "SyntaxError: Unexpected token u in JSON at position 0"
I can't see what could be causing this?
Component:
<template>
<div class="mb-5 mt-5 container">
<div class="list-group">
<a href="#" class="list-group-item list-group-item-action active">
My List
<span class="badge badge-light">{{List.length}}</span>
</a>
<a
href="#"
class="list-group-item list-group-item-action"
v-for="(result, index) in List"
:key="index"
>
{{result.collectionName}}
<br>
<b>{{result.artistName}}</b>
<br>
<div class="row">
<div class="col-md-6">
<button
class="btn btn-success btn-sm btn-block"
v-on:click="removeElement(index)"
>Remove</button>
</div>
<div class="col-md-6">
<button
class="btn btn-info btn-sm btn-block"
#click="toiTunesAlbum(result)"
>View on iTunes</button>
</div>
</div>
</a>
</div>
<button class="btn btn-info mt-5 btn-lg btn-block" #click="saveList()">Save</button>
</div>
</template>
<script>
export default {
name: "List",
props: ["List"],
mounted() {
if(localStorage.result) this.result = JSON.parse(this.result);
},
methods: {
saveList() {
localStorage.result = this.result;
localStorage.setItem('result', JSON.stringify(this.result));
/* eslint-disable no-console */
console.log(this.result + 'List Saved');
/* eslint-disable no-console */
},
removeElement: function(index) {
this.List.splice(index, 1);
},
resizeArtworkUrl(result) {
return result.artworkUrl100.replace("100x100", "160x160");
},
toiTunesAlbum(result) {
window.open(result.collectionViewUrl, "_blank");
}
}
};
</script>
<style scoped>
.album-cover {
width: 100%;
height: auto;
background-color: aqua;
}
.album-container {
height: 350px;
}
</style>
Any ideas on how I can save to local storage the rest api result that would be great. Should I be using the List array or result?
I think your problem is that you are only initializing this.result if localStorage.result has a value. If localStorage.result doesn’t have a value the first time you call saveList this.result is still undefined. Try setting a default value or skip trying to save if the value is still undefined. I am also not sure of the relationship between this.List and this.result, since List seems to be a list of results?

Categories

Resources