Nuxt/Vue Route Params update Meta title - vue.js

So I'm using Nuxt and Axios to grab JSON data via route.params.id all working fine, but now I need to get some of that data into my meta header and can't work out how to do it, see failed example below.
pages/posts/_id/_slug.vue
<template>
<div id="content" class="block float-left w-full pt-32">
<div class="block text-black mx-auto my-0 w-10/12" v-for="post in getData($route.params.id)" :key="post.id">
<!-- column 1 -->
<main class="block float-left md:pr-16 w-full md:w-9/12">
<h1 class="block float-left w-full text-black mb-8 uppercase title">
<span class="leading-none font-extrabold text-xl md:text-3xl">{{post.loc}}</span><br/>
<span class="leading-none font-bold text-2xl md:text-5xl">{{post.serv}}</span>
</h1>
<div class="content block float-left w-full" v-html="post.cont" />
</main>
<!-- column 1 -->
</div>
</div>
</template>
This works fine
import json from '~/static/mpc-data.json'
export default {
This is where it goes wrong
head () {
return {
meta_title: post.loc + post.serv,
meta: [
{ hid: 'title', name: 'title', content: this.meta_title },
{ hid: 'og:title', name: 'og:title', content: this.meta_title }
]
}
}
The remainder - all works fine
data () {
return {
posts: json,
title: ''
}
},
methods: {
getData (id) {
const data = this.posts
return data.filter((item) => {
return item.id === id
})
},
getArea (loc) {
const data = this.posts
return data.filter((item) => {
return item.loc === loc
})
}
}
}

I think It's not meta_title but title.
more reference:
https://nuxtjs.org/api/pages-head/

Related

My page reloads, and the added task wont register

I am creating a to do list for my exam. For some reason the page keep reload when I click add task, and the tasks wont register. I am new to Vue.js and Javascript.
I have problems with finding the issue. It is a simple code, not to complex, but the add task part is not working.
Here is my code:
<template >
<section class="todolist">
<h1 class="title">{{ title }}</h1>
<form class="container">
<h3 class="container__title">New Task </h3>
<input class="container__input" type="text" placeholder="Enter task" v-model="task">
<button class="container__button" #click="addPlanningTask">Add Task</button>
<h3 class="container__title-second">To Do List</h3>
<ul>
<li v-for="(task, index) in tasks" :key="index">
<span>{{ task.name }} </span>
<button class="todolist__button" #click="deletePlanningTask">Remove</button>
</li>
</ul>
<h4 class="container__list-title" v-if="task.length === 0">
List is empty!</h4>
</form>
</section>
<div class="guestlist">
<h4 class="guestlist__title">{{ invited }}</h4>
<span>{{ guestList }}</span>
<button class="guestlist__button" #click="toggleGuestList">Guestlist</button>
<div v-if="isGuestListVisible === true">
<ul>
<li v-for="guest in people" :key="guest.id.value">
{{ guest.name.first }} {{ guest.name.last }}</li>
</ul>
</div>
</div>
</template>
export default {
props: {
titleName: {
type: String,
default: 'todolist',
},
},
data() {
return {
title: 'Planning the party!',
task: '',
tasks: [{
name: ''
}],
invited: 'Who is invited?',
guestList: '',
people: [],
name: '',
isGuestListVisible: false
}
},
created() {
this.addGuest();
},
methods: {
async addGuest() {
const url = 'https://randomuser.me/api/?page=2&results=8';
const res = await fetch(url);
const { results } = await res.json();
this.people = results;
},
toggleGuestList() {
this.isGuestListVisible = !this.isGuestListVisible;
},
addPlanningTask() {
if(this.task.length === 0)
return;
this.tasks.push({
name: this.task
});
},
deletePlanningTask(index) {
this.tasks.splice(index, 1);
},
},
};
</script>
You can use #submit.prevent on form if you don't want to reload page:
new Vue({
el: "#demo",
props: {
titleName: {
type: String,
default: 'todolist',
},
},
data() {
return {
title: 'Planning the party!',
task: '',
tasks: [],
invited: 'Who is invited?',
guestList: '',
people: [],
name: '',
isGuestListVisible: false
}
},
created() {
this.addGuest();
},
methods: {
async addGuest() {
const url = 'https://randomuser.me/api/?page=2&results=8';
const res = await fetch(url);
const { results } = await res.json();
this.people = results;
},
toggleGuestList() {
this.isGuestListVisible = !this.isGuestListVisible;
},
addPlanningTask() {
if (this.task.length === 0) return;
this.tasks.push({ name: this.task });
},
deletePlanningTask(index) {
this.tasks.splice(index, 1);
},
},
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="demo" >
<section class="todolist">
<h1 class="title">{{ title }}</h1>
<form class="container" #submit.prevent>
<h3 class="container__title">New Task </h3>
<input class="container__input" type="text" placeholder="Enter task" v-model="task">
<button class="container__button" #click="addPlanningTask">Add Task</button>
<h3 class="container__title-second">To Do List</h3>
<ul>
<li v-for="(task, index) in tasks" :key="index">
<span>{{ task.name }} </span>
<button class="todolist__button" #click="deletePlanningTask">Remove</button>
</li>
</ul>
<h4 class="container__list-title" v-if="task.length === 0">List is empty!</h4>
</form>
</section>
<div class="guestlist">
<h4 class="guestlist__title">{{ invited }}</h4>
<span>{{ guestList }}</span>
<button class="guestlist__button" #click="toggleGuestList">Guestlist</button>
<div v-if="isGuestListVisible === true">
<ul>
<li v-for="guest in people" :key="guest.id.value">
{{ guest.name.first }} {{ guest.name.last }}</li>
</ul>
</div>
</div>
</div>

Parse XML from an API in Nuxt using XML2JS

I'm trying to Loop through the API in nuxt using XML2JS and then looping through the data to display it within an html file.
It's working when console logged but it's not looping through within the HTML. There arent any errors its, just blank and not displaying anything in the html.
<template>
<article>
<TheHeader />
<h1>Destinations</h1>
<main class="mt-8 mx-auto max-w-screen-xl px-4 sm:mt-12 sm:px-6 md:mt-20 xl:mt-24">
<section v-for="(mountain, index) in mountains" :key="index">
<div v-for="category in mountain.property" :key="category.id">
<div class="lg:grid lg:grid-cols-12 lg:gap-8 mt-12">
<div class="sm:text-center md:max-w-2xl md:mx-auto lg:col-span-6 lg:text-left">
<h2 class="mt-1 text-4xl tracking-tight leading-10 font-extrabold text-gray-900 sm:leading-none sm:text-6xl lg:text-5xl xl:text-6xl">
{{ category.propertyname }}
</h2>
<p class="mt-3 text-base text-gray-500 sm:mt-5 sm:text-xl lg:text-lg xl:text-xl">
{{ category.propertyaddress }}
</p>
</div>
<div class="mt-12 relative sm:max-w-lg sm:mx-auto lg:mt-0 lg:max-w-none lg:mx-0 lg:col-span-6 lg:flex lg:items-center">
<div class="relative mx-auto w-full rounded-lg shadow-lg">
<button type="button" class="relative block w-full rounded-lg overflow-hidden focus:outline-none focus:shadow-outline">
<img class="w-full" :src="category.photos.img.main._url" :alt="category.photos.img.caption">
<div class="absolute inset-0 w-full h-full flex items-center justify-center">
</div>
</button>
</div>
</div>
</div>
</div>
</section>
</main>
</article>
</template>
<script>
const xml2js = require('xml2js');
export default {
data() {
return {
mountains: []
}
},
async fetch() {
this.mountains = await fetch('http://localhost:3000/api/')
.then( res => res.text() )
.then( data=> {
const xml = data;
xml2js.parseString(xml, (err, result) => {
if(err) {
throw err;
}
const output = JSON.stringify(result, null, 4);
console.log(output);
});
})
}
}
</script>
JSON:
{
"scdata": {
"property": [
{
"propertycode": "123456"
Any help would be much appreciated, please let me know if you need me to provide any more information.
Thanks.
I guess you are not returning any data to the mountains. Might be possible that's why you didn't get any display at visual while you got data in const output
So, Please try with the replace the fetch function with below
<template>
<div class="container">
<h1>Test demo of xml parse</h1>
<div v-for="(category, index) in mountains" :key="index">
<div class="property-name"> <h3>Property Code:</h3><span>category.propertycode</span>
</div>
</div>
</div>
</template>
<script>
const xml2js = require('xml2js');
export default {
data() {
return {
mountains: []
}
},
methods: {
xmlToJSON: (str, options) => {
return new Promise((resolve, reject) => {
xml2js.parseString(str, options, (err, jsonObj) => {
if (err) {
return reject(err);
}
resolve(jsonObj);
});
});
}
},
async fetch() {
const xmlData = await fetch('<YOUR-API-URL>')
.then(res => res.text())
.then(data => {
console.log('<<==');
const xml = data;
return data;
});
const jsonData = await this.xmlToJSON(xmlData);
if (jsonData && jsonData.scdata && jsonData.scdata.property) this.mountains = jsonData.scdata.property
}
}
</script>

How to make pagination?

How to make pagination. I tried many times already, but I can’t do it. I have to paginate without Laravel. The problem is that I can not make a cycle that will display the number of pages, each page should have 10 posts, and there are 98 posts in total. I made the property to be calculated, thanks to which you can find out how many pages there will be. I made a page switch that works. But for some reason, the cycle with which I will display the number of pages does not work for me, I cannot understand what the problem is?
Screenshot
My Vue js:
import axios from 'axios';
export default {
name: 'app',
data () {
return{
counter: 1,
zero: 0,
posts: [],
createTitle: '',
createBody: '',
visiblePostID: '',
}
},
watch: {
counter: function(newValue, oldValue) {
this.getData()
}
},
created(){
this.getData()
},
computed: {
evenPosts: function(posts){
return Math.ceil(this.posts.length/10);
}
},
methods: {
getData() {
axios.get(`http://jsonplaceholder.typicode.com/posts?_start=${this.counter}+${this.zero}&_limit=10`).then(response => {
this.posts = response.data
})
},
// even: function(posts) {
// return Math.ceil(this.posts.length/10)
// },
deleteData(index, id) {
axios.delete('http://jsonplaceholder.typicode.com/posts/' + id)
.then(response => {
console.log('delete')
this.posts.splice(index, 1);
})
.catch(function(error) {
console.log(error)
})
},
addPost() {
axios.post('http://jsonplaceholder.typicode.com/posts/', {
title: this.createTitle,
body: this.createBody
}).then((response) => {
this.posts.unshift(response.data)
})
},
changePost(id, title, body) {
axios.put('http://jsonplaceholder.typicode.com/posts/' + id, {
title: title,
body: body
})
},
},
}
My html:
<div id="app">
<div class="smallfon">
<div class="blocktwitter"><img src="src/assets/twitter.png" class="twitter"/></div>
<div class="addTextPost">Add a post</div>
<input type="text" v-model="createTitle" class="created"/>
<input type="text" v-model="createBody" class="created"/>
<div><button #click="addPost()" class="addPost">AddPost</button></div>
<div class="post1">
<div class="yourPosts">Your Posts</div>
<ul>
<li v-for="(post, index) of posts" class="post">
<p><span class="boldText">Title:</span> {{ post.title }}</p>
<p><span class="boldText">Content:</span> {{ post.body }}</p>
<button #click="deleteData(index, post.id)" class="buttonDelete">Delete</button>
<button #click="visiblePostID = post.id" class="buttonChange">Change</button>
<div v-if="visiblePostID === post.id" class="modalWindow">
<div><input v-model="post.title" class="changePost"><input v-model="post.body" class="changePost"></div>
<button type="button" #click="changePost(post.id, post.title, post.body)" class="apply">To apply</button>
</div>
</li>
</ul>
<button type="button" #click="counter -=1" class="prev">Предыдущая</button>
<!-- <div class="counter">{{ counter }}</div> --> <span v-for="n in evenPosts" :key="n.id">{{ n }} </span>
<button type="button" #click="counter +=1" class="next">Следущая</button>
<!-- <span v-for="n in evenPosts" :key="n.id">{{ n }} </span> -->
</div>
</div>
</div>
If you bind a limit to your fetching request (axios.get(...&_limit=10)), you can't return a paginate count because your computed evenPost property will always return 1 i.e Math.ceil(10/10) == 1
To fix your pagination, remove the parameters query to get the data:
getData() {
axios.get('https://jsonplaceholder.typicode.com/posts').then(response => {
this.posts = response.data
})
}
Then change the default counter page to 0 and add a computed property to return 10 posts based on it:
data () {
return {
counter: 0,
//...
}
},
computed: {
paginatedPosts() {
const start = this.counter * 10;
const end = start + 10;
return this.posts.slice(start, end);
}
}
Now you can iterate on this property:
<ul>
<li v-for="(post, index) of paginatedPosts" class="post">
...
</li>
</ul>
Basic live example

Is it possible to sync Vuejs components displayed multiple times on the same page?

I have a web page that displays items. For each items there is a button (vuejs component) which allow user to toggle (add/remove) this item to his collection.
Here is the component:
<template lang="html">
<button type="button" #click="toggle" name="button" class="btn" :class="{'btn-danger': dAdded, 'btn-primary': !dAdded}">{{ dText }}</button>
</template>
<script>
export default {
props: {
added: Boolean,
text: String,
id: Number,
},
data() {
return {
dAdded: this.added,
dText: this.text,
dId: this.id
}
},
watch: {
added: function(newVal, oldVal) { // watch it
this.dAdded = this.added
},
text: function(newVal, oldVal) { // watch it
this.dText = this.text
},
id: function(newVal, oldVal) { // watch it
this.dId = this.id
}
},
methods: {
toggle: function(event) {
axios.post(route('frontend.user.profile.pop.toggle', {
pop_id: this.dId
}))
.then(response => {
this.dText = response.data.message
let success = response.data.success
this.dText = response.data.new_text
if (success) {
this.dAdded = success.attached.length
let cardPop = document.getElementById('card-pop-'+this.dId);
if(cardPop)
cardPop.classList.toggle('owned')
}
})
.catch(e => {
console.log(e)
})
}
}
}
</script>
For each item, the user can also open a modal, loaded by a click on this link:
<a href="#" data-toggle="modal" data-target="#popModal" #click="id = {{$pop->id}}">
<figure>
<img class="card-img-top" src="{{ URL::asset($pop->img_path) }}" alt="Card image cap">
</figure>
</a>
The modal is also a Vuejs component:
<template>
<section id="pop" class="h-100">
<div class="card">
<div class="container-fluid">
<div class="row">
<div class="col-12 col-lg-1 flex-column others d-none d-xl-block">
<div class="row flex-column h-100">
<div v-for="other_pop in pop.other_pops" class="col">
<a :href="route('frontend.pop.collection.detail', {collection: pop.collection.slug, pop: other_pop.slug})">
<img :src="other_pop.img_path" :alt="'{{ other_pop.name }}'" class="img-fluid">
</a>
</div>
<div class="col active order-3">
<img :src="pop.img_path" :alt="pop.name" class="img-fluid">
</div>
</div>
</div>
<div class="col-12 col-lg-6 content text-center">
<div class="row">
<div class="col-12">
<img :src="pop.img_path" :alt="pop.name" class="img-fluid">
</div>
<div class="col-6 text-right">
<toggle-pop :id="pop.id" :added="pop.in_user_collection" :text="pop.in_user_collection ? 'Supprimer' : 'Ajouter'"></toggle-pop>
</div>
<div class="col-6 text-left">
<!-- <btnaddpopwhishlist :pop_id="propid" :added="pop.in_user_whishlist" :text="pop.in_user_whishlist ? 'Supprimer' : 'Ajouter'"></btnaddpopwhishlist> -->
</div>
</div>
</div>
<div class="col-12 col-lg-5 infos">
<div class="header">
<h1 class="h-100">{{ pop.name }}</h1>
</div>
<div class="card yellow">
<div class="card p-0">
<div class="container-fluid">
<div class="row">
<div class="col-3 py-2">
</div>
<div class="col-6 py-2 bg-lightgray">
<h4>Collection:</h4>
<h3>{{ pop.collection ? pop.collection.name : '' }}</h3>
</div>
<div class="col-3 py-2 bg-lightgray text-center">
<a :href="route('frontend.index') + 'collections/' + pop.collection.slug" class="btn-round right white"></a>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
</template>
<script>
export default {
props: {
id: Number
},
data() {
return {
pop: {
collection: {
}
}
}
},
ready: function() {
if (this.propid != -1)
this.fetchData()
},
watch: {
id: function(newVal, oldVal) { // watch it
// console.log('Prop changed: ', newVal, ' | was: ', oldVal)
this.fetchData()
}
},
computed: {
imgSrc: function() {
if (this.pop.img_path)
return 'storage/images/pops/' + this.pop.img_path
else
return ''
}
},
methods: {
fetchData() {
axios.get(route('frontend.api.v1.pops.show', this.id))
.then(response => {
// JSON responses are automatically parsed.
// console.log(response.data.data.collection)
this.pop = response.data.data
})
.catch(e => {
this.errors.push(e)
})
// console.log('fetchData')
}
}
}
</script>
Here is my app.js script :
window.Vue = require('vue');
Vue.component('pop-modal', require('./components/PopModal.vue'));
Vue.component('toggle-pop', require('./components/TogglePop.vue'));
const app = new Vue({
el: '#app',
props: {
id: Number
}
});
I would like to sync the states of the component named toggle-pop, how can I achieve this ? One is rendered by Blade template (laravel) and the other one by pop-modal component. But they are just the same, displayed at different places.
Thanks.
You could pass a state object as a property to the toggle-pop components. They could use this property to store/modify their state. In this way you can have multiple sets of components sharing state.
Your component could become:
<template lang="html">
<button type="button" #click="toggle" name="button" class="btn" :class="{'btn-danger': sstate.added, 'btn-primary': !sstate.added}">{{ sstate.text }}</button>
</template>
<script>
export default {
props: {
sstate: {
type: Object,
default: function() {
return { added: false, text: "", id: -1 };
}
}
},
data() {
return {};
},
methods: {
toggle: function(event) {
axios.post(route('frontend.user.profile.pop.toggle', {
pop_id: this.sstate.id
}))
.then(response => {
this.sstate.text = response.data.message
let success = response.data.success
this.sstate.text = response.data.new_text
if (success) {
this.sstate.ddded = success.attached.length
let cardPop = document.getElementById('card-pop-'+this.sstate.id);
if(cardPop)
cardPop.classList.toggle('owned')
}
})
.catch(e => {
console.log(e)
})
}
};
</script>
Live demo
https://codesandbox.io/s/vq8r33o1w7
If you are 100% sure that all toggle-pop components should always have the same state, you can choose to not define data as a function. Just declare it as an object.
data: {
dAdded: this.added,
dText: this.text,
dId: this.id
}
In https://v2.vuejs.org/v2/guide/components.html#data-Must-Be-a-Function, it mentions
a component’s data option must be a function, so that each instance
can maintain an independent copy of the returned data object
If Vue didn’t have this rule, clicking on one button would affect the
data of all other instances
Since you want to sync the data of all toggle-pop component instances, you don't have to follow the data option must be a function rule.

Returning promise from api and putting into global data

Sorry if this is obvious but new to vue and my js not so great.....
I'm trying to get some data from an api and then put that data into a global object so it can be passed down to some child components.
I can't seem to $set the data into the global projects object, it either returns undefined or returns a promise.
parent --
<template>
<div id="app">
<section class="intro">
<h1>WELCOME</h1>
</section>
<transition name="fade"> <router-view class="view" :computedProjects=computedProject ></router-view></transition>
</div>
</template>
<script>
import projectsList from './components/projectsList'
var client = contentful.createClient({
// This is the space ID. A space is like a project folder in Contentful terms
space: '',
// This is the access token for this space. Normally you get both ID and the token in the Contentful web app
accessToken: '',
})
var PRODUCT_CONTENT_TYPE_ID = 'project'
export default {
name: 'app',
components: {
projectsList
},
data: function() {
return {
users:'',
projects:'',
}
},
methods: {},
created : function () {
client.getEntries().then((response) => {
console.log(response)
this.projects = response
console.log(this.projects)
return response
});
},
computed: {
computedProject: function() {
if (!this.projects) return null;
return this.projects;
}
}
}
}
child ---
<template>
<section class="project-list">
<swiper :options="swiperOption">
<swiper-slide v-for="project in projects" v-bind:ref="'project' + project.name" :key="project.name" data-swiper-parallax="-100">
<div class="project-cover wrapper">
{{project.name}}
<router-link :to="{ name: 'project', params: { id: project.name }}">
<div class="cover-img" :style="{ 'background-image': 'url(../static/img/projects/'+project.name+'/'+'project-details-header.jpg)' }"> </div>
<div class="project-copy"></div>
<div href="#" class="tilter tilter--4">
<figure class="tilter__figure">
<img class="tilter__image" :src="'+project.fields.coverImage.fields.file.url+'" alt="" />
<div class="tilter__deco tilter__deco--shine"></div>
<div class="tilter__deco tilter__deco--overlay" v-bind:style="{ backgroundImage: project.gradient }"></div>
<figcaption class="tilter__caption">
<h1 class="projectName tilter__title title">{{project.name}}</h1>
<h2 class="tilter__description">{{project.tagline}}</h2>
</figcaption>
<svg class="tilter__deco tilter__deco--lines" viewBox="0 0 300 415">
<path d="M20.5,20.5h260v375h-260V20.5z" />
</svg>
</figure>
</div>
</router-link>
</div>
</swiper-slide>
<div class="swiper-pagination" slot="pagination"></div>
</swiper>
</section>
</template>
<script>
export default {
data: function () {
return {
swiperOption: {
autoplay:false,
setWrapperSize :true,
observeParents:true,
keyboardControl : true,
centeredSlides :true,
freeMode:false,
freeModeSticky:true,
pagination: '.swiper-pagination',
paginationType: 'progress',
watchSlidesProgress : true,
watchSlidesVisibility : true,
speed:650,
grabCursor:true,
parallax:true
},
}
},
props :['computedProject'],
created : function () {
console.log(this.projects)
},
any help will be appreciated.
You can just assign the vue data variable in the callback of getEntries like following:
created : function () {
var self = this
client.getEntries().then((response) => {
console.log(response)
self.projects = response
})
console.log(this.projects)
},