How to use a querySelector in Nuxt 3? - javascript

I'm trying to initialise IntersectionObserver in each page of my website built with Nuxt3.
Therefore, I want to access each HTML element that has a specific CSS class. However, on page change, I noticed that via onMounted hook the detected elements are from the previous page.
Here a easy to reproduce example:
app.vue
<template>
<div>
<NuxtPage />
</div>
</template>
pages/index.vue
<script setup lang="ts">
onMounted(() => {
console.group("index.vue");
console.log(document.querySelector("#container"));
console.groupEnd();
});
</script>
<template>
<div id="container">
<h1>INDEX</h1>
<NuxtLink to="/work">
Go to work
</NuxtLink>
</div>
</template>
pages/work.vue
<script setup lang="ts">
onMounted(() => {
console.group("work.vue");
console.log(document.querySelector("#container"));
console.groupEnd();
});
</script>
<template>
<div id="container">
<h1>WORK</h1>
<NuxtLink to="/">
Go to index
</NuxtLink>
</div>
</template>
Simply, the result in the console always come from the previous DOM. Here the steps:
Load the page on index.vue, you see the right element in the console.
Go to work.vue using the link.
See the console showing the exact same result as previously, yet with an added empty class attribute on #container
My question is, why does onMounted hook doesn't show the right DOM on page change?
I tried to set the page transition to the default mode:
definePageMeta({
pageTransition: {
mode: 'default',
},
});
Nothing changed.
NOTE: I am using nuxt version: 3.0.0-rc.9

For this kind of usage, you should use template refs: https://vuejs.org/guide/essentials/template-refs.html#accessing-the-refs
Otherwise, Vue will not behave as expected. More details can be found in this other answer.

Related

(Nuxt) Vue component doesn't show up until page refresh

I'm storing nav items in my Vuex store and iterating over them for conditional output, in the form of a Vue/Bulma component, as follows:
<b-navbar-item
v-for='(obj, token) in $store.state.nav'
v-if='privatePage'
class=nav-link
tag=NuxtLink
:to=token
:key=token
>
{{obj.text}}
</b-navbar-item>
As shown, it should be output only if the component's privatePage data item resolves to true, which it does:
export default {
data: ctx => ({
privatePage: ctx.$store.state.privateRoutes.includes(ctx.$route.name)
})
}
The problem I have is when I run the dev server (with ssr: false) the component doesn't show up initially when I navigate to the page via a NuxtLink tag. If I navigate to the page manually, or refresh it, the component shows.
I've seen this before in Nuxt and am not sure what causes it. Does anyone know?
recommendation :
use mapState and other vuex mapping helper to have more readable code :).
dont use v-for and v-if at the same element
use "nuxt-link" for your tag
use / for to (if your addresses dont have trailing slash)
<template v-if='privatePage'>
<b-navbar-item
v-for='(obj, token) in nav'
class=nav-link
tag="nuxt-link"
:to="token" Or "`/${token}`"
:key="token"
>
{{obj.text}}
</b-navbar-item>
</template>
and in your script :
<script>
import {mapState} from 'vuex'
export default{
data(){
return {
privatePage: false
}
},
computed:{
...mapState(['privateRoutes','nav'])
},
mounted(){
// it's better to use name as a query or params to the $route
this.privatePage = this.privateRoutes.includes(this.$route.name)
}
}
</script>
and finally if it couldn't have help you , I suggest to inspect your page via dev tools and see what is the rendered component in html. it should be an <a> tag with href property. In addition, I think you can add the link address (that work with refresh and not by nuxt link) to your question, because maybe the created href is not true in navbar-item.
NOTE: token is index of nav array . so your url with be for example yourSite.com/1.so it's what you want?
This question has been answered here: https://stackoverflow.com/a/72500720/12747502
In addition, the solution to my problem was a commented part of my HTML that was outside the wrapper div.
Example:
<template>
<!-- <div>THIS CREATES THE PROBLEM</div> -->
<div id='wrapper'> main content here </div>
</template>
Correct way:
<template>
<div id='wrapper'>
<!-- <div>THIS CREATES THE PROBLEM</div> -->
main content here
</div>
</template>

Why do background images load when navigating by anchor link but not by routerLink?

I have a feeling the answer to this question is quite simple, but I cannot find an answer to it anywhere. I am build a very simple app with Vue.js (v2.6.11) that consists of just two pages, a home page and another page with a form. Now, on both of these pages, there are several parallax containers made with MaterializeCSS. If you're unfamiliar, a parallax container is basically just a div with an image as its background, and when the user scrolls, the background image moves at a different rate than the foreground of the site.
My problem is that when I navigate from the home page to the page with the form using a Vue.js link, <router-link :to="{name: "FormPage"}>Form Page</router-link>, the images in the parallax containers DO NOT load. However, if I refresh the page, the images load fine and everything is well. Similarly, If I replace the <router-link> with a simple Form Page the images load fine, everything works as it should.
So my question is this: why don't my parallax container images load on the page navigated to with <router-link></router-link>, but then when I navigate to that same page with a the images do load? In other words, what is <router-link></router-link> doing that prevents my parallax container images from loading??
Any and all feedback would be appreciated. Otherwise, I hope you have a marvelous day, and thank you for you time in reading and or answering my question :)
--Update--
CODE:
This is the component on the homepage that contains the link in question:
<template>
<div id="index-banner" class="parallax-container" style="height: 400px;">
<div class="section no-pad-bot">
<div class="container">
<br><br>
<h1 class="header center white-text">{{ translations.title }}</h1>
<div class="row center">
<h5 class="header col s12 white-text light">{{ translations.subtitle }}</h5>
</div>
<div class="row center">
<!-- <router-link :to="{name: 'Generator', force: true }" class="btn-large waves-effect waves-light teal lighten-1 center-align">{{ buttonText }}</router-link> -->
{{ translations.buttonText }}
</div>
</div>
</div>
<div class="parallax"><img :src="img" alt="Unsplashed background img 1"></div>
</div>
</template>
<script>
export default {
name: 'Banner1',
props: {
translations: String,
img: String
},
}
</script>
<style>
</style>
This is the 'view' that is navigated to, containing the parallax containers in question:
<template>
<div>
<PageTitle
:translations="$t('generatorPage.components.pageTitle')"
img="/imgs/parallax5.jpeg"
/>
<SigForm
:translations="$t('generatorPage.components.sigForm')"
/>
<Separator
img="/imgs/parallax5.jpeg"
/>
</div>
</template>
<script>
import PageTitle from '#/components/banner/parallax/PageTitle'
import Separator from '#/components/banner/parallax/Separator'
import SigForm from '#/components/generator/SigForm'
export default {
name: 'Generator',
components: {
PageTitle,
SigForm,
Separator,
}
}
</script>
<style>
</style>
Here is the 'PageTitle' component with a parallax container:
<template>
<div class="parallax-container form-parallax-container">
<h1 class="white-text center">{{ translations.title }}</h1>
<div class="parallax"><img :src="img"></div>
</div>
</template>
<script>
export default {
name: 'PageTitle',
props: {
translations: String,
img: String
}
}
</script>
<style>
</style>
And here is the 'Separator' with a parallax container:
<template>
<div class="parallax-container form-parallax-container">
<div class="parallax"><img :src="img"></div>
</div>
</template>
<script>
export default {
name: 'Separator',
props: {
img: String
}
}
</script>
<style>
</style>
---UPDATE: Complete repo & sandboxed app---
Someone mentioned it would be eaiser if I provided a sandboxed app or whatever to debug it, so I just made the github repo public and also uploaded it to codesandbox.io. You'll notice that on codesandbox.io, the parallax images aren't even loading at all...on the homepage, on the form page, or upon refresh!
Ok so this is not the answer, but I needed to post some code to illustrate.
I have to confess that I'm not fully au-fait with JS frameworks, but I do know materializecss very well. I've been trying to help another user with a React based problem where the sidenav stops working when the react router is used - until he refreshes the page, when it works fine again.
So here's my hunch:
Parallax is a component that needs initialising like so:
document.addEventListener('DOMContentLoaded', function() {
var elems = document.querySelectorAll('.parallax');
var instances = M.Parallax.init(elems);
});
// Or with jQuery
$(document).ready(function(){
$('.parallax').parallax();
});
If you leave that initialisation code out, it won't work. And, taken from the docs for select fields dynamically added components need reinitialising.
You must initialize the select element as shown below. In addition,
you will need a separate call for any dynamically generated select
elements your page generates.
So my hunch is that when the page is re-rendered via a navigator, we get a new component added to the dom - but this is after the initialisation has already run. So that component is not initialised.
Could be wrong, hope I'm not for your sake more than mine. As I said, I don't know Vue or React very well at all, and my suggestions to run the init at each render didn't work for the React issue - but there are so many moving parts it's hard to rule out.

Vue template isn't rendering in for loop

So after following a beginner Vue tutorial to setup a Todo app, I decided to try to adapt some parts of it for a website I'm trying to make. What I'm stuck on is that despite everything saying my for-loop is supposed to work, it doesn't.
The project itself was created using the vue-cli, and most of the code copy-pasted from the tutorial. (which is working fine with its own for-loop)
It seems like the data might be not passed onto the template maybe?
I have tried:
having the info inside the props and data sections
passing whole object and only parameters to the template
tried with hard-coded values inside array which is iterated on
(After setting up a new vue-cli project:)
App.vue:
<template>
<div id="app">
<create-section v-on:create-section="addSection" />
<section v-for="section in sections" v-bind:key="section.title" :info="section"></section>
</div>
</template>
<script>
import CreateSection from "./components/CreateSection";
import Section from "./components/Section";
export default {
name: "App",
components: {
CreateSection,
Section
},
data() {
return {
sections: []
};
},
methods: {
addSection(section) {
this.sections.push({
title: section.title,
description: section.description
});
console.log(
"Added to sections! : " + section.title + " | " + section.description
);
console.log("Sections length: " + this.sections.length);
}
}
};
</script>
Section.vue
<template>
<div class="ui centered card">
<div class="content">
<div class="header">{{ info.title }}</div>
<div>{{ info.description }}</div>
</div>
</div>
</template>
<script type = "text/javascript" >
export default {
props: {info: Object},
data() {
return {};
}
};
</script>
Expected result:
Display Section template on the website (after creating it with addSection that another script calls. Not included for brevity)
Actual result:
Nothing is displayed, only a empty tag is added
I believe the problem is that you've called it Section. As <section> is a standard HTML element you can't use it as a component name.
There is a warning built into the library but it seems to be case sensitive, which isn't entirely helpful. Try changing your components section to this:
components: {
CreateSection,
section: Section
},
You should then see the warning.
The fix would just be to call it something else.
This is mentioned in the first entry in the style guide:
https://v2.vuejs.org/v2/style-guide/#Multi-word-component-names-essential
section is an existing HTML5 element, you should name your section component something different.
If you really want to name the component Section, register it as 'v-section'
The problem is that when you do the loop in the <section v-for="section in sections" v-bind:key="section.title" :info="section"></section> the Array sections is not ready, there is nothing there.. so when you add new things to this array you need to trigger (computed prop) to send again the data to the section component.
Aside from the issue with using an existing HTML5 command as a name for your Vue component (you should change that to another name by the way), you should also look into how you declared the props within Section.vue. The code below shows the correct way to do it:
<script type = "text/javascript" >
export default {
props: ['info'],
data() {
return {};
}
};
</script>
The props take in the name of the property being declared from the parent component and should be a string.
Hope this helps.

How to use an image as a button in Vue.js?

I'm trying to make a login button as a single-file-component in Vue.js (it's a Rails app with a Vue.js front-end). If you click this button, it's supposed to take you to the an external provider's login page.
How can I use an image as a button? I'm guessing you use v-on:click for the actual redirect, but I'm stuck there.
Right now, this code below shows a hardcoded button that looks like img(src="../assets/img/login_button.png"). You can click on it, but that's obviously not what I want. I want to show the actual png image, not the path.
// LoginButton.vue
<template lang="pug">
#login-button
<button v-on:click="redirect_to_login">img(src="../assets/img/login_button.png")</button>
</template>
<script lang="ts">
import { Vue, Component } from 'vue-property-decorator';
#Component
export default class LoginButton extends Vue{
redirect_to_login():void{ // I haven't written this method yet
}
}
</script>
Is there any reason you can't just use normal HTML image inside your button? I haven't used pug before.
<button v-on:click="redirect_to_login"><img src="../assets/img/login_button.png" /></button
Though since you're using Vue and not an actual HTML form you might not even need a button you could just add the click binding to the image instead
<img src="../assets/img/login_button.png" v-on:click="redirect_to_login" />
I am not familiar with pug, so I don't know what the correct syntax you'll need is. But you can use the <router-link> tag to set the route. For example (using Vuetify)
<router-link to="/">
<v-img src="/path/to/img.gif"/>
</router-link>
Either you can use:
<a #click="Redirect">
<img src='IMAGE_SRC' />
</a>
or
<img #click="Redirect" src='IMAGE_SRC'/>
new Vue({
el: '#app',
methods:
{
Redirect()
{
window.location.href = "https://jsfiddle.net/";
//or
//this.$router.push('LINK_HERE'); // if ur using router
}
}
})
Demo LINK:
https://jsfiddle.net/snxohqa3/5/

How to dynamically populate iron-list elements

So I have an iron-list element for a user's data history. The iron-list is not part of a custom element. It is simply on the page. I want to populate once the user has successfully logged in. Perhaps it is just my inexperience with polymer, but there doesn't seem to be a straightforward way to do this. First attempt (simplified for reading, e.g. I don't actually use jquery, there's lots of error-handling code I'm omitting, etc):
<iron-list as="item" style='height: 100%;' id='history-list'>
<template>
<div style='min-height: 140px;'>
<ul>
<!-- various fields for each record as list items -->
</ul>
</div>
</template>
</iron-list>
<script>
//once user is logged in
var items = $.getJSON('userhistoryscript');
//setAttribute doesn't work either
document.getElementById('history-list').items = items;
</script>
I would swear this worked in an earlier version of Polymer. But it doesn't seem to work now, which is fine, but I need an alternative.
Some alternatives I've considered:
Have iron-ajax element in same DOM scope and set '
the URL once the user is logged in to trigger the
xhr request. I'm not sure whether or not that'd work.
Wrap the list in a custom element and use an
iron-meta-query per chrisW's answer.
Those options are terrible. I cannot believe there is no simpler way to accomplish this feat. How do I conditionally fetch data based on user input and dynamically add an iron-list to the page (or update one that's already there)? Is there really no API for this use case?
UPDATE
Thank you for your answers. Turns out that my original code actually works fine: it was actually a build process issue. For some reason iron-list did not get installed when I installed the project dependencies through bower. I took out the vulcanized import (which must not have contained a ref to iron-list either) and imported all the elements directly, then I got the 404 and figured out what had happened.
I think that for best practices, you should use this.$.historyList to refeer id on this element. Anyway, when you get data to populate iron-listyou should use this.set('items', data); An example using your element looks like:
<iron-list>
<template is="dom-repeat" items="{{data}}" as="history">
<!--history.property-->
</template>
</iron-list>
<script>
Polymer({
properties:{
data:{type:Array, value:[],}
},
_functionToSetDataWhenUserIsLoggedIn: function(data){
this.set('data',data);
}
});
</script>
Edit
An example of iron-list
<template is="dom-bind">
<iron-ajax url="data.json" last-response="{{data}}" auto></iron-ajax>
<iron-list items="[[data]]" as="item">
<template>
<div>
Name: <span>[[item.name]]</span>
</div>
</template>
</iron-list>
</template>
This example is using an ajax call that executes automatically and populates the iron-listwithout the need to create a customized element.
More about iron-list on:
https://elements.polymer-project.org/elements/iron-list
I didn't entirely understand your question. Hope this helps.
<iron-list items="[[data]]" as="item">
<template>
<div>
Name: <span>[[item.name]]</span>
</div>
</template>
</iron-list>
properties:{
data:{type:Array, value:[],}
},
// the attached function is automatically called
attached: function() {
// Use an iron meta in the element that you keep track in of login information
// or create an onLogin listener
var isLoggedIn = new Polymer.IronMetaQuery({key: 'isLoggedIn'}).value,
if (isLoggedIn) {
var jsonData = $.getJSON('userhistoryscript');
this.set('data',jsonData);
}
}
Side note, when access elements by ids in Polymer elements, make sure you do it this way:
this.$.elementId
or
Polymer.dom('#elementId')
Edit since you don't want to create a custom polymer element
Source Code
<template is="dom-bind">
<iron-list id="list">
</iron-list>
</template>
<script>
document.addEventListener('onLogin', function(event) {
var list = document.getElementById('#list');
var jsonDataObjects = $.getJSON('userhistoryscript');
for (var i = 0; i < jsonDataObjects.length; i++) {
var div = document.createElement('div');
div.textContent = jsonDataObjects[i].info; // change this line
list.appendChild(div);
}
});
</script>

Categories

Resources