Styles and fonts are loaded after the page has been mounted - javascript

Every time I use Nuxt.js in my projects I encounter the same mistake with styles. Some of them are enabled after the page is mounted. I tried to import styles from a file from a static folder, tried to use styles in the file (tag style), with the scoped attribute, without it, but each time some styles are included after the page has already been mounted. There are no problems in pure Vue js.
Look here, this is the state I was talking about. It lasts about half a second
And after that the page becomes normal
Sometimes this state persists until the user scrolls the page, sometimes it does not disappear at all
To clarify, I prefer the scoped attribute in my projects, without importing files with styles.
Edit
Ok. I don't know will it helps, but there is the code snippets
This is a test page
<template>
<div>
<app-promo
:data="{
titles: ['адреса пиццерий', 'roni napoletana'],
bgImg: 'page-addresses/promo/promo_bg.png'
}"
>
<app-search></app-search>
</app-promo>
</div>
</template>
This is a search component
<template>
<div v-click-outside="closeResults" class="search">
<div class="search__bar">
<input placeholder="Начните вводить название улицы, чтобы найти ближайшую пиццерию" v-model="searchRequest" #keypress.enter="search" type="text" class="search__input">
<button #click="search" class="search__submit"><img src="#/static/img/page-addresses/promo/zoom.svg" alt="Search"></button>
</div>
<div :class="['search__results', {'search__results_active': showResults}]">
<nuxt-link
:to="item.link"
class="search__result"
v-for="item in searchedItems"
:key="item.title"
>
{{ item.title }}
</nuxt-link>
</div>
</div>
</template>
<script>
export default {
data() {
return {
searchedItems: [
{
title: 'Истикбол улица',
link: '/'
},
{
title: 'Шевченко улица',
link: '/'
},
{
title: 'Тараккиёт улица',
link: '/'
}
],
searchRequest: '',
showResults: false
}
},
methods: {
search() {
this.showResults = true
},
closeResults() {
this.showResults = false
}
},
}
</script>
This is a promo component
<template>
<section class="promo">
<div class="promo__bg"><img src="#/static/img/page-main/promo/promo_bg.png" alt=""></div>
<div class="promo__inner">
<h1 class="promo__title">
<template v-for="title in data.titles">
{{ title }}
</template>
</h1>
<h2 class="promo__subtitle" v-if="data.subtitle">{{ data.subtitle }}</h2>
<slot></slot>
</div>
<div class="promo__label" v-if="data.labelImg">
<img src="#/static/img/page-main/promo/label_img.svg" alt="Img">
</div>
</section>
</template>
<script>
export default {
props: {
data: {
default: {
titles: '',
subtitle: '',
labelImg: '',
bgImg: ''
}
}
}
}
</script>
My nuxtconfig is standard. The styles are too.

Related

BlogCards Component doesn't show up on vue

I have a web app which shows the blog posts in a grid. But the BlogCards component in the Home.vue just outputs nothing, whereas it should output the blogs in a grid format. All the datas are stored in firebase. If I go to /blogs, I can see the blogs in grid format, but it doesn't work on the Home.vue. It also spits out the Vue Warn: property or method "blogPostsCards" is not defined on the instance but referenced during render.
I took this code from this tutorial at 5:31:05 minute mark.
Any solution to this problem.
Home.vue
<template>
<div class="home">
<BlogPost :post="post" v-for="(post, index) in blogPostsFeed" :key="index" />
<div class="blog-card-wrap">
<div class="container">
<h3>View more recent blogs</h3>
<div class="blog-cards">
<BlogCards :post="post" v-for="(post, index) in blogPostsCard" :key="index" />
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import BlogPost from '../components/BlogPost.vue'
import BlogCards from '../components/BlogCards.vue'
export default {
name: "Home",
components: {
BlogPost,
BlogCards,
Arrow
},
computed : {
blogPostsCards() {
return this.$store.getters.blogPostsCards;
},
blogPostsFeed() {
return this.$store.getters.blogPostsFeed;
},
}
};
</script>
BlogCards.vue
<template>
<div class="blog-card">
<img :src="post.blogCoverPhoto" alt="">
<div class="info">
<h4>{{ post.blogTitle }}</h4>
<h6>Posted on: {{ new Date(post.blogDate).toLocaleString('en-us', {dateStyle: "long"})}}</h6>
<router-link class="link" to="#" >
View Post <Arrow class="arrow" />
</router-link>
</div>
</div>
</template>
<script>
export default {
name: "blogCard",
props: ["post"],
computed: {
editPost() {
return this.$store.state.editPost
},
}
}
</script>
And getter function in store/index.js
getters:{
blogPostsFeed(state){
return state.blogPosts.slice(0,2);
},
blogPostsCards(state) {
return state.blogPosts.slice(2,6);
},
},
<BlogCards :post="post" v-for="(post, index) in blogPostsCard" :key="index" />
In your Home.vue >> change blogPostsCard to blogPostsCards because you use blogPostsCards in your computed so it gives you that error.

Vue v-if not hiding mobile header

I have this project where I try to add a mobile view menu. This menu is displayed by clicked a button. The button I have created switches a boolean on and off. But when the value of the boolean changes the v:if on the menu doesn't hide it. It keeps on showing.
This is my menu item inside the template:
<template>
<div>
<div> ... main menu ... </div>
<div :v-if="menuOpened" class="bg-purple-primary h-10 z-20">
<p>Hello World</p>
</div>
</div>
</template>
<script>
export default {
name: 'Header',
data () {
return {
menuOpened: false
}
},
methods: {
switchMenuState () {
this.menuOpened = !this.menuOpened
console.log(this.menuOpened)
}
}
}
</script>
You do not need that colon before v-if directive
<template>
<div>
<button #click="switchMenuState()">Switch</button>
<div v-if="menuOpened" class="bg-purple-primary h-10 z-20">
<p>Hello World</p>
</div>
</div>
</template>
<script>
export default {
name: "Header",
data() {
return {
menuOpened: false,
};
},
methods: {
switchMenuState() {
this.menuOpened = !this.menuOpened;
},
},
};
</script>

Vue.js slots - how to retrieve slot content in computed properties

I have a problem with vue.js slots. On one hand I need to display the slot code. On the other hand I need to use it in a textarea to send it to external source.
main.vue
<template>
<div class="main">
<my-code>
<template v-slot:css-code>
#custom-css {
width: 300px
height: 200px;
}
</template>
<template v-slot:html-code>
<ul id="custom-css">
<li> aaa </li>
<li> bbb </li>
<li> ccc </li>
</ul>
</template>
</my-code>
</div>
</template>
my-code.vue
<template>
<div class="my-code">
<!-- display the code -->
<component :is="'style'" :name="codeId"><slot name="css-code"></slot></component>
<slot name="html-code"></slot>
<!-- send the code -->
<form method="post" action="https://my-external-service.com/">
<textarea name="html">{{theHTML}}</textarea>
<textarea name="css">{{theCSS}}</textarea>
<input type="submit">
</form>
</div>
</template>
<script>
export default {
name: 'myCode',
props: {
codeId: String,
},
computed: {
theHTML() {
return this.$slots['html-code']; /* The problem is here, it returns vNodes. */
},
theCSS() {
return this.$slots['css-code'][0].text;
},
}
}
</script>
The issues is that vue doesn't turn the slot content. It's an array of <VNode> elements. Is there a way to use slots inside the textarea. Or a way to retrieve slot content in the theHTML() computed property.
NOTE: I use this component in vuePress.
You need to create a custom component or a custom function to render VNode to html directly. I think that will be the simplest solution.
vnode to html.vue
<script>
export default {
props: ["vnode"],
render(createElement) {
return createElement("template", [this.vnode]);
},
mounted() {
this.$emit(
"html",
[...this.$el.childNodes].map((n) => n.outerHTML).join("\n")
);
},
};
</script>
Then you can use it to your component
template>
<div class="my-code">
<!-- display the code -->
<component :is="'style'" :name="codeId"
><slot name="css-code"></slot
></component>
<slot name="html-code"></slot>
<!-- send the code -->
<Vnode :vnode="theHTML" #html="html = $event" />
<form method="post" action="https://my-external-service.com/">
<textarea name="html" v-model="html"></textarea>
<textarea name="css" v-model="theCSS"></textarea>
<input type="submit" />
</form>
</div>
</template>
<script>
import Vnode from "./vnode-to-html";
export default {
name: "myCode",
components: {
Vnode,
},
props: {
codeId: String,
},
data() {
return {
html: "", // add this property to get the plain HTML
};
},
computed: {
theHTML() {
return this.$slots[
"html-code"
]
},
theCSS() {
return this.$slots["css-code"][0].text;
},
},
};
</script>
this thread might help How to pass html template as props to Vue component

Popup does not open in App.vue when clicking on a search result

My goal is when clicking on any search item to open the Popup.
Why does not work — this.$emit('openPopup', bookId); in the method selectBook(bookId)
There is a component of Results.vue, which displays search results by using Google Books API:
<template>
<div class="results">
<ul class="results-items">
<li
class="book"
#click="selectBook(result.id)"
>
<img
:src="'http://books.google.com/books/content?id=' + result.id + '&printsec=frontcover&img=1&zoom=1&source=gbs_api'"
class="cover"
>
<div class="item-info">
<div class="bTitle">{{ result.volumeInfo.title }}</div>
<div
class="bAutors"
v-if="result.volumeInfo.authors"
>
{{ result.volumeInfo.authors[0] }}
</div>
</div>
</li>
</ul>
</div>
</template>
<script>
export default {
props: ['result'],
data() {
return {
bookId: '',
};
},
methods: {
selectBook(bookId) {
this.$router.push(`bookId${bookId}`);
this.$store.dispatch('selectBook', bookId);
this.$emit('hideElements', bookId);
this.$emit('openPopup', bookId);
},
},
};
</script>
Rusults screenshot
Is the main App.vue, which I want to display Popup:
<template>
<div id="app">
<div class="container">
<Header />
<popup
v-if="isOpenPopup"
#closePopup="closeInfoPopup"
#openPopup="showModal"
/>
<router-view/>
</div>
</div>
</template>
<script>
import Header from '#/components/Header.vue';
import Popup from '#/components/Popup.vue';
import 'bootstrap/dist/css/bootstrap.css';
export default {
components: {
Header,
Popup,
},
data() {
return {
isOpenPopup: false,
};
},
methods: {
showModal() {
console.log('click');
this.isOpenPopup = true;
},
closeInfoPopup() {
this.isOpenPopup = false;
},
},
};
</script>
The component Popup.vue
<template>
<div class="popup">
<div class="popup_header">
<span>Popup name</span>
</div>
<div class="popup_content">
<h1>Hi</h1>
<slot></slot>
<button #click="closePopup">Close</button>
</div>
</div>
</template>
<script>
export default {
name: 'popup',
props: {},
data() {
return {};
},
methods: {
closePopup() {
this.$emit('closePopup');
},
},
};
</script>
You are listening on popup component but triggering events on Result
Don't remember that events are bound to component.
You have several options how to handle it
Add event listeners (like #openPopup) only on Result component and use portal-vue library to render popup on top level
use global events, this.$root.emit and listen also on this.$root. In this case you add event listener in mount() hook and don't forget unregister it in beforeDestroy() hook
you can use Vuex and popup visibility and related data in global application store provided by Vuex

Is there a vue.js equivalent of ngTemplateOutlet?

Does vue.js have an equivalent of Angular's *ngTemplateOutlet directive? Let's say I have some components defined like this:
<template>
<div id="independentComponent">
Hello, {{firstName}}!
</div>
</template>
<script>
export default {
name: "independentComponent",
props: ['firstName']
}
</script>
...
<template>
<div id="someChildComponent">
<slot></slot>
<span>Let's get started.</span>
</div>
</template>
<script>
export default {
name: "someChildComponent"
}
</script>
I want to be able to do something like this:
<template>
<div id="parentComponent">
<template #indepdentInstance>
<independentComponent :firstName="firstName" />
</template>
<someChildComponent>
<template #indepdentInstance></template>
</someChildComponent>
</div>
</template>
<script>
export default {
name: "parentComponent",
components: {
someChildComponent,
independentComponent
},
data() {
return {
firstName: "Bob"
}
}
}
</script>
In Angular, I could accomplish this with
<div id="parentComponent">
<someChildComponent>
<ng-container *ngTemplateOutlet="independentInstance"></ng-container>
</someChildComponent>
<ng-template #independentInstance>
<independentComponent [firstName]="firstName"></independentComponent>
</ng-template>
</div>
But it looks like Vue requires the element to be written to the DOM exactly where it is in the template. Is there any way to reference an element inline and use that to pass to another component as a slot?
You cannot reuse templates like ngTemplateOutlet, but can combine idea of $refs, v-pre and runtime template compiling with v-runtime-template to achieve this.
First, create reusable template (<ng-template #independentInstance>):
<div ref="independentInstance" v-show="false">
<template v-pre> <!-- v-pre disable compiling content of template -->
<div> <!-- We need this div, because only one root element allowed in templates -->
<h2>Reusable template</h2>
<input type="text" v-model="testContext.readWriteVar">
<input type="text" v-model="readOnlyVar">
<progress-bar></progress-bar>
</div>
</template>
</div>
Now, you can reuse independentInstance template:
<v-runtime-template
:template="$refs.independentInstance.innerHTML"
v-if="$refs.independentInstance">
</v-runtime-template>
But keep in mind that you cannot modify readOnlyVar from inside independentInstancetemplate - vue will warn you with:
[Vue warn]: Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value. Prop being mutated: "readOnlyVar"
But you can wrap it in object and it will work:
#Component({
components: {
VRuntimeTemplate
}
})
export default class ObjectList extends Vue {
reusableContext = {
readWriteVar: '...'
};
readOnlyVar = '...';
}
You could try Portal vue written by LinusBorg a core Vue team member.
PortalVue is a set of two components that allow you to render a
component's template (or a part of it) anywhere in the document - even
outside the part controlled by your Vue App!
Sample code:
<template>
<div id="parentComponent">
<portal to="independentInstance">
<!-- This slot content will be rendered wherever the <portal-target>
with name 'independentInstance' is located. -->
<independent-component :first-name="firstName" />
</portal>
<some-child-component>
<portal-target name="independentInstance">
<!--
This component can be located anywhere in your App.
The slot content of the above portal component will be rendered here.
-->
</portal-target>
</some-child-component>
</div>
</template>
There is also a vue-simple-portal written by the same author that is smaller but that mounts the component to end of body element.
My answer from #NekitoSP gave me an idea for a solution. I have implemented the sample below. It worked for me. Perhaps you want to use it as a custom component with props.
keywords: #named #template #vue
<template>
<div class="container">
<div ref="templateRef" v-if="false">write here your template content and add v-if for hide in current place</div>
....some other contents goes here
<p v-html="getTemplate('templateRef')"></p>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
Vue.extend({
methods:{
getTemplate(tempRef){
return this.$refs[tempRef].innerHTML
}
}
})
</script>
X-Templates
Use an x-template. Define a script tag inside the index.html file.
The x-template then can be referenced in multiple components within the template definition as #my-template.
Run the snippet for an example.
See the Vue.js doc more information about x-templates.
Vue.component('my-firstname', {
template: '#my-template',
data() {
return {
label: 'First name'
}
}
});
Vue.component('my-lastname', {
template: '#my-template',
data() {
return {
label: 'Last name'
}
}
});
new Vue({
el: '#app'
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<my-firstname></my-firstname>
<my-lastname></my-lastname>
</div>
<script type="text/x-template" id="my-template">
<div>
<label>{{ label }}</label>
<input />
</div>
</script>
Not really sure i understand your problem here, but i'll try to give you something that i will opt to do if i want to add two components in one template.
HeaderSection.vue
<template>
<div id="header_id" :style="'background:'+my_color">
welcome to my blog
</div>
</template>
<script>
export default {
props: ['my_color']
}
</script>
BodySection.vue
<template>
<div id="body_id">
body section here
</div>
</template>
<script>
export default {
}
</script>
home.vue
<template>
<div id="parentComponent">
<header-section :color="my_color" />
<body-section />
</div>
</template>
<script>
import HeaderSection from "./components/HeaderSection.vue"
import BodySection from "./components/BodySection.vue"
export default {
name: "home",
components: {
HeaderSection,
BodySection
},
data() {
return {
my_color: "red"
}
}
}
</script>

Categories