How to show variable inside function in vue3 template - vue.js

How can I display the variable inside template? I use Nuxtjs version 3.2.
I set return but its not show in the template
<template>
<div>
<p> Countdown: {{ `${days}` }} {{ minutes }} {{ seconds }} </p>
</div>
</template>
<script>
const countDownDate = new Date("Apr 28, 2023 16:37:52").getTime();
const myfunc = setInterval(function() {
const now = new Date().getTime();
var timeleft = countDownDate - now;
let days = Math.floor(timeleft / (1000 * 60 * 60 * 24));
let hours = Math.floor((timeleft % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
let minutes = Math.floor((timeleft % (1000 * 60 * 60)) / (1000 * 60));
let seconds = Math.floor((timeleft % (1000 * 60)) / 1000);
return { days , hours , minutes , seconds}
}, 1000)
</script>

Just use Vue's reactivity
Define your variables with ref()
const days = ref(0);
And set them in your function using .value:
days.value = Math.floor(timeleft / (1000 * 60 * 60 * 24));
Here is the Playground
const { createApp, ref } = Vue;
const countDownDate = new Date("Apr 28, 2023 16:37:52").getTime();
const App = {
setup() {
const days = ref(0);
const hours = ref(0);
const minutes = ref(0);
const seconds = ref(0);
const myfunc = setInterval(function() {
const now = new Date().getTime();
var timeleft = countDownDate - now;
days.value = Math.floor(timeleft / (1000 * 60 * 60 * 24));
hours.value = Math.floor((timeleft % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
minutes.value = Math.floor((timeleft % (1000 * 60 * 60)) / (1000 * 60));
seconds.value = Math.floor((timeleft % (1000 * 60)) / 1000);
}, 1000)
return { days, hours, minutes, seconds }
}
}
const app = createApp(App);
app.mount('#app');
#app { line-height: 1.5; }
[v-cloak] { display: none; }
<div id="app" v-cloak>
<b>Countdown:</b><br/>
<p> Days {{ days }}, Hours {{ hours }}, Minutes: {{ minutes }}, Seconds: {{ seconds }} </p>
</div>
<script src="https://unpkg.com/vue#3/dist/vue.global.prod.js"></script>
<script setup> version here.

With the watchEffect working.
<template>
<div>
<p>Countdown: {{ `${days}` }} {{ hours }} {{ minutes }} {{ seconds }} </p>
</div>
</template>
<script>
import { ref, watchEffect } from 'vue';
export default {
setup() {
const countDownDate = new Date('Apr 28, 2023 16:37:52').getTime();
const days = ref(0);
const hours = ref(0);
const minutes = ref(0);
const seconds = ref(0);
watchEffect(() => {
const now = new Date().getTime();
const timeleft = countDownDate - now;
days.value = Math.floor(timeleft / (1000 * 60 * 60 * 24));
hours.value = Math.floor((timeleft % (1000 * 60 * 60 * 24)) / (1000 * 60 *
60));
minutes.value = Math.floor((timeleft % (1000 * 60 * 60)) / (1000 * 60));
seconds.value = Math.floor((timeleft % (1000 * 60)) / 1000);
});
return { days, hours, minutes, seconds };
},
};
</script>

Related

Pagination vue 3

I have a local array with data which i pass to a table.
I created variables: page,limit,totalPage and function changePage.
In hook onMounted I calculate the number of pages based on the given limit.
Through the v-for loop, displayed the pages and made them clickable on the function, but I don't understand how to set the limit of displayed table elements, and how to make pagination work.
<template>
<div class="containerApp">
<Dialog
:showForm="showForm"
#changeToggle="changeToggle"
#hideDialog="hideDialog"
>
<PostForm #create="createPost" />
</Dialog>
<Select v-model="selectedSort" :options="sortOptions" />
</div>
<input type="text" v-model="searchQuery" />
<Table :tableData="searchAndSort" />
<div class="page_wrapper">
<div
v-for="(pageNumber, i) in totalPage"
:key="i"
#click="changePage(pageNumber)"
class="page"
:class="{ 'current-page': page === pageNumber }"
>
{{ pageNumber }}
</div>
</div>
</template>
<script>
import Table from "./components/Table.vue";
import Dialog from "./components/UI/Dialog.vue";
import PostForm from "./components/PostForm.vue";
import Select from "./components/UI/Select.vue";
import { ref } from "#vue/reactivity";
import { computed, onMounted } from "#vue/runtime-core";
export default {
name: "App",
components: {
...
},
setup() {
const showForm = ref(false);
const searchQuery = ref("");
const tableData = ref([
...
]);
const selectedSort = ref("");
const sortOptions = ref([
...
]);
const page = ref(1);
const limit = ref(5);
const totalPage = ref(0);
onMounted(() => {
totalPage.value = Math.ceil(tableData.value.length / limit.value);
});
const changePage = (pageNumber) => {
page.value = pageNumber;
};
const createPost = (post) => {
tableData.value.push(post);
showForm.value = false;
};
const changeToggle = (toggle) => {
showForm.value = !toggle;
};
const hideDialog = (val) => {
showForm.value = val;
};
const sortedPosts = computed(() => {
...
});
const searchAndSort = computed(() => {
...
});
return {
showForm,
tableData,
selectedSort,
sortOptions,
searchQuery,
changeToggle,
hideDialog,
createPost,
sortedPosts,
searchAndSort,
page,
limit,
totalPage,
changePage,
};
},
};
</script>
In order to show a portion of an array you can use Array.slice. (docs)
in your searchAndSort you should do something like this:
const searchAndSort = computed(() => {
const start = (page.value - 1) * limit.value;
const end = (page.value * limit.value)+1;
return tableData.value.slice(start, end);
});
page - 1 * limit - initially this will result in 0 * 5 meaning start from 0 (-1 is used because the page start from 1. When you go to the second page this will result in 1 * 5, etc.
end is defined by the limit itself multiplying the page.
So on the first page, you will slice the array from 0 to 5, on the second page - from 5 to 10, etc.
const end = (page.value * limit.value)+1; - +1 will give you the 5th element because Array.slice exlude the item at the end index)
You should add some checks as well.

Vue- watch for audio time change

I have an audio file which plays songs. I am currently making a slider that is equal to the songs length and current time. You can't use player.currentTime in the watch component in Vue so how would you go about updating a value in realtime equal to player current time.
I currently have the v-model="player.currentTime" but that only updates when I pause the songs and not real-time.
This is what I have so far
player: new Audio()
this.player.src = this.songs[this.songIndex].src
this.player.play()
Player:
<input
type="range"
name="timeStamp"
ref="time"
v-model.lazy="this.player.currentTime"
step="0.1"
class="w-[100%] hover:cursor-pointer"
/>
You have to listen to the timeupdate event. I made a simple sample code:
Output:
<template>
<div style="border: 1px solid gray; border-radius: 5px; padding: 5px;">
<div>
<button #click="play">Play | Pause</button>
{{ timeLabel }}
</div>
<div>
<input
type="range"
:min="0"
:max="duration"
v-model="currentTime"
#input="updateTime"
>
</div>
</div>
</template>
<script>
export default {
name: 'BaseAudioPlayerTest',
data() {
return {
src: 'Spring-Flowers.mp3',
player: null,
duration: 0,
currentTime: 0,
timeLabel: '00:00:00',
};
},
methods: {
play() {
if (this.player.paused) {
this.player.play();
this.duration = this.player.duration;
} else {
this.player.pause();
}
},
updateTime() {
this.player.currentTime = this.currentTime;
},
timeupdate() {
this.currentTime = this.player.currentTime;
const hr = Math.floor(this.currentTime / 3600);
const min = Math.floor((this.currentTime - (hr * 3600)) / 60);
const sec = Math.floor(this.currentTime - (hr * 3600) - (min * 60));
this.timeLabel = `${hr.toString()
.padStart(2, '0')}:${min.toString()
.padStart(2, '0')}:${sec.toString()
.padStart(2, '0')}`;
},
},
mounted() {
this.player = new Audio(this.src);
this.player.addEventListener('timeupdate', this.timeupdate, false);
},
};
</script>
You can find more info here.

D3 working properly only in first instance of Vue 3 component

I'm working on Vue 3 app where I would like to use multiple instances of one component. Each component should have its instance of D3 for displaying various SVG images. In my case D3 works as intended only on first instance of Vue component.
Dots are random generated by D3. I can see when inspecting elements that none of dots has been appended in second instance of component. Screenshot of the problem may be found here.
My component with D3 looks like this:
<template>
<div class="fill-100">
<svg ref="svgRef" width="400" height=667>
<g></g>
</svg>
</div>
</template>
<script>
import {ref, onMounted} from "#vue/runtime-core";
import {select, zoom} from "d3";
export default {
name: "SldSvgD3",
props: ["id"],
setup() {
const svgRef = ref(null);
onMounted(() =>{
const svg = select(svgRef.value);
svg.append("svg")
let data = [], width = 400, height = 667, numPoints = 100;
let zoom3 = zoom()
.on('zoom', handleZoom);
function handleZoom(e) {
select('svg g')
.attr('transform', e.transform);
}
function initZoom() {
select('svg')
.call(zoom3);
}
function updateData() {
data = [];
for(let i=0; i<numPoints; i++) {
data.push({
id: i,
x: Math.random() * width,
y: Math.random() * height
});
}
}
function update() {
select('svg g')
.selectAll('circle')
.data(data)
.join('circle')
.attr('cx', function(d) { return d.x; })
.attr('cy', function(d) { return d.y; })
.attr('r', 3);
}
initZoom();
updateData();
update();
});
return {svgRef}
}
}
</script>
<style lang="scss">
.fill-100{
width: 100%;
height: 100%;
}
</style>
Implementation of D3 zoom and pan taken from this site
What I didn't know is that scope of d3.select() call is global for the whole app. Solution in my case was just creating unique id for root div and selecting this div before any manipulation.
This question was very helpful to me.
Complete code:
<template>
<div class="fill-100" :id="'sld_div'+this.id">
</div>
</template>
<script>
import {ref, onMounted} from "#vue/runtime-core";
import * as d3 from "d3";
export default {
name: "SldSvgD3",
props: ["id"],
setup(props) {
const svgRef = ref(null);
const svg_width = 400;
const svg_height = 667;
onMounted(() =>{
const svg = d3
.select("#sld_div"+props.id)
svg.append("svg")
.attr("id","sld_root"+props.id)
.attr("width", svg_width)
.attr("height", svg_height)
.append("g")
.attr("id","sld_root_g"+props.id)
let data = [], width = 600, height = 400, numPoints = 100;
let zoom = d3.zoom()
.on('zoom', handleZoom);
function handleZoom(e) {
d3.select("#sld_div"+props.id)
.select('svg g')
.attr('transform', e.transform);
}
function initZoom() {
d3.select("#sld_div"+props.id)
.select('svg')
.call(zoom);
}
function updateData() {
data = [];
for(let i=0; i<numPoints; i++) {
data.push({
id: i,
x: Math.random() * width,
y: Math.random() * height
});
}
}
function update() {
d3.select("#sld_div"+props.id)
.select('svg g')
.selectAll('circle')
.data(data)
.join('circle')
.attr('cx', function(d) { return d.x; })
.attr('cy', function(d) { return d.y; })
.attr('r', 3);
}
initZoom();
updateData();
update();
});
return {svgRef}
}
}
</script>
<style lang="scss">
.fill-100{
width: 100%;
height: 100%;
}
</style>

“ReferenceError: document is not defined” in Nuxt.js

I am building an app using Nuxtjs. I want to show the live clock in the app. It is showing a live clock until I refresh the page. Once I refresh the page the app gets vanished and a message come “ReferenceError: document is not defined”.
I placed the Clock component inside tags in index.vue file but still getting the same issue.
index.vue file
<template>
<div>
<Navbar />
<Clock />
<Footer />
</div>
</template>
<script>
import Navbar from '../components/Navbar.vue'
import Footer from '../components/Footer.vue'
import Clock from '../components/Clock.vue'
export default {
components: {
Navbar,
Footer,
Clock,
},
}
</script>
<style scoped>
//ignore the css
</style>
Clock.vue
<template>
<div class="clock">
<span>{{ currentDate }}</span>
<span id="clock">{{ startTime() }}</span>
</div>
</template>
<script>
export default {
name: 'Clock',
data() {
return {
currentDate: new Date().toDateString(),
}
},
methods: {
startTime() {
const today = new Date()
let h = today.getHours()
let m = today.getMinutes()
let s = today.getSeconds()
let session = 'AM'
if (h === 0) {
h = 12
}
if (h > 12) {
h = h - 12
session = 'PM'
}
m = this.checkTime(m)
s = this.checkTime(s)
document.getElementById('clock').innerHTML =
h + ':' + m + ':' + s + session
setTimeout(this.startTime, 1000)
},
checkTime(i) {
if (i < 10) {
i = '0' + i
}
return i
},
},
}
</script>
<style scoped>
// ignore the css
</style>
Add ref to your element in the clock.vue and access it in the method using ref.
Your element will be
<span ref="clock">{{ startTime() }}</span>
In your methode you can reference it with
this.$refs.clock.innerHTML = h + ':' + m + ':' + s + session

Vue.js progress bar with values from API call

I've got a simple component that pulls data from an API and I would like to add a progress bar that indicates the next data refresh. The API calls are done with a 10 seconds interval and data itself is usually refreshed every minute.
The value for the progress bar is based on date: 2018-12-04T16:10:09.508367Z where I retrieve seconds usingmoment library: moment().diff(2018-12-04T16:10:09.508367Z, 'seconds')
Because this can be a random value the steps for the progress bar are not equal and they don't start from a full bar.
I was wondering if there is a way to improve this?
This is my pseudo code on codesandbox to illustrate that.
<template>
<b-container fluid>
<b-row>
<b-col md="12" class="p-0 fixed-top">
<b-progress
:value="progressCounter"
:max="progressMax"
height="5px"
variant="warning"
>
</b-progress>
</b-col>
</b-row>
<b-row>
<b-col md="12" class="">
{{ progressMax }} / {{ progressCounter }}
</b-col>
</b-row>
</b-container>
</template>
<script>
export default {
name: "ProgressBar",
data() {
return {
progressCounter: 60,
progressMax: 60,
interval: null,
refresh: 10000,
time: 0
};
},
methods: {
getRandomInt(min, max) {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min + 1)) + min;
},
progress() {
/*
this.time is just a pseudo code here, the real progressCounter is recived from
an API call and seconds are calculated by deducting time from now:
this.progressCounter = 60 - moment().diff(created, 'seconds')
"created" comes from an API call and it's refreshed (~) every minute
*/
if (this.time <= 10) {
this.time = this.getRandomInt(40, 59);
} else {
this.time -= 10;
}
this.progressCounter = this.time;
console.log(
`this.time: ${this.time}, this.progressCounter: ${this.progressCounter}`
);
}
},
mounted() {
this.interval = setInterval(
function() {
this.progress();
}.bind(this),
this.refresh
);
},
beforeDestroy() {
clearInterval(this.interval);
}
};
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped></style>