You should go through these resources
https://dev.to/alexmourer/sharing-data-between-components-invuejs-48me
https://dev-notes.eu/2018/05/passing-data-between-vue-components/
Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.
Hi, I have a nav bar in my application that has a search function. The nav bar is a vue component and the main body of my site is a vue component. My goal is to have a user beable to search in the nav bar from any point in the site and return a new page of results. The issue I am having is how to pass the data. I first tried using Vuex because I thought the data would be stored as I loaded the new html page. I discovered that Vuex worked great if I did a search when I was at '/'. However, if the user does a search when they are looking at a specific event '/event/namehere', then it would redirect back to the home page but without the variable passed. I then looked into Vue Router but I have already built out my application and I am not ready to make it an SPA. My issue is the the fact that I when I do an axios post I can only get back json.
How do I submit this axios search post from the navbar anywhere on the site and have it load the homepage with the results (without rebuilding my app as a SPA)?
You should go through these resources
https://dev.to/alexmourer/sharing-data-between-components-invuejs-48me
https://dev-notes.eu/2018/05/passing-data-between-vue-components/
I know about emitting, but what about when it loads a new page and then wants to pass data?
You could save it within the session and pass it as a prop to the vue component.
Oh interesting! I was also thinking about adding is as a variable to the url address bar
@chrisgrim options are as follows:
Hi @nolros Thanks so much for your message. So it turns out I did the not recommended way. I wanted to use the Vue router but I am using laravel web routes as my navigation (not doing a SPA) and I couldn't get Vue router to actually redirect to a new url. The solution I finally figured out was number 3/ passing it as a url. Then in the new page I grab the info using the $route.query. I would be soooo interested in learning if there is a way to use Vue router to redirect. Here is my current Vue Navbar search file:
<template>
<div class="nav-search">
<div class="nav-search__input">
<multiselect
v-model="searchBoxInput"
:options="options"
open-direction="bottom"
placeholder="Try Los Angeles"
label="name"
:loading="isLoading"
:show-labels="false"
:internal-search="false"
:options-limit="30"
:limit="5"
track-by="name"
@search-change="asyncFind"
:show-no-results="false"
:allow-empty="false">
<template slot="selection" slot-scope="{ values, search, isOpen }"><span class="multiselect__single" v-if="values.length && !isOpen">{{ values.length }} options selected</span></template>
</multiselect>
<button @click.prevent="searchEvents()">Search</button>
</div>
</div>
</template>
<script>
import Multiselect from 'vue-multiselect'
import format from 'date-fns/format'
import _ from 'lodash'
export default {
components: {
Multiselect,
},
computed: {
urlLoc: function () {
return `/index/search?name=${this.searchBoxInput.name}&latitude=${this.searchBoxInput.latitude}&longitude=${this.searchBoxInput.longitude}`;
},
},
data() {
return {
searchBoxInput: _.has(this.$route.query, 'latitude') ? this.$route.query : [],
options: [],
isLoading: false,
}
},
methods: {
asyncFind (query) {
axios.get('/api/city/search', { params: { keywords: query } })
.then(response => {
this.options = response.data;
})
.catch(error => {
console.log(error)
});
},
searchEvents(post) {
//add the searched date value to the location search
if(_.has(this.searchBoxInput, 'latitude'))
{
window.location.href = this.urlLoc;
};
},
};
</script>
and then when it redirects to /index/search?latitude=4.343 etc... and in my vue page body file I have
<template>
<div>
<h2>Our Latest Events</h2>
<div id="app">
<div id="grid-section">
<div v-for="event in events">
<event-listing-item :event="event"></event-listing-item>
</div>
</div>
</div>
</div>
</template>
<script>
import _ from 'lodash';
import Multiselect from 'vue-multiselect';
import format from 'date-fns/format';
import { mapGetters } from 'vuex'
export default {
components: {
Multiselect,
},
computed: {
...mapGetters([
'events'
]),
},
name: "searchEvents",
data() {
return {
value: '',
list: [],
price: '',
eventName: '',
location: '',
searchObject: this.initializeSearchObject(),
}
},
methods: {
initializeSearchObject() {
return {
latitude: '',
longitude: '',
}
},
updateFilter() {
_.extend(this.searchObject, this.$route.query);
this.$store.dispatch('searchEvents', this.searchObject);
},
},
mounted() {
this.updateFilter();
}
};
</script>
dispatch searchEvents then fires
searchEvents({commit}, event) {
axios.post('/api/search', event)
.then(res => {
commit('SEARCH_EVENTS', res.data)
}).catch(err => {
console.log(err)
})
},
which finally passes the data to my vuex event variable. Now that I write this out it does seem like a lot of steps but when I tried to use this.$router.push to go to /index/search page it didn't work because I am not doing a SPA. Thank you so much for any help.
@chrisgrim a redirect in JS is as simple as window.location.href = and in vue router it is router.go(path)
I started working on a SPA search component, porting some JS, etc. loads of cleanup required along with Vue features e.g. key events, etc. but this gets you close to what you looking to do for an SPA and autocomplete. I use Vue Portal to push the results to "body" of the page to minimize CSS etc. Also using Tailwind. I'll keep cleaning up this week and then post it a codepen or something
<template>
<div class="relative flex flex-row justify-center w-full dark__form--input-group">
<div class="control relative flex flex-row flex-wrap justify-center items-end w-full dark__form--control">
<input type="text"
autocomplete="off"
v-model="search"
id="search"
v-focus
class="form--dark__input"
placeholder="Enter Search Term Here"/>
<!-- Vue Search List Start-->
<portal to="searchMain">
<div @click.stop="toggleSearch($event)"
class="core-base--width screen--overlay active search-active flex flex-row flex-wrap justify-center items-start content-start pattern noise">
<ul v-cloak
class="search--list relative w-full flex flex-row flex-wrap justify-center items-start content-start z-10 list-reset"
v-if="results">
<li v-for="(result,key) in filterResults"
:id="result.id"
class="relative f--col-4 pattern diagonal-02 noise--hover p-4 mb-2 h--300 "
:class="[(key + 1 === count) ? activeClass : '', menuItem,getHeight]">
<a class="search__item--link relative w-full justify-center items-top flex-wrap z-20"
@click.stop="toggleSearch($event)"
:title="result.name"
:href="result.url">
<div class="search__content--container">
<div
class="relative w-full flex flex-row justify-center items-center z-20 h-auto pb-4">
<img class="relative inline-block image--square-050"
:src="result.image.url"
:alt="result.image.alt"
:title="result.image.name">
</div>
<p class="relative w-full font__osw f--light f--21 color--e2 text-center no-underline select-none uppercase"
v-text="result.type">
</p>
<div class="search__content--group">
<h6 v-html="highlight(result.name)"
class="relative w-full f--regular f--space-03 color--e2 f--pop text-center select-none pt-4 pb-2 f--21">
</h6>
</div>
<p class="relative w-full f--14 f--thin color--d1 f--pop text-center select-none pb-4">
{{result.description | truncate(200,'...')}}
</p>
</div>
</a>
</li>
</ul>
</div>
</portal>
<!-- Vue Search List End-->
</div>
</div>
<!-- search box container ends -->
</template>
<script>
export default {
name: "SearchBox",
props: {
searchTerm: {
type: String,
required: false
},
},
directives: {
focus: {
// directive to ensure focus is set in input if hidden
inserted: function (el) {
el.focus()
}
}
},
data: function () {
return {
searchState: false,
search: '',
count: 0,
width: 0,
menuItem: 'menu-item',
activeClass: 'active',
keywords: null,
results: [],
searchTerms: {},
searchMatches: {},
keyCount: 0,
responsive: {
lg: 12,
md: 8,
sm: 6,
xs: 3
}
}
},
computed: {
getHeight() {
return "h--" + this.$mq;
},
filterResults() {
return _.slice(this.results, 0, 8);
},
},
methods: {
highlight(text) {
let searchables = this.search.split(" ");
if (_.size(searchables)) {
text = findReplace(text, searchables, "<span class='search--highlighted'>$&</span>");
}
return text;
},
toggleSearch(e) {
this.results = '';
this.count = 0;
this.keyCount = 0;
this.searchMatches = {};
Event.fire("toggle-search", true);
},
clearData: function (e) {
if (e.target.id !== 'search') {
this.results = '';
this.count = 0;
this.keyCount = 0;
this.searchMatches = {};
}
},
getSearchTerms: function (resource) {
let vm = this;
this.axiosService.fetch(resource)
.then(response => {
vm.searchTerms = response.data;
})
.catch(error => {
});
},
getPosts() {
// Note can add a throttle or debounce
let self = this;
if (this.search.trim() !== '') {
return new Promise((resolve, reject) => {
axios.post('/search', {search: self.search})
.then(response => {
resolve((self.results = response.data))
})
.catch(error => {
reject(error.response.data);
});
});
}
},
selectPost: function (keyCode) {
// If down arrow key is pressed
if (keyCode === 40 && this.count < this.results.length) {
this.count++;
}
// If up arrow key is pressed
if (keyCode === 38 && this.count > 1) {
this.count--;
}
// If enter key is pressed
if (keyCode === 13) {
// Go to selected post
// document.getElementById(this.count).childNodes[0].click();
}
},
},
mounted: function () {
let self = this;
/*
* We check if a search term has been injected into the component and if so then that is the search term.
* example - we redirect from teh server with a term. We then get psots / products, etc
*/
if (!_.isEmpty(this.searchTerm)) {
this.search = this.searchTerm;
this.getPosts();
}
// To clear search widget when click on body
document.body.addEventListener('click', function (e) {
self.clearData(e);
});
// check whether arrow keys are pressed
document.getElementById('search').addEventListener('keydown', function (e) {
if (_.includes([37, 38, 39, 40, 13], e.key)) {
// To prevent cursor from moving left or right in text input
if (e.key === 38 || e.key === 40) {
e.preventDefault();
}
// If post list is cleared and search input is not empty then call ajax again on down arrow key press
if (e.key === 40 && self.results === "") {
return self.getPosts();
}
// select the post / result item
self.selectPost(e.key);
} else {
// check to see if the user is backspacing or moving arrow left or id they press eneter
// 8 bs, 37 left arrow, del 46
if ((e.key === 8 || e.key === 46) && self.keyCount > 1) {
self.keyCount--;
} else if (e.key !== 16) {
self.keyCount++;
}
// after 3 valid keydowns then we conduct a search for the terms
if (self.keyCount >= 3) {
self.getPosts();
}
}
});
this.$nextTick(this.init());
},
}
</script>
Hi @nolros So I tried this.$router.go('/newpage') and all it does is reload the current page. I have looked into it and it looks like Vue Router changed the way go works. Is there any way at all to use Router to redirect to a new url with data? As far as I can see there is not.
Vue Portal does seem like it will work but I worry about speed. If I am loading all my returned events using Vue Portal in the body below could that cause a website loading hit?
Also I realized with Vue Portal I would have to have a v-if="show on user search" on every page which seems like an inefficient way to work.
@chrisgrim you keep mentioning the data. What data are you needing to pass through / along with redirect and why? Are you redirecting to same app or different app?
@chrisgrim example of the page setup. The portal and the navigation AND/OR search would be on each page so there is no need to pass data.
Example: home or app.blade.php
<!doctype html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<!-- CSRF Token -->
<meta name="csrf-token" content="{!!csrf_token()!!}"/>
<script>
window.Laravel = {!! json_encode([ 'csrfToken' => csrf_token()]) !!};
</script>
<!-- Fonts -->
<link href="https://fonts.googleapis.com/css?family=Poppins:200,300,400" rel="stylesheet">
<!-- Styles -->
<link rel="stylesheet" href="{!! mix('css/app.css') !!}" type="text/css">
<!-- Meta Boilerplate -->
</head>
<body>
<main id="app">
<navigation-search></navigation-search>
<portal-target name="searchResults" class="fixed w-full w-auto"></portal-target>
@yield('content')
@include('pages.partials.footer')
</main>
<script src="{!! mix('js/app.js') !!}"></script>
</body>
</html>
The Vue Comp. Note I have not tested it and it would require CSS work, etc. Best to combine this with you nav so that search and Nav are on all pages.
<template>
<div class="relative flex flex-row justify-center w-full">
<!-- This is an example search button-->
<div class="relative flex flex-row justify-end items-center content-center">
<button @click.stop="toggleSearch"
class="search--btn">
<svg class="search--btn-svg" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24" width="24" height="24">
<g fill="none" stroke="currentColor" stroke-linecap="round"
stroke-linejoin="round">
<circle cx="10.5" cy="10.6" r="7.3"></circle>
<path d="M15.6 15.8l4.9 4.8"></path>
</g>
</svg>
</button>
</div>
<!-- This is an example search form / btn-->
<div v-if="active"
class="relative flex flex-row flex-wrap">
<form method="POST"
autocomplete="off"
name="search"
action="/api/post/search"
@submit.prevent="onSubmit"
class="relative">
<input type="text"
autocomplete="off"
v-model="search"
id="search"
v-focus
class="form--dark__input"
placeholder="Enter Search Term Here"/>
<button type="submit">
<span class="relative inline-block w-full uppercase">Search</span>
</button>
</form>
</div>
<!-- Search results would go here which will show up in home blade-->
<portal to="searchResults"
v-if="active">
<!-- Click anywhere on teh ul will toggle search off-->
<ul @click.stop="toggleSearch"
class="relative flex flex-row flex-wrap">
<li v-for="(result,key) in results"
class="relative inline-block w-1/3"
:id="result.id">
<!-- The rest of your HTML goes here-->
</li>
</ul>
</portal>
</div>
</template>
<script>
export default {
name: "NavigationSearch",
props: {
searchTerm: {
type: String,
required: false
},
},
directives: {
focus: {
// directive to ensure focus is set in input if hidden
inserted: function (el) {
el.focus()
}
}
},
data: function () {
return {
searchState: false,
search: {},
results: {},
active: false,
searchTerms: {},
}
},
computed: {
hasResults() {
return _.size(this.results);
},
toggleSearch() {
this.active =! this.active;
},
},
methods: {
getPosts() {
// Note can add a throttle or debounce
let self = this;
if (this.search.trim() !== '') {
return new Promise((resolve, reject) => {
axios.post('/search', {search: self.search})
.then(response => {
resolve((self.results = response.data))
})
.catch(error => {
reject(error.response.data);
});
});
}
},
selectPost: function (keyCode) {
// If down arrow key is pressed
if (keyCode === 40 && this.count < this.results.length) {
this.count++;
}
// If up arrow key is pressed
if (keyCode === 38 && this.count > 1) {
this.count--;
}
// If enter key is pressed
if (keyCode === 13) {
// Go to selected post
// document.getElementById(this.count).childNodes[0].click();
}
},
},
mounted: function () {
let self = this;
/*
* We check if a search term has been injected into the component and if so then that is the search term.
* example - we redirect from teh server with a term. We then get psots / products, etc
*/
if (!_.isEmpty(this.searchTerm)) {
this.search = this.searchTerm;
this.getPosts();
}
// To clear search widget when click on body
document.body.addEventListener('click', function (e) {
self.clearData(e);
});
// check whether arrow keys are pressed
document.getElementById('search').addEventListener('keydown', function (e) {
if (_.includes([37, 38, 39, 40, 13], e.key)) {
// To prevent cursor from moving left or right in text input
if (e.key === 38 || e.key === 40) {
e.preventDefault();
}
// If post list is cleared and search input is not empty then call ajax again on down arrow key press
if (e.key === 40 && self.results === "") {
return self.getPosts();
}
// select the post / result item
self.selectPost(e.key);
} else {
// check to see if the user is backspacing or moving arrow left or id they press eneter
// 8 bs, 37 left arrow, del 46
if ((e.key === 8 || e.key === 46) && self.keyCount > 1) {
self.keyCount--;
} else if (e.key !== 16) {
self.keyCount++;
}
// after 3 valid keydowns then we conduct a search for the terms
if (self.keyCount >= 3) {
self.getPosts();
}
}
});
},
}
</script>
Any Laravel page that extends app.blade.php will have search. Example, blog or products.blade.php
@extends('app')
@section('content')
{{-- Any HTML page that extends from the app.blade.php--}}
@endsection
The problem with this method is the content just gets pushed down right? I am trying to send search result data through. The problem is submitting with ajax.
Please or to participate in this conversation.