How to properly set a h1 in another component? - vue.js

I'm on the path of learning Vue and i have a question that you might find trivial but i struggle to find an answer. First of all, i'm using Vue with Vue Router, in order to build a test blog.
Let's say the h1 of my page is inside the header component, itself included inside the layout.
But then, how can i set the content of this h1 tag for each page, since it belongs to the header, which is only included in the layout? One solution i found was to set meta.h1 on each route declaration, something like that:
{
path: 'articles',
meta: {
h1: 'All the articles'
}
}
Then, inside the header, i can use a method to get the value of this.$route.meta.h1. Something like that:
methods: {
h1() {
return this.$route.meta.h1
}
}
It works, but it seems kind of wrong, since i don't think that's the job of the route file to handle this. To make it simple, what is the best way, the preferred way, to handle this situation? ty :)
[EDIT]
Here is a simple example page from the bootstrap documentation that could well illustrate my question: https://getbootstrap.com/docs/4.0/examples/blog/
Title of a longer featured blog post
is the h1 and would probably be dynamic, changing from page to page. But it's part of the header component, not part of the articles, of the forum, of the comments or any other component... so if i can reformulate my question, that would be "how can i set this h1 title elegantly" ? Don't think of something complicated, this is a very basic question from a beginner looking toward some answers :p

You can pass props via routes like this, but I'm not sure how you would supply that variable.
I'm not sure of the structure of your app, and if the header component is the header for the entire app, or if it's just a header for each article.. If it's a header just for the article, there is no need to emit events.. (see example below).
[CodePen mirror]
const appHeader = {
template: "#app-header",
computed: {
dynamicHeader() {
let r = this.$route.name;
// caps first letter
let f = r.charAt(0).toUpperCase() + r.slice(1);
switch(f){
case "Home": return f + " Page!";
case "Contacts": return "Contact Us!";
case "About": return f + " Us!!"
}
}
}
};
const contactsPage = {
template: "#contacts-page"
};
const aboutPage = {
template: "#about-page"
};
const homePage = {
template: "#home-page"
};
const routes = [
{
path: "/",
name: "home",
components: {
header: appHeader,
content: homePage
}
},
{
path: "/contact",
name: "contacts",
components: {
header: appHeader,
content: contactsPage
}
},
{
path: "/about",
name: "about",
components: {
header: appHeader,
content: aboutPage
}
}
];
const router = new VueRouter({ routes });
new Vue({
router
}).$mount("#app");
nav.mainNav > * {
padding: 0 0.75rem;
text-decoration: none;
}
nav.mainNav > *:nth-last-child(n+2) {
border-right: 1px solid #aaa;
}
#headerContainer {
background-color: yellow;
margin-bottom: -20px;
}
#page {
border: 1px solid black;
margin: 20px 20px 20px 20px;
}
#page > * {
margin: 10px 10px 10px 10px;
text-align: center;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.10/vue.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue-router/3.0.4/vue-router.min.js"></script>
<div id="app">
<div>
<router-view name="header"></router-view>
<router-view id="page" name="content"></router-view>
</div>
</div>
<!-- -------------------- -->
<!-- APP HEADER COMPONENT -->
<!-- -------------------- -->
<script type="text/x-template" id="app-header">
<div>
<div id="headerContainer">
<h1>{{ dynamicHeader }}</h1>
</div>
<nav class="mainNav">
<router-link
:to="{ name: 'home' }"
>Home</router-link>
<router-link
:to="{ name: 'about' }"
>About</router-link>
<router-link
:to="{ name: 'contacts' }"
>Contacts</router-link>
</nav>
</div>
</script>
<!-- -------------------- -->
<!-- ----------------------- -->
<!-- CONTACTS PAGE COMPONENT -->
<!-- ----------------------- -->
<script type="text/x-template" id="contacts-page">
<div style="background-color:blue;">
<h2>this is the contacts page</h2>
</div>
</script>
<!-- ----------------------- -->
<!-- -------------------- -->
<!-- ABOUT PAGE COMPONENT -->
<!-- -------------------- -->
<script type="text/x-template" id="about-page">
<div style="background-color:green;">
<h2>this is the about page</h2>
</div>
</script>
<!-- -------------------- -->
<!-- ------------------- -->
<!-- HOME PAGE COMPONENT -->
<!-- ------------------- -->
<script type="text/x-template" id="home-page">
<div style="background-color:red;">
<h2>this is the home page</h2>
</div>
</script>

I found a solution that seems to better fit what i requested, which is using an event bus as described in this video:
https://www.youtube.com/watch?v=jzh4zQcfB0o
To sum up what the guy says, the idea is to use another instance of Vue as an event bus in order to emit and listen to events between components not related to each other. This way, i can set the h1 title of my page from any components.

Related

Vue.js different notations

What notation/style/something else does a Vue.js documentation use? It looks like this:
It has var vm = new Vue({ before it
While my Vue looks differently something like this:
<template>
<router-link class="element" :to="{name: 'Home', params: {id: id}}">
<div class="icon"></div>
<div class="operation-type">
<span class="access-type">{{accesstype}}</span>
<span>{{name}}</span>
</div>
</router-link>
</template>
<script>
export default {
name: 'WriteElement',
props: {
write: Object,
},
}
</script>
<style>
.element {
display: block;
text-decoration: none;
}
</style>
I installed Vue through Vue CLI (and it comes with bunch of instruments that I have no idea what they do)

Nuxt - Using router.push inside a component not changing pages correctly

index.vue -
<template>
<div>
<Header />
<div class="container">
<SearchForm />
</div>
</div>
</template>
<script>
const Cookie = process.client ? require('js-cookie') : undefined
export default {
data() {
return {
form: {
email: '',
password: ''
},
show: true
}
},
methods: {
logout() {
// Code will also be required to invalidate the JWT Cookie on external API
Cookie.remove('auth')
this.$store.commit('setAuth', {
auth: null,
user_type: null
})
}
}
}
</script>
<style>
.container {
/* margin: 0 auto; */
/* min-height: 100vh; */
display: flex;
justify-content: center;
align-items: center;
text-align: center;
}
</style>
jobs.vue -
<template>
<div>
<Header />
<SearchForm />
<b-container class="main_container">
<b-row>
<h1> Results for "{{q}}"</h1>
</b-row>
<b-row>
<ul id="array-rendering">
<li v-for="item in results" :key="item.job_id">
{{ item.job_title }}
{{ item.job_city }}
{{ item.job_state }}
{{ item.job_work_remote }}
</li>
</ul>
</b-row>
</b-container>
</div>
</template>
<script>
const Cookie = process.client ? require('js-cookie') : undefined
export default {
// middleware: 'notAuthenticated',
watchQuery: ['q'],
data() {
return {
q: null,
results: []
}
},
async fetch() {
this.q = this.$route.query.q
this.results = await this.$axios.$get('/api/job/search', {
params: {
keyword: this.q,
}
})
},
methods: {
}
}
</script>
<style>
.container {
align-items: center;
text-align: center;
}
</style>
SearchForm.vue component -
<template>
<div id='searchFormDiv'>
<b-form inline #submit.prevent="onSubmit">
<label class="sr-only" for="inline-form-input-name"> keyword</label>
<b-form-input v-model="form.keyword" id="inline-form-input-name" class="mb-2 mr-sm-2 mb-sm-0" placeholder="Job title or keyword" size="lg"></b-form-input>
<label class="sr-only" for="inline-form-input-username">location</label>
<b-input-group class="mb-2 mr-sm-2 mb-sm-0">
<b-form-input v-model="form.location" id="inline-form-input-username" size="lg" placeholder="City, state or zip"></b-form-input>
</b-input-group>
<b-button type="submit" variant="primary">Find Jobs</b-button>
</b-form>
</div>
</template>
<script>
import {
BIconSearch,
BIconGeoAlt
} from 'bootstrap-vue'
export default {
data() {
return {
form: {
keyword: '',
location: ''
}
}
},
created () {
this.form.keyword = this.$route.query.q
},
methods: {
onSubmit() {
this.$router.push({
path: 'jobs',
query: {
q: this.form.keyword
}
});
}
},
components: {
BIconSearch,
BIconGeoAlt
},
}
</script>
<style>
#searchFormDiv {
margin-top: 50px
}
</style>
The route for "http://localhost:3000/" returns the index.vue page.
In this vue page, I have a component with a search form. Once you complete these form and hit the seach button, it will re-direct to a results page
if this.form.keyword = "Data", the next URL will be "http://localhost:3000/jobs?q=Data" and it will be using the jobs.vue page.
The issue I'm running into is the CSS is not being loaded from the jobs.vue page. It's still coming from the index.vue page for some reason. If I refresh the page, then the CSS from jobs.vue is loading. I need the CSS to load from jobs.vue on the initial redirect. All of the query data is working as expected so thats a plus.
However, the following CSS is being applied from index.vue for some reason instead of the CSS from the jobs.vue page -
display: flex;
justify-content: center;
Does anyone know whats going on here? This app is SSR and not SPA.
You have to scope your css from the index.vue page to the other pages with the scoped directive (see docs https://vue-loader.vuejs.org/guide/scoped-css.html)
<style scoped>
/* local styles */
</style>
<style>
/* global styles */
</style>
You can add your global CSS in your layouts/default.vue file.
This solved the issue -
methods: {
onSubmit() {
window.location = 'http://localhost:3000/jobs?q=' + this.form.keyword;
}
},

How to change users mentioned to a html a tag in a post? (VUE-NODE)

I have a body property that contains text that involves #username1 #username2. I want to change where are nicknames #user1 #user2 etc to router-links, its possible to do it with regex?
The final code need to be like this (without the # in link profile)
<router-link :to='/profile/username1'></router-link>
I share the solution in this codepen
CODE:
<template>
<div>
<h3>Original</h3>
<div id="original" class="box">
{{body}}
</div>
<h3>Replaced</h3>
<div id="replaced"
class="box"
v-html="bodyReplaced"
></div>
</div>
</template>
<script>
export default {
data() {
return {
body: 'Hi #jrambo and #jwick I am #cincarnato',
};
},
computed: {
bodyReplaced(){
/* Using string.replace
1 parameter: regex to match
2 parameter: a function that return the new text (the funcion receive the string matched by regular expression)
*/
return this.body.replace(
/#\w+/g,
(user) => '<a href="#" >'+user+'</a>'
)
}
}
};
</script>
<style>
.box{
border: 1px solid grey;
padding: 10px;
margin: 10px;
}
</style>
Use named routes and params for route
<router-link :to="{ name: 'user', params: { username: yourUserNameProp }}"></router-link>
Named routes:
const router = new VueRouter({
routes: [
// dynamic segments start with a colon
{ name:'user', path: '/profile/:username', component: User }
]
})

VueJS: Chrome devtools doesn't instantly update an array (in parent component). Why?

In the following code, I'm trying to get selected users from a list of given users (users array) via checkbox input (defined in a child component) in an array selectedUsers (defined in parent).
The problem is when I check/select the user, devtools doesn't update on first reaction. I've to navigate away from the selected component in devtools and then comeback to see the updated array.
app.vue (parent)
<template>
<div class="container">
<div class="row">
<div v-for="(user,idx) in users" class="col-xs-12 col-md-6 col-md-offset-3">
<child :user="user" :userIdx="idx" :selectedUsers="selectedUsers" /> <span>{{user.name }}</span>
</div>
</div>
</div>
</template>
<script>
import child from './child.vue'
export default {
data: function() {
return{
users: [
{id: 1, name: 'Allen'},
{id: 2, name: 'Jack'},
{id: 3, name: 'Obama'},
{id: 4, name: 'Donald'},
{id: 5, name: 'Winston'},
selectedUsers: []
}
},
components: {
child
}
}
</script>
child.vue (child)
<template>
<span>
<input type="checkbox" :value="user.name" #change="onSelectedUser">
</span>
</template>
<script>
export default {
props: ['user', 'userIdx', 'selectedUsers'],
data(){
},
methods: {
onSelectedUser() {
var idx = this.userIdx
if(event.target.checked) {
this.selectedUsers[idx] = event.target.value
}
if(event.target.checked == false) {
this.selectedUsers.splice(idx, 1)
}
}
}
}
</script>
<style scoped>
input[type="checkbox"] {
width: 20px;
height: 20px;
margin-right: 10px;
border: 3px solid red;
}
input[type="checkbox"]:checked {
width: 30px;
height: 30px;
}
</style>
Thanks
In this line of code, this.selectedUsers[idx] = event.target.value, I'm directly setting the value of an Array which vue is not able to pickup these direct modification to the array.
Excerpt from the below linked page
When you modify an Array by directly setting an index (e.g. arr[0] = val) or modifying its length property. Similarly, Vue.js cannot pickup these changes.
More info VueJS common gotchas - why DOM isn't updating

Vue.js: Known issues with scoped styles and the v-html directive?

I'm just wondering if there is a known issue with scoped styles and the v-html directive? I seem to find that either applying the same styling to the parent or removing the scoped key work from styles everything seems to work ok...?
Example component with the issue (this works with the wordpress API if anyone with a Wordpress site want's to test with their setup):
<template>
<div v-bind:id="posts">
<div v-for="(post, i) in posts" v-bind:key="i">
<div v-html="post.content.rendered"></div>
</div>
</div>
</template>
<script>
export default {
name: "Posts",
props: {
msg: String
},
data() {
return {
posts: null
};
},
created() {
this.$http.get(this.$store.state.endpoint + "posts").then(response => {
this.posts = response.body;
});
}
};
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped lang="scss">
h3 {
margin: 40px 0 0;
}
ul {
list-style-type: none;
padding: 0;
}
li {
display: inline-block;
margin: 0 10px;
}
a {
color: #42b983;
}
p {
color: #42b983; // v-html in this example spits out numerous <p></p> tags...
}
</style>
*for replicating example, you can replace this.$store.state.endpoint with your Wordpress API endpoint, e.g., http://localhost:8000/wp-json/wp/v2 or similar.
From vue-loader docs:
Dynamically Generated Content
DOM content created with v-html are not affected by scoped styles, but you can still style them using deep selectors.
So to style the dynamic content, you should use deep selectors as seen in the following example:
<div class="posts">
<div v-for="(post, i) in posts" v-bind:key="i">
<div v-html="post.content.rendered"></div>
</div>
</div>
...
<style scoped>
.posts >>> p {
color: blue;
}
</style>
demo