How override the root app by Vuetify in Vuepress? - vuepress

I write a documentation about a custom VueJS (v2) framework with Vuepress (version 1.7).
By default, the root element is the classic VueJs <app>. But I need it's v-app from Vuetify.
I created a custom theme with this main layout :
<template>
<v-app style="background: white;">
<div
class="theme-container"
:class="pageClasses"
#touchstart="onTouchStart"
#touchend="onTouchEnd"
>
<Navbar
v-if="shouldShowNavbar"
#toggle-sidebar="toggleSidebar"
/>
<div
class="sidebar-mask"
#click="toggleSidebar(false)"
/>
<Sidebar
:items="sidebarItems"
#toggle-sidebar="toggleSidebar"
>
<template #top>
<slot name="sidebar-top" />
</template>
<template #bottom>
<slot name="sidebar-bottom" />
</template>
</Sidebar>
<Home v-if="$page.frontmatter.home" />
<Page
v-else
:sidebar-items="sidebarItems"
>
<template #top>
<slot name="page-top" />
</template>
<template #bottom>
<slot name="page-bottom" />
</template>
</Page>
</div>
</v-app>
</template>
<script>
import Home from '#theme/components/Home.vue'
import Navbar from '#theme/components/Navbar.vue'
import Page from '#theme/components/Page.vue'
import Sidebar from '#theme/components/Sidebar.vue'
import { resolveSidebarItems } from '../util'
export default {
name: 'Layout',
components: {
Home,
Page,
Sidebar,
Navbar
},
data () {
return {
isSidebarOpen: false
}
},
computed: {
shouldShowNavbar () {
const { themeConfig } = this.$site
const { frontmatter } = this.$page
if (
frontmatter.navbar === false
|| themeConfig.navbar === false) {
return false
}
return (
this.$title
|| themeConfig.logo
|| themeConfig.repo
|| themeConfig.nav
|| this.$themeLocaleConfig.nav
)
},
shouldShowSidebar () {
const { frontmatter } = this.$page
return (
!frontmatter.home
&& frontmatter.sidebar !== false
&& this.sidebarItems.length
)
},
sidebarItems () {
return resolveSidebarItems(
this.$page,
this.$page.regularPath,
this.$site,
this.$localePath
)
},
pageClasses () {
const userPageClass = this.$page.frontmatter.pageClass
return [
{
'no-navbar': !this.shouldShowNavbar,
'sidebar-open': this.isSidebarOpen,
'no-sidebar': !this.shouldShowSidebar
},
userPageClass
]
}
},
mounted () {
this.$router.afterEach(() => {
this.isSidebarOpen = false
})
},
methods: {
toggleSidebar (to) {
this.isSidebarOpen = typeof to === 'boolean' ? to : !this.isSidebarOpen
this.$emit('toggle-sidebar', this.isSidebarOpen)
},
// side swipe
onTouchStart (e) {
this.touchStart = {
x: e.changedTouches[0].clientX,
y: e.changedTouches[0].clientY
}
},
onTouchEnd (e) {
const dx = e.changedTouches[0].clientX - this.touchStart.x
const dy = e.changedTouches[0].clientY - this.touchStart.y
if (Math.abs(dx) > Math.abs(dy) && Math.abs(dx) > 40) {
if (dx > 0 && this.touchStart.x <= 80) {
this.toggleSidebar(true)
} else {
this.toggleSidebar(false)
}
}
}
}
}
</script>
The generated html is :
<body>
<div id="app">
<div id="app" data-app="true" class="v-application v-application--is-ltr theme--light" style="background: white;">
...
</div>
</div>
</body>
As you see, it has two div with the id "app". The second is expected, but not the first. I don't know where it comes from. Maybe automatically added by Vuepress.
How can I remove the first div?

Related

Adding an image to react-google-maps InfoWindow content

Note: I am using the "react-google-maps" api and this is how my current InfoWindow is set up
{showingInfoWindow && selectedPlace === spot._id && <InfoWindow
className="info-window"
onCloseClick={onInfoWindowClose}
position={{lat: spot.lat, lng: spot.lng}}
>
<div className="iw-container">
<strong className="iw-title">{spot.name}</strong>
<div className="iw-content">
{spot.location}
<div>Added By: {currentUser.displayName === spot.user ? "Me" : spot.user}</div>
<div>{spot.type}</div>
<div>{spot.desc}</div>
<div>{moment(spot.createdAt).format("MMM Do YYYY")}</div>
{/* <img src={`/server/uploads/${spot.createdAt.split('.')[0]+"Z"}.jpg`}> </img> */}
</div>
</div>
</InfoWindow>}
I was wondering how I add an image to the infowindow, I've seen it done with a content prop in other api's, and react-google-maps docs has a prop for updating the content, but I can't find how to set the content on their documentation. Any help is appreciated!
You can directly add an <img> tag as a child of the <infowindow>
Sample code snippet:
import React, { Component } from 'react';
import {
withGoogleMap,
GoogleMap,
Marker,
InfoWindow
} from 'react-google-maps';
class Map extends Component {
constructor(props) {
super(props);
this.state = {
isOpen: false
};
}
handleToggleOpen = () => {
this.setState({
isOpen: true
});
};
handleToggleClose = () => {
this.setState({
isOpen: false
});
};
render() {
const GoogleMapExample = withGoogleMap(props => (
<GoogleMap
defaultCenter={{ lat: -33.86882, lng: 151.209296 }}
defaultZoom={13}
>
<Marker
key={this.props.index}
position={{ lat: -33.86882, lng: 151.209296 }}
onClick={() => this.handleToggleOpen()}
>
{this.state.isOpen && (
<InfoWindow
onCloseClick={this.props.handleCloseCall}
>
<img src="https://www.australia.com/content/australia/en/places/sydney-and-surrounds/guide-to-sydney/jcr:content/mainParsys/imagecontainer/imageContainerParsys/imagehighlights_835593945/ImageTile/imageHighlightsSrc.adapt.740.medium.jpg" width="250px" height="250px"/>
</InfoWindow>
)}
</Marker>
</GoogleMap>
));
return (
<div>
<GoogleMapExample
containerElement={<div style={{ height: `500px`, width: '500px' }} />}
mapElement={<div style={{ height: `100%` }} />}
/>
</div>
);
}
}
export default Map;
I figured out the problem: I needed to use a self-closing img tag.
instead of
<img src="..."> </img>
it must be
<img src="..."/>

My project Vue 3 vuelidate sameAs not wokring

I'm working on a project written in vue 3 and I want to validate it on the login page but sameAs validate not working:
<div class="row">
<label for="password">Şifrə təkrarla</label>
<input
#blur="v$.repassword.$touch()"
v-model.lazy="repassword"
type="text"
name="repassword"
v-bind:class="{ 'is-invalid': !v$.repassword.$invalid }"
placeholder="********"
/>
<small class="validate_message" v-if="!v$.repassword.sameAs.$response"
>Yuxarıda yazdığınız şifrə ilə üst-üstə düşmür.</small
>
</div>
My script:
<script>
import useVuelidate from "#vuelidate/core";
import { sameAs } from "#vuelidate/validators";
export default {
setup() {
return { v$: useVuelidate() };
},
data() {
return {
repassword: "",
};
},
validations() {
return {
repassword: {
sameAs: sameAs(function() {
return this.password;
}),
},
};
},
};
</script>

Real-time search engine with VueJS and Laravel

I am doing the search engine section in VueJS and Laravel, but I have a problem that does not allow me to advance in the other sections. The search engine opens and everything but when I write it only sends the first letter or 2 but not all of them like this in this image:
image of the data you send
the data that I write
After that it shows me the following error in console:
Uncaught (in promise) NavigationDuplicated: Avoided redundant navigation to current location: "/search?q=th"
Now showing my search engine code:
<template>
<div class="form_MCycW">
<form autocomplete="off" #sumbit.prevent>
<label class="visuallyhidden" for="search">Search</label>
<div class="field_2KO5E">
<input id="search" ref="input" v-model.trim="query" name="search" type="text" placeholder="Search for a movie, tv show or person..." #keyup="goToRoute" #blur="unFocus">
<button v-if="showButton" type="button" aria-label="Close" #click="goBack">
<svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 15 15"><g fill="none" stroke="#fff" stroke-linecap="round" stroke-miterlimit="10" stroke-width="1.5"><path d="M.75.75l13.5 13.5M14.25.75L.75 14.25"/></g></svg>
</button>
</div>
</form>
</div>
</template>
<script>
import { mapState } from 'vuex';
export default {
data() {
return {
query: this.$route.query.q ? this.$route.query.q : ''
}
},
computed: {
showButton() {
return this.$route.name === 'search';
},
...mapState({
search: state => state.event.fromPage
})
},
mounted() {
this.$refs.input.focus();
},
methods: {
goToRoute() {
if (this.query) {
this.$router.push({
name: 'search',
query: { q: this.query },
});
} else {
this.$router.push({
path: this.fromPage,
});
}
},
goBack() {
this.query = '';
this.$router.push({
path: '/',
});
},
unFocus (e) {
if (this.$route.name !== 'search') {
const target = e.relatedTarget;
if (!target || !target.classList.contains('search-toggle')) {
this.query = '';
this.$store.commit('closeSearch');
}
}
}
}
}
</script>
This is the other section of the search engine:
<template>
<main class="main">
<div class="listing">
<div class="listing__head"><h2 class="listing__title">{{ title }}</h2></div>
<div class="listing__items">
<div class="card" v-for="(item, index) in data.data" :key="index">
<router-link :to="{ name: 'show-serie', params: { id: item.id }}" class="card__link">
<div class="card__img lazyloaded"><img class="lazyload image_183rJ" :src="'/_assets/img/covers/posters/' + item.poster" :alt="item.name"></div>
<h2 class="card__name">{{ item.name }}</h2>
<div class="card__rating">
<div class="card__stars"><div :style="{width: item.rate * 10 + '%'}"></div></div>
<div class="card__vote">{{ item.rate }}</div>
</div>
</router-link>
</div>
</div>
</div>
</main>
</template>
<script>
import { mapState } from 'vuex';
let fromPage = '/';
export default {
name: "search",
metaInfo: {
bodyAttrs: {
class: 'page page-search'
}
},
computed: {
...mapState({
data: state => state.search.data,
loading: state => state.search.loading
}),
query() {
return this.$route.query.q ? this.$route.query.q : '';
},
title() {
return this.query ? `Results For: ${this.query}` : '';
},
},
async asyncData ({ query, error, redirect }) {
try {
if (query.q) {
this.$store.dispatch("GET_SEARCH_LIST", query.q);
} else {
redirect('/');
}
} catch {
error({ message: 'Page not found' });
}
},
mounted () {
this.$store.commit('openSearch');
this.$store.commit('setFromPage', fromPage);
if (this.data.length == 0 || this.data === null) {
this.$store.dispatch("GET_SEARCH_LIST", this.query);
}
setTimeout(() => {
this.showSlideUpAnimation = true;
}, 100);
},
beforeRouteEnter (to, from, next) {
fromPage = from.path;
next();
},
beforeRouteUpdate (to, from, next) {
next();
},
beforeRouteLeave (to, from, next) {
const search = document.getElementById('search');
next();
if (search && search.value.length) {
this.$store.commit('closeSearch');
}
}
};
</script>
In my routes section it is defined as follows:
{
name: 'search',
path: '/search',
component: require('../views/' + themeName + '/control/search/index').default
}
It is supposed to be a real-time search engine. I would appreciate your help in solving this problem...
What you need is a debounce. What it does is that it wait or delay till the user had finished typing before the model get updated or before you send it to the server.
An example of how it works is here
Here is a package for it.
https://github.com/vuejs-tips/v-debounce

Search functionality with rest api prevent DDOSing the server

The Problem
I have a search component and component which implements the search component. When I type something in the search bar after 1/2 second of not typing (debounce) the server should be hit and the results should be returned.
The solution i am trying to implement comes from this post on Stackoverflow
The code
This leads me to the following code.
I have search.vue
<template>
<label for="search">
<input
id="search"
class="w-full py-2 px-1 border-gray-900 border"
type="text"
name=":searchTitle"
v-model="searchFilter"
:placeholder="searchPlaceholder"
autocomplete="off"
v-on:keydown="filteredDataset"
/>
</label>
</template>
<script>
import {debounce} from 'lodash';
export default {
props: {
searchPlaceholder: {
type: String,
required: false,
default: ''
},
searchName: {
type: String,
required: false,
default: 'search'
}
},
data() {
return {
searchFilter: '',
}
},
methods: {
filteredDataset() {
console.log('event fired');
this.$emit('searchValue', this.searchFilter);
}
},
}
</script>
And product.vue
<template>
<div>
<div class="my-4">
<search
search-placeholder=""
search-name=""
v-on:searchValue="filterValue = $event"
v-model="productsFiltered"
>
</search>
<div class="flex w-full py-1 border px-2 my-2" v-for="product in productsFiltered"> (...)
</div>
</div>
</div>
</div>
</template>
<script>
import {debounce} from 'lodash';
export default {
data() {
return {
products: [],
filterValue: '',
filteredProducts: ''
}
},
computed: {
productsFiltered: {
get(){
console.log('getter called');
return this.filteredProducts;
},
set: _.debounce(function(){
console.log('setter called');
if (this.filterValue.length < 1) {
this.filteredProducts = [];
}
axios.get(`${apiUrl}search/` + this.filterValue)
.then(response => {
this.products = response.data.products;
const filtered = [];
const regOption = new RegExp(this.filterValue, 'ig');
for (const product of this.products) {
if (this.filterValue.length < 1 || product.productname.match(regOption)) {
filtered.push(product);
}
}
this.filteredProducts = filtered;
});
}, 500)
}
},
}
</script>
The result
The result is that the setter in the computed property in product.vue does not get called and no data is fetched from the server. Any ideas on how to solve this?
Your first code block imports debounce but does not use it. It also declares a prop, searchName, that isn't used. These aren't central issues, but clutter makes it harder to figure out what's going on.
Your second code block uses v-model but does not follow the required conventions for getting v-model to work with components:
the component must take a prop named value
the component must emit input events to signal changes to value
You have the component emit searchValue events, and handle them with a v-on that sets a data item. You seem to expect the v-model to call the setter, but as I noted, you haven't hooked it up to do so.
From what's here, you don't even really need to store the input value. You just want to emit it when it changes. Here's a demo:
const searchComponent = {
template: '#search-template',
props: {
searchPlaceholder: {
type: String,
required: false,
default: ''
}
},
methods: {
filteredDataset(searchFilter) {
console.log('event fired');
this.$emit('input', searchFilter);
}
}
};
new Vue({
el: '#app',
data() {
return {
products: [],
filterValue: '',
filteredProducts: ''
}
},
components: {
searchComponent
},
computed: {
productsFiltered: {
get() {
console.log('getter called');
return this.filteredProducts;
},
set: _.debounce(function() {
console.log('setter called');
if (this.filterValue.length < 1) {
this.filteredProducts = [];
}
setTimeout(() => {
console.log("This is the axios call");
this.filteredProducts = ['one','two','three'];
}, 200);
}, 500)
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.js"></script>
<template id="search-template">
<label for="search">
<input
id="search"
class="w-full py-2 px-1 border-gray-900 border"
type="text"
name=":searchTitle"
:placeholder="searchPlaceholder"
autocomplete="off"
#input="filteredDataset"
/>
</label>
</template>
<div id="app">
<div class="my-4">
<search-component search-placeholder="enter something" v-model="productsFiltered">
</search-component>
<div class="flex w-full py-1 border px-2 my-2" v-for="product in productsFiltered"> (...)
</div>
</div>
</div>

Display passed image in template, in VUE

So I have this code:
<template>
<div id="search-wrapper">
<div>
<input
id="search_input"
ref="autocomplete"
placeholder="Search"
class="search-location"
onfocus="value = ''"
#keyup.enter.native="displayPic"
>
</div>
</div>
</template>
<script>
import VueGoogleAutocomplete from "vue-google-autocomplete";
export default {
data: function() {
return {
search_input: {},
pic: {}
};
},
mounted() {
this.autocomplete = new google.maps.places.Autocomplete(
this.$refs.autocomplete
);
this.autocomplete.addListener("place_changed", () => {
let place = this.autocomplete.getPlace();
if (place.photos) {
place.photos.forEach(photo => {
let pic=place.photos[0].getUrl()
console.log(pic);
});
}
});
},
methods: {
displayPic(ref){
this.autocomplete = new google.maps.places.Autocomplete(
this.$refs.autocomplete);
this.autocomplete.addListener("place_changed", () => {
let place = this.autocomplete.getPlace();
if (place.photos) {
place.photos.forEach(photo => {
let pic=place.photos[0].getUrl()
console.log(pic);
});
}
})
},
}
}
I want to pass the "pic" parameter, resulted in displayPic, which is a function, into my template, after one of the locations is being selected.
I've tried several approaches, but I'm very new to Vue so it's a little bit tricky, at least until I'll understand how the components go.
Right now, the event is on enter, but I would like it to be triggered when a place is selected.
Any ideas how can I do that?
Right now, the most important thing is getting the pic value into my template.
<template>
<div id="search-wrapper">
<div>
<input style="width:500px;"
id="search_input"
ref="autocomplete"
placeholder="Search"
class="search-location"
onfocus="value = ''"
v-on:keyup.enter="displayPic"
#onclick="displayPic"
>
<img style="width:500px;;margin:5%;" :src="pic" >
</div>
</div>
</template>
<script>
import VueGoogleAutocomplete from "vue-google-autocomplete";
export default {
data: function() {
return {
search_input: {},
pic:""
};
},
mounted() {
this.autocomplete = new google.maps.places.Autocomplete(
this.$refs.autocomplete,
{componentRestrictions: {country: "us"}}
);
},
methods: {
displayPic: function(){
this.autocomplete.addListener("place_changed", () => {
let place = this.autocomplete.getPlace();
if (place.photos) {
place.photos.forEach(photo => {
this.pic=place.photos[0].getUrl()
});
}
})
},
}
}
</script>

Resources