How to check if user is in fullscreen mode inside a vue component? - vue.js

I have a vue component and I wanted to track when the user changes the browser to fullscreen or exits the full screen. I tried adding event listeners when the component is mounted but these events are never called when I try to go full screen or return to the windowed mode. No errors are displayed in the console. The component itself is not full screen. I have a laravel blade page running and this code is inside one of the vue components being displayed there.
Here is the code in the Vue Component:
<script>
export default {
mounted() {
document.addEventListener('fullscreenchange',this.logFullScreen);
console.log("Mounted");
},
methods: {
currentDatetime: function () {
var today = new Date();
var date = today.getFullYear()+'-'+(today.getMonth()+1)+'-'+today.getDate();
var time = today.getHours() + ":" + today.getMinutes() + ":" + today.getSeconds();
return date+' '+time;
},
logFullScreen: function () {
if(document.fullscreenElement)
{
console.log("Changed to fullscreen at "+this.currentDatetime);
}
else
{
console.log("Exited fullscreen at "+this.currentDatetime);
}
}
},
destroyed() {
document.removeEventListener('fullscreenchange',this.logFullScreen);
}
}
</script>

When you go fullscreen by clicking F11. The event 'fullscreenchange' won't be triggerd.
fullscreenchange event is triggered when a fullscreenrequest is invoked.
You can try this:
$(document).on('keydown', function(event) {
if (event.which == 122) {
// check if fullscreen or not ...
}
});
Sorry for my bad English

Related

show nav icon if open in small resolution or mobile vue js

i have issue how to show nav icon if open in mobile screen,
this is the code is bellow
export default {
data () {
return {
navIcon:false
}
},
computed:{
},
methods:{
reversedMessage: function () {
return this.navIcon=true
},
test:function(){
if(screen.width < 960){
return this.navIcon=true
}
}
}
}
Use media breakpoints in CSS or if you don't want to go in that direction just use this plugin of vue vue-breakpoints . It will save your time as well on applying logics. Use show-at and hide-at with breakpoints according to your requirement

Open modal dialog on event bus event

I've created a backend and am now trying to build a frontend, using it. I'm very new to Vue.js and am having a hard time telling it to do what I want; probably because of missing some basic concepts. Hopefully someone can point me in the right direction.
The App.vue groups following components: Header, main section (routed), footer and a modal login dialog.
The issue I'm trying to solve is to display the modal login dialog when clicking the Login button (which lives in the header component); currently, nothing besides the messages being logged happens.
For this I've created an event bus and am firing an event:
export default {
name: 'SppdTeamTunerHeader',
methods: {
emitShowLoginDialogEvent () {
EventBus.$emit('ShowLoginDialog', true)
}
}
}
Emitting the event works as I can see in the Vue DevTools for Chrome.
Here's the complete code of App.vue:
<template>
<div id="app">
<SppdTeamTunerHeader/>
<router-view></router-view>
<SppdTeamTunerFooter/>
<LoginDialogModal
v-show="isLoginDialogVisible"
/>
</div>
</template>
<script>
import SppdTeamTunerHeader from '#/components/TheHeader'
import SppdTeamTunerFooter from '#/components/TheFooter'
import LoginDialogModal from '#/components/LoginDialogModal'
import { EventBus } from '#/common/EventBus'
export default {
name: 'App',
components: {
SppdTeamTunerHeader,
SppdTeamTunerFooter,
LoginDialogModal
},
data: function () {
return {
isLoginDialogVisible: false
}
},
mounted () {
EventBus.$on('ShowLoginDialog', function (isVisible) {
console.log('Setting ShowLoginDialog isVisible=' + isVisible + '. isLoginDialogVisible=' + this.isLoginDialogVisible)
if (isVisible) {
this.isLoginDialogVisible = true
} else {
this.isLoginDialogVisible = false
}
console.log('Finished setting isLoginDialogVisible=' + this.isLoginDialogVisible)
})
},
destroyed () {
EventBus.$off('ShowLoginDialog')
}
}
</script>
When checking the console, following is being printed when clicking the login button:
Setting ShowLoginDialog isVisible=true. isLoginDialogVisible=undefined
Finished setting isLoginDialogVisible=true
The value logged for isLoginDialogVisible can't come from the variable defined in the data function as it prints undefined, whereas it has been defined as false (I guess that's my main problem).
I've read quite a few articles about the subject, e.g:
https://codingexplained.com/coding/front-end/vue-js/why-components-data-properties-must-be-functions
https://v2.vuejs.org/v2/guide/instance.html#Data-and-Methods
The modal dialog example I've based the implementation comes from here: https://alligator.io/vuejs/vue-modal-component/
This is happening because you are not using an Arrow function. Instead of a plain function, use arrow function like this:
mounted () {
// Note the use of arrow function.
EventBus.$on('ShowLoginDialog', (isVisible) => {
// .. All your code
})
}
If you use plain function function () {}, then this pointer is not accessible within inner function. Arrow function will lexically bind this pointer to mounted() function's this context. So use an arrow function i.e. () => {};
Note: If you insist on using plain old function syntax then use closure variable to keep track of this pointer:
mounted () {
// Assign this pointer to some closure variable
const vm = this;
EventBus.$on('ShowLoginDialog', function (isVisible) {
console.log('Setting ShowLoginDialog isVisible=' + isVisible + '. isLoginDialogVisible=' + vm.isLoginDialogVisible)
if (isVisible) {
vm.isLoginDialogVisible = true
} else {
vm.isLoginDialogVisible = false
}
console.log('Finished setting isLoginDialogVisible=' + vm.isLoginDialogVisible)
})
}
This has nothing to do with Vue.js. It is a typical JavaScript behavior.
I believe your listener for the EventBus events needs to be accessible to App. Right now EventBus and App are two separate instances. You could mount the event handler inside App like this:
mounted () {
EventBus.$on('ShowLoginDialog', function (isVisible) {
...
});

Vuejs 'beforeunload' event not triggered as expected

I have registered 'beforeunload' event on created hook of the component used by routes of vue router.
I want to call this event handler in order to remove user on browser tab close or browser tab refresh or browser close.
On ComponentA
created (){
window.addEventListener('beforeunload', () => {
this.removeUser()
return null
})
}
Smilarly on ComponentB
created (){
window.addEventListener('beforeunload', () => {
this.removeUser()
return null
})
}
And my router.js
{
path: '/staff/call/:session_key',
name: 'Staff Call',
component: ComponentA,
meta: {auth: true}
},
{
path: '/consumer/call/:session_key',
name: 'Consumer Call',
component: ComponentB
},
Here 'beforeunload' event handler is triggered randomly. That is sometimes it get triggered and sometimes not. I count find any pattern when it is triggered and when it is not.
What am I missing here?
Edit
I'd guess the most likely culprit then is exactly what #PatrickSteele said. From MDN:
Note: To combat unwanted pop-ups, some browsers don't display prompts
created in beforeunload event handlers unless the page has been
interacted with; some don't display them at all. For a list of
specific browsers, see the Browser_compatibility section.
I'd say it's likely you're seeing inconsistent behavior because you are sometimes not interacting with the page.
This may be a syntax error. created should be a method
created () {
window.addEventListener('beforeunload', this.removeUser)
},
methods: {
removeUser () {
//remove user here
}
}
A fiddle working: https://jsfiddle.net/e6m6t4kd/3/
It's work for me. while do something before reload or close in
vue.js
created() {
window.onbeforeunload = function(){
return "handle your events or msgs here";
}
}
I had to do some fiddling on the above examples, I believe this is the most robust solution:
let app1 = new Vue({
delimiters: ['[[', ']]'],
el: '#app',
data: {
dirty_form: true,
},
created () {
console.log('created')
window.addEventListener('beforeunload', this.confirm_leaving)
},
methods: {
confirm_leaving (evt) {
if (this.dirty_form) {
const unsaved_changes_warning = "You have unsaved changes. Are you sure you wish to leave?";
evt.returnValue = unsaved_changes_warning;
return unsaved_changes_warning;
};
};
},
});
If you want detect page refresh/change in Vue whenever you press F5 or Ctrl + R, You may need to use Navigation Timing API.
The PerformanceNavigation.type, will tell you how the page was accessed.
created() {
// does the browser support the Navigation Timing API?
if (window.performance) {
console.info("window.performance is supported");
}
// do something based on the navigation type...
if(performance.navigation.type === 1) {
console.info("TYPE_RELOAD");
this.removeUser();
}
}
Not sure why none of the above were fully working for me in vue 3 composition api. Abdullah's answer partially works but he left out how to remove the listener.
setup() {
const doSomething = (e) => {
// do stuff here
return true
}
onBeforeMount(() => {
window.onbeforeunload = handleLeaveWithoutSaving
})
onUnmounted(() => {
window.onbeforeunload = null
})
}

Open a VueJS component on a new window

I have a basic VueJS application with only one page.
It's not a SPA, and I do not use vue-router.
I would like to implement a button that when clicked executes the window.open() function with content from one of my Vue Components.
Looking at the documentation from window.open() I saw the following statement for URL:
URL accepts a path or URL to an HTML page, image file, or any other resource which is supported by the browser.
Is it possible to pass a component as an argument for window.open()?
I was able to use some insights from an article about Portals in React to create a Vue component which is able to mount its children in a new window, while preserving reactivity! It's as simple as:
<window-portal>
I appear in a new window!
</window-portal>
Try it in this codesandbox!
The code for this component is as follows:
<template>
<div v-if="open">
<slot />
</div>
</template>
<script>
export default {
name: 'window-portal',
props: {
open: {
type: Boolean,
default: false,
}
},
data() {
return {
windowRef: null,
}
},
watch: {
open(newOpen) {
if(newOpen) {
this.openPortal();
} else {
this.closePortal();
}
}
},
methods: {
openPortal() {
this.windowRef = window.open("", "", "width=600,height=400,left=200,top=200");
this.windowRef.addEventListener('beforeunload', this.closePortal);
// magic!
this.windowRef.document.body.appendChild(this.$el);
},
closePortal() {
if(this.windowRef) {
this.windowRef.close();
this.windowRef = null;
this.$emit('close');
}
}
},
mounted() {
if(this.open) {
this.openPortal();
}
},
beforeDestroy() {
if (this.windowRef) {
this.closePortal();
}
}
}
</script>
The key is the line this.windowRef.document.body.appendChild(this.$el); this line effectively removes the DOM element associated with the Vue component (the top-level <div>) from the parent window and inserts it into the body of the child window. Since this element is the same reference as the one Vue would normally update, just in a different place, everything Just Works - Vue continues to update the element in response to databinding changes, despite it being mounted in a new window. I was actually quite surprised at how simple this was!
You cannot pass a Vue component, because window.open doesn't know about Vue. What you can do, however, is to create a route which displays your component and pass this route's URL to window.open, giving you a new window with your component. Communication between the components in different windows might get tricky though.
For example, if your main vue is declared like so
var app = new Vue({...});
If you only need to render a few pieces of data in the new window, you could just reference the data model from the parent window.
var app1 = window.opener.app;
var title = app.title;
var h1 = document.createElement("H1");
h1.innerHTML = title;
document.body.appendChild(h1);
I ported the Alex contribution to Composition API and works pretty well.
The only annoyance is that the created window ignores size and position, maybe because it is launched from a Chrome application that is fullscreen. Any idea?
<script setup lang="ts">
import {ref, onMounted, onBeforeUnmount, watch, nextTick} from "vue";
const props = defineProps<{modelValue: boolean;}>();
const emit = defineEmits(["update:modelValue"]);
let windowRef: Window | null = null;
const portal = ref(null);
const copyStyles = (sourceDoc: Document, targetDoc: Document): void => {
// eslint-disable-next-line unicorn/prefer-spread
for(const styleSheet of Array.from(sourceDoc.styleSheets)) {
if(styleSheet.cssRules) {
// for <style> elements
const nwStyleElement = sourceDoc.createElement("style");
// eslint-disable-next-line unicorn/prefer-spread
for(const cssRule of Array.from(styleSheet.cssRules)) {
// write the text of each rule into the body of the style element
nwStyleElement.append(sourceDoc.createTextNode(cssRule.cssText));
}
targetDoc.head.append(nwStyleElement);
}
else if(styleSheet.href) {
// for <link> elements loading CSS from a URL
const nwLinkElement = sourceDoc.createElement("link");
nwLinkElement.rel = "stylesheet";
nwLinkElement.href = styleSheet.href;
targetDoc.head.append(nwLinkElement);
}
}
};
const openPortal = (): void => {
nextTick().then((): void => {
windowRef = window.open("", "", "width=600,height=400,left=200,top=200");
if(!windowRef || !portal.value) return;
windowRef.document.body.append(portal.value);
copyStyles(window.document, windowRef.document);
windowRef.addEventListener("beforeunload", closePortal);
})
.catch((error: Error) => console.error("Cannot instantiate portal", error.message));
};
const closePortal = (): void => {
if(windowRef) {
windowRef.close();
windowRef = null;
emit("update:modelValue", false);
}
};
watch(props, () => {
if(props.modelValue) {
openPortal();
}
else {
closePortal();
}
});
onMounted(() => {
if(props.modelValue) {
openPortal();
}
});
onBeforeUnmount(() => {
if(windowRef) {
closePortal();
}
});
</script>
<template>
<div v-if="props.modelValue" ref="portal">
<slot />
</div>
</template>

Handle methods differently in Vue depending on mobile or not

I'm having trouble setting up my Vue components to handle their methods differently if the user is on mobile. For instance a navigation drop down, if a user clicks on a link, I want to prevent them from going to that location, but instead drop down the drop down. Whereas on desktop, I want them to go to it if they click on it and only drop down on hover. I'll need this for so many other aspects of my project.
I have a main Vue instance:
var Main = new Vue({
el: 'body',
data: {
mobile: true
},
ready: function() {
if( document.clientWidth >= 992 )
{
this.mobile = false;
}
}
});
export default Main;
Then for my components, I'm doing something like this:
import Main from './../Main';
var NavLink = Vue.component('navlink', {
template: '#nav-link-template',
replace: true,
data: function() {
return {
}
},
props: ['text', 'url'],
ready: function() {
},
methods: {
handleClick: function(e) {
e.preventDefault();
console.log(Main.mobile);
if( Main.mobile )
{
if( this.$children.length )
{
// Has dropdown
this.$children[0].dropDown();
}
else
{
// No dropdown so redirect user
window.location = this.url;
}
}
else
{
// Not mobile so let user go
window.location = this.url;
}
}
}
});
Not only does Main.mobile return the default value no matter what resolution because their ready methods seem to run BEFORE the Main ready method.. but this also feels like the wrong setup.
Thanks for any insight.
First, according to you code, you dont need Main commonjs module to be a vuejs instance. Make it as a simple js object
Main = {
mobule: document.clientWidth >= 992
}
export default Main;
Or you may want to handle client window size dynamically
var Main = new Vue({
created: function() {
// dunno why v-on="resize: func" not working
global.window.addEventListener('resize', function () {
//calc width and heigh somehow
self.$broadcast('resize', width, heigh);
});
}
});
export default Main;