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

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;
}
},

Related

Vuejs2.6 - Images not loading

I know there are several other questions about it, but none of them seems to help me.
I'm trying to create a simple Image component, but I images are not loading since I tried to use them in a component.
<template>
<!-- <img :click="go()" :src="require(`../assets/img/${filename}`)"> -->
<img :click="go()" :src="`../assets/img/'${filename}`">
</template>
<script>
export default {
name: 'Imagem',
data() {
return {
filename: ''
}
},
methods: {
go() {
console.log('image click')
}
}
}
</script>
<style scoped>
img{
min-width: 20%;
}
</style>
The first line throws error:
The second line, without require, gives no error but don't load the image (i restarted the project and cleaned cache)
This is the view where Imagem component is imported:
<template>
<div class="login-images">
<!-- <img #click="novoProfessor" src="../assets/professor.png" alt="" srcset="">
<img #click="novoAluno" src="../assets/aluno.png" alt="" srcset=""> -->
<Imagem :filename="'professor.png'" />
<Imagem :filename="'aluno.png'" />
</div>
</template>
<script>
import Imagem from '../components/Imagem';
export default {
name: 'Login',
components: {
Imagem
},
props: {
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
.login-images {
min-width: 100%;
min-height: 50%;
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: center;
}
.login-images > * {
margin: 10%;
}
</style>
Change your Imagem component like this:
<template>
<img :src="imageURL" #click="go">
</template>
<script>
export default
{
name: 'Imagem',
props:
{
filename:
{
type: 'String',
required: true
}
},
computed:
{
imageURL()
{
return `../assets/img/${this.filename}`;
}
},
methods:
{
go(event)
{
console.log('You clicked on the "' + event.target.src + '"');
}
}
}
</script>

How to simply navigate from One Component (Home Page ) to Another Component by clicking button using Router concept in Vue.js

I know basics of html, css and js. I have just started learning Vue.js. There is a Home Page in my Vue JS Application which has two buttons. On Click of that button, navigation should happen. (New Component to be loaded). But, in the current code, on button click, navigation is not happening. Please Assist. Copying few file as seen below.
App.vue
<template>
<h3> Home </h3>
<button #click="goToCreate()"> Create Package </button>
<br><br>
<button #click="goToEdit()"> Update Package </button>
</template>
<script>
export default {
name: 'App',
components: {
},
methods:{
goToCreate(){
this.$router.push('/createpackage');
},
goToEdit(){
this.$router.push('/updatepackage');
}
}
}
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
main.js
import { createApp } from 'vue'
import { createRouter, createWebHistory } from 'vue-router'
import App from './App.vue'
import CreatePackage from './components/CreatePackage.vue'
import SearchAndUpdatePackage from './components/SearchAndUpdatePackage.vue'
const router = createRouter({
history: createWebHistory(),
routes:[
{
path : '/createpackage',
component:CreatePackage
},{
path : '/updatepackage',
component:SearchAndUpdatePackage
}
]
})
const app= createApp(App);
app.use(router).mount('#app')
config.js
let baseUrl = ''
if (process.env.NODE_ENV === 'production') {
baseUrl = 'http://yourdomain.com/api/'
}
else {
baseUrl = 'http://localhost:9000/'
}
export const apiHost = baseUrl
CreatePackage.vue
<template>
<div>
<form name="createPackageForm" #submit="submitNewPackage" method="post">
<input type="number" name="noOfPostpaid" placeholder="PostPaid" v-model="posts.noOfPostpaid">
<br> <br> <br>
<input type="number" name="noOfPrepaid" placeholder="PrePaid" v-model="posts.noOfPrepaid">
<br> <br> <br>
<button>Submit</button>
</form>
</div>
</template>
<script>
import { apiHost } from '../config'
import axios from 'axios'
export default {
name:"CreatePackage",
data(){
return{
posts: {
noOfPostpaid:null,
noOfPrepaid:null
}
}
},
methods:{
submitNewPackage(e){
console.warn(apiHost+'tdg/createpackage/'+this.posts.noOfPostpaid+'/'+this.posts.noOfPrepaid);
e.preventDefault();
axios.post(apiHost+'tdg/createpackage/'+this.posts.noOfPostpaid+'/'+this.posts.noOfPrepaid,{
headers: {
"Access-Control-Allow-Origin": "*"
}},null).then(
response => {
console.log(response.data)}
).catch(e => {
console.log(e);
})
this.posts.noOfPostpaid='';
this.posts.noOfPrepaid='';
}
}
}
</script>
SearchAndUpdatePackage.vue
<template>
<div>
<input type="search" name="accountUUID" placeholder="Account UUID" v-model="posts.accountUUID">
<br> <br> <br>
<button #click="searchAccountUUID">Search </button>
<br> <br> <br>
<textarea id="myTextArea" cols=100 rows=20 v-model="posts.responseJSON"></textarea>
</div>
</template>
<script>
import { apiHost } from '../config'
export default {
name:"SearchAndUpdatePackage",
data(){
return{
posts: {
accountUUID:null,
responseJSON:null
},
}
},
methods:{
searchAccountUUID(e){
const url=apiHost+'tdg/carbon/'+this.posts.accountUUID;
console.log(url);
e.preventDefault();
fetch(url).then(response => response.json())
.then(data=>this.posts.responseJSON=JSON.stringify(data,null,4))
.catch(e => {
console.log(e);
})
console.log(this.posts.responseJSON);
}
}
}
</script>
With Vue-Router, you need to use the router-view component. When you navigate to a URL defined in your routes config, Vue-Router will match that URL and display the associated component.
It's common to place it in App.vue:
<template>
<h3> Home </h3>
<button #click="goToCreate()"> Create Package </button>
<br><br>
<button #click="goToEdit()"> Update Package </button>
<router-view />
</template>
<script>
export default {
name: 'App',
methods: {
goToCreate() {
this.$router.push('/createpackage');
},
goToEdit() {
this.$router.push('/updatepackage');
}
}
}
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
You might have an issue with it being a direct descendent of <template>, but I'm not sure.

How to call a method or directive inside interpolated strings?

I want to log the value of the options on change.
The only problem is that I am using it inside string interpolation.
createStickyToolBox() {
const fonts = ['Pacifico', 'VT323', 'Quicksand', 'Inconsolata', 'Times New Roman'];
const options = fonts.map((font) => `<option value="${font}"> ${font} </option>`);
const div = document.createElement('div');
div.id = 'container';
div.innerHTML = `<select id="test" name="form-select" ${options} </select>`;
div.className = 'sticky-toolbox';
div.style = `position: absolute; top: ${topOffset}px; left: ${leftOffset}px;`;
document.getElementById('canvas-wrapper').appendChild(div);
},
In the innerHtml element is the select tag with the options defined above. How can I access the values and log them for example?
One of the super-powers of Vue.js relies on its component-oriented architecture.
While your snippet above could, in theory, work, it doesn't benefit from the advantages that Vue.js has to offer as a framework.
A much cleaner approach, would be to create a Vue.js component:
<template>
<div :style="{ top: topOffset + 'px', left: leftOffset + 'px' }" id="container">
<select id="test" name="form-select">
<option #change="log" v-for="option in options" :value="option">
{{ option }}
</option>
</select>
</div>
</template>
<script>
export default {
props: ['options', 'topOffset', 'leftOffset'];
data() {return {};},
methods: {
log(event) {
console.log(event.target.value);
}
}
};
</script>
<style scoped>
#id {
positon: absolute;
}
</style>
And, in the parent component, use the children as follows:
<template>
<div id="canvas-wrapper">
<button #click="addContainer" type="button"> Add new container </button>
<custom-container v-for="container in containers" :options="container.options" :topOffset="container.topOffset" :leftOffste="container.leftOffset"/>
</div>
</template>
<script>
import Container from './Container.vue';
export default {
components: { Container },
data() {
return {
containers: []
};
},
methods: {
addContainer() {
let newContainer = {
options: ['Pacifico', 'VT323', 'Quicksand', 'Inconsolata', 'Times New Roman'],
topOffset: 5,
leftOffset: 10,
};
this.containers.push(newContainer);
}
}
};
</script>
<style scoped>
#id {
positon: absolute;
}
</style>

Why does Bootstrap Autocomplete send vue.js router go to /#/exclamationmark?

Why does it happen that my Vue router navigates to /#/! without apparent reason?
This seems to happen when I fire an event from an autocomplete form built with Bootstrap Autocomplete and trigger a function.
Calling the same function by clicking a button does not lead to the problem.
This is the parent component where the event is emitted to
<style scoped>
</style>
<template>
<div id="appspace">
<div id="leftbar">
</div>
<div id="workarea">
<div id="mapblock">
</div>
<div id="infoblock">
<div class="form-group"><label for="gotoff">Go to</label>
<autosuggest #locselect="locSelect($event)" id="gotoff"></autosuggest>
</div>
<button v-on:click="searchAround()" type="button" class="btn btn-primary">Search</button>
</div>
</div>
<div id="rightbar">
</div>
</div>
</template>
<script>
module.exports = {
data: function () {
return {
};
},
components: {
autosuggest: httpVueLoader('components/base/autosuggest.vue'),
},
mounted: function(){
},
destroyed: function(){
},
methods: {
setMarkerInCenter: function(){
this.locSelect({ value: { lng: 12, lat: 14 }})
},
locSelect: function(e) {
console.log('locSelect');
console.log(e);
},
},
}
</script>
and this is the component emitting the event:
<style scoped>
.autocomplete {
position: relative;
width: 130px;
}
.autocomplete-results {
padding: 0;
margin: 0;
border: 1px solid #eeeeee;
height: 120px;
overflow: auto;
}
.autocomplete-result {
list-style: none;
text-align: left;
padding: 4px 2px;
cursor: pointer;
}
.autocomplete-result:hover {
background-color: #4AAE9B;
color: white;
}
</style>
<template>
<div class="input-group">
<input ref="ac" class="form-control">
<div class="input-group-append">
<div class="input-group-text"><i class="fa fa-compass" style="height:0.5em;padding:0;margin:0;margin-bottom:4px"></i></div>
</div>
</div>
</template>
<script>
module.exports = {
mounted: function(){
var i = this.$refs.ac;
var c = this
$(i).autoComplete({ resolverSettings: { url: '/api/gc/autocomplete' } });
$(i).on('autocomplete.select', function(e, sel) {
e.preventDefault();
c.$emit('locselect', sel);
e.preventDefault();
});
},
}
</script>
Any leads as to how to debug this?
I couldn't find the reason why this autocomplete changes the route, that behavoir
seems to be undocumented. But here's a method to temporarily prevent this behavior until you find the solution, add this global route guard to your router to prevent navigation to '/#/!' route:
router.js
const router = new VueRouter({ ... })
router.beforeEach((to, from, next) => {
console.log(to)
// TEMP PATCH:
// Autocomplete changes route to '/#/!'
if (to.path !== '/#/!') {
next()
}
})
Just make sure that console.log(to) actually has a property path === '/#/!'

Use method in template, out of instance vue

This is warning when i click on go to contact in tab about: "Property or method "switchTo" is not defined on the instance but referenced during render. Make sure to declare reactive data properties in the data option.
(found in component )."
How do I fix this?
new Vue({
el: '#app',
data: {
currentPage: 'home',
},
methods: {
switchTo: function(page) {
this.currentPage = page;
}
},
components: {
home: {
template: `#home`,
},
about: {
template: `#about`,
},
contact: {
template: '#contact'
}
}
})
.navigation {
margin: 10px 0;
}
.navigation ul {
margin: 0;
padding: 0;
}
.navigation ul li {
display: inline-block;
margin-right: 20px;
}
input, label, button {
display: block
}
input, textarea {
margin-bottom: 10px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.1.10/vue.js"></script>
<div id="app">
<div class="navigation">
<ul>
<li>Home</li>
<li>About</li>
</ul>
</div>
<div class="pages">
<keep-alive>
<component v-bind:is="currentPage">
</component>
</keep-alive>
</div>
</div>
<template id="home">
<p>home</p>
</template>
<template id="about">
<p>about go to contact</p>
</template>
<template id="contact">
<p>contact</p>
</template>
Just change your about template to this
<template id="about">
<p>about go to contact</p>
</template>
new Vue({
el: '#app',
data: {
currentPage: 'home',
},
methods: {
switchTo: function(page) {
this.currentPage = page;
}
},
components: {
home: {
template: `#home`,
},
about: {
template: `#about`,
},
contact: {
template: '#contact'
}
}
})
.navigation {
margin: 10px 0;
}
.navigation ul {
margin: 0;
padding: 0;
}
.navigation ul li {
display: inline-block;
margin-right: 20px;
}
input, label, button {
display: block
}
input, textarea {
margin-bottom: 10px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.1.10/vue.js"></script>
<div id="app">
<div class="navigation">
<ul>
<li>Home</li>
<li>About</li>
</ul>
</div>
<div class="pages">
<keep-alive>
<component v-bind:is="currentPage">
</component>
</keep-alive>
</div>
</div>
<template id="home">
<p>home</p>
</template>
<template id="about">
<p>about go to contact</p>
</template>
<template id="contact">
<p>contact</p>
</template>
I already solved a problem like this in this question: Calling methods in Vue build
It's not the same problem so it's not a repeated question, but the answer is the same:
In the created hook, add the component to window.componentInstance like this:
methods: {
foo () {
console.log('bar')
}
},
created () {
window.componentInstance = this
}
Then you can call the method anywhere like this:
window.componentInstance.foo()