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

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>

Related

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

<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).

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.

Beginner array issue - Javascript

I'm having an issue with an app in JavaScript.
I seem to randomly have an issue with my array not showing up in JavaScript - I've tried remaking the program several times and sometimes it works, sometimes it doesn't. This is a sample of my latest failed attempt. Could anyone tell me exactly why the array is not appearing in the browser? I've tried to set up a filter and form. I'm trying to create a list with a filter objects in the array.
<!DOCTYPE html>
<html>
<head>
<title>Work</title>
</head>
<body>
<h1>Todos</h1>
<todo class="tddiv"></todo>
<input type="text" class="todo" placeholder="type here">
<form class="todo-form">
<input type="text" placeholder="input-todo-text" name="addTodo">
<button>Submit Text</button>
</form>
<script src="script.js"></script>
</body>
</html>
JavaScript
let todos = [{
text: 'Order cat food',
completed: false
}, {
text: 'Clean kitchen',
completed: true
}, {
text: 'Buy food',
completed: true
}, {
text: 'Do work',
completed: false
}, {
text: 'Exercise',
completed: true
}]
const filters = {
searchText: ''
}
const renderTodos = function(todos, filters) {
const filter = todos.filter(function(todo) {
return
todo.text.toLowerCase().includes(filters.searchText.toLowerCase())
})
document.querySelector('.tddiv').innerHTML = ''
filter.forEach(function(a) {
const add = document.createElement('p')
add.textContent = a.text
document.querySelector('.tddiv').appendChild(add)
})
}
renderTodos(todos, filters)
document.querySelector('.text').addEventListener('input', function(e) {
filters.searchText = e.target.value
renderTodos(todos, filters)
})
There is a new line after your return statement in the filter method that prevents the includes method call(the js interpreter replaces the new line with a ;)
Change your document.querySelector('.text') with querySelector('input[type=text]'), and replace e.target.value with this.value in this querySelector(this refers here to the input element).
let todos = [{
text: 'Order cat food',
completed: false
}, {
text: 'Clean kitchen',
completed: true
}, {
text: 'Buy food',
completed: true
}, {
text: 'Do work',
completed: false
}, {
text: 'Exercise',
completed: true
}]
const filters = {
searchText: ''
}
const renderTodos = function(todos, filters) {
const filter = todos.filter(function(todo) {
return todo.text.toLowerCase().includes(filters.searchText.toLowerCase())
})
document.querySelector('.tddiv').innerHTML = ''
filter.forEach(function(a) {
const add = document.createElement('p')
add.textContent = a.text
document.querySelector('.tddiv').appendChild(add)
})
}
renderTodos(todos, filters)
document.querySelector('input[type=text]').addEventListener('input', function(e) {
filters.searchText = this.value
renderTodos(todos, filters)
})
<h1>Todos</h1>
<todo class="tddiv"></todo>
<input type="text" class="todo" placeholder="type here">
<form class="todo-form">
<input type="text" placeholder="input-todo-text" name="addTodo">
<button>Submit Text</button>
</form>
<script src="script.js"></script>
This can work with e.target.value as well, you just have add class "text" on the input type. The main issue was new line after return statement. It will always return undefined and filter array will be empty. For more information please refer following link. MDN JavaScript Grammar
<!DOCTYPE html>
<html>
<head>
<title>Work</title>
</head>
<body>
<h1>Todos</h1>
<todo class="tddiv"></todo>
<input type="text" class="todo" placeholder="type here">
<form class="todo-form">
<input class="text" type="text" placeholder="input-todo-text" name="addTodo">
<button >Submit Text</button>
</form>
<script src="script.js"></script>
</body>
</html>
let todos = [{
text: 'Order cat food',
completed: false
}, {
text: 'Clean kitchen',
completed: true
}, {
text: 'Buy food',
completed: true
}, {
text: 'Do work',
completed: false
}, {
text: 'Exercise',
completed: true
}]
const filters = {
searchText: ''
}
const renderTodos = function (todos, filters) {
const filter = todos.filter(function (todo) {
return todo.text.toLowerCase().includes(filters.searchText.toLowerCase())
})
document.querySelector('.tddiv').innerHTML = ''
filter.forEach(function (a) {
const add = document.createElement('p')
add.textContent = a.text
document.querySelector('.tddiv').appendChild(add)
})
}
renderTodos(todos, filters)
document.querySelector('.text').addEventListener('input', function (e) {
filters.searchText = e.target.value
renderTodos(todos, filters)
})
This should work.
<input type="text" placeholder="input-todo-text" name="addTodo" class="text"/>
const renderTodos = function(todos, filters) {
const filter = todos.filter(function(todo) {
if(todo.text.toLowerCase().includes(filters.searchText.toLowerCase()) && filters.searchText!="")
{
console.log(todo);
return todo;
}
})

Vue bind value to object by key when button click

I am new to Vue, i want to create some buttons from my data and display their information when clicking the button. The object 'pets' has two keys: id and info. (My data is larger and my code more complicated, i was trying to simplify it here.)
data() {
return {
selectedpet: undefined,
message: undefined,
pets : [
{
id: 1,
info: "yellow"
},
{
id: 2,
info: "brown"
},
{
id: 3,
info:"huge"
}
]
}
}
And I created some buttons by the data with the 'id' displayed on the buttons, and the variable 'selectedpet' will record the clicked button:
<div v-for="pet in pets :key="pet.id">
<button #click="selectedpet = pet">
<i>{{pet.id}}</i>
</button>
What i need to do is to create a div, which text will display the 'info' of the clicked button. How can i set the message to the 'info' of the click button?
<div id="printselectedpet">Selected pet: {{ message }}</div>
No need for message data
<div id="printselectedpet">Selected pet: {{ selectedpet.info }}</div>
I think you just need to set pet.info to message.
See below code:
var vm = new Vue({
el : "#vueRoot",
data: {
selectedpet: undefined,
message: undefined,
pets : [
{
id: 1,
info: "yellow"
},
{
id: 2,
info: "brown"
},
{
id: 3,
info:"huge"
}
]
}
});
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.13/dist/vue.js"></script>
<div id="vueRoot">
<div v-for="pet in pets" :key="pet.id">
<button #click="message = pet.info">
<i>{{pet.id}}</i>
</button>
</div>
<div id="printselectedpet" v-if="message">Selected pet: {{ message }}</div>
</div>
Below is my solution
new Vue({
el:"#app",
data:{
message:"I am initial message",
buttonz:[{id:1,info:"this is first button"},{id:2,info:"this is secoind button"},{id:3,info:"this is my third button"}]
},
methods:{
getMyKey(myKey){
if(myKey != undefined){
this.message = this.buttonz[myKey].info;
}else{
this.message = "Cannot Get the Key";
}
}
}
});
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.13/dist/vue.js"></script>
<div id="app">
<div>
{{message}}
</div>
<button v-for="(but,index) in buttonz" :key="but.id" #click="getMyKey(index)">
My ID: {{but.id}}
</button>
</div>

Meteor app not inserting elements into collection

I am learning Meteor JS and followed a tutorial to build a menu builder shopping list. I am trying to add some features to it. I added one feature successfully, but now I am trying to create an organization feature where users can join an organization and see all shopping lists related to that organization. The first step is to allow for adding an organization by users.
The form appears, and I was able to insert in to the database from the console, but when I use autoform the objects are not being inserted into the database.
I recently upgraded from Meteor 1.3 to 1.4. I don't believe that is an issue since all of the other forms on the app are inserting properly still.
I have a feeling it has something to do with subscribe/publish, but I am not sure what I am doing wrong.
HTML- neworganization.html
<template name='NewOrganization'>
<div class='new-organization-container'>
<i class='fa fa-close'></i>
{{#autoForm collection='Organizations' id='insertOrganizationForm' type='insert'}}
<div class='form-group'>
{{> afQuickField name='organization'}}
</div>
<div class='form-group'>
{{> afQuickField name='members'}}
</div>
<button type="submit" class="btn btn-primary">Add</button>
{{/autoForm}}
</div>
</template>
organizations.html
<template name='Organizations'>
<h3>Your Organizations</h3>
{{#if $.Session.get 'newOrganization'}}
{{> NewOrganization }}
{{else}}
<button class='btn btn-organization btn-primary'>Add an Organization</button>
<button class='btn btn-join'>Join an Organization</button>
<button class='btn btn-deny'>Leave an Organization</button>
{{/if}}
<section class='organization-list'>
{{#if Template.subscriptionsReady}}
{{#each organizationList}}
{{> OrganizationItem}}
{{/each}}
{{else}}
<p>Loading...</p>
{{/if}}
JS- organizations.js
Template.Organizations.onCreated(function() {
this.autorun(() => {
this.subscribe('organizations');
});
});
Template.Organizations.helpers({
organizations() {
return Organizations.find({});
}
});
Template.Organizations.events({
'click .btn-organization': () => {
Session.set('newOrganization', true);
}
});
Template.NewOrganization.helpers({
organizationList: () => {
var organizationItems = Organizations.find({});
return organizationItems;
}
});
newOrganization.js
if (Meteor.isClient) {
Meteor.subscribe('organizations');
}
Template.NewOrganization.events ({
'click .fa-close': function () {
Session.set('newOrganization', false);
}
});
collections/organizations.js
import SimpleSchema from 'simpl-schema';
SimpleSchema.extendOptions(['autoform']);
Organizations = new Mongo.Collection('organizations');
Organizations.allow({
insert: function(userId){
return !!userId;
},
update: function(userId, doc){
return !!userId;
}
});
OrganizationSchema = new SimpleSchema ({
organization: {
label: "Organization Name",
type: String
},
id: {
label: "ID",
type: String,
autoform: {
type: "hidden"
}
},
members: {
type: Array
},
"members.$": Object,
"members.$.name": String,
"members.$.role": String,
inOrganization: {
type: Boolean,
defaultValue: true,
autoform: {
type: 'hidden'
}
},
createdAt: {
type: Date,
label: "CreatedAt",
autoform: {
type: "hidden"
},
autoValue: function() {
return new Date();
}
}
});
Meteor.methods({
deleteOrganizations: function(id) {
Organizations.remove(id);
}
});
Organizations.attachSchema(OrganizationSchema);
The problem is in the way the Schema was designed. I had inserted an id into the schema. My reasoning was that I wanted to have a way to add and remove members from an organization. What I did not take into account was that Mongo autogenerates an id for database object and by designing my schema in this way, I was creating a conflict. I removed the id from my schema and removed the problem.
Here is the new collections/organizations.js file:
import SimpleSchema from 'simpl-schema';
SimpleSchema.extendOptions(['autoform']);
Organizations = new Mongo.Collection('organizations');
Organizations.allow({
insert: function(userId){
return !!userId;
},
update: function(userId, doc){
return !!userId;
}
});
OrganizationSchema = new SimpleSchema ({
organization: {
label: "Organization Name",
type: String
},
members: {
type: Array
},
"members.$": Object,
"members.$.name": String,
"members.$.role": String,
inOrganization: {
type: Boolean,
defaultValue: true,
autoform: {
type: 'hidden'
}
},
createdAt: {
type: Date,
label: "CreatedAt",
autoform: {
type: "hidden"
},
autoValue: function() {
return new Date();
}
}
});
Meteor.methods({
deleteOrganizations: function(id) {
Organizations.remove(id);
}
});
SimpleSchema.debug = true;
Organizations.attachSchema(OrganizationSchema);

Categories

Resources