Parse XML from an API in Nuxt using XML2JS - vue.js

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>

Related

VueJs | How to combine a In and out animation with library Animate.css?

I'm starting with VueJs.
I would like to combine animation in and out in a modal.
I think I should do a function but can't find how to.
Here my code:
<template>
<div class="backgroundImage" :style="{'background-image': 'url(' + require('../../assets/bkg.jpg') + ')'}">
<div v-if="showModal" #click="showModal = false"></div>
<div class="modal" v-if="showModal">
<div class="animate__animated animate__bounceIn animate__slow">
<img id="synthesisFt" src="../../assets/popup-ftt.jpg" alt="Logo FunkTheTown" title="FunkTheTown" #click="showModal = false"/>
</div>
</div>
<div>
<img class="logo-img" src="../../assets/logo.png" alt="Logo FunkTheTown" title="FunkTheTown" />
</div>
</div>
</template>
<script>
export default {
data () {
return {
showModal: false
}
},
mounted:function(){
this.popup()
},
methods: {
popup : function () {
setTimeout(() => {
this.showModal = true;
}, 3000);
},
// classChange : function () {
// showmodal = false;
// }
}
}
for the off I would like to use animate__animated animate__bounceOut when I click on the modal.
I've resolved the issue, here the code :
<template>
<div class="backgroundImage" :style="{'background-image': 'url(' + require('../../assets/bkg.jpg') + ')'}">
<div v-if="showModal" #click="showModal = false"></div>
<div class="modal" v-if="showModal">
<div class="animate__animated animate__bounceIn animate__slow">
<img id="synthesisFt" src="../../assets/popup-ftt.jpg" alt="Logo FunkTheTown" title="FunkTheTown" #click="anim2()"/>
</div>
</div>
<div class="animate__animated animate__backInDown animate__slow" v-if="showModalLogin">
<div class="modalLogin">
<label>Login</label>
<input class="loginInput" type="text" placeholder="email...">
<label>Password</label>
<input class="loginPassword" type="password" placeholder="password...">
<button class="connexion">Connexion</button>
<button class="join">Join</button>
</div>
</div>
<div>
<img class="logo-img" src="../../assets/logo.png" alt="Logo FunkTheTown" title="FunkTheTown" />
</div>
<div class="animate__animated animate__rotateIn" v-if="showModalLogo" >
<img class="full_logo" src="../../assets/F2T_logo_color.png" >
</div>
<div class="animate__animated animate__rotateIn" v-if="showModalLogo">
<img class="full_logo_right" src="../../assets/F2T_logo_coloreverse.png" >
</div>
</div>
</template>
<script>
export default {
data () {
return {
showModal: false,
showModalLogin: false,
showModalLogo:false,
}
},
mounted:function(){
this.popup()
},
methods: {
popup : function () {
setTimeout(() => {
this.showModal = true;
}, 3000);
},
anim2 : function () {
this.showModal= false;
this.showModalLogin= true;
this.showModalLogo =true;
const element = document.querySelector('.logo-img');
element.classList.add('animate__animated','animate__bounceOut');
}
// classChange : function () {
// showmodal = false;
// }
}
}

Display an random Object from API Vue.js

I need to display a random object from an array. The array comes from the API.
<template>
<div class="container">
<div class="box">
<p class="name"> {{chosenName.title}} </p>
<p class="description"> {{chosenName.description}} </p>
<hr>
<img v-bind:src="chosenName.urlToImage" alt="">
<p class="author"> {{chosenName.author}} </p>
</div>
<div class="box">
<p class="name"> {{chosenName.title}} </p>
<p class="description"> {{chosenName.description}} </p>
<hr>
<img v-bind:src="chosenName.urlToImage" alt="">
<p class="author"> {{chosenName.author}} </p>
</div>
<button v-on:click="choose" id="choose-button">One more time</button>
</div>
</template>
#Options({
props: {
msg: String
},
data() {
return {
artworks: [],
errors: [],
chosenName: '',
}
},
created() {
axios.get(url)
.then(response => {
this.artworks = response.data.names;
})
.catch(e => {
this.errors.push(e)
})
},
methods: {
choose() {
const chosenNumber = Math.floor(Math.random() * this.artworks.length);
this.chosenName = this.artworks[chosenNumber];
console.log(chosenNumber)
}
}
})
I managed to display the random object on click. What I would like to have is the object appearing at page load. I tried to put the function in the mounted cycle but with no good result like this ---> this.choose();
you need to call this.choose() in your mounted/created hook after the promise is fulfilled if you want to make it appear on the page load as well. e.g:
mounted () {
axios.get(url)
.then(response => {
this.artworks = response.data.names;
this.choose()
})
.catch(e => {
this.errors.push(e)
})
}

Nuxt/Vue Route Params update Meta title

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/

VueJs // V-for with v-modal // Data update issue after button clicked

UPDATED!!! Full component's code added:
I've inputs rendered via v-for loop. List of inputs depends on an API response. I need a "plus" and "minus" buttons to change input's value. I almost find a solution but value changes only on sibling input change but not on button click.
Here is my code...
<template>
<div class="allPlaces" id="allPlaces">
<div class="wiget__row wiget__row--top" id="topRow">
<div class="wiget__details wiget__details--allPlaces">
<div class="wiget__logo">
<img class="wiget__img wiget__img--logo" width="28" height="30" src="../../img/kassy_logo_img.png" alt="">
<img class="wiget__img wiget__img--logoTXT" width="59" height="28" src="../../img/kassy_logo_text.png" alt="">
</div>
<div class="wiget__eventDetails">
<p class="wiget__eventName">{{this.show[0].title}}</p>
<div class="wiget__place">
<span class="wiget__venue">{{this.building[0].title}}, </span>
<span class="wiget__venueHall">{{this.hall[0].title}}</span>
</div>
<div class="wiget__dateOfEvent">
<span class="wiget__time">{{this.getTime(this.event[0].date)}}</span>
<span class="wiget__date">{{this.getDate(this.event[0].date)}}</span>
</div>
</div>
</div>
</div>
<div class="allPlaces__wrapper">
<h1 class="allPlaces__title">Оформление заказа</h1>
<p class="allPlaces__description">Для оформления заказа выберите нужное количество мест</p>
<div class="allPlaces__content">
<div class="allPlaces__entrance" v-for="(entrance, index) in places.entrance" :key="entrance.id">
<div class="allPlaces__infoBlock">
<div>
<div class="allPlaces__available">
<span class="allPlaces__label allPlaces__label--places">Доступно мест:</span>
<span class="allPlaces__data">&nbsp{{entrance.vacant_places}}</span>
</div>
<div class="allPlaces__title allPlaces__title--entrance">{{getEntranceName(entrance)}}</div>
</div>
<div class="allPlaces__price">
<span class="allPlaces__label">Цена: </span>
<span class="allPlaces__data">{{entrance.price}}</span>
</div>
</div>
<div class="allPlaces__orderBlock">
<div class="allPlaces__inputBlock">
<input class="allPlaces__input" type="number" name="amount" v-bind:id="tickets"
v-model="tickets[index]" #blur="showLabel($event, index)">
<label class="allPlaces__label allPlaces__label--input"
#click="hideLabel($event, index)">Количество мест
</label>
</div>
<div class="allPlaces__btnBlock">
<button class="allPlaces__btn allPlaces__btn--minus" type="button" name="button"
#click="removeTicket(index)"></button>
<button class="allPlaces__btn allPlaces__btn--plus" type="button" name="button"
#click="addTicket(index)">
</button>
</div>
<button class="allPlaces__btn allPlaces__btn--confirm" type="button" name="button"
#click="addEntrancePlaceToCart(entrance, index)">
<img class="allPlaces__img allPlaces__img--cart" src="../../img/cartWhite.png" alt="Корзина">
<span class="allPlaces__text allPlaces__text--cart">В корзину</span>
</button>
</div>
</div>
</div>
</div>
<div class="wiget__row wiget__row--bottom" id="bottomRow">
<div class="wiget__row">
<div class="wiget__amountBlock">
<span class="wiget__tickets">
<span>Билеты:</span>
<span class="wiget__amount wiget__amount--tickets">{{this.ticketsInCart.count}}</span>
<span>шт.</span>
</span>
<div class="wiget__money">
<span class="wiget__money wiget__money--label">Итого:</span>
<p>
<span class="wiget__amount wiget__amount--money">{{this.ticketsInCart.total}}&nbsp</span>
<span class="wiget__amount wiget__amount--money">руб.</span>
</p>
</div>
</div>
<div class="wiget__btnBlock">
<button class="wiget__btn wiget__btn--goToHall" type="button" name="button"
#click="goToHall()">
Выбрать на схеме
</button>
<button class="wiget__btn wiget__btn--confirm" type="button" name="button"
#click="goToCartPage($event)">Оформить заказ</button>
</div>
</div>
<div class="wiget__row wiget__row--service">
<span class="wiget__service">Сервис предоставлен:</span>
Kassy.ru
</div>
</div>
</div>
</template>
<script>
import vueMethods from '../../mixins/methods'
import { mapState } from 'vuex'
export default {
name: 'allPlaces',
mixins: [vueMethods],
data () {
return {
tickets: []
}
},
mounted () {
this.$nextTick(function () {
window.addEventListener('resize', this.updateAllPlacesOnResize)
this.setupAllPlaces()
})
},
methods: {
setupAllPlaces () {
let allPlaces = document.getElementById('allPlaces')
let topRow = document.querySelector('.wiget__row--top')
let wrapper = document.querySelector('.allPlaces__wrapper')
let bottomRow = document.querySelector('.wiget__row--bottom')
let allPlacesHeight = allPlaces.clientHeight
let topRowHeight = topRow.clientHeight
let bottomRowHeight = bottomRow.clientHeight
let wrapperHeight = allPlacesHeight - topRowHeight - bottomRowHeight
console.log('topRowHeight ', topRowHeight)
console.log('allPlacesHeight ', allPlacesHeight)
console.log('bottomRowHeight ', bottomRowHeight)
console.log('wrapperHeight ', wrapperHeight)
wrapper.style.minHeight = wrapperHeight + 'px'
},
updateAllPlacesOnResize (event) {
this.setupAllPlaces()
},
getEntranceName (entrance) {
let sectionId = entrance.section_id
let section = this.section
let sectionName = section.filter(function (e) {
return e.id === sectionId
})
return sectionName[0].title
},
addTicket (index) {
console.log(this.tickets)
console.log(this.tickets[index])
this.tickets[index] = parseInt(this.tickets[index]) + 1
return this.tickets
},
removeTicket (index) {
this.tickets[index] = parseInt(this.tickets[index]) - 1
},
addEntrancePlaceToCart (entrance, index) {
console.log('entrance.id to add to cart ', entrance.id)
let db = this.db
let places = parseInt(this.tickets[index])
console.log('places ', places)
console.log('index ', index)
let sessionId = this.sessionId
let entranceId = parseInt(entrance.id)
let params = {db, places, sessionId, entranceId}
this.$store.dispatch('addEntrancePlaceToCart', params) // Добавили место в корзину
},
goToHall () {
this.$store.dispatch('closeAllPlaces')
this.$store.dispatch('openHallPlan')
},
hideLabel (e, index) {
console.log('CLICKED')
console.log('index click', index)
let input = document.getElementsByClassName('allPlaces__input')
let target = e.target
input[index].focus()
console.log('this.tickets ', this.tickets)
console.log(target)
if (this.tickets === '') {
target.style.display = 'block'
} else {
target.style.display = 'none'
}
},
showLabel (e, index) {
console.log('BLUR')
console.log('index blur', index)
let label = document.getElementsByClassName('allPlaces__label allPlaces__label--input')
console.log(this.tickets[index])
if (this.tickets[index] === '' || this.tickets[index] === undefined) {
label[index].style.display = 'block'
} else {
label[index].style.display = 'none'
}
}
},
destroyed () {
window.removeEventListener('resize', this.setupAllPlaces)
},
computed: {
...mapState({
db: state => state.onload.currentDb,
currentEvent: state => state.onload.currentEvent,
modals: state => state.modals,
metric: state => state.onload.eventData.metric,
section: state => state.onload.eventData.section,
show: state => state.onload.eventData.show,
event: state => state.onload.eventData.event,
building: state => state.onload.eventData.building,
hall: state => state.onload.eventData.hall,
places: state => state.onload.eventData.places,
placesSeated: state => state.onload.eventData.places.place,
sessionId: state => state.cart.sessionId,
ticketsInCart: state => state.cart.ticketsInCart
})
}
}
</script>
<style scoped lang="stylus">
#import '../../styles/Root/allPlaces'
#import '../../styles/Root/wiget'
</style>
Please advise
You tickets is not correctly initialized. The data populated a empty array but the template will add new element without using the VueJs way of adding reactivity.
What happen is similare to:
let tickets = [];
tickets[0] = ...;
In VueJs you should use push to insert an element to an array and not using the "sparse implementation".
So when your places is being populated inside your store you should create the tickets table from the size of it. Something similare to the following in a watcher or elsewhere depending on your need:
this.tickets = this.place.map(_ => 0);

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.