I have a 'Documentation' page which has 3 sub-menus in it.
What I'm trying to do is no matter which sub-menu is clicked I want it to go to one main documentation Vue component and then pass a variable into it so a sub-component also loads in the main component.
This is what I have so far:
Nav.vue
<template>
<div>
<nav class="navbar navbar-expand-lg">
<ul class="navbar-nav ml-auto h-100 d-flex align-items-center" style="margin-right: 5rem">
<div class="nav-item dropdown" #mouseover="openDocDropDown" #mouseleave="closeDocDropDown">
<div ref="docDropdown" class="d-flex align-items-center" role="button" data-toggle="dropdown" style="height: 42px; cursor: default">
<i class="far fa-file-alt" style="font-size: 1.9rem" />
Documentation
<i class="ml-2" :class="[docCollapsed ? 'fa-caret-down' : 'fa-caret-up', 'fas']" />
</div>
<div v-if="!docCollapsed" class="dropdown-menu subMenus" aria-labelledby="navbarDropdown">
<router-link to="/documentation" tag="div" class="nav-item d-flex align-items-center p-3" #click.native="docMenuClicked('admin')">
<i class="far fa-file-pdf" style="font-size: 1.9rem" />
<span class="pt-1 pl-1">Administrator documentation</span>
</router-link>
<router-link to="/documentation" tag="div" class="nav-item d-flex align-items-center p-3" #click.native="docMenuClicked('reseller_channel')">
<i class="far fa-file-pdf" style="font-size: 1.9rem" />
<span class="pt-1 pl-1">Reseller/Channel documentation</span>
</router-link>
<router-link to="/documentation" tag="div" class="nav-item d-flex align-items-center p-3" #click.native="docMenuClicked('cust')">
<i class="far fa-file-pdf" style="font-size: 1.9rem" />
<span class="pt-1 pl-1">Customer documentation</span>
</router-link>
</div>
</div>
<router-link to="/contactus" tag="li" :class="[currentPage.includes('/conactus/') ? 'router-link-exact-active router-link-active' : '', 'nav-item mainMenuLink d-flex align-items-center']">
<i class="d-none d-xl-inline icon-nav_contact_us iconSize" /> Contact us
</router-link>
</ul>
</nav>
</div>
</template>
<script>
export default {
name: "TopNav",
props: [ 'hasErrored' ],
data() {
return {
unCollapsed: true,
docCollapsed: true,
docMenuItemClicked: ''
}
},
computed: {
...mapGetters({
user: 'user'
}),
currentPage() {
return this.$route.path;
}
},
methods: {
openUnDropDown() {
this.$refs.unDropdown.visible = true;
this.unCollapsed = false;
},
closeUnDropDown() {
this.$refs.unDropdown.visible = false;
this.unCollapsed = true;
},
openDocDropDown() {
this.$refs.docDropdown.visible = true;
this.docCollapsed = false;
},
closeDocDropDown() {
this.$refs.docDropdown.visible = false;
this.docCollapsed = true;
},
docMenuClicked(data) {
this.docMenuItemClicked = data;
setTimeout(() => {
VueEvent.$emit('document-menu-clicked', { menuClicked: this.docMenuItemClicked });
this.docCollapsed = true;
}, 1)
}
}
}
</script>
DocumentationMain.vue
<template>
<div>
Main Document Page
{{ menuClicked }}
<ResellChanDocs v-if="menuClicked === 'reseller_channel'" />
<CustDocs v-else-if="menuClicked === 'cust'" />
<AdminDocs v-else />
</div>
</template>
<script>
import ResellChanDocs from './DocumentationResellerChannel';
import CustDocs from './DocumentationCustomer';
import AdminDocs from './DocumentationAdministrator';
export default {
name: 'MainDocumentation',
components: {
ResellChanDocs,
CustDocs,
AdminDocs
},
data() {
return {
menuClicked: 'admin'
}
},
beforeMount() {
VueEvent.$on('document-menu-clicked', (data) => {
this.menuClicked = data.menuClicked;
});
}
}
</script>
routes.js
import Home from './components/Home.vue';
import ContactUs from './components/ContactUs';
import Documentation from './components/DocumentationMain';
export const routes = [
{ path: '', component: Home },
{ path: '/contactus', component: ContactUs },
{ path: '/documentation', component: Documentation, props: true },
{ path: '*', redirect: '/' }
];
Gives
Issues
Is there an easier way of doing this?
When I click a menu my 'DocumenationMain' doesn't seem to register the VueEvent.$emit
When I click a link the second time, the correct component is rendered
When I click a link, all my other menus highlight blue
I get the feeling I'm doing this wrong
Just use Dynamic Route Matching
export const routes = [
{ path: '', component: Home },
{ path: '/contactus', component: ContactUs },
{ path: '/documentation/:doctype', component: Documentation, props: true },
{ path: '*', redirect: '/' }
];
nav.vue
<router-link to="/documentation/reseller" />
<router-link to="/documentation/cust" />
<router-link to="/documentation/admin" />
Documentation.vue
props: {
doctype: {
type: String,
default: 'admin'
}
}
Router should now pass :doctype value (from the URL) into Documentation component doctype prop - use it to decide what sub-component to activate inside...
Related
Ok...king..
I'm trying to pass a value to child when a click event occur from other component it will change the prop value from parent. But it only shown the first mount value that pass.
topdown component that emit change
<template>
<div class="dropdown">
<button
class="btn btn-secondary dropdown-toggle h-75"
type="button"
id="dropdownMenuButton1"
data-bs-toggle="dropdown"
aria-expanded="false"
>
{{ value }}
</button>
<ul class="dropdown-menu" aria-labelledby="dropdownMenuButton1" role="menu">
<li v-for="option in options" :key="option">
<a
class="dropdown-item"
#click="(value = option);dunder;dundet;"
href="javascript:void(0)"
>{{ option }}</a
>
</li>
</ul>
</div>
</template>
<script>
export default {
name: "TopDown",
data() {
return {
options: ["Edit", "Delete"],
value: "",
};
},
computed:{
dunder(){
return this.$emit("edit-task", this.value)
},
dundet(){
return this.$emit("edit-task-index",this.value)
}
}
};
</script>
<style>
</style>
parent component
<template>
<div
v-for="(item, index) in tasker"
:items="item"
:key="index"
class="border border-dark "
>
<section class="d-flex justify-content-between">
<h4 class="w-50 font-weight-bold fs-5">
<u>{{ item.title }}</u>
</h4>
<TopDown
#edit-task="val"
#edit-task-of="show(item, index)"
:index="index"
/>
</section>
<p class="text-start">{{ item.description }}</p>
<GoTask :showVal="showVal" :bool="bool" />
</div>
</template>
<script>
import TopDown from "#/components/TopDown.vue";
import GoTask from "#/components/GoTask.vue";
export default {
inheritAttrs: false,
components: {
TopDown,
GoTask,
},
data() {
return {
takss: {
items: "sss",
index: "",
},
showVal: "",
bool: false,
};
},
name: "Taski",
props: ["tasker"],
methods: {
show(item, index) {
this.takss.items = item;
this.takss.index = index;
},
val(val) {
if (val == "Edit") {
setTimeout(() => {
console.log(this.takss);
this.showval = this.takss;
console.log(this.showval);
console.log(this.bool)
}, 1000);
this.bool = !this.bool;
}
},
},
};
</script>
<style></style>
child component
<template>
<section
v-if="bools"
class="bg-white"
style="z-index: 20"
#click="closeModal"
></section>
<section
v-if="bools"
class="position-absolute"
style="
z-index: 50;
left: 50%;
top: 50%;
height: 100vh;
margin-top: 20vh;
"
>
<form
class="mt-10"
#submit.prevent="editTask"
>
<input
class="border rounded p-2 mb-2"
v-model.lazy="newTask.title"
placeholder="Title"
type="text"
/>
<textarea
ref="texsearch"
rows="20"
class=" p-2 mb-2"
v-model.lazy="newTask.description"
placeholder="Task Details"
type="text"
></textarea>
<button
class="border rounded p-2 bg-success text-white"
type="submit"
#submit.prevent=""
>
New Task
</button>
</form>
</section>
<button #click="test">dd</button>
</template>
<script>
export default {
inheritAttrs: false,
name: "GoTask",
props: ["tasker", "showVal", "bool"],
data() {
return {
showVals: this.showVal,
bools: this.bool,
newTask: {
title: "",
description: "",
},
};
},
methods: {
test() {
console.log(this.bools);
console.log(this.showVal);
},
ModalOpen() {
this.bools = true;
},
closeModal() {
this.bools = false;
},
showModal() {
this.bools = true;
// auto focus
this.$nextTick(() => {
this.$refs.textsearch.focus();
});
},
showtheVal() {
console.log(this.showtheVal);
},
},
};
</script>
<style></style>
When I click the button form other component that emit change of #edit-task-of and #edit-task the it doesn't send the new value of bool and showval to child component the bool value still false as same as first mount and showVal = "" when click button that run test function to see this two value at child. I'm totally new to vue. Thanks
You do mistake in GoTask component , method test, first console.log(this.bools);, bools is local state which is not updated after prop bool changed, just do correct console.log(this.bool);. You don't need to set props to local state, you can use props directly.
GoTask correct example:
props: ["tasker", "showVal", "bool"],
data() {
return {
newTask: {
title: "",
description: "",
},
};
},
methods: {
test() {
console.log(this.bool);
console.log(this.showVal);
},
codesandbox
I am a beginner in VueJS and I am facing a problem.
Here in main.js I pass the user variable to App.vue via a props.
By default its value is {}
In main.js the getLoginStatus() method listens to the firebase authentication status, when a user logs in or out main.js.this.user changes.
user in main.js is passed to App.vue thanks to template, but when it changes as a result of the getLoginStatus() call it is not changed in App.vue (the child), whereas in main.js it does change.
How can this be done?
Thanks
My main.js :
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import firebase from "firebase";
Vue.config.productionTip = false
var firebaseConfig = {
CONFIGURATION
};
// Initialize Firebase
firebase.initializeApp(firebaseConfig);
firebase.analytics();
new Vue({
el: '#app',
router,
template: '<App :user="user" ></App>',
components: { App },
data(){
return {
user : {}
}
},
methods:{
getLoginStatus(){
firebase.auth().onAuthStateChanged(function(u) {
if (u) {
this.user = u
console.log("// User is signed in by Phone Number : ", u.phoneNumber)
} else {
this.user = null
console.log("// No user is signed in.")
}
});
console.log("this.user new value : "+this.user);
},
},
updated(){
this.getLoginStatus()
},
created(){
this.getLoginStatus()
}
})
App.vue :
<template>
<div id="app">
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<a class="navbar-brand" href="#">Navbar</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav">
<li class="nav-item">
<router-link class="nav-link" to="/">Home</router-link>
</li>
<li class="nav-item">
<router-link class="nav-link" to="/register">Register/About old</router-link>
</li>
<li class="nav-item">
<router-link class="nav-link" to="/dashboard">Dashboard</router-link>
</li>
<li class="nav-item">
<button class="nav-link btn" #click="logout">Logout</button>
</li>
</ul>
</div>
</nav>
<div class="container mt-5">
<router-view />
</div>
<p v-if="user">logged{{user}}</p>
<p v-else>not logged{{user}}</p>
</div>
</template>
<script>
import firebase from 'firebase'
export default {
name: 'app',
props: ['user'],
methods:{
logout() {
firebase
.auth()
.signOut()
.then(() => {
alert('Successfully logged out');
if(this.$router.currentRoute.path!='/'){
this.$router.push('/')
}
})
.catch(error => {
alert(error.message);
if(this.$router.currentRoute.path!='/'){
this.$router.push('/')
}
});
},
},
}
</script>
You're using an unbound function here:
firebase.auth().onAuthStateChanged(function(u) {
So when setting this.user inside this function, this is not pointing to your Vue instance. You can either .bind(this) your function or just use an arrow function which will auto bind:
firebase.auth().onAuthStateChanged(u => {
I am new to Vue. I am building a simple app that will list all countries and when you click on a particular country it shows you more details about the country. Idea is to open country details in a modal.
I'm stuck with displaying that modal. The modal opens, but in the background. It also opens a detail page.
CountryDetail.vue:
<script>
import axios from 'axios';
export default {
name: 'country-detail',
props: [ 'isDarkTheme' ],
data () {
return {
pending: false,
error: null,
countryInfo: null,
alpha3Code: [],
alpha3CodetoString: [],
}
},
mounted () {
this.pending = true;
axios
.get(`https://restcountries.eu/rest/v2/name/${this.$route.params.country}?fullText=true`)
.then((response) => {
(this.countryInfo = response.data)
this.alpha3CodetoString = this.alpha3Code.join(';');
})
.catch(error => (this.error = error ))
.finally( () => { this.pending = false });
},
filters: {
formatNumbers (value) {
return `${value.toLocaleString()}`
}
}
}
</script>
<template>
<modal v-model="show">
<div class="modal-mask" :class="{ darkTheme : isDarkTheme }" name="modal">
<div class="modal-wrapper">
<div class="modal-container">
<div class="modal-header">
<slot name="header">
<h1 v-if="error !== null">Sorry, an error has occurred {{error}}</h1>
<div class="loaderFlex"><div v-if="pending" class="loader"></div></div>
</slot>
</div>
<div v-for="country in countryInfo" class="countryTile modal-body" v-bind:key="country.id">
<slot name="body">
<img v-bind:src="country.flag" alt="Country Flag" class="flag">
<div class="country-details">
<h1>{{country.name}}</h1>
<div class="listDiv">
<ul>
<li><span>Population:</span> {{country.population | formatNumbers }}</li>
<li><span>Capital:</span> {{country.capital}}</li>
<li><span>Iso:</span> {{country.alpha3Code}}</li>
</ul>
<ul>
<li><span>Currencies:</span> {{country.currencies['0'].name}}</li>
<li><span>Languages:</span>
<span
v-for="(language, index) in country.languages"
v-bind:key="index"
class="languages">
{{language.name}}<span v-if="index + 1 < country.languages.length">, </span>
</span>
</li>
</ul>
</div>
</div>
</slot>
</div>
<div class="modal-footer">
<slot name="footer">
<a #click="$router.go(-1)" class="backBtn"><i class="fas fa-arrow-left" />Go Back</a>
</slot>
</div>
</div>
</div>
</div>
</modal>
</template>
Home.vue:
<script>
import axios from 'axios';
export default {
name: 'home',
props: [ 'isDarkTheme' ],
data () {
return {
pending: false,
error: null,
countryInfo: null,
search: '',
darkMode: false,
}
},
mounted () {
this.pending = true;
axios
.get('https://restcountries.eu/rest/v2/all')
.then(response => (this.countryInfo = response.data))
.catch(error => (this.error = error ))
.finally( () => { this.pending = false });
},
filters: {
formatNumbers (value) {
return `${value.toLocaleString()}`
}
},
computed: {
filteredCountries: function () {
return this.countryInfo.filter((country) => {
if (this.region === '' ) {
return country.name.toLowerCase().match(this.search.toLowerCase());
} else if (this.search !== '') {
return country.name.toLowerCase().match(this.search.toLowerCase());
} else {
return ('blbla');
}
})
}
},
}
</script>
<template>
<div class="home" :class="{ darkTheme : isDarkTheme }">
<div class="searchBar">
<div class="searchContainer">
<i class="fas fa-search searchIcon"></i>
<input
class="searchInput"
type="text"
v-model="search"
aria-label="Search for a country..."
placeholder="Search for a country..."
/>
<ul class="searchResults"></ul>
</div>
</div>
<h1 v-if="error !== null">Sorry, an error has occurred {{error}}</h1>
<div class="loaderFlex"><div v-if="pending" class="loader"></div></div>
<div v-if="countryInfo" class="tileGrid" #click="showModal = true">
<div v-for="country in filteredCountries" class="countryTile" v-bind:key="country.id">
<router-link
:to="{ name: 'country-detail', params: {country: country.name }}"
class="linkTile"
>
<img v-bind:src="country.flag" alt="Country Flag" class="flag">
<div class="text">
<h1>{{ country.name }}</h1>
</div>
</router-link>
</div>
</div>
</div>
</template>
The router-link will always redirect you to another page, because its basically <a href="..."> see here. You don't need router if you just want to show the detail on a modal, you could just add the modal component inside the Home.vue component, then bind the modal and the countryName with props, then pass them in when clicking a button.
Home.vue:
<template>
<div>
<button #click="showDetail">
Show Detail
</button>
<CountryDetail :countryName="countryName" :showModal="showModal"/>
<div>
</template>
<script>
import CountryDetail from './CountryDetail.vue'
export default {
name: 'Home',
components: { CountryDetail },
data: () => ({
countryName: '',
showModal: false,
}),
methods: {
showDetail() {
this.showModal = true;
},
},
}
</script>
And instead of making request on mounted, you could use watch to do something like watching for the showModal prop, and make request everytime it has a truthy value. Like this:
CountryDetail.vue:
<template>
<modal v-model="showModal">
<!-- modal content -->
</modal>
</template>
<script>
export default {
name: 'CountryDetail',
props: ['countryName', 'showModal'],
watch: {
'showModal': {
deep: true,
handler(val) {
if (val && this.countryName !== '') {
// Make request
}
}
}
}
}
</script>
I am putting together a SPA using Vue with VueRouter. The site works and I am able to click the link and get to the menu items. I see in the url that the # still preceedes the link name. For example, if I click on Services in the menu, the url states https://sitename.com/#services
I am using mode: 'history' which is supposed to take care of this but, I believe I am missing something. If anyone can look over my code and help me out with this, I would sure appreciate it. Thank you.
In my root directory, I have the router.js
import Vue from 'vue';
import VueRouter from 'vue-router';
Vue.use(VueRouter);
function load(componentName) {
return () => import(`#/components/${componentName}`);
}
const router = new VueRouter({
mode: 'history',
routes: [
{
path: '/',
name: 'Home',
component: load('Home')
}
,
{
path: '/services',
name: 'Services',
component: load('Services')
},
{
path: '/portfolio',
name: 'Portfolio',
component: load('Portfolio')
},
{
path: '/about',
name: 'About',
component: load('About')
},
{
path: '/team',
name: 'Team',
component: load('Team')
},
{
path: '/contact',
name: 'Contact',
component: load('Contact')
},
{
path: '/privacy',
name: 'Privacy',
component: load('Privacy')
},
{
path: '/success',
name: 'Success',
component: load('Success')
}
]
});
export default router;
in my source directory and within components, I have the Nav.vue component
You can see I am using a conditional that says if the index of 0 is # etc. Which I thought was supposed to eliminate the hash but it does not.
Nav.vue
<template>
<div>
<nav class="navbar navbar-expand-lg navbar-dark fixed-top" id="mainNav">
<div class="container">
<a class="navbar-brand js-scroll-trigger" href="/">
<span class="first-r">R</span>
<span class="second-r">R</span>
<span class="spark">Spark</span>
</a>
<button
class="navbar-toggler navbar-toggler-right"
type="button"
data-toggle="collapse"
data-target="#navbarResponsive"
aria-controls="navbarResponsive"
aria-expanded="false"
aria-label="Toggle navigation"
>
Menu
<i class="fas fa-bars"></i>
</button>
<div class="collapse navbar-collapse" id="navbarResponsive">
<ul class="navbar-nav text-uppercase ml-auto">
<li class="nav-item" v-for="(linkObj, ind) in navList" :key="ind">
<a
class="nav-link js-scroll-trigger"
:href="linkObj.path"
v-if="linkObj.path.indexOf('#') === 0"
>{{linkObj.name}}</a>
<router-link
class="nav-link"
js-scroll-trigger
v-else
:to="linkObj.path"
>{{linkObj.name }}</router-link>
</li>
</ul>
</div>
</div>
</nav>
</div>
</template>
<script>
export default {
data: () => ({
navList: [
{
name: "Services",
path: "#services"
},
{
name: "Portfolio",
path: "#portfolio"
},
{
name: "About",
path: "#about"
},
{
name: "Team",
path: "#team"
},
{
name: "Contact",
path: "#contact"
}
]
})
};
</script>
<style lang="">
</style>
in SRC in my App.vue
<template>
<div id="app">
<router-view />
</div>
</template>
<script>
export default {};
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
}
</style>
main.js
import Vue from 'vue';
import App from './App.vue';
import router from '../router';
import VueResource from "vue-resource"
import './assets/css/style.css'
import './assets/vendor/bootstrap/css/bootstrap.min.css';
import './assets/vendor/fontawesome-free/css/all.min.css';
Vue.config.productionTip = false
Vue.use(VueResource);
new Vue({
router,
render: h => h(App),
}).$mount('#app')
For the sake of space, I will only show the minimum of the Home.vue component
<template>
<div>
<Nav></Nav>
<div>
<header class="masthead">
<div class="container">
<div class="masthead-holder">
<img src="../assets/img/RRLogo2.png" class="mt-5 logo" alt />
<div class="intro-text">
<div class="intro-lead-in">Welcome RR Spark Web Studio!</div>
<div class="intro-heading text-uppercase">Hire Us For Your Next Web Project</div>
<a
class="btn tell-me btn-xl text-uppercase js-scroll-trigger"
href="#services"
>Tell Me More</a>
</div>
</div>
</div>
</header>
</div>
<div>
<section class="page-section" id="services">
<div class="container">
<div class="row">
<div class="col-lg-12 text-center">
<h2 class="section-heading text-uppercase">Services</h2>
<h3 class="section-subheading text-muted">Delivering your ideal website is what we do.
</h3>
</div>
</div>
<div class="row text-center">
<div class="col-md-4">
<span class="fa-stack fa-4x">
<i class="fas fa-circle fa-stack-2x icon-color"></i>
<i class="fas fa-paint-brush fa-stack-1x fa-inverse"></i>
</span>
<h4 class="service-heading">Web Design</h4>
<p
class="text-muted text-justify"
>We take the time to really know your company to get a better understanding of your goals, vision and where you want to go in order to create that perfect, unique website that will help you reach your specific target audience. Your site needs to be user friendly, functional and visually pleasing and that is where are designer comes in to ensure the design is exactly what you want.</p>
</div>
<div class="col-md-4">
<span class="fa-stack fa-4x">
<i class="fas fa-circle fa-stack-2x icon-color"></i>
<i class="fas fa-globe fa-stack-1x fa-inverse"></i>
</span>
<h4 class="service-heading">Web Development</h4>
<p
class="text-muted text-justify"
>Working hand in hand with our designer, we use the latest in web technology, such as HTML; CSS3; JavaScript, to bring you a site that is fast, SEO (Search Engine Optimization) friendly as well as highly responsive. Keeping up with the latest Standards from W3C, your project will be built for a superior user experience on mobile, tablet as well as PC.</p>
</div>
<div class="col-md-4">
<span class="fa-stack fa-4x">
<i class="fas fa-circle fa-stack-2x icon-color"></i>
<i class="fas fa-laptop fa-stack-1x fa-inverse"></i>
</span>
<h4 class="service-heading">Content Management</h4>
<p
class="text-muted text-justify"
>Keeping up with changes on your site in this fast-paced world means that your site will constantly need new and fresh content. Using a content management system (CMS), such as WordPress allows you, the user, the ability to quickly add new content whenever you wish, maintain and implement marketing campaigns and monitor analytics all from within the user dashboard. We provide either WordPress modifications or Custom theming solutions.</p>
</div>
</div>
<div class="row mt-5">
<div class="col-lg-12 text-center">
<h2 class="section-heading text-uppercase">Ready? So Are We</h2>
<div class="row">
<div class="col-lg-6 p-5">
<h3
class="section-subheading text-muted mb-4"
>For a no-obligation quote, click the button below and tell us all about your project.</h3>
<a
class="btn tell-me btn-xl mt-0 mb-4 text-uppercase js-scroll-trigger"
href="#contact"
>CLICK ME</a>
</div>
<div class="col-lg-6 p-5">
<h3
class="section-subheading text-muted mb-4"
>If you don't like contact forms, ask us a question via WhatsApp using the button below.</h3>
<a
class="btn tell-me btn-xl mt-0 text-uppercase js-scroll-trigger"
href="https://api.whatsapp.com/send?phone=525517043338"
target="_blank"
>WhatsApp</a>
</div>
</div>
<!--end row CTA-->
</div>
</div>
</div>
</section>
</div>
<!--other sections-->
<div>
<section class="page-section" id="contact">
<div class="container">
<div class="row">
<div class="col-lg-12 text-center">
<h2 class="section-heading text-uppercase">Contact Us</h2>
<h3
class="section-subheading text-white"
>We are ready to get your project up and live.</h3>
</div>
</div>
<div class="row">
<div class="col-lg-12">
<Contact></Contact>
</div>
</div>
</div>
</section>
</div>
<Footer></Footer>
</div>
</template>
<script>
import Nav from "./Nav";
import Footer from "./Footer";
import Contact from "./Contact";
export default {
name: "App",
components: {
Nav,
Contact,
Footer
},
data: () => ({
portfolioJSON: [
{
linkToModal: "#portfolioModal1",
image: require("../assets/img/portfolio/icoimanismall.jpg"),
header: "Imani Luz Y HarmonÃa",
caption: "Graphic Design"
},
{
linkToModal: "#portfolioModal2",
image: require("../assets/img/portfolio/icopsicliliansmall.jpg"),
header: "Transformando Vidas",
caption: "Web Development"
},
{
linkToModal: "#portfolioModal3",
image: require("../assets/img/portfolio/icopsicovintsmall.jpg"),
header: "Viaje a tu Interior",
caption: "Web Development"
},
{
linkToModal: "#portfolioModal4",
image: require("../assets/img/portfolio/algovisolutionsthumb.png"),
header: "Algovi Solutions Landing Page",
caption: "Web Development"
},
{
linkToModal: "#portfolioModal5",
image: require("../assets/img/portfolio/naplesdentalthumb.png"),
header: "Naples Dental Clinic",
caption: "Web Development"
},
{
linkToModal: "#portfolioModal6",
image: require("../assets/img/portfolio/xploreflythumb.png"),
header: "Xplorefly Travel Agency",
caption: "Web Development"
}
]
})
};
</script>
<style lang="">
</style>
Again, thank you for looking at this as I am new to VueJS and learning.
In Nav.vue your navList array containing the paths has a #. The paths has to be identical with the path you defined on your router.js. Try changing your array
from:
navList: [
{
name: "Services",
path: "#services"
},
{
name: "Portfolio",
path: "#portfolio"
},
{
name: "About",
path: "#about"
},
{
name: "Team",
path: "#team"
},
{
name: "Contact",
path: "#contact"
}
]
to
navList: [
{
name: "Services",
path: "/services"
},
{
name: "Portfolio",
path: "/portfolio"
},
{
name: "About",
path: "/about"
},
{
name: "Team",
path: "/team"
},
{
name: "Contact",
path: "/contact"
}
]
if you can't modify the array try modifing the string like:
<router-link :to="linkObj.path.replace('#','/')">{{ linkObj.name }}</router-link>
Conclusion
For routing Navigation to render other components in the <router-view /> tag, try to use exclusive the <router-link to="/path"> tag. The <a href="/path"> tag will also navigate you but this will reload the page.
If you want to navigate to anchors in your page you can still use the <a href="#anchorId">
it bcs your tag contain # in a href="#".
its not part VueRouter, you need to remove it and make new function to scroll to specific area or div.
let say:
<a class="btn tell-me btn-xl text-uppercase js-scroll-trigger" href="#services">Tell Me More</a>
You need to remove the '#' inside the 'href' then add #click="goToSection('yourAreaWantToJump')"
then need to add scroller to specific div. in this case u need to scroll to section 'services'
I'm trying to use the same component to handle the Add and Edit part of my app. I'm using Firebase, so I'm checking if there is an id in the route params, if there is, it renders as the edit form, if not, renders as the add form. But this doesn't work and it has some wierd behaviour.
Here is the code for the ContactForm component
<template>
<div>
<div class="card mb-3">
<div class="card-header">{{ editing ? 'Edit' : 'Add' }} Contact</div>
<div class="card-body">
<form #submit.prevent="addContact">
<TextInputGroup
label="Name"
name="name"
placeholder="Enter your name..."
v-model="contact.name"
for="name"
/>
<TextInputGroup
type="email"
label="Email"
name="email"
placeholder="Enter your email..."
v-model="contact.email"
/>
<TextInputGroup
type="phone"
label="Phone"
name="phone"
placeholder="Enter your phone number..."
v-model="contact.phone"
/>
<input type="submit" value="Add Contact" class="btn btn-block btn-light" />
</form>
</div>
</div>
</div>
</template>
<script>
import TextInputGroup from "../layout/TextInputGroup";
import { db } from "../../firebase";
export default {
components: {
TextInputGroup
},
data() {
return {
contact: "",
editing: false,
email: "",
name: "",
phone: ""
};
},
methods: {
addContact() {
const newContact = {
name: this.name,
email: this.email,
phone: this.phone,
createdAt: new Date()
};
db.collection("contacts")
.add(newContact)
.then(docRef => {
console.log("Document written with ID: ", docRef.id);
})
.catch(error => {
console.error("Error adding document: ", error);
});
this.$router.push("/");
},
getContactById() {
db.collection("contacts")
.doc(this.$route.params.id)
.get()
.then(snapshot => {
if (!snapshot.exists) return;
this.contact = snapshot.data();
});
},
updateContact() {
const newContact = {
name: this.contact.name,
email: this.contact.email,
phone: this.contact.phone
};
db.collection("contacts")
.doc(this.$route.params.id)
.update(newContact)
.then(() => {
console.log("Updated document with ID: ");
})
.catch(function(error) {
console.error("Error updating document: ", error);
});
this.$router.push("/");
}
},
mounted() {
if ("id" in this.$route.params) {
this.getContactById();
this.editing = true;
console.log("id");
} else {
console.log("ups");
// this
}
}
};
</script>
This is the github link and the live app
I just cloned you repository and tested in local, Added fixes to use single form for editing and adding
Here is the code for following files, just copy paste this code in below mentioned files
src/components/contact/ContactForm.vue
<template>
<div>
<div class="card mb-3">
<div class="card-header">{{ editing ? 'Edit' : 'Add' }} Contact</div>
<div class="card-body">
<form #submit.prevent="addContact">
<TextInputGroup
label="Name"
name="name"
placeholder="Enter your name..."
v-model="contact.name"
for="name"
/>
<TextInputGroup
type="email"
label="Email"
name="email"
placeholder="Enter your email..."
v-model="contact.email"
/>
<TextInputGroup
type="phone"
label="Phone"
name="phone"
placeholder="Enter your phone number..."
v-model="contact.phone"
/>
<input type="submit" value="Add Contact" class="btn btn-block btn-light" />
</form>
</div>
</div>
</div>
</template>
<script>
import TextInputGroup from "../layout/TextInputGroup";
import { db } from "../../firebase";
var temp = Object.freeze({
name: '',
email: '',
phone: '',
});
export default {
components: {
TextInputGroup
},
props: {
type: {
type: String,
default: '',
},
},
data() {
return {
contact: Object.assign({}, temp),
editing: false,
};
},
methods: {
addContact() {
this.contact.createdAt = new Date();
db.collection("contacts")
.add(this.contact)
.then(docRef => {
console.log("Document written with ID: ", docRef.id);
})
.catch(error => {
console.error("Error adding document: ", error);
});
this.$router.push("/");
},
getContactById() {
db.collection("contacts")
.doc(this.$route.params.id)
.get()
.then(snapshot => {
if (!snapshot.exists) return;
this.contact = snapshot.data();
});
},
updateContact() {
db.collection("contacts")
.doc(this.$route.params.id)
.update(this.contact)
.then(() => {
console.log("Updated document with ID: ");
})
.catch(function(error) {
console.error("Error updating document: ", error);
});
this.$router.push("/");
}
},
created() {
if ("id" in this.$route.params) {
this.getContactById();
this.editing = true;
console.log("id");
} else {
console.log("ups");
// this
}
},
watch: {
type(val) {
if (val == 'add') {
this.contact = Object.assign({}, temp);
}
}
}
};
</script>
src/components/contact/ContactItem.vue
<template>
<div>
<div class="card card-body mb-3">
<h4>
{{ contact.name }}
<i
class="fas fa-sort-down pointer"
#click="showContactInfo = !showContactInfo"
></i>
<i class="fas fa-times delete right delete" #click="deleteContact(contact.id)"></i>
<router-link :to="{path: `contact/edit/${contact.id}`, params: { id: contact.id }, query: { type: 'edit' }}">
<i class="fas fa-pencil-alt edit right"></i>
</router-link>
</h4>
<ul class="list-group" v-if="showContactInfo">
<li class="list-group-item">Email: {{ contact.email }}</li>
<li class="list-group-item">Phone: {{ contact.phone }}</li>
</ul>
</div>
</div>
</template>
<script>
import { db } from "../../firebase";
export default {
props: {
contact: {
type: Object,
required: true
}
},
data() {
return {
showContactInfo: false
};
},
methods: {
deleteContact(id) {
db.collection("contacts")
.doc(id)
.delete()
.then(function() {
console.log("Document successfully deleted!");
})
.catch(function(error) {
console.error("Error removing document: ", error);
});
}
}
};
</script>
<style scoped>
.pointer {
cursor: pointer;
}
.right {
cursor: pointer;
float: right;
}
.edit {
color: black;
margin-right: 1rem;
}
.delete {
color: red;
}
</style>
src/components/layout/Navbar.vue
<template>
<div>
<nav class="navbar navbar-expand-sm navbar-dark bg-danger mb-3 py-0">
<div class="container">
Contact Manager
<div>
<ul class="navbar-nav mr-auto">
<li class="nav-item">
<router-link to="/" class="nav-link">
<i class="fas fa-home" />
Home
</router-link>
</li>
<li class="nav-item">
<router-link :to="{ path: '/contact/add', query: { type: 'add' } }" class="nav-link">
<i class="fas fa-plus" />
Add
</router-link>
</li>
<li class="nav-item">
<router-link to="/about" class="nav-link">
<i class="fas fa-question" />
About
</router-link>
</li>
</ul>
</div>
</div>
</nav>
</div>
</template>
src/views/ContactForm.vue
<template>
<ContactForm :type="formType" />
</template>
<script>
// # is an alias to /src
import ContactForm from "#/components/contact/ContactForm.vue";
export default {
name: "home",
data() {
return {
formType: '',
};
},
components: {
ContactForm
},
watch: {
'$route.query.type': {
handler: function(type) {
this.formType = type;
},
deep: true,
immediate: true
}
}
};
</script>