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;
Related
We are building a web application using Vue JS and PHP, we are new to Vue JS. The server-side execution is fine, the API is able to fetch data as JSON. While trying out a static array display before making the API call, we find that the function in imported "app.js" is not getting called and the table displayed is empty. Please let us know what we might be doing wrong. Appreciate your help.
import Vue from 'vue';
export const MY_CONST = 'Vue.js';
export let memberList = new Vue({
el: '#members',
data: {
members: []
},
mounted: function () {
this.getAllMembers();
},
methods: {
getAllMembers: function () {
/*
axios.get("https://xxxxxx.com/services/api.php")
.then(function (response) {
memberList.members = response.data.members;
});
*/
memberList.members = [{ "empname": "Dinesh Dassss" },
{ "empname": "Kapil Koranne" }];
}
}
});
This is the Vue component. The members object is empty.
<script>
import * as mykey from './app.js'
export default {
name: 'Home',
props: {
msg: String
},
data() {
return {
message: `Hello ${mykey.MY_CONST}!`,
members: mykey.memberList.members
}
}
};
</script>
You can also use this reference for current instance reference:
getAllMembers: function () {
var me = this;
/*
axios.get("https://xxxxxx.com/services/api.php")
.then(function (response) {
// direct this not works here but we have
//saved this in another variable and scope of a var is there
me.members = response.data.members;
});
*/
// this reference works fine here.
this.members = [{ "empname": "Dinesh Dassss" },
{ "empname": "Kapil Koranne" }];
}
I want a global dialog component which is called only by JavaScript. And never had custom content within it. So I don't want to put any HTML tag like <my-dialog ref="myDialog"></my-dialog> in my code. Just call this.$ref.myDialog.show().
I have a HTML tag version. How to instance the component only in JavaScript?
I think you neeed to create a JS window with a component inside
here is an example:
var componentName = "my-dialog";
var model = {d:1};
var d = document.createElement("div"); // JavaScript Window
document.body.appendChild(d);
d.id = 'win' + componentName;
var app = new Vue({
render(h, data) {
return h(componentName, { on: { 'close': this.close }, props: { model: this.model } });
},
el: d, data: { wait: false, error: "", after: 0, model },
mounted() {
},
methods: {
close() {
this.$destroy();
$(d).remove(); // remove the window by jQuery
}
}
})
First you need to assign Vue to window.vue.
window.vue = new Vue({ // options })
then call it using js. vue.$ref.myDialog.show()
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
})
}
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>
I have a very simple page, just a few lines of code and I would like to use vue-router without a vue-component.
I would like to get the string "my-search-query" after the root:
www.example.com/my-search-query
and use it in my main Vue() object. Is this possible?
Thank you!
It is possible to use vue-router without a vue-component and still be notified about path changes in the main Vue() app object:
You can get the "my-search-query" string from www.example.com/#/my-search-query like this:
const router = new VueRouter({
routes: [
{ path: '/:question' }
]
})
const app = new Vue({
el: '#app',
router,
watch: {
'$route.params.question': function(newVal, oldVal) {
alert(newVal === 'my-search-query');
}
}
});
Thanks to #samayo for pointing me in the right direction!
Since the router is part of your view model instance, you can create a computed function that returns the path value of the router. For example, you can do the following:
data: function() {
router: new VueRouter()
},
computed: function() {
routerPath: function() {
return this.router.currentRoute.path;
}
},
watch: function() {
routerPath: function(val) {
switch(val) {
case '/':
// #TODO: logic for home
break;
case '/path1':
var query = this.router.currentRoute.query;
// #TODO: logic form query params
break;
}
}
}
With this, the router will manage the history for you, but you don't have to divide your code up into components. For example, I have a pretty small page and just wanted to put everything into the same viewModel without breaking everything up into components.
As tom_h has said you can use window.location.pathname if you just want to get the path name. If you want to be notified when this pathname changes, then first initialize it to your data(){} object and watch it as:
data () {
return {
path: window.location.pathname
}
},
watch: {
path (newpath, oldpath) {
alert('location path just changed from ' + newpath + ' to ' + oldpath )
}
}