Vue.js: Return image with method after axios.get - javascript

<li v-for="people in projectData.employees" :key="people._id">
<b-img :src="colleagueImages(people)"
</li>
async colleagueImages(people) {
console.log(people); // => max#stackoverflow.com
let profileImage = await axios.get("http://myapilink.com/image?id=" + people + "&s=200&def=avatar", {
headers: {
'accept': 'image/jpeg'
}
});
console.log(profileImage);
return 'data:image/jpeg;base64,' + btoa(
new Uint8Array(profileImage.data)
.reduce((data, byte) => data + String.fromCharCode(byte), '')
);
}
The console.log(profileImage) returns the following:
The API I am using is returning a Base64 Image.
With my current code I only get the following error in my browser console:
[Vue warn]: Invalid prop: type check failed for prop "src". Expected String, got Promise.

Since you don't have all the data you need to render in the first place, you have to change attributes afterwards. First, you need to use Vue components for your items, so your "src" attribute will be reactive; second, you start the requests for your items after you rendered your app. Please see this mockup.
Vue.component('todo-item', {
template: `
<li>
<label>
<input type="checkbox"
v-on:change="toggle()"
v-bind:checked="done">
<del v-if="done">
{{ text }}
</del>
<span v-else>
{{ text }}
</span>
<span v-if="like">
♥ {{like}}
</span>
</label>
</li>
`,
props: ['id', 'text', 'done', 'like'],
methods: {
toggle: function(){
this.done = !this.done
}
}
})
let todos = [
{id: 0, text: "Learn JavaScript", done: false, like: null },
{id: 1, text: "Learn Vue", done: false, like: null },
{id: 2, text: "Play around in JSFiddle", done: true, like: null },
{id: 3, text: "Build something awesome", done: true, like: null }
]
const v = new Vue({
el: "#app",
data: {
todos: todos
}
})
todos.forEach((item) => {
// This is just a mock for an actual network request
window.setTimeout(() => {
item.like = Math.ceil(Math.random() * 100)
}, Math.random() * 2000)
})
https://jsfiddle.net/willywongi/gsLqda2y/20/
In this example I have the basic todo-list app with a fake "like" count for each item, which is calculated asynchronously. After setting up my app, I wait for the "like" attribute values (in my example I just wait a random value of milliseconds).

Related

How to map a list with sections

I have this array of data, and I'm trying to list them with sections, already knew how to use .map to list the array, but failed to list each list item under it's section, instead I had to use .map with each section.
Section 1 Data (Dashboard):
const DashboardListItems = [
{
name: "Home",
to: "/",
icon: LineStyleIcon
},
{
name: "Analytics",
to: "/analytics",
icon: TimelineIcon
},
];
Section 2 Data (QuickMenu):
const QuickMenuListItems = [
{
name: "Users",
to: "/users",
icon: PermIdentityIcon
},
{
name: "Posts",
to: "/posts",
icon: AssignmentIcon
},
{
name: "Coupons",
to: "/coupons",
icon: DiscountIcon
},
{
name: "Shops",
to: "/shops",
icon: StoreIcon
},
{
name: "Products",
to: "/products",
icon: StorefrontIcon
},
{
name: "Transactions",
to: "/transactions",
icon: AttachMoneyIcon
},
{
name: "Reports",
to: "/reports",
icon: BarChartIcon
},
];
The Code:
<div className="sidebarMenu">
<h3 className="sidebarTitle">Dashboard</h3>
<ul className="sidebarList">
{DashboardListItems.map((dashboardListItems, index) => {
const Icon = dashboardListItems.icon;
return (
<Link to={dashboardListItems.to} className="link">
<li
onClick={() => setActiveIndex(index)}
className={`sidebarListItem ${
index === activeIndex ? "active" : ""}`}>
<Icon className="sidebarIcon"/>
{dashboardListItems.name}
</li>
</Link>
);
})}
</ul>
</div>
I had to use it twice for the other section, the question is how avoid this ?
let me help you out:
If you want the data to be shown on the webpage you have to iterate both variables using map, because if you use forEach you can iterate the data but nothing is returned, so it can't be displayed without using map.
I think the proper way of performing this is using a component or a function where you can pick the props of the data and processing it, but in any case you will need to do both loops to pass the data stored in QuickMenuList and DashboardListItems.
function processElement({name, to, icon}){
//process data
}
Another way to deal with this is declaring both variables as one, instead of declaring the data inside 2 variables just use one, they have the same props.
I hope it helps you :)

I need to save single objects in LocalStorage, but i have the whole array saved

I don't get how i am supposed to save only a single object a not the whole array. I am trying to create a movie watchlist. If I click "add to watchlist", the single object should be saved in LocalStorage. If I hit remove from watchlist, the object should get removed. I tried to write down methods to regulate all of that, but i guess somethings wrong. The data comes from an API request. Here's the code:
<template>
<div>
<div class="card" v-for="movie in movies"
:key="movie.id">
{{movie.title}}
{{movie.release_date}}
<button type="submit" #click="storeMovie" >
Aggiungi
</button>
<button type="submit" #click="removeMovie">
Rimuovi
</button>
</div>
</div>
</template>
<script>
import axios from 'axios'
export default {
//Cambiare il nome con quello del componente creato
name: 'HomeComp',
data () {
return {
movies: [],
movie: "",
}
},
mounted () {
axios
.get('https://api.themoviedb.org/3/movie/popular?api_key=###&language=it-IT&page=1&include_adult=false&region=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)
if (localStorage.movies) {
this.movies = JSON.parse(localStorage.movies);
}
},
watch: {
movies: {
handler(newMovies) {
localStorage.movies = JSON.stringify(newMovies);
},
deep:true
}
},
methods: {
getMovie() {
this.movies = JSON.parse(localStorage.getItem("movie"));
},
storeMovie() {
if (this.movie.length) {
// push the new movie to list
this.movies.push(this.movie);
// store the data in localStorage
localStorage.setItem("movies", JSON.stringify(this.movies));
// clear the input
this.movie = "";
}
},
removeMovie() {
localStorage.removeItem('movie');
}
},
}
</script>
<style scoped lang="scss">
/*Inserire style componente*/
</style>
tried to parse ad stringify, but i think i'm doing it wrong in some way. Also written some methods, not working
Few observations as per the code you posted :
As you want to store the new movie through input, Aggiungi button should come outside of v-for loop.
For removeStore event, You need to pass the store id from a template so that we can filter out the movies array.
Live Demo :
new Vue({
el: '#app',
data: {
movies: [],
movie: ''
},
mounted() {
// This data will come from API, Just for a demo purpose I am using mock data.
this.movies = [{
id: 1,
title: 'Movie A',
release_date: '06/12/2022'
}, {
id: 2,
title: 'Movie B',
release_date: '07/12/2022'
}, {
id: 3,
title: 'Movie C',
release_date: '08/12/2022'
}, {
id: 4,
title: 'Movie D',
release_date: '09/12/2022'
}, {
id: 5,
title: 'Movie E',
release_date: '10/12/2022'
}]
},
methods: {
storeMovie() {
const newMovieID = this.movies.at(-1).id + 1;
this.movies.push({
id: newMovieID,
title: this.movie,
release_date: '06/12/2022'
})
},
removeMovie(movieID) {
this.movies = this.movies.filter(({ id }) => id !== movieID)
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div>
Add new movie : <input type="text" v-model="movie"/>
<button type="submit" #click="storeMovie()">
Aggiungi
</button>
</div><br>
<div class="card" v-for="movie in movies"
:key="movie.id">
{{movie.title}}
{{movie.release_date}}
<button type="submit" #click="removeMovie(movie.id)">
Rimuovi
</button>
</div>
</div>

vue js not displaying nested json

My vue.js code is unable to access a reference in a returned json object.
The vue.js code
new Vue({
el: '#axios',
data: function() {
return {
bikes: null,
baseurl: "https://" + document.querySelector('#axios').dataset.hostportname + "/",
}
},
mounted () {
axios({
method: 'get',
url: 'api/v1/bicycles',
baseURL: this.baseurl
})
.then(response => {
this.bikes = response.data
console.log("now bikes are " + JSON.stringify(this.bikes[0].owner.userName));
})
.catch(error => console.log("There is an error getting bikes: " + error))
}
})
If the HTML file (just a part) is
<div id="axios" th:data-hostportname="${hostportname}">
<li v-for="bike in bikes" :key="bike.id">
{{ bike.make }} ------- {{ bike.owner }}
</li>
</div>
Then the html output is
dawes -------- { "id": 1, "userName": "user1"}
whyte -------- { "id": 2, "userName": "user2"}
whyte -------- { "id": 3, "userName": "user3"}
And the console.log output is
now bikes are "user1"
if I try to output the owner id alone
<div id="axios" th:data-hostportname="${hostportname}">
<li v-for="bike in bikes" :key="bike.id">
{{ bike.make }} ------ {{ bike.owner.id }}
</li>
</div>
no output with a console error of
TypeError: "bike.owner is undefined"
So the Axios code is returning the correct data. Each object in the array is accessible. But the nested object within each array member is not accessible at a field level.
Just to make clear, if I ask for {{ bike.owner }} then I get a displayed the oener record that is referenced by the bike record. If I ask for {{ bile.owner.id }} then I get the console.log error of bike.owner is undefined and nothing is displayed. So I don't see how this is a loading problem unless bike.owner.id takes longer to retrieve than bike.owner, even though the latter displays.
Can someone explain what I am misunderstanding?
Regards.
So I found the root cause and it had nothing to do with vue.
Basically I have two models - owners and bicycles. Bicycles have a ref to a single owner. And owners have ref to an array of bicycles. I added #JsonIdentityInfo to both. And that was my problem.
Changing this to having #JsonIdentityInfo only in owner then allowed it to work.
Many thanks for all of the suggestions.
I think the response you are getting is as follows where owner is also a string.
So you might have to parse it before assigning it to bikes.
[{
make: 'one',
owner: '{ "id": 1, "userName": "user1"}'
}, {
make: 'two',
owner: '{ "id": 2, "userName": "user2"}'
}, {
make: 'three',
owner: '{ "id": 3, "userName": "user2"}'
}]
Hope this helps.
Update 1
Can you try adding a loading flag?
https://v2.vuejs.org/v2/cookbook/using-axios-to-consume-apis.html
new Vue({
el: '#axios',
data: function() {
return {
bikes: null,
loading: true
baseurl: "https://" + document.querySelector('#axios').dataset.hostportname + "/",
}
},
mounted () {
axios({
method: 'get',
url: 'api/v1/bicycles',
baseURL: this.baseurl
})
.then(response => {
this.bikes = response.data
console.log("now bikes are " + JSON.stringify(this.bikes[0].owner.userName));
})
.catch(error => console.log("There is an error getting bikes: " + error)).finally(() => this.loading = false)
}
})
And add v-if to your block
<div id="axios" v-if="!loading" th:data-hostportname="${hostportname}">
<li v-for="bike in bikes" :key="bike.id">
{{ bike.make }} ------ {{ bike.owner.id }}
</li>
</div>

getElementById("myID").value returns "Cannot read property 'value' of null" error and when I console log "myID" I get the whole class text

Here is my stateless function in which the user can put in a time value.
As you can see I am setting the ID for the input field with props.UserInputTimeID (I think this may be part of the error). This is because I want to reuse this component with different ID's.
const UserInputTime = (props) =>
{
return (
<div>
<form>
{props.UserInputTimeMessage}
<input type="time" id={props.UserInputTimeID} defaultValue={"00:01"} min="00:01" max={props.maxTime}></input>
<input onClick={props.userInputTimeHandler} type="submit" value="Send"></input>
</form>
</div>
)
};
In my container (App.js) where I call to render this component I set the prop values as shown below. My ID's are set to "studyTimer" and "breakTimer"
render() {
return (
<div >
<DisplayTimer UserInputTimeID={"studyTimer"}/>
<UserInputTime
UserInputTimeMessage={"Set the length of your study time from 1 - 59 minutes"}
UserInputTimeID={"studyTimer"}
maxTime={"00:59"}
userInputTimeHandler={{/* todo - placeholder for function */}}
/> {/*study timer*/}
{console.log("A test to see what the value is : " + UserInputTime)}
<DisplayTimer UserInputTimeID={"breakTimer"}/>
<UserInputTime
UserInputTimeMessage={"Set the length of your break from 1-20 minutes"}
UserInputTimeID={"breakTimer"}
maxTime={"00:20"}
userInputTimeHandler={{/* todo - placeholder for function */}}
/> {/*break timer*/}
</div>
);
}
In my component DisplayTimer I have the following code which is where the error is showing. It says that the value for the ID is null.
const DisplayTimer = (props) =>
{
let newTimerValue = document.getElementById(props.UserInputTimeID).value;
return(
<div>
</div>
)
};
When I looked at the console the statement I got back was weird:
A test to see what the value is : function UserInputTime(props) //todo check if this variable should use camelcase or pascalcase
{
return react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("div", {
__source: {
fileName: _jsxFileName,
lineNumber: 6
},
__self: this
}, react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("form", {
__source: {
fileName: _jsxFileName,
lineNumber: 7
},
__self: this
}, props.UserInputTimeMessage, react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("input", {
type: "time",
id: props.UserInputTimeID,
defaultValue: "00:01",
min: "00:01",
max: props.maxTime,
__source: {
fileName: _jsxFileName,
lineNumber: 9
},
__self: this
}), react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("input", {
onClick: props.userInputTimeHandler,
type: "submit",
value: "Send",
__source: {
fileName: _jsxFileName,
lineNumber: 10
},
__self: this
})));
}

Vue JS nested loop search not returning results

I'm building a key-command resource and giving VueJS a whirl while doing so. I'm a newbie but am gaining the grasp of things (slowly...).
I want to be able to search in a global search form for key commands I'm defining as actions within sections of commands (see data example below). I would like to search through all the actions to show only those that match the search criteria.
My HTML is below:
<div id="commands">
<input v-model="searchQuery" />
<div class="commands-section" v-for="item in sectionsSearched"
:key="item.id">
<h3>{{ item.section }}</h3>
<div class="commands-row" v-for="command in item.command" :key="command.action">
{{ command.action }}
</div>
</div>
</div>
My main Vue instance looks like this:
import Vue from 'vue/dist/vue.esm'
import { commands } from './data.js'
document.addEventListener('DOMContentLoaded', () => {
const element = document.getElementById("commands")
if (element != null) {
const app = new Vue({
el: element,
data: {
searchQuery: '',
commands: commands
},
computed: {
sectionsSearched() {
var self = this;
return this.commands.filter((c) => {
return c.command.filter((item) => {
console.log(item.action)
return item.action.indexOf(self.searchQuery) > -1;
});
});
},
}
});
}
});
And finally the data structure in data.js
const commands = [
{
section: "first section",
command: [
{ action: '1' },
{ action: '2' },
{ action: '3' },
],
},
{
section: "second section",
command: [
{ action: 'A' },
{ action: 'B' },
{ action: 'C' },
]
},
]
export { commands };
I'm able to output the commands using the console.log(item.action) snippet you see in the computed method called sectionsSearched.
I see no errors in the browser and the data renders correctly.
I cannot however filter by searching in real-time. I'm nearly positive it's a combination of my data structure + the computed method. Can anyone shed some insight as to what I'm doing wrong here?
I'd ideally like to keep the data as is because it's important to be sectioned off.
I'm a Rails guy who is new to this stuff so any and all feedback is welcome.
Thanks!
EDIT
I've tried the proposed solutions below but keep getting undefined in any query I pass. The functionality seems to work in most cases for something like this:
sectionsSearched() {
return this.commands.filter((c) => {
return c.command.filter((item) => {
return item.action.indexOf(this.searchQuery) > -1;
}).length > 0;
});
},
But alas nothing actually comes back. I'm scratching my head hard.
There is a issue in your sectionsSearched as it is returning the array of just commands.
See this one
sectionsSearched() {
return this.commands.reduce((r, e) => {
const command = e.command.filter(item => item.action.indexOf(this.searchQuery) > -1);
const section = e.section;
r.push({
section,
command
});
}, []);
}
const commands = [
{
section: "first section",
command: [
{ action: '1' },
{ action: '2' },
{ action: '3' },
],
},
{
section: "second section",
command: [
{ action: 'A' },
{ action: 'B' },
{ action: 'C' },
]
},
]
const element = document.getElementById("commands")
if (element != null) {
const app = new Vue({
el: element,
data: {
searchQuery: '',
commands: commands
},
computed: {
sectionsSearched() {
var self = this;
return this.commands.filter((c) => {
// the code below return an array, not a boolean
// make this.commands.filter() not work
// return c.command.filter((item) => {
// return item.action.indexOf(self.searchQuery) > -1;
// });
// to find whether there has command action equal to searchQuery
return c.command.find(item => item.action === self.searchQuery);
});
},
}
});
}
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<div id="commands">
<input v-model="searchQuery" />
<div class="commands-section" v-for="item in sectionsSearched"
:key="item.id">
<h3>{{ item.section }}</h3>
<div class="commands-row" v-for="command in item.command" :key="command.action">
{{ command.action }}
</div>
</div>
</div>
Is that work as you wish ?
sectionsSearched() {
return this.commands.filter((c) => {
return c.command.filter((item) => {
return item.action.indexOf(this.searchQuery) > -1;
}).length > 0;
});
},
}
since filter will always return an array(empty or not) which value always is true.

Categories

Resources