I have a main app page component with a search bar. The Search results that come back is in cards And I am looking to set up a "Click here to view more detail" that would be placed in each card. And it would link to the Details page of the one result clicked. How do I link these components on Vue and if the id could be passed? I am hoping that upon click of the button the component renders on the same page and not a new tab.
Thank you!
App.vue
<template>
<div id="app">
<Header/>
<SearchForm v-on:search="search"/>
<SearchResults
v-if="results.length > 0"
v-bind:results="results"
v-bind:reformattedSearchString="reformattedSearchString"/>
<Pagination
v-if="results.length > 0"
v-bind:prevPageToken="api.prevPageToken"
v-bind:next_page="api.scrollId"
v-on:prev-page="prevPage"
v-on:next-page="nextPage"
/>
</div>
</template>
<script>
import Header from './components/layout/Header';
import SearchForm from './components/SearchForm';
import SearchResults from './components/SearchResults';
import Pagination from './components/Pagination';
import Details from './components/Details'
import axios from 'axios';
export default {
name: 'app',
components: {
Header,
SearchForm,
SearchResults,
Pagination,
Details
},
data() {
return {
results: [],
reformattedSearchString: '',
api: {
baseUrl: 'https://test.org/api/v1/articles?',
max: 25,
q: '',
prevPageToken: '',
scrollId: ''
}
};
},
methods: {
search(searchParams) {
this.reformattedSearchString = searchParams.join(' ');
this.api.q = searchParams.join('+');
const { baseUrl, q, max} = this.api;
const apiUrl = `${baseUrl}&term=${q}&title_like=${q}&recent&max=${max}&full_results`;
this.getData(apiUrl);
},
prevPage() {
const { baseUrl, q, max, prevPageToken } = this.api;
const apiUrl = `${baseUrl}&term=${q}&title_like=${q}&max=${max}&pageToken=${prevPageToken}`;
this.getData(apiUrl);
},
nextPage() {
const { baseUrl, q, max,scrollId } = this.api;
const apiUrl = `${baseUrl}&term=${q}&title_like=${q}&max=${max}&recent&full_results&scroll_id=${scrollId}`;
this.getData(apiUrl);
},
getData(apiUrl) {
axios
.get(apiUrl)
.then(res => {
this.results = res.data.success.data;
this.api.prevPageToken = res.data.success.data.prevPageToken;
this.api.next_page = res.data.scrollId;
})
.catch(error => console.log(error))
}
}
};
</script>
Searchresults.vue
<template>
<div class="container mb-3">
<div class="d-flex mb-3">
<div class="mr-auto">
<h3>Search Results for "{{ reformattedSearchString }}"</h3>
</div>
<div class="btn-group ml-auto" role="group">
<button
#click="changeDisplayMode('grid')"
type="button"
class="btn btn-outline-secondary"
v-bind:class="{ active: displayMode === 'grid' }"
>
<i class="fas fa-th"></i>
</button>
<button
#click="changeDisplayMode('list')"
type="button"
class="btn btn-outline-secondary"
v-bind:class="{ active: displayMode === 'list' }"
>
<i class="fas fa-list"></i>
</button>
</div>
</div>
<div class="card-columns" v-if="displayMode === 'grid'">
<div class="card" v-bind:key="result._gddid" v-for="result in results">
<ArticleGridItem v-bind:result="result"/>
</div>
</div>
<div v-else>
<div class="card mb-2" v-bind:key="result._gddid" v-for="result in results">
<ArticleListItem v-bind:result="result"/>
</div>
</div>
</div>
</template>
<script>
import ArticleListItem from './ArticleListItem';
import ArticleGridItem from './ArticleGridItem';
import Details from './Details';
export default {
name: 'SearchResults',
components: {
ArticleListItem,
ArticleGridItem,
Details,
},
data() {
return {
title: 'Search Results',
displayMode: 'grid'
};
},
methods: {
changeDisplayMode(displayMode) {
this.displayMode = displayMode;
}
},
props: ['results', 'reformattedSearchString']
};
</script>
ArticleListItem.vue
<template>
<div>
<div class="card-body">
<h6 class="card-text">{{ result.title }}</h6>
<p
class="card-subtitle mb-2 text-muted"
>{{ result.publisher }} | {{ result.journal }} | {{ result.year }}</p>
<a :href="'https://test.org/api/articles?docid=' + result._gddid" target="_blank">
<i class="fa fa-download" alt="Download"> </i>
</a>
<router-link>
<v-btn dark to="{name:'Details', params: {id: article._gddid}}">
Click here for more Details
</v-btn>
</router-link>
<router-view></router-view>
</div>
</div>
</template>
<script>
export default {
name: 'ArticleListItem',
props: ['result'],
}
</script>
index.js
import Vue from 'vue';
import Router from 'vue-router';
import Details from '#/components/Details';
Vue.use(Router)
export default new Router({
routes: [
{
path: '/Details/:id',
name: 'Details',
component: Details
}
]
});
main.js
import Vue from 'vue'
import App from './App.vue'
import router from './router/index.js'
import moment from 'moment'
Vue.config.productionTip = false
Vue.filter('formatDate', function (value) {
if (!value) return ''
return moment(value.toString()).format('MM/DD/YYYY hh:mm')
})
new Vue({
router,
render: h => h(App),
}).$mount('#app')
use <router-link> tag for it to work
When using Vue Router, instead of using <a> tags, you can use <router-link> tags.
Here's a basic example similar to the Vue Router documentation for dynamic route matching.
If you have this...
const router = new VueRouter({
routes: [
{ path: '/Details/:id', component: DetailsPage, name: 'Details' }
]
})
You can have router-links declared in, say, your ArticleListItem.vue file like:
<v-btn dark to="{name:'Details', params: {id: article.id}}">
Click here for more Details
</v-btn>
You can't just go to a generic details page. There needs to be some dynamic part of the route (which I've called id in my example) to specify which article's page to go to.
Edit
An additional problem is in your Router setup:
From the Vue Router docs:
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)
...needs to be in your main.js file. The declaration of Vue.use in your index.js file isn't doing anything. Just for conciseness. Make it one file:
WhateverYourOneTrueFileIsGoingToBeCalled.js
import Vue from 'vue'
import App from './App.vue'
import VueRouter from 'vue-router';
import Details from '#/components/Details';
import moment from 'moment'
Vue.config.productionTip = false
Vue.filter('formatDate', function (value) {
if (!value) return ''
return moment(value.toString()).format('MM/DD/YYYY hh:mm')
})
Vue.use(VueRouter)
router = new VueRouter({
routes: [
{
path: '/Details/:id',
name: 'Details',
component: Details
}
]
})
new Vue({
router,
render: h => h(App),
}).$mount('#app')
Related
Sorry for the heavy text. All of my router-views work, except for one, which shows blank. I do not see any console errors of warnings and the format is identical between views - the only difference is the template. This was working, but I started a new project because my package.json and dependencies got messy. I've read through the code ad nauseum and I just can't work out why it wont show. The code is condensed as there's lots. If you prefer, here's a link to a sandbox: https://codesandbox.io/s/condescending-monad-5o8qw
<template>
<div class="review-movie-detail">
<div class="movie-image">
<img :src="(`https://image.tmdb.org/t/p/original/${movie.poster_path}`)" alt="Movie Poster" />
</div>
<table class="movie-rating-details">
<tr> <h2>{{movie.original_title}}</h2> </tr>
<p> </p>
<tr>Gore rating: <span class="emojiRatings" >{{getGoreEmoji()}} </span></tr>
<tr><input v-model = "goreRating" type="range" min="1" max="100" class="slider" id="myRange"></tr>
<tr> <div class="star-rating"> <star-rating v-model="rating"> </star-rating></div></tr>
<tr><b-button class="block-button">Submit review</b-button></tr>
</table>
</div>
</template>
<script>
import { ref, onBeforeMount } from 'vue';
import env from '#/env.js'
import { useRoute } from 'vue-router';
import StarRating from 'vue-star-rating'
export default {
components : {StarRating},
setup () {
const movie = ref({});
const route = useRoute();
onBeforeMount(() => {
fetch(`https://api.themoviedb.org/3/movie/${route.params.id}?api_key=${env.apikey}`)
.then(response => response.json())
.then(data => {
movie.value = data;
});
});
return {
movie
}
},
data() {
return {
goreRating: '50',
shockRating : '50',
jumpRating: '50',
plotRating: '50',
supernaturalRating: '50',
rating: '3.5'
}
},
methods: {
getGoreEmoji() {
let emojiRating = ["🩸", "🩸🩸", "🩸🩸🩸", "🩸🩸🩸🩸", "🩸🩸🩸🩸🩸", "🩸🩸🩸🩸🩸🩸"]
return emojiRating[(Math.floor(this.goreRating/20))]
},
}
}
and here is my router:
import { createRouter, createWebHistory } from 'vue-router'
import Home from '../views/Home.vue'
import MovieDetail from '../views/MovieDetail.vue'
import ReviewMovie from '../views/ReviewMovie.vue'
const routes = [
{
path: '/',
name: 'Home',
component: Home
},
{
path: '/movie/:id',
name: 'Movie Detail',
component: MovieDetail
},
{
path: '/movie/:id/review',
name: 'Review Movie',
component: ReviewMovie
}
]
const router = createRouter({
history: createWebHistory(process.env.BASE_URL),
routes
})
export default router
and app.Vue to display the router view...
<template>
<div>
<header>
<GoBack />
<router-link to="/">
<h1><span>Horror</span>Hub</h1>
</router-link>
</header>
<main>
<router-view></router-view>
</main>
</div>
</template>
<script>
import GoBack from "#/./components/GoBack"
export default {
components: {
GoBack
}
}
</script>
How can I find the root cause of this issue (pun intended)?
As you are using Vue 3, you need to use vue-star-rating#next
npm install vue-star-rating#next
or
yarn add vue-star-rating#next
in package.json, it should be
"vue-star-rating": "^2.1.0"
and use the new new syntax as well
<star-rating v-model:rating="rating"></star-rating>
Here is the working codesandbox:
https://codesandbox.io/s/little-sea-xccm4?file=/src/views/ReviewMovie.vue
I am learning VueJs and trying to understand how to extract route params via props.
I was looking at the following documentation, where it seems to have three options to have this done, but I cannot understand it quite well so far - https://github.com/vuejs/vue-router/tree/dev/examples/route-props.
I have tried adding props: true to my router object array (routes.js file posted below) with no success as well.
As this is vue-cli study project I will post the separate pertinent blocks of code in order to try to illustrate this properly.
Main - App.vue below:
<template>
<div class="container">
<div class="row">
<div class="col-xs-12 col-sm-8 col-sm-offset-2 col-md-6 col-md-offset-3">
<h1>Routing</h1>
<hr>
<app-header></app-header>
<router-view></router-view>
</div>
</div>
</div>
</template>
<script>
import Header from './components/Header.vue'
export default {
components: {
appHeader: Header
}
}
</script>
<style>
</style>
Component - Header.vue below:
<template>
<ul class="nav nav-pills">
<router-link to="/" tag="li" active-class="active" exact><a>Home</a></router-link>
<router-link to="/user/10" tag="li" active-class="active"><a>User 1</a></router-link>
<router-link to="/user/5" tag="li" active-class="active"><a>User 2</a></router-link>
</ul>
</template>
Component - User.vue below:
<template>
<div>
<h1>The User Page</h1>
<hr>
<br>
<p>Route ID: {{id}}</p>
<button class="btn btn-primary" #click="goHome">Go to Home</button>
</div>
</template>
<script>
export default {
data(){
return {
id: this.$route.params.id
}
},
watch: {
'$route'(to, from) {
this.id = to.params.id;
}
},
methods: {
goHome(){
this.$router.push('/')
}
}
}
</script>
main.js below:
import Vue from 'vue'
import VueRouter from 'vue-router'
import App from './App.vue'
import {routes} from "./routes";
Vue.use(VueRouter)
export const router = new VueRouter({
routes,
mode: 'history'
})
new Vue({
el: '#app',
router,
render: h => h(App)
})
routes.js below (located at SRC folder):
import User from './components/user/User.vue'
import Home from './components/Home.vue'
export const routes = [
{path: '', component: Home},
{path: '/user/:id', component: User}
]
Do I need to also set Props at User.vue component as well in order to make it work and quit using watch?
In other words, I would like to see my user route being listened at <p>Route ID: {{id}}</p> from this hardcoded 10 to 5 using this new method which I cannot understand, mentioned at the top of this post.
Could anyone please walk me through this issue in this specific situation?
Thanks in advance to all.
According to the docs on passing Props to Route components, you can decouple it, with the props option on the router config.
import User from './components/user/User.vue'
import Home from './components/Home.vue'
export const routes = [
{path: '', component: Home},
{path: '/user/:id', component: User, props: true}
]
<template>
<div>
<h1>The User Page</h1>
<hr>
<br>
<p>Route ID: {{id}}</p>
<button class="btn btn-primary" #click="goHome">Go to Home</button>
</div>
</template>
<script>
export default {
props: ['id'],
methods: {
goHome() {
this.$router.push('/')
}
}
}
</script>
The name is defined in all my component instances with the correct capitalisation but I still get the error, I've found many similar problems online but the problem always seems to be with the name, however I'm 100% sure all my components have exactly the same name as those in the routes.js folder. Anyone have any ideas for possible solutions? Thanks.
App.vue
<template>
<div id="app">
<router-link to="/search"></router-link>
<router-view> </router-view>
</div>
</template>
<script>
import Search from './components/Search.vue';
import Apod from './components/Apod.vue';
import News from './components/News.vue';
import NotFound from './components/NotFound.vue';
export default {
name: 'app',
components: {
NotFound: NotFound,
Search: Search,
Apod: Apod,
News: News
}
}
</script>
main.js
import Vue from 'vue'
import App from './App.vue'
import VueRouter from 'vue-router';
Vue.config.productionTip = false
new Vue({
el: '#app',
router: VueRouter,
render: h => h(App),
}).$mount('#app')
index.js
import Vue from 'vue';
import VueRouter from 'vue-router';
import Apod from '#/components/Apod';
import Search from '#/components/Search';
import News from '#/components/News';
import NotFound from '#/components/NotFound';
Vue.use(VueRouter)
export default new VueRouter({
routes: [
{
path: '/apod',
name: 'Apod',
component: Apod,
},
{
path: '/search',
name: 'Search',
component: Search,
},
{
path: '/news',
name: 'News',
component: News,
}
]
})
Search.vue
<template>
<div class="search">
<h2> Search: </h2>
<h5>Home</h5>
<form v-on:submit.prevent="getResult(query)">
<input type="text" placeholder="search" v-model="query"/>
</form>
<div class="wrapper">
<div class="results" >
<div class="row" v-for="(r, index) in Math.ceil(results.length / 4)" v-if="index <= 3">
<span class="result" v-for="result in results.slice((r - 1) * 4, r * 4)">
<img :src="result.links[0].href" :alt="result.data[0].keywords[0]"/>
<p class="text"> {{ result.data[0].title }} </p>
</span>
</div>
</div>
</div>
</div>
import axios from 'axios';
export default {
name: 'Search',
data() {
return {
query: '',
results: ''
}
},
methods: {
getResult(query) {
axios.get('https://images-api.nasa.gov/search?q=' + query + '&media_type=image')
.then(response => {
this.results = response.data.collection.items;
console.log(response);
});
}
}
}
In main your should write:
import Vue from 'vue'
import App from './App.vue'
import VueRouter from './path-to-your-router-init-file';
Vue.config.productionTip = false
new Vue({
el: '#app',
router: VueRouter,
render: h => h(App),
}).$mount('#app')
working with laravel 5.7 and vue.js my
app.js file is as following,
require('./bootstrap');
window.Vue = require('vue');
import VueRouter from 'vue-router'
Vue.use(VueRouter)
let routes = [
{ path: '/dashboard', component: require('./components/Dashboard.vue') },
{ path: '/profile', component: require('./components/Profile.vue') }
]
const router = new VueRouter({
routes // short for `routes: routes`
})
Vue.component('example-component', require('./components/ExampleComponent.vue').default);
const app = new Vue({
el: '#app',
router
});
and I need link following link with vue file
<router-link to="/dashboard" class="nav-link">
and Dashboard.vue file is like this
<template>
<div class="container">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card card-default">
<div class="card-header">Dashboard Component</div>
<div class="card-body">
I'm an example component.
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
mounted() {
console.log('Component mounted.')
}
}
</script>
but when I click above link to dashbord.vue file it is not loading. only display url in the address bar. my console error is as following
[Vue warn]: Failed to mount component: template or render function not defined. found in ---> <Anonymous> <Root>
how can fix this error
You need add App.vue
<template>
<div>
<router-link to="/dashboard" class="nav-link">Dashboard</router-link>
</div>
</template>
<script>
export default {
// some code here if needed
}
</script>
and then use it in main.js
require('./bootstrap'); // maybe better use import "./bootstrap"
import Vue from 'vue';
import VueRouter from 'vue-router';
import App from "./App.vue";
import Dashboard from './components/Dashboard.vue';
import Profile from './components/Profile.vue';
import ExampleComponent from './components/ExampleComponent.vue';
Vue.use(VueRouter)
window.Vue = Vue; // why you do that???
let routes = [
{ path: '/dashboard', component: Dashboard },
{ path: '/profile', component: Profile }
]
const router = new VueRouter({
routes // short for `routes: routes`
});
Vue.component('example-component', ExampleComponent);
new Vue({
router,
render: (h) => h(App)
}).$mount('#app');
Following some tutorials I have created a simple menu like the following:
HTML
Vue.component('main-menu', {
template: `
<div>
<ul class="uk-tab uk-margin-bottom">
<li v-for="tab in tabs" :class="{ 'uk-active': tab.selected }">
<a #click="selectTab(tab.name)" href="#">{{ tab.name }}</a>
</li>
</ul>
<slot></slot>
</div>
`,
data() {
return {
tabs: []
}
},
created() {
this.tabs = this.$children
},
methods: {
selectTab(name) {
for (tab of this.tabs) {
tab.selected = (tab.name == name)
}
}
},
})
Vue.component('tab', {
props: {
name,
active: {
default: false
},
},
template: `
<div v-show="selected">
<ul v-show="subTabs.length > 1" class="uk-subnav uk-subnav-pill">
<li v-for="subTab in subTabs" :class="{ 'uk-active': subTab.selected }">
<a #click="selectSubTab(subTab.name)" href="#">{{ subTab.name }}</a>
</li>
</ul>
<slot></slot>
</div>
`,
data() {
return {
selected: false,
subTabs: []
}
},
created() {
this.selected = this.active
this.subTabs = this.$children
this.selected = this.active
},
methods: {
selectSubTab(name) {
for (subTab of this.subTabs) {
subTab.selected = (subTab.name == name)
}
}
},
})
Vue.component('sub-tab', {
props: {
name,
active: {
default: false
},
},
template: `
<div v-show="selected">
<slot></slot>
</div>
`,
data() {
return {
selected: false
}
},
created() {
this.selected = this.active
},
})
new Vue({
el: '#root',
})
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Static title for now</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/uikit/2.27.4/css/uikit.gradient.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.5/vue.js"></script>
</head>
<body class="uk-margin uk-margin-left uk-margin-right">
<div id="root">
<main-menu>
<tab name="Tab 1" :active="true">
<sub-tab name="Sub 1A" :active="true">
<div>1A</div>
</sub-tab>
<sub-tab name="Sub 1B">
<div>1B</div>
</sub-tab>
</tab>
<tab name="Tab 2">
<sub-tab name="Sub 2A" :active="true">
<div>2A</div>
</sub-tab>
</tab>
<tab name="Tab 3">
<sub-tab name="Sub 3A" :active="true">
<div>3A</div>
</sub-tab>
<sub-tab name="Sub 3B">
<div>3B</div>
</sub-tab>
</tab>
</main-menu>
</div>
<script src="main.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/uikit/2.27.4/js/uikit.min.js"></script>
</body>
</html>
I wanted to add some routing, so whenever I click tab or sub-tab, it will update address, and other way around, whenever I change the address, it will update the state of the page and show the correct div.
I have read the official documentation for vue-router and looked up some more tutorials, but I cannot figure out how to do it. It seems to be designed the way where different components are shown on different links.
What am I missing?
The setup I use combines vuex and route using vuex-router-sync. It seems like quite a bit of code, and while the example bellow that I grabbed from my notes does not include child/sub categories, it's not too difficult to add these.
// ::::: main.js :::::
import Vue from 'vue';
import store from './store'; // 1 - load vuex
import router from './router'; // 2 - load router
import App from './App.vue';
import { sync } from 'vuex-router-sync'; // 3 - load vuex-router-sync
sync(store, router) // 4 - call synch on vuex and route
new Vue({
el: '#app',
store, // 5 - add vuex to app
router, // 5 - add router to app
components: {
App
}
})
// ::::: store.js :::::
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const store = new Vuex.Store({
state: {
menu: [{
name: 'Home',
path: '/',
component: require('./routes/home')
},
{
name: 'Page1',
path: '/page1',
component: require('./routes/page1')
},
{
name: 'Page2',
path: '/page2',
component: require('./routes/page2')
},
]
},
getters: {
menu: state => {
return state.menu || []
}
}
})
export default store
// ::::: router.js :::::
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)
import HomePage from './routes/home.vue'
import Page1 from './routes/page1.vue'
import Page2 from './routes/page2.vue'
const routes = [{
path: '/',
component: HomePage
},
{
path: '/page1',
component: Page1
},
{
path: '/page2',
component: Page2
}
]
const router = new VueRouter({
// mode: 'history',
mode: 'hash',
linkActiveClass: 'is-active',
scrollBehavior: () => ({
y: 0
}),
routes
})
export default router
<!-- App.vue -->
<template>
<div id="App">
<navigation></navigation>
<router-view class="animated"></router-view>
</div>
</template>
<script>
import Navigation from './components/Navigation'
export default {
components: {
Navigation
}
}
</script>
Navigation
import store from '../store'
import {
mapGetters
} from 'vuex'
export default {
data: function() {
return {
error: {}
}
},
computed: {
...mapGetters([
'routePath'
]),
menu() {
return store.getters.menu
},
}
}
<template>
<div>
NAVIGATION
<ul class="navbar_wrap">
<li v-for="(item, index) in menu" :key="index" :class="routePath === item.path ? 'selected' : 'not-selected'">
<router-link :to="item.path" :exact="true" v-if="item.path">
{{item.name}}
</router-link>
</li>
</ul>
</div>
</template>
the routePath getter is defined in store
routePath: state => {
return state.route.path
}