Scenario:
I am using Nuxt in universal mode. The app works with a headless CMS that provides a simple layout of components that should be rendered, along with component names and their props - something like this:
[
{
"tag": "div",
"class": "col",
"component": "dogs",
"props": {
"dogs": [
{
"id": 1,
"name": "Barky"
},
{
"id": 2,
"name": "Jumpy"
}
]
}
},
{
"tag": "div",
"class": "col",
"component": "cats",
"props": {
"cats": [
{
"id": 1,
"name": "Miouwy"
},
{
"id": 2,
"name": "Fluffy"
}
]
}
}
]
As I understand, I have to apply the components to the DOM before Nuxt makes the "snapshot" and delivers it to the client. My plan was to mount the components in the created() lifecycle - which is, only looking by the names, not the way to go.
The main problem:
I want to mount components dynamically to the DOM, which does not exist yet.
As an example of the thing I want to escape from - mounting the components after Nuxt delivered the snapshot - in the mounted() lifecycle.
<template>
<section class="container">
<div ref="layout-container" class="row"></div>
</section>
</template>
<script>
import { mapGetters } from 'vuex';
import Vue from 'vue';
import Dogs from '#/components/Dogs';
import Cats from '#/components/Cats';
export default {
components: {
Dogs,
Cats
},
fetch({ store }) {
store.dispatch('landing/updateContent');
},
computed: {
...mapGetters({
content: 'landing/content',
})
},
beforeCreate() {
Vue.component('dogs', Dogs);
Vue.component('cats', Cats);
},
mounted() {
this.content.forEach((item) => {
const CompClass = Vue.component(item.component);
const instance = new CompClass({ propsData: item.props }).$mount();
this.$refs['layout-container'].appendChild(instance.$el);
})
}
};
</script>
Big thanks for any directions in advance!
EDIT: Repo with this example: https://github.com/jpedryc/nuxt-test-render
SOLUTION
My main problem was creating a rendered layout and trying to hook it up after the DOM was already delivered to the client. Instead, I should render the component within the virtual Vue DOM - right before the "snapshot" moment.
And that's what I did eventually - not mount a rendered component:
<template>
<section class="container">
<page-component/> <!-- A component without <template>, but with render() -->
</section>
</template>
The PageComponent consists only of:
import ...
export default {
components: {
Dogs,
Cats,
},
beforeCreate() {
Vue.component('dogs', Dogs);
Vue.component('cats', Cats);
},
render: function (h) {
return createDynamicLayout(h);
},
}
The createDynamicLayout(h) is just a simple function that creates a tree of:
return h('div', { 'class': 'row' }, [
h('div', { 'class': 'col' }, h(<somwhere_call_dogs_component>)),
h('div', { 'class': 'col' }, h(<somwhere_call_cats_component>)),
])
Try implementing this with dynamic components
template:
<component :is="item.component" />
script:
components: { cats: Cats, dogs: Dogs },
Then you don't need the direct DOM manipulation hack. Which can't work when Nuxt is running on the server (because you don't have a real DOM on the server) and also prevents the reactivity system from picking up changes in the pets array.
Related
So I have a json file with this structure:
[
{
"id": 1,
"name": "name1",
"img": "pic1.jpg"
}
]
I am making a vue project where after I import it I'm trying to use as the img name in src for my image:
<div v-for="item in countries" :key="item.id">
<img :src="../src/assets/images/`item.img`" alt="">
</div>
But image doesn't appear, rest of the data is fine. How do I get the image?
Home.vue script
<script>
import { useDark, useToggle } from '#vueuse/core'
import jsonData from "../data/country.json"
export default {
name: "Home",
setup() {
const isDark = useDark();
const toggleDark = useToggle(isDark);
console.log(isDark.value);
return {
isDark,
toggleDark
}
},
data: function () {
return {
isSideOpen: false,
drawer: true,
isTheme: false,
listView: false,
show: false,
}
},
data() {
return {
countries: jsonData
}
},
}
</script>
Vite.config.js
import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';
import vue from '#vitejs/plugin-vue'
export default defineConfig({
plugins: [
vue(),
laravel({
input: [ 'resources/js/app.js'], //worls best without css
refresh: true,
}),
],
});
I use i18n to provide multilingual support to an application, I'd like the charts I provide with highcharts to have the same behaviour.
<i18n>
{
"fr": {
"test": "en fançais"
},
"en": {
"test": "in english"
}
}
</i18n>
<template>
<highcharts :options="options"></highcharts>
</template>
<script>
import '#/plugins/highcharts.js'
export default {
data() {
return {
options: {
chart: {
type: 'bar'
},
series: [{
name: $t('test'),
data: [1.0, 0.3, 0],
color: '#beab9d'
}],
xAxis: {
categories: ["1", "2", "3"]
}
}
}
}
}
</script>
But when building the application, I got :
'$t' is not defined
There is a way to access i18n values from the script part of a component ?
When I navigate to my dynamic components trough navigation bar, vue-meta title, content, and schema are displayed correctly, but when I refresh the page or click on the external link I get a value of undefined.
i have stored title content and schema in the json file.
metaInfo() {
return {
title: `${this.seoTitle}`,
meta: [
{name: "robots", content: "index,follow"},
{
name: 'description',
content: `${this.seoContent}`
}
],
link: [
{rel: 'favicon', href: 'logo.ico'}
],
script: [{
type: 'application/ld+json',
json: this.markups
}]
}
},
data() {
return {
seoTitle: this.$route.params.title,
seoContent: this.$route.params.content,
markups:this.$route.params.markup,
}
}
<div class="landing-group-tours box" v-for="tour in boatTours" :key="tour.id">
<router-link
:to="{name: 'details', params:{id: tour.id, title: tour.seoTitle, content: tour.seoContent, markup:tour.markup}}">
</div>
<script>
import tours from '#/data/privateToursData.json'
export default{
data(){
return{
boatTours: tours
{
}
}
</script>
You should store the router parameters in the router itself or in the URL, not the link. What you do is passing objects internally when you click the link, but as you noticed, when you click the browser refresh button these params are gone.
What happens is that Vue will load the app and router, identify what components are responsible for rendering the route and pass the detected parameters from your router to the components. Hence losing any additional data you had in your link before.
Try to keep only the dynamic params in your router and load the rest in the component, based on app logic. I.e. Assuming your route looks like /details/:id, you should initialize the SEO params in your details component.
Typically these come from the backend and for faster access I would transform the array to literal object and access the record by key. I.e. transform the array from:
[
{ "id": 1008; "title": "title1", "content": "....", ... },
{ "id": 1009, "title": "..."... }
]
to
{
"1008": { title: "title1", content: "....", ... },
"1009": { .... }
}
and then store it in VueX (https://vuex.vuejs.org/guide/getters.html)
getters: {
// ...
getBoatTour: (state) => (id) => {
return state.boatTours[id] || { title: 'Not found', content: '......' }
}
}
data() {
const SEOdata = store.getters.getBoatTour(this.$route.params.id);
return {
seoTitle: SEOdata.title,
seoContent: SEOdata.content,
markups: SEOdata.markup,
}
}
I am a beginner in Vue and I want to create a project that uses Chart.js. I am using Vuex and am having a trouble to generate the data into the component.
<script>
import{Line} from 'vue-chartjs';
import {mapGetters,mapActions} from 'vuex';
export default {
extends:Line,
data: () => ({
chartdata: {
labels: ['January', 'February'],
datasets: [
{
label: 'Data One',
backgroundColor: '#f87979',
data: this.currentDeath
}
]
},
options: {
responsive: true,
maintainAspectRatio: false
}
}),
methods:{
...mapActions(["fetchData"]),
},
computed:{
...mapGetters(["currentDeath"])
},
created(){
this.fetchData()
},
mounted(){
const dates = this.
this.renderChart(this.chartData,this.options)
}
}
</script>
<style scoped>
</style>
It says on localhost that "currentDeath is undefined". However if I were to print it on screen it is populated with an array of data. Anyone knows how I can access its data?
I'm using the vue-table-2 component : https://github.com/matfish2/vue-tables-2 and I'm struggling to make it work as I want.
I have an API (using API Platform) which is already making all the pagination works. So when I fetch for the first time my list of companies it gives the first ten results + the total rows. I store all of this in my vuex store and I am able to display the list in my table (with useVuex false or true so I don't really understand how this parameter works). The issue is I cannot paginate because I only got ten results and can't get the total rows count to change so I do not get the pagination element at the bottom and can't bind something to it to fetch the other pages later.
Since I'm pretty new to VueJs I can't figure out how this should work with my API. Here is my code so far:
My DataTable element :
<v-client-table name="company" :columns="columns" :data="companies" :options="options" :theme="theme" id="dataTable">
<b-button slot="actions" slot-scope="props" variant="secondary" size="sm" class="btn-pill">Edit</b-button>
</v-client-table>
And my script :
<script>
import Vue from 'vue'
import { ClientTable, Event } from 'vue-tables-2'
import { mapGetters } from 'vuex'
Vue.use(ClientTable)
export default {
name: 'DataTable',
components: {
ClientTable,
Event,
},
data: function() {
return {
columns: ['name', 'actions'],
options: {
headings: {
name: 'Name',
actions: 'Actions',
},
sortable: ['name'],
filterable: ['name'],
sortIcon: {
base: 'fa',
up: 'fa-sort-asc',
down: 'fa-sort-desc',
is: 'fa-sort',
},
pagination: {
chunk: 5,
edge: false,
nav: 'scroll',
},
},
useVuex: true,
theme: 'bootstrap4',
template: 'default',
}
},
computed: {
...mapGetters({
companies: 'companyModule/companies',
totalCompanies: 'companyModule/totalCompanies',
}),
},
}
</script>
This is in my component loading the data where I specify how many items per page I want my api to send me and the page I want:
created() {
this.$store.dispatch('companyModule/FETCH_COMPANIES', {
page: 1,
nbItemPerPage: 10,
})
},
My store looks like this:
import ApiService from '#/services/APIService'
export const companyModule = {
strict: true,
namespaced: true,
state: {
companies: [],
totalCompanies: 0,
},
getters: {
companies: state => state.companies,
totalCompanies: state => state.totalCompanies,
},
mutations: {
SET_COMPANIES(state, data) {
state.companies = data.companies
state.totalCompanies = data.totalCompanies
},
},
actions: {
FETCH_COMPANIES(context, payload) {
payload.entity = 'companies'
return ApiService.get(payload).then(data => {
context.commit('SET_COMPANIES', data)
})
},
},
}
When I received my data, I stored everything in my companies state and for now I'm storing everything I'm getting from my API and this looks like this :
{
"#id": "/api/admin/companies/1",
"#type": "Company",
"id": 1,
"name": "Test Company",
}
Thanks in advance for your help !
watch: {
'companies': {
handler (newValue, oldValue) {
this.totalRows=this.companies.length;
},
deep: true
}
},
Vue “watch” is a powerful reactive option attribute to the vue framework that helps front-end developers listen to changes made on a value and then react to it.
So, once the totalRows is set, you can assign it to the table attributes and the table will change accordingly.
For my case, I used it for bootstrap table as follows:
<b-pagination
:total-rows="totalRows"
:per-page="perPage"
></b-pagination>