How do you use components with data from Nuxt Content Module - vue.js

A component that has a hidden input field should have a the value of the current page title. In Nuxt you can accomplish it using asyncData for all pages except for pages using Nuxt Content Module. The Nuxt Content Module does not allow asyncData but fetch is allowed but. Example:
components/Form.vue
<template>
<div>
<input
id="page"
type="hidden"
:value="article.title"
required
/>
</div>
</template>
<script>
export default {
async fetch() {
this.article = await this.$content('articles', this.params).fetch()
},
data() {
return { article: {} }
},
}
</script>
pages/articles/_slug.vue
<template>
<div>
<h1>{{ article.title }}</h1>
<Form />
</div>
</template>
<script>
export default {
async asyncData({ $content, params }) {
const article = await $content('articles', params.slug).fetch()
return { article }
},
}
</script>
This code gets no errors but the component never shows page title in the hidden input only in the h1 tag. (edit: fixed typo)

How I solved this was with props.
components/Form.vue
<template>
<form>
<label for="title"> Title </label>
<input name="title" type="hidden" :value="title" required />
<button type="submit">Send</button>
</form>
</template>
<script>
export default {
props: {
title: {
type: String,
required: false,
default: ''
},
}
}
</script>
and then in the pages/_slug.vue
<template>
<div>
<nuxt-content :document="doc" />
<Form :title="doc.title" />
</div>
</template>
This should work for you.

Related

How to display many times the same component with vuex link inside

I'm new to Vue and Vuex.
I made a component with radio button, and this component is two-way bind with the store.
Now that the component is working, i would like to use it for multiple item by calling it several time, and indicates to it which item of the vuex store it should be bound to. In this way, i'll be able to loop of the array containing all item on which apply the radio button modification.
In my store, I have this array :
state {
[...other data...]
arrayX {
item1: { name : 'a', value : 'nok'}
item2: { name : 'b', value : 'ok}},
[...other data...]
This array is bind with v-model inside the component :
<template>
<div class="radio">
<input type="radio" name="a" value="nok" v-model="arrayX.item1.value" id="nok"/>
<label for="nok"> Nok </label>
</div>
<div class="radio">
<input type="radio" name="a" value="" v-model="arrayX.item1.value" id="empty"/>
<label for="empty">None</label>
</div>
<div class="radio">
<input type="radio" name="a" value="ok_but" v-model="arrayX.item1.value" id="ok_but"/>
<label for="ok_but">Ok but</label>
</div>
<div class="radio">
<input type="radio" name="a" value="ok" v-model="arrayX.item1.value" id="ok"/>
<label for="ok">Ok</label>
</div>
</div>
</template>
<script>
import { mapFields } from 'vuex-map-fields';
export default {
name: 'SwitchColors',
computed:{
...mapFields(['arrayX' ])
}
}
</script>
I created an example scenario in my Vue 2 CLI sandbox app. Each radio button component is bound to the Vuex store array element through a computed property. I pass the store array index value as a prop from the parent to the radio component.
RadioButtonVuex.vue
<template>
<div class="radio-button-vuex">
<hr>
<div class="row">
<div class="col-md-6">
<input type="radio" value="One" v-model="picked">
<label>One</label>
<br>
<input type="radio" value="Two" v-model="picked">
<label>Two</label>
</div>
</div>
</div>
</template>
<script>
export default {
props: {
pickedIndex: {
type: Number,
required: true
}
},
computed: {
picked: {
get() {
return this.$store.state.radioSelections[this.pickedIndex].picked;
},
set(newValue) {
this.$store.commit('updateSelection', { index: this.pickedIndex, newValue: newValue });
}
}
},
}
</script>
<style scoped>
label {
margin-left: 0.5rem;
}
</style>
Parent.vue
<template>
<div class="parent">
<h3>Parent.vue</h3>
<radio-button-vuex :pickedIndex="0" />
<radio-button-vuex :pickedIndex="1" />
</div>
</template>
<script>
import RadioButtonVuex from './RadioButtonVuex.vue'
export default {
components: {
RadioButtonVuex
}
}
</script>
store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex);
export default new Vuex.Store({
state: {
radioSelections: [
{
picked: 'One'
},
{
picked: 'Two'
}
]
},
mutations: {
updateSelection(state, payload) {
state.radioSelections[payload.index].picked = payload.newValue;
}
}
})

It's possible to pass an object which has some methods to the child component in the Vue 3 JS

Parent component has the method "startMethods" which just also has some other method "onDecrementStart ". OnDecrementStart method just only call Alert.
Under this line, added a code example but that code didn't work.
<template>
<div class="parent">
<div class="main">
<img alt="Vue logo" src="../assets/logo.png">
<SettingsBoard :max="maxValue" :start="startValue"
:startInc="startMethods"
/>
</div>
</div>
</template>
<script>
import SettingsBoard from "#/components/SettingsBoard";
export default {
name: "Main",
components: {
SettingsBoard
},
methods: {
startMethods: {
onDecrementStart () {
alert('Decrement')
},
onIncrementStart () {
alert('Incriment')
}
},
}
}
</script>
SettingsBoard component
<template>
<div class="container">
<label>
<button :#click="startInc.onDecrementStart()">-</button>
</label>
</div>
</template>
<script>
export default {
name: "SettingsBoard",
props: {
startInc: Object
},
}
</script>
I want to get like that if it's possible.
<template>
<div class="container">
<label>
<button :#click="startInc.onDecrementStart()">-</button>
<button :#click="startInc.onIncrementtStart()">+</button>
</label>
</div>
</template>
<script>
export default {
name: "SettingsBoard",
props: {
startInc: Object
},
}
</script>
To run a method in the parent component you should emit a custom event which has the parent method as handler :
<label>
<button #click="$emit('decrement')">-</button>
<button #click="$emit('increment')">+</button>
</label>
in parent component :
<div>
<SettingsBoard
#decrement="onDecrementStart"
#increment="onIncrementStart"
/>
...
methods: {
onDecrementStart() {
this.count--;
},
onIncrementStart() {
this.count++;
},
},
LIVE EXAMPLE

Trigger submit with prevent default

I'm trying to call submit method on form via refs from my script. On form tag I have #submit.prevent.
<template>
<div id="app">
<form ref="search-form" #submit.prevent="sub">
<input type="text" required />
</form>
<button #click="submitForm">click me</button>
</div>
</template>
<script>
export default {
name: "App",
methods: {
submitForm() {
this.$refs["search-form"].submit();
},
sub() {
console.log("submitted");
},
},
};
</script>
My problems are that when I click button:
page get reloaded like prevent default is not working
sub() is not called at all
Why?
Your question is not clear what you want to do but maybe you need this
const app = new Vue({
methods: {
submitForm() {
this.$refs["search-form"].submit();
},
sub() {
alert('hello');
},
}
})
app.$mount("#app")
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<form ref="search-form" #submit.prevent="sub">
<input type="text" required/>
<button #click.native.prevent="submitForm">Send</button>
</form>
</div>
<template>
<div id="app">
<form ref="search-form">
<input type="text" required />
</form>
<button #click.stop.prevent="processForm">click me</button>
</div>
</template>
<script>
export default {
name: "App",
methods: {
processForm() {
// validation here
this.$refs["search-form"].onsubmit = this.submitForm();
},
submitForm() {
// AJAX here
console.log("submitted");
},
},
};
</script>
⚠️ I assume that when you use prevent means you are going to manually submit the form at a later stage (maybe after client side validation). Then its either you use AJAX or Axios to submit the form to the server side. Calling submit() anywhere here will still reload your page.

How to pass data between components when using Vue-Router

How do I pass data from one component to another when using Vue router?
I'm building a simple CRUD app that have different components.
My conponents are:
App.vue - where I render the router-view
Contacts.vue - where I have the array of objects of contacts
ContactItem.vue - handle how the contact is displayed (gets contact as a prop from contact.vue
AddContact.vue - add new contact
EditContact.vue - edit selected contact
On the AddContact component, I have a form the user fills and then clicks on the submit button to add the form to the main component in Contacts.vue but when I emit an event and call it on the Contacts.vue component, it doesn't work. I get no output but from devtools I can see the event was triggered from AddContact.vue component.
Here is the Github link
<!-- App.vue -->
<template>
<div>
<Navbar />
<div class="container">
<router-view #add-contact="addContact" />
</div>
</div>
</template>
<script>
import Navbar from "./components/layout/Navbar";
export default {
components: {
Navbar
}
};
</script>
<!-- Contacts.vue -->
<template>
<div>
<div v-for="(contact) in contacts" :key="contact.id">
<ContactItem :contact="contact" />
</div>
</div>
</template>
<script>
import ContactItem from "./ContactItem";
export default {
components: {
ContactItem
},
data() {
return {
contacts: [
{
id: 1,
name: "John Doe",
email: "jdoe#gmail.com",
phone: "55-55-55"
},
{
id: 2,
name: "Karen Smith",
email: "karen#gmail.com",
phone: "222-222-222"
},
{
id: 3,
name: "Henry Johnson",
email: "henry#gmail.com",
phone: "099-099-099"
}
]
};
},
methods: {
addContact(newContact) {
console.log(newContact);
this.contacts = [...this.contacts, newContacts];
}
}
};
</script>
<!-- AddContact.vue -->
<template>
<div>
<div class="card mb-3">
<div class="card-header">Add Contact</div>
<div class="card-body">
<form #submit.prevent="addContact">
<TextInputGroup
label="Name"
name="name"
placeholder="Enter your name..."
v-model="name"
for="name"
/>
<TextInputGroup
type="email"
label="Email"
name="email"
placeholder="Enter your email..."
v-model="email"
/>
<TextInputGroup
type="phone"
label="Phone"
name="phone"
placeholder="Enter your phone number..."
v-model="phone"
/>
<input type="submit" value="Add Contact" class="btn btn-block btn-light" />
</form>
</div>
</div>
</div>
</template>
<script>
import TextInputGroup from "../layout/TextInputGroup";
export default {
components: {
TextInputGroup
},
data() {
return {
name: "",
email: "",
phone: ""
};
},
methods: {
addContact() {
const newContact = {
name: this.name,
email: this.email,
phone: this.phone
};
this.$emit("add-contact", newContact);
}
}
};
</script>
Well there are more ways to send data from component to component:
You could create a new Vue instance in your main.js file and call it eventBus
export const eventBus = new Vue();
After that you can import this bus wherever you need it:
import { eventBus } from "main.js"
Then you can send events over this global bus:
eventBus.$emit("add-contact", newContact);
At the other component you need to import this bus again and listen to this event in your "created" lifecycle:
created(){
eventBus.$on("add-contact", function (value){
console.log(value)
})
}
The other way is to store it centralized in vuex state. With "created" you can call this data. Created gets executed after your vue instance is created

Access infromation of grandparent component from grandchild component in vue.js

I have a parent component in Vue called RecipeView, and it is an inline-component. inside it, i have these components:
comments. and inside comments, i have comment and NewCommentForm, has it shows in the picture below.
I am passing in the RecipeView component the id as a prop, and would like to access it in the NewCommentForm component in order to set an endpoint that i will post to and save the comment.
This is the RecipeView component:
<recipe-view :id="{{$recipe->id}}">
<comments :data="{{$recipe->comment}}"#added="commentsCount++"></comments>
</recipe-view>
and the script for it is this:
<script>
import Comments from '../components/Comments.vue';
export default {
props: ['initialCommentsCount','id'],
components: {Comments},
data(){
return {
commentsCount: this.initialCommentsCount,
recipe_id:this.id
};
}
}
</script>
The comments component looks like this:
<template>
<div>
<div v-for="comment in items">
<comment :data="comment"></comment>
</div>
<new-comment-form :endpoint="'/comments/**Here should go the id from parent RecipeView component**'" #created="add"></new-comment-form>
</div>
</template>
<script>
import Comment from './Comment.vue';
import NewCommentForm from './NewCommentForm.vue';
export default {
props: ['data'],
components: {Comment, NewCommentForm},
data() {
return {
items: this.data,
endpoint: ''
}
},
methods: {
add(comment) {
this.items.push(comment);
this.$emit('added');
}
}
}
</script>
and this is the NewCommentForm component:
<template>
<div>
<div class="field">
<p class="control">
<input class="input"
type = "text"
name="name"
placeholder="What is your name?"
required
v-model="name">
</p>
</div>
<div class="field">
<p class="control">
<textarea class="textarea"
name="body"
placeholder="Have your say here..."
required
v-model="body">
</textarea>
</p>
</div>
<button type="submit"
#click="addComment"
class="button is-medium is-success">send</button>
</div>
</template>
<script>
export default {
props:['endpoint'],
data(){
return {
body:'',
name:'',
}
},
methods:{
addComment(){
axios.post(this.endpoint, {
body:this.body,
name: this.name
}).then(({data}) => {
this.body = '';
this.name = '';
this.$emit('created', data);
});
}
}
}
</script>
Thanks for the help.