How to manipulate DOM in Vue dynamically and detect user OS - vue.js

I am trying to detect user OS using the User Agent in Vue. I've tried everything I know like using document and getElementById and I tried to use ref's as shown below, but I just can't get it to work. Any help is welcomed, thank you.
<template>
<v-container>
<v-container fluid d-flex justify-center align-center flex-xs-column class="grey lighten-3">
<div class="d-flex justify-space-between align-center flex-xs-column">
<div class="d-flex flex-column">
HERE ---><h1 ref="downloadTitle">yep</h1> <----
HERE ---><p ref="downloadDesc">nope</p> <----
<!-- <h1 v-if="navigator.userAgent.includes('win') == true"> Download For Windows </h1>
<h1 v-if="navigator.userAgent.includes('Linux') == true"> Download For Linux </h1>
<h1 v-if="navigator.userAgent.includes('Android') == true || navigator.userAgent.includes('like Mac') == true"> Not Available For Download On This Device </h1> -->
<v-btn dark min-width="100" max-width="20%" class="ma-auto">Download</v-btn>
</div>
<v-divider vertical></v-divider>
<v-img src="https://gamepedia.cursecdn.com/minecraft_gamepedia/6/62/Dirt_JE2_BE1.png" max-height="10%" class="ma-8"></v-img>
</div>
</v-container>
<v-divider class="grey"></v-divider>
<v-container fluid d-flex justify-center class="mb-12">
<div class="d-flex flex-column justify-center text-center">
<h1 class="ma-4"> Download For Your Other Devices </h1>
<div class="d-flex justify-space-around">
<v-card min-width="40%" class="text-center d-inline-block">
<v-img src="https://upload.wikimedia.org/wikipedia/commons/thumb/5/5f/Windows_logo_-_2012.svg/1024px-Windows_logo_-_2012.svg.png" aspect-ratio="1" max-width="100" contain class="ma-auto mt-4"></v-img>
<v-card-subtitle class="d-block text-wrap ma-auto">Download For Windows</v-card-subtitle>
<v-card-actions>
<v-btn dark class="ma-auto" href="">Download</v-btn>
</v-card-actions>
</v-card>
<v-card min-width="40%" class="text-center d-inline-block">
<v-img src="https://cdn3.iconfinder.com/data/icons/logos-brands-3/24/logo_brand_brands_logos_linux-512.png" aspect-ratio="1" max-width="100" contain class="ma-auto mt-4"></v-img>
<v-card-subtitle class="d-block text-wrap text-center">Download For Linux</v-card-subtitle>
<v-card-actions>
<v-btn dark class="ma-auto" href="">Download</v-btn>
</v-card-actions>
</v-card>
</div>
</div>
</v-container>
</v-container>
<script>
export default {
name: "Downloads",
methods: {
detectOS: function detectOS() {
var name = "Unknown OS";
var download;
var desc;
if (navigator.userAgent.includes("win") != -1) {
name = "Windows";
}
else if (navigator.userAgent.includes("Mac") != -1) {
name = "Mac";
}
else if (navigator.userAgent.includes("Linux") != -1) {
name = "Linux";
}
else if (navigator.userAgent.includes("Android") != -1) {
name = "Android OS";
}
else if (navigator.userAgent.includes("like Mac") != -1) {
name = "iOS";
}
else {
name;
};
},
mounted() {
detectOS();
this.$ref.downloadTitle.innerHTML = download;
this.downloadDesc.innerHTML = desc;
alert("hey");
}
};
</script>
<script scoped></script>
I've tried putting it all in mounted and created, however nothing works. Thanks in advance!

Of course nothing works, mate, your detectOS method doesn't return or update anything.
Also what is all this code in the detectOS method?
if (navigator.userAgent.includes("win") != -1)
...
This code doesn't make any sense, and it's not how you get OS name. You can learn how to do it on this page
After you fix this, do the folowing:
Step 1:
Move all your properties to data(). Mounted hook cannot access your desc, download, and name properties inside the detectOS method.
data () {
return {
desc: '',
download: '',
osName: 'Unknown OS'
}
},
methods: {
...
Step 2:
Make sure your detectOS method gets OS name properly. Log the name variable and make sure is not still equal to "Unknown OS"
...
else {
name;
};
console.log(name)
If it's still equals to "Unknown OS" it means you're still not getting the OS name properly.
Step 3:
Update the osName property so that other methods can use it.
data () {
return {
desc: '',
download: '',
osName: 'Unknown OS'
}
},
methods: {
detectOS() {
// Get the OS name
...
// Update property
this.osName = name
}
}

Like here
data: {
userAgent:"",
userOs:""
},
mounted() {
console.log('userAgent; ',navigator.userAgent);
this.userAgent = navigator.userAgent || '';
this.userOs = navigator.platform || '';
let osType = this.userOs.toLowerCase();
if(osType.includes('win')) alert('win');
else if(osType.includes('mac')) alert('mac');
else if(osType.includes('ios')) alert('ios');
else if(osType.includes('android')) alert('android');
else if(osType.includes('linux')) alert('linux');
else alert('unkown os');
}

Related

Passing props from child to parent Vuejs

I have this child component called BaseCardAnnotationOption
<template>
<v-card>
<v-card-title class="text-h5 text-center indigo darken-1 lighten-2">
<slot name="title"> </slot>
</v-card-title>
<v-card-text>
<v-checkbox
v-model="includeAnnotations1"
label="Include Annotations"
#change="deselectDisabled()"
></v-checkbox>
<v-radio-group class="mt-0 ml-12" v-model="annotationOption1">
<v-radio
:value="embedIntoImage"
label="Embedded Into Image"
:disabled="includeAnnotations1 === false"
></v-radio>
<v-radio
:value="burnIntoImage"
label="Burn Into Image"
:disabled="includeAnnotations1 === false"
></v-radio>
<v-checkbox
class="mt-0 ml-8"
v-model="maintainColor1"
label="Maintain Annotation Color "
:disabled="annotationOption1 === 0 || includeAnnotations1 === false"
></v-checkbox>
</v-radio-group>
<v-checkbox
class="mt-0"
v-model="burnReduction1"
label="Burn Reduction"
></v-checkbox>
</v-card-text>
</v-card>
</template>
<script>
export default {
name: "BaseCardAnnotationOption",
data() {
return {
includeAnnotations1: false,
embedIntoImage: null,
burnIntoImage: null,
burnReduction1: false,
maintainColor1: false,
annotationOption1: null
};
},
methods: {
deselectDisabled() {
return this.includeAnnotations1 === false
? ((this.annotationOption1 = null), (this.maintainColor1 = false))
: ((this.annotationOption1 = 0), (this.maintainColor1 = false));
}
}
};
</script>
<style scoped></style>
And i am using it in another parent component named viewer like this
<base-card-annotation-option>
<template v-slot:title>
Print Document
</template>
</base-card-annotation-option>
Now i want to use this function in Viewer(parent) which requires data that has been
declared in BaseCardAnnotationOption(child) as shown below
<base-tooltip-button
#click="
onPrintFile(
document,
asPdf,
includeAnnotations1,
annotationOption1,
burnReduction1,
maintainColor1
)
"
>
Print
<template v-slot:toolTip>
Print
</template>
</base-tooltip-button>
So basically some data has been declared in child which i want to send to parent . How can that be achieved ? Thanks

How do I lazy load item lists on Vuejs and Vuetify's lazyload?

Im trying to make an infinite scroll list but it's really not lazy loading, been stuck with this for hours, the whole list will come in.
.....
<v-col
v-for="(post, i) in posts"
:key="i"
cols="12"
>
<v-lazy
v-model="isActive"
:options="{
threshold: .5
}"
transition="fade-transition"
>
{{Content here}}
</....
API used for test : https://jsonplaceholder.typicode.com/posts
There is a new virtual-scroller component, but it doesn't work with responsive content like grid rows/cols. Instead use v-lazy...
I discovered that the columns need to have defined min-height (approx. to the expected height of the cards) in order for the v-lazy intersection observer to work. Use something like a v-sheet or v-responsive to set the min-height and contain the cards.
Also bind the v-model of the v-lazy to each post (ie: post.isActive), instead of a global isActive var...
<v-col lg="3" md="4" sm="6" cols="12" v-for="(post, index) in posts">
<v-sheet min-height="250" class="fill-height" color="transparent">
<v-lazy
v-model="post.isActive" :options="{
threshold: .5
}"
class="fill-height">
<v-card class="fill-height" hover>
<v-card-text>
<v-row :key="index" #click="">
<v-col sm="10" cols="12" class="text-sm-left text-center">
#{{ (index+1) }}
<h2 v-html="post.title"></h2>
<div v-html="post.body"></div>
</v-col>
</v-row>
</v-card-text>
</v-card>
</v-lazy>
</v-sheet>
</v-col>
Demo: https://codeply.com/p/eOZKk873AJ
I can suggest another solution with v-intersect, which works perfect for me.
Sorry, the snippet may be not working as composed of my code, but the idea should be pretty clear
<template>
<v-list class="overflow-y-auto" max-height="500">
<v-list-item v-for="item in items">
{{ item.name }}
</v-list-item>
<v-skeleton-loader v-if="moreDataToAvailable" v-intersect="loadNextPage" type="list-item#5" />
</v-list>
</template>
<script lang="ts">
import Vue from 'vue'
const pageSize = 10
export default Vue.extend({
data(): any {
return {
pageLoaded: 0,
totalCount: 100,//fetch from API
items: []
}
},
computed: {
moreDataToAvailable (): boolean {
return Math.ceil(this.totalCount / pageSize) - 1 > this.pageLoaded
}
},
methods {
async loadNextPage (entries: IntersectionObserverEntry[]) {
if (entries[0].isIntersecting && this.moreDataToAvailable) {
const nextPage = this.pageLoaded + 1
const loaded = await this.loadPage(nextPage) //API call
loaded.data.forEach((item: any) => this.items.push(item))
this.totalCount = loaded.totalCount
this.pageLoaded = nextPage
}
},
}
})
</script>

validateAll doesn't work with inputs generated by v-for

I've got a form in which the inputs are added dynamically with the v-for loop. Each field should be validated, and before user submit the form it should be checked wherever it's valid or not. The problem is that the this.$validator.validateAll() always return true, even if the inputs are invalid. What I'm doing wrong?
<div id="app">
<v-app id="inspire">
<v-flex md4 offset-md4>
<form data-vv-scope="photoForm">
<v-text-field v-for="index in 5"
:key="index"
:label="'photo' + index"
:error-messages="errors.collect('photoForm.photoName' + index)"
v-validate="'max:10'"
:data-vv-name="'photoForm.photoName' + index"
color="purple" autocomplete="on"
counter="10" >
</v-text-field>
</form>
<p>Is valid form? {{ validationResult }}</p>
</v-flex>
<v-btn #click="validate" color="purple" dark>
validate
</v-btn>
</v-app>
</div>
Vue.use(VeeValidate);
new Vue({
el: "#app",
data() {
return {
validationResult: ''
}
},
methods: {
validate() {
this.$validator.validateAll('photoForm').then(result => {
this.validationResult = result
})
}
}
});
And codepen where I reproduce the problem: https://codepen.io/anon/pen/jjrJdE
You need to store your form data somewhere so the validation has something to work on, I assume.
See https://codepen.io/cwg999/pen/MMjWNj?editors=1011
The main changes I made were to put your dynamically generated inputs into your data() and used that to reference them in the for-loop.
(note: you can also use v-model instead of :value/#input)
<v-text-field v-for="o,i in photoForm"
:key="i"
:label="o.label+ ' ' + (i+1)"
:error-messages="errors.collect('photoForm.photoName' + i)"
v-validate="'max:10'"
:name="'photoName' + i"
:value=o.value
#input="o.value = $event"
color="purple" autocomplete="on"
counter="10" >
</v-text-field>
data() {
return {
validationResult: '',
photoForm:[
{label:'Photo',value:''},
{label:'Photo',value:''}
]
}
},

Extracting the information in a prop in a Vue child component

I'm passing a object as a prop to a child component but I can't reference one of its elements (user_id) to use in a method in that child component.
My code (slightly abbreviated) is:
<template>
<div class="back">
<v-app id="inspire">
<v-content>
<v-container fluid>
<v-card flat>
<v-card-title>
<div>
<div class="headline">
{{data.title}}
</div>
<span class="grey--text">{{data.user}} said {{data.created_at}}</span>
</div>
<v-spacer></v-spacer>
<v-badge color="deep-orange accent-3" left overlap>
<span slot="badge">7</span>
<v-icon color="grey lighten-1" large>
insert_comment
</v-icon>
</v-badge>
<!--<v-btn color="deep-orange accent-3">5 replies</v-btn>-->
</v-card-title>
<v-card-text v-html="data.body"></v-card-text>
<v-card-actions v-if="own">
<v-btn icon small>
<v-icon color="deep-orange accent-3">edit</v-icon>
</v-btn>
<v-btn icon small>
<v-icon color="red">delete</v-icon>
</v-btn>
</v-card-actions>
</v-card>
<return-button></return-button>
</v-container>
</v-content>
</v-app>
</div>
</template>
<script>
export default {
name: "ShowQuestion",
props: ['data'],
data() {
return {
own: this.Own(),
}
},
methods: {
Own: function () {
return this.UserID() == this.user_id <---HERE BE DRAGONS! (reported as 'undefined')
},
UserID: function () {
... returns the 'sub' from the JWT token
return sub;
}
},
}
</script>
While the correct information is being displayed in the template, I also need to be able to compare the user's ID from the token with that contained in the prop (i.e. data.user_id). My reading suggests the solution will involve converting the object to an array, but that's beyond my current skill level too. I'd appreciate some pointers to a solution.
Thanks,
Tom
If you can render data.user_id in your template you can use it anywhere, but I'd probably do something like this to solve your problem:
props: ['data']
data() {
return {
}
},
computed: {
UserId() {
return //however you get your id
}
},
Then your v-if could just be this:
<v-card-actions v-if="UserId === data.user_id">

vue-router linking with parameter (error on refresh?)

Why when I get directed from a `router-link' to a component with a parameter, the parameter works, but then when refresh the page it doesn't? (situation explained below)
routes.js contains these two paths:
{
path: '/myspaces',
name: 'myspaces',
component: MySpaces
},
{
path: '/myspaces/:spaceID',
name: 'returnToSpaces',
component: MySpaces,
props: true
}
The concept behind it is that I pass spaceID via a <router-link>, from 1 page to another. This works. The spaceID is passed on correctly.
Room.vue - has a router-link to MySpaces.vue
<router-link :to="{ name: 'returnToSpaces', params: { spaceID: spaceID } }">
<v-btn>
<h3> go back </h3>
</v-btn>
</router-link>
When I'm on the room.vue and I click on the button, it redirects me to the myspaces.vue as the link myspaces/1 correctly with a spaceID. However If I type myspaces/1 manually instead of being redirected, it doesn't work. It gives me the error: Cannot read property 'rooms' of undefined. This prop is linked to the spaceID which, so most likely when I refresh it, it doesn't link the /1 to the spaceID parameter?
myspaces.vue
<template>
<v-container>
<v-layout>
<!-- My spaces -->
<v-flex md8 xs12>
<v-layout row wrap>
<!-- The rooms, allRoomsObj returns all rooms in the space with the id of selectedSpace. -->
<v-flex v-for="room in allRoomsObj"
:key="room.id"
xs12
sm6
md6
lg6
:class="{'roomDesktop': !$vuetify.breakpoint.xs, 'roomMobile': $vuetify.breakpoint.xs}"
>
<!-- A room -->
<v-card class="card-round">
<!-- Image -->
<v-carousel :cycle="false" hide-delimiters :hide-controls="room.images.length <= 1">
<!--:hide-controls="images.length <= 1"-->
<v-carousel-item v-for="image in room.images" :src="image.src" :key="image.id"></v-carousel-item>
</v-carousel>
<!-- Information -->
<v-card-text primary-title>
<v-layout>
<v-flex xs11>
<!-- MISSING INFORMATION IN STORE -->
<h4 class="roomType"> <router-link :to="{ name: 'room', params: { spaceID: selectedSpaceObj[0].id, roomID: room.id } }">{{ room.type }}</router-link> </h4>
<h2> {{ room.name }} </h2>
</v-flex>
<v-flex xs1 hidden-sm-and-down>
<v-btn #click="selectedRoom = room.id"
:flat="selectedRoom !== room.id"
:outline="selectedRoom !== room.id"
fab
class="selectRoomBtn"
depressed
>
</v-btn>
</v-flex>
</v-layout>
</v-card-text>
</v-card>
</v-flex>
</v-layout>
</v-flex>
<!-- Sidebar -->
<v-flex hidden-sm-and-down sm4 lg4 class="sidebarSticky">
<v-layout row wrap>
<!--1 room details, selectedRoomObj returns 1 room with id of selectedRoom, that is in the space with id selectedSpace.-->
<v-flex v-for="room in selectedRoomObj" :key="room.id">
<v-card class="card-round">
<!-- Show only 1 image -->
<v-card-media v-for="image in room.images.slice(0,1)" :src="image.src" height="200px" :key="image.id">
</v-card-media>
<v-card-text>
<!-- Side bar - room name -->
<h2 class="sidebarRoomName"> {{ room.name }} </h2>
<!-- description -->
<p> {{ room.description }} </p>
<!-- overview button-->
<p> <router-link :to="{ name: 'room', params: { spaceID: selectedSpace, roomID: selectedRoom } }">room overview..</router-link></p>
<!-- styles/pins/moodboard -->
</v-card-text>
</v-card>
</v-flex>
</v-layout>
</v-flex>
</v-layout>
</v-container> <!-- End of MAIN CONTENT-->
</template>
<script>
import { mapState } from 'vuex';
export default {
name: "myspaces",
props: [
'spaceID'
],
data() {
return {
filterMaxLength: 3,
selectedSpace: 0,
selectedRoom: 0
}
},
created() {
// Default selected space (first in json)
this.selectedSpace = this.spaces[0].id;
// console.log("spaces " + this.spaces[0].id)
if (this.spaceID != null) {
this.selectedSpace = this.spaceID;
}
// Default selected room (first in json)
this.selectedRoom = this.spaces[0].rooms[0].id;
// If spaceID is received, change the room to the first room in that space.
if (this.spaceID != null) {
var backToSpace = this.spaces.filter(aSpace => aSpace.id == this.spaceID)
this.selectedRoom = backToSpace[0].rooms[0].id
}
},
computed: {
// Get 'spaces' from store.
...mapState([
'spaces'
]),
// Grab all the rooms in the selected space.
allRoomsObj() {
if (!this.selectedSpaceObj) {
return {};
} else {
return this.selectedSpaceObj[0].rooms;
}
},
// Grab the space that with the id that equals to the selectedSpace.
selectedSpaceObj() {
if (!this.selectedSpace) {
return {};
} else {
return this.spaces.filter(aSpace => aSpace.id === this.selectedSpace);
}
},
// Grab the room in the selected space, with the room id that equals to selectedRoom.
selectedRoomObj() {
if (!this.selectedSpaceObj) {
return {};
} else {
return this.selectedSpaceObj[0].rooms.filter(aRoom => aRoom.id === this.selectedRoom);
}
}
}
}
</script>
I found the root of the error:
For some reason on refresh the below code doesn't work. Even though this.selectedSpace has a value of (ex. 1). It does not work, if I replace it with the value 1, it does however work....?
this.spaces.filter(aSpace => aSpace.id === this.selectedSpace)
When I try to change created() to beforeRouteEnter() I get the error:
I think the problem might be the type strict comparison inside this code (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Comparison_Operators):
this.spaces.filter(aSpace => aSpace.id === this.selectedSpace)
Can you try to specify the type of your property to be same as the type of the id of the this.spaces objects? e.g.:
props: {
spaceID: {
required: true,
type: Integer
}
}
This should give you a warning if the spaceID is not of the same type