How can I use a Map with pinia/vue3 - vuejs3

I am trying to set values in a map stored in a pinia store (persisted into localstorage)
in this minimal (non) working example pushing elements into a list works fine but adding a element into a Map does not persist.
component.vue
<script setup lang="ts">
import { useStore } from "./store"
const store = useStore()
</script>
<template>
<main>
<button
#click="store.createA"
>
Create new Array item
</button>
<ul v-if="store.array.length > 0">
<li v-for="(item) in store.array">
{{ item }}
</li>
</ul><br/>
<button
#click="store.createO"
>
Create Map Object
</button>
<ul v-if="store.mapObj.size > 0">
<li v-for="(item) in store.mapObj">
{{ item[1] }}
</li>
</ul>
</main>
</template>
store.ts
import { defineStore } from "pinia"
export const useStore = defineStore({
id: "character-manager",
state: () => ({ array: [] as Array<{key: string, value: string }>,
mapObj: new Map() as Map<string,string>}),
actions: {
createA() {
this.array.push({key: "key", value: "value"})
},
createO() {
this.mapObj.set("key", "value")
},
},
persist: {
enabled: true,
strategies: [{ storage: localStorage }],
},
})

Related

Selected state on nav item in Next 13

I want to have a selected state on a nav item in next 13, I could find no way of getting any context on a server component so ended up with a client component like this
'use client';
import Image from 'next/image';
import Styles from '../styles/header.module.css';
import Link from 'next/link';
import { usePathname } from 'next/navigation';
interface MainRoute {
name: string;
path: string;
index: number;
}
const mainRoutes = [
{ name: 'home', path: '/' },
{ name: 'path 2', path: '/path2' },
{ name: 'path 3', path: '/path3' },
] as MainRoute[];
export default function Header({}) {
const path = usePathname();
return (
<header>
<div className={Styles.header}>
<h1>
App title
</h1>
</div>
<div className={Styles.header}>
<ul id="mainNav">
{mainRoutes.map((route, index) => (
<li key={index}>
<Link
className={path === route.path ? Styles.selected : ''}
href={route.path}
>
{route.name}
</Link>{' '}
{index !== mainRoutes.length - 1 ? <span>|</span> : ''}
</li>
))}
</ul>
</div>
</header>
);
}
Is this the best way to achieve this basic styling?
As far as I am aware, now all of this code has to be shipped over to the client.

Passing information into sub component in storybook

Is there a way to pass information down though components in storybook that do not inheartly pass props though.
A proacticle example of this being that i have a language select button (usually pulls its information from a store so no information is passed to it), for storybook i have some props avaiable for this so i can run tests etc without having dirrect access to the store. this component on its own works fine.
On top of this i have a header nav bar that utlises this component, but does not pass informaton to it (as its self managed), however when trying to now set this component up in storybook the language select does not display as not having data.
I was reading though the multi-component documentation but struggling to get this to work for this use case and begining to wonder if this is going to be possiable and if so whats the best way to approach this.
in terms of code this is what i currently have
//header.vue
<template>
<div class="HeaderComponent">
//other component code here
<language-selector/>
</div>
</div>
</nav>
</div>
</template>
<script setup lang="ts">
import LanguageSelector from "#/components/HeaderAndFooter/LanguageSelector.vue";
import _ from "underscore";
import ImageModel from "#/helpers/classes/core/ImageModel";
import { defineProps } from "vue";
import HeaderNavigation from "#/helpers/classes/Navigation/HeaderNavigation";
// eslint-disable-next-line no-unused-vars
const props = defineProps({
menu: {
type: Object as () => {[key: string]: HeaderNavigation} | undefined,
default: undefined
},
icon: {
type: Object as () => ImageModel | undefined,
default: undefined
}
})
</script>
///languageSelector.vue
<template>
<div class="languageSelector" v-if="props.languages.length > 1">
<div class="dropdown">
<button class="btn btn-secondary dropdown-toggle" type="button" id="languageDropdownButton" data-bs-toggle="dropdown" aria-expanded="false">
{{currentLang.name}}
</button>
<ul class="dropdown-menu" aria-labelledby="languageDropdownButton">
<li v-for="language in props.languages" :key="language.i18n">
<a class="dropdown-item" href="#" #click="ChangeLanguage(language.i18n)">{{language.name}}</a>
</li>
</ul>
</div>
</div>
</template>
<script setup lang="ts">
import {useLanguageStore} from "#/store/languageStore";
import {computed, defineProps} from "vue";
import Language from "#/helpers/classes/Language";
const props = defineProps({
languages: {
languages: Object as () => Language[],
default: useLanguageStore().availableLanguages
},
})
const currentLang = computed(() => useLanguageStore().currentLanguage);
function ChangeLanguage(i18nCode : string)
{
useLanguageStore().updateCurrentLanguage(i18nCode);
}
</script>
///header.story.js
import LanguageSelector from "#/components/HeaderAndFooter/LanguageSelector.vue";
import { DefaultLangSelector } from './LanguageSelector.stories.js';
// More on default export: https://storybook.js.org/docs/vue/writing-stories/introduction#default-export
export default {
title: 'Framework/Header',
component: HeaderComponent,
// More on argTypes: https://storybook.js.org/docs/vue/api/argtypes
argTypes: {},
};
// More on component templates: https://storybook.js.org/docs/vue/writing-stories/introduction#using-args
const Template = (args) => ({
components: { HeaderComponent, LanguageSelector },
setup() {
return { args };
},
template: '<header-component v-bind="args" />',
});
export const Primary = Template.bind({});
Primary.args = {
menu: {
"Item 1": {
"children": {},
"id": "1",
"title": "Item 1"
},
"Item 2": {
"children": {},
"id": "2",
"title": "Item 2"
}
},
icon:
{
"url": "https://th.bing.com/th/id/R.25df89f6bbce9ce2324fb746d01940ea?rik=WpLZgCWrgPXbLA&pid=ImgRaw&r=0"
},
...DefaultLangSelector.args = {
languages: [
{"name" : "English", "i18n" : "en"},
{"name" : "Spanish", "i18n" : "es"},
],
}
};

How do I display a route parameter (Vue / Firebase)

I'm creating a blog with free sewing patterns as content. I'm using route parameters to receive each blog individually. However, I'm getting a blank page when trying to retrieve its data from firebase firestore. Please help.
The blog's id appears on my address bar:
http://localhost:8080/#/admin/single-pattern/4LIS362IEWa7RKEv79g8
But it renders a blank page. I cant see my blog content.
This is my route path code. I've added a parameter of :id in my singlepattern. The SinglePattern component is where I will get the individual blog's data:
{
path: "/admin",
name: "admin",
component: Admin,
meta: {
auth: true,
},
children: [
{
path: "dashboard",
name: "dashboard",
component: Dashboard,
},
{
path: "manage-patterns",
name: "manage-patterns",
component: ManagePatterns,
},
{
path: "single-pattern/:id",
name: "single-pattern",
component: SinglePattern,
},
],
},
Here is my "ListPattern" component's code. ListPattern is where all my sewing blogs are displayed.
<template>
<div class="list-blogs">
<h1>LIST BLOG TITLES</h1>
<br />
<input type="text" v-model="search" placeholder="search blogs" />
<div
class="blog-cover"
v-for="pattern in filteredPatterns"
:key="pattern.id"
>
<div>
<router-link v-bind:to="'/admin/single-pattern/' + pattern.id">
<h3 style="cursor: pointer" v-rainbow>
{{ pattern.title | uppercase }}
</h3></router-link
>
</div>
<p
:style="'background-color: var(--lightgrey)'"
:inner-html.prop="pattern.description | snippet"
></p>
</div>
</div>
</template>
<script>
import firebase from "firebase";
import searchMixin from "../mixins/searchMixin";
// Basic Use - Covers most scenarios
import { VueEditor } from "vue2-editor";
import Quill from "quill";
const AlignStyle = Quill.import("attributors/style/align");
Quill.register(AlignStyle, true);
// import $ from "jquery";
import Swal from "sweetalert2";
window.Swal = Swal;
const Toast = Swal.mixin({
toast: true,
position: "top-end",
showConfirmButton: false,
timer: 3000,
});
window.Toast = Toast;
export default {
name: "ManagePatterns",
components: { VueEditor },
data() {
return {
patterns: [],
pattern: {
title: null,
description: null,
image: null,
},
search: "",
};
},
firestore() {
return {
patterns: firebase.firestore().collection("free-patterns"),
};
},
computed: {},
},
};
</script>
And this is my 'SinglePattern' component where the clicked blog/pattern is displayed.
<template>
<div class="single-pattern">
<div class="blog-cover">
<div>
</div>
<div v-if="pattern">
<h3 style="cursor: pointer">
{{ pattern.title }}
</h3>
<div v-if="pattern.description">
<p
:style="'background-color: var(--lightgrey)'"
:inner-html.prop="pattern.description"
></p>
</div>
</div>
</div>
</div>
</template>
<script>
import firebase from "firebase";
import searchMixin from "../../mixins/searchMixin";
export default {
data() {
return {
id: this.$route.params.id,
patterns: [],
pattern: {
title: null,
description: null,
image: null,
},
};
},
firestore() {
return {
patterns: firebase.firestore().collection("free-patterns"),
};
},
mixins: [searchMixin],
created() {
console.log(this.$route.params.id);
var pat = this;
firebase
.firestore()
.collection("free-patterns")
.doc(this.$route.params.id)
.get()
.then(function(doc) {
if (doc.exists) {
pat.pattern = doc.data().pattern;
} else {
console.log("no such doc");
}
});
},
methods: {},
};
</script>
It works. I just had to change the code in my created() hook in 'SingePattern' component.
created() {
console.log(this.$route.params.id);
var docRef = firebase
.firestore()
.collection("free-patterns")
.doc(this.$route.params.id);
docRef
.get()
.then((doc) => {
if (doc.exists) {
this.pattern = doc.data();
} else {
// doc.data() will be undefined in this case
console.log("No such document!");
}
})
.catch((error) => {
console.log("Error getting document:", error);
});

Vue/Nuxt - Iterating Through Firestore Collection of Documents using v-for

First Vue/Firebase project. Using NuxtJS (with Vue v2.x) with Vuetify and Firestore. Attempting to iterate through a collection of documents and render Vuetify Card components with relevant data. Currently, I'm getting a list of components with the exact same data from a single Document. I able to log Collection items to the console, so I know the data is arriving correctly.
<template>
<section class="container">
<div>
<ul v-for="article in articles" id="articleCardList" :key="article">
<li>
<v-card dark>
<div class="d-flex flex-no-wrap justify-space-between">
<v-avatar class="ma-3" size="125" tile>
<v-img :key="article.thumbnail" :src="thumbnail"></v-img>
</v-avatar>
<div>
<v-card-title :key="article.title" class="text-h5">{{
title
}}</v-card-title>
<v-card-subtitle :key="article.description">{{
description
}}</v-card-subtitle>
<v-card-actions>
<v-card-text
:key="article.source"
class="ml-2 mt-3"
fab
icon
height="40px"
right
width="40px"
>{{ source }}
</v-card-text>
<v-card-text
:key="article.date"
class="ml-2 mt-3"
fab
icon
height="40px"
right
width="40px"
>{{ date }}
</v-card-text>
</v-card-actions>
</div>
</div>
</v-card>
</li>
</ul>
</template>
<script>
import { fireDb } from '~/plugins/firebase.js'
export default {
data() {
return {
writeSuccessful: false,
readSuccessful: false,
articles: this.articles,
}
},
methods: {
async readFromFirestore() {
fireDb
.collection('autoracing_articles')
.get()
.then((querySnapShot) => {
querySnapShot.forEach((doc) => {
this.articles = [
this.id,
this.title,
this.description,
this.thumbnail,
this.date,
this.link,
this.source,
]
console.log(this.articles)
})
})
},
},
}
</script>
It doesn't look like articles is populated correctly.
To collect all the documents into an array, initialize this.articles to an empty array, then use Array.prototype.push() to append an object of the document fields:
export default {
data() {
return {
articles: []
}
},
methods: {
async readFromFirestore() {
this.articles = []
fireDb
.collection('autoracing_articles')
.get()
.then((querySnapShot) => {
querySnapShot.forEach((doc) => {
this.articles.push({
id: doc.id,
title: doc.data().title,
description: doc.data().description,
thumbnail: doc.data().thumbnail,
date: doc.data().date,
link: doc.data().link,
source: doc.data().source,
})
})
})
},
}
}

How should I be referencing firebase data in Vue component?

I can see the data in the Vue console but I'm getting an error in the console: 'Property or method "rsvp" is not defined on the instance but referenced during render.'
How should I be referencing the firebase data?
<template>
<ul>
<li
v-for="(data, index) in rsvp"
:key="index"
>
<div
v-for="(name, index) in data.name"
:key="index"
>
{{ name }}
</div>
<div v-if="data.dietRestrictions">
Diet Restrictions: {{ data.dietRestrictions }}
</div>
</li>
</ul>
</template>
<script>
import { db } from "../../config/db";
export default {
name: "Results",
firebase: {
rsvp: db.ref("rsvp"),
},
}
</script>
I just forgot to declare rsvp in data().
data() {
return {
rsvp: {},
}
},

Resources