Using the same component template and updating the API data - api

I'm building a page that has a embeds a specific twitch stream video. I'm only displaying one video at the top of my page.
Twitch has an embed code that allows you to grab the channel you want to watch and it will display the embedded video and chat. It requires a div id to target the DOM to add the embedded video.
https://dev.twitch.tv/docs/embed/everything/
My problem is when I click on another page, that uses the same template, it doesn't replace the video. Rather, it adds another IFRAME embed video to the id. So every time I click on the page, it just adds another video to the div id.
I'm using the watch function to update other elements of the page. So when I click on another page, using the same template, the data updates correctly. Everything works and updates except for that embed video.
Is there a way to clear out that div id every time I click another another page? I apologize in advance. I've only been learning Vuejs for a couple of weeks now, and it's all rather new to me.
Here is why my template looks like:
<template>
<div class="video heading-title container">
<div class="streamWrapper">
<div class="row">
<div v-for="live in streams" class="col-12 stream-card">
<div class="twitch-vid-wrapper">
<div id="twitch-embed"></div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import appService from '../service.js'
import '../embedTwitch.min.js' // twitch video embed script
export default {
name: 'Video',
data: function() {
return {
id: this.$route.params.id,
streams: []
}
},
created() {
this.getFirstLiveStream()
this.getLiveStreams()
},
watch: {
'$route' (to, from) {
this.id = to.params.id
this.getLiveStreams()
this.getFirstLiveStream()
}
},
methods: {
getLiveStreams(game){
game = this.$route.params.id;
appService.getLiveStreams(game).then(data => {
this.live = data
});
},
getFirstLiveStream(game) {
game = this.$route.params.id;
appService.getFirstLiveStream(game).then(data => {
this.streams = data
let channelName = this.streams[0].channel.display_name
appService.getTwitchStream(channelName)
});
}
}
}
</script>
Here is the method I have in my service:
const appService = {
getFirstLiveStream(game) {
return new Promise((resolve) => {
axios.get('/kraken/streams/?sort=views&stream_type=live&game='+game)
.then((response) => {
// send variables to calc the offset
var total = response.data._total;
var query = this.calculateSingleOffset(game, total)
resolve(query)
})
})
},
getTwitchStream(channel) {
return setTimeout(function(){
new Twitch.Embed('twitch-embed', {
width: '100%',
height: 480,
channel: channel
});
}
, 500);
}
}
Thanks!

As I understood, what you need is how to assign different id for one twitch template inside each instance of the component.
The solution:
add one data property like twitchId
simply uses Date.now() to generate unique id (this method is just one demo, you can use own methods to get one ID).
then bind <div :id="twitchId"> which will embed into twitch video.
Vue.config.productionTip = false
function embedContent(elementId) {
let contents = ['I am a test', 'nothing', 'connecting', 'bla bla ...', 'Puss in boots', 'Snow White']
document.getElementById(elementId).innerHTML = '<p>' + contents[Math.floor(Math.random()*contents.length)] + '</p>'
}
Vue.component('twitch', {
template: `<div class="video">
<h2>{{name}}</h2>
<div :id="twitchId"></div>
</div>`,
props: ['name'],
data() {
return {
twitchId: ''
}
},
created: function () {
this.twitchId = Date.now().toString(16)
},
mounted: function () {
embedContent(this.twitchId)
}
})
app = new Vue({
el: "#app",
data: {
videos: [
'channel 1',
'World Cup for Football',
'StackOverFlow'
]
}
})
.video {
border: 1px solid red;
}
<script src="https://unpkg.com/vue#2.5.16/dist/vue.js"></script>
<div id="app">
<div>
<twitch v-for="(item, index) in videos" :name="item" :key="index"></twitch>
</div>
</div>

Related

How to build a bridge of events between different components in Vue?

I need to solve it
1) click mainMidLeft component
2) after clicked, to move slideLeftTop component
http://joxi.ru/ZrJBvERH1JVa8r
The problem I dont quite understand how to do this in right way..
Is it okay to create in mainMidLeft a method where I will do somethik like this:
move: () => {
document.querySelector(`.slideLeftTop`).style.position .....
}
The best practice is to use Vuex State manager with computed methods (getters) and watchers
I have made a working example for you on jsfiddle.
https://jsfiddle.net/n4e_m16/wujafg5e/4/
For more info on how vuex works please go to Here
Please let me know if you need more help :)
const store = new Vuex.Store({
state: {
mainMidLeftState: false,
},
getters: {
mainMidLeftState: state => state.mainMidLeftState,
},
mutations: {
toggleMainMidLeft: (state, payload) => {
state.mainMidLeftState = !state.mainMidLeftState
},
},
})
Vue.component('main-mid-left', {
data() {
return {
}
},
computed: {
mainMidLeftState() {
return this.$store.state.mainMidLeftState
},
},
methods: {
toggleMainMidLeft() {
this.$store.commit('toggleMainMidLeft')
// alert(this.mainMidLeftState)
},
}
})
Vue.component('slide-left-top', {
data() {
return {
}
},
computed: {
mainMidLeftState() {
return this.$store.state.mainMidLeftState
},
},
watch: {
mainMidLeftState: function(val) {
alert("YES, computed property changed and alert have been triggered by watcher in slide top left component")
}
},
methods: {
}
})
const app = new Vue({
el: '#app',
store,
})
div {
color: black;
}
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<script src="https://unpkg.com/vuex#3.0.1/dist/vuex.js"></script>
<div id="app">
<!-- inlining the template to make things easier to read - all of below is held on the component not the root -->
<main-mid-left inline-template>
<div>
<h4>
main mid left
</h4>
<button v-on:click="toggleMainMidLeft()">toggle Main Mid Left State</button>
<div v-show="mainMidLeftState == true">State is true</div>
<div v-show="mainMidLeftState == false">State is false</div>
</div>
</main-mid-left>
<slide-left-top inline-template>
<div>
<h4>
slide left top
</h4>
<div v-show="mainMidLeftState == true">State is true</div>
<div v-show="mainMidLeftState == false">State is false</div>
</div>
</slide-left-top>
</div>
If you don't want to use vuex, you can create a new Vue instance as an event bus (I believe this is mentioned somewhere in the Vue tutorial):
const EventBus = new Vue()
Import EventBus to where you need it and you can send an event by:
EventBus.$emit('event-name', data)
And add the following script in created() of your receiver component:
EventBus.$on('event-name', ($event) => {
// Do something
})
I hope this helps |´・ω・)ノ

Fetch data in component on initiation using parameters from Vuex store

I am new to Vue and am trying to build a simple movie app, fetching data from an API and rendering the results. I want to have an incremental search feature. I have an input field in my navbar and when the user types, I want to redirect from the dashboard view to the search results view. I am unsure of how to pass the query params from the navbar to the search results view.
Here is my App.vue component
<template>
<div id="app">
<Navbar></Navbar>
<router-view/>
</div>
</template>
<script>
import Navbar from './components/Navbar.vue'
export default {
name: 'App',
components: {
Navbar
},
}
</script>
And here is my navbar component where I have the input field
<template>
<nav class="navbar">
<h1 class="logo" v-on:click="goToHome">Movie App</h1>
<input class="search-input" v-on:keyup="showResults" v-model="query" type="text" placeholder="Search..."/>
</nav>
</template>
<script>
import router from '../router/index'
export default {
data: function () {
return {
query: this.query
}
},
methods: {
goToHome () {
router.push({name: 'Dashboard'})
},
showResults () {
//here on each key press I want to narrow my results in the SearchedMovies component
}
}
}
</script>
If I use router.push to the SearchedMovies component then I am only able to pass the query as a parameter once. I thought about using Vuex to store the query and then access it from the SearchedMovies component, but surely there is a better way of doing it?
I also read about using $emit but since my parent contains all the routes, I'm not sure how to go about this.
You don't need to redirect user anywhere. I've made a small demo to show how one might do it. I used this navbar component as you described and emit an event from it:
const movies = {
data: [
{
id: 0,
title: 'Eraserhead',
},
{
id: 1,
title: 'Erazerhead',
},
{
id: 2,
title: 'Videodrome',
},
{
id: 3,
title: 'Videobrome',
},
{
id: 4,
title: 'Cube',
},
]
};
Vue.component('navbar', {
template: '<input v-model="filter" #input="onInput" placeholder="search">',
data() {
return {
filter: '',
};
},
methods: {
onInput() {
this.$emit('filter', this.filter);
}
}
});
// this is just a request imitation.
// just waiting for a second until we get a response
// from the datasample
function request(title) {
return new Promise((fulfill) => {
toReturn = movies.data.filter(movie => movie.title.toLowerCase().indexOf(title.toLowerCase()) !== -1)
setTimeout(() => fulfill(toReturn), 1000);
});
}
new Vue({
el: '#app',
data: {
movies: undefined,
loading: false,
filter: '',
lastValue: '',
},
methods: {
filterList(payload) {
// a timeout to prevent
// instant request on every input interaction
this.lastValue = payload;
setTimeout(() => this.makeRequest(), 1000);
},
makeRequest() {
if (this.loading) {
return;
}
this.loading = true;
request(this.lastValue).then((response) => {
this.movies = response;
this.loading = false;
});
}
},
mounted() {
this.makeRequest('');
}
})
<script src="https://unpkg.com/vue"></script>
<div id="app">
<navbar v-on:filter="filterList"></navbar>
<ul v-if="!loading">
<li v-for="movie in movies" :key="movie.id">{{ movie.title }}</li>
</ul>
<p v-else>Loading...</p>
</div>
Also jsfiddle: https://jsfiddle.net/oniondomes/rsyys3rp/
If you have any problem to understand the code above let me know.
EDIT: Fixed some bugs and added a couple of comments
EDIT2(after the comment below):
Here's what you can do. Every time user inputs something inside a navbar you call a function:
// template
<navbar v-on:input-inside-nav-bar="atInputInsideNavBar"></navbar>
// script
methods: {
atInputInsideNavBar(userInput) {
this.$router.push({
path: '/filtred-items',
params: {
value: userInput
}
})
}
}
Then inside you 'searched movies' page component you can access this value so:
this.$route.params.value // returns userInput from root component

Vuejs DOM doesn't update after fetching data

I have bound an array events to a component tag <scheduler> containing events to fill in a scheduler app (dhtmlx scheduler). However, the DOM doesn't seeem to refresh itself when data is retrieved by the getEvents methods triggered when vue instance is created.
There is 2 vue files I work with: App.vue containing the main app component and the Scheduler.vue file containing the scheduler component.
The thing is that when I modify something in the Scheduler.vue file and save it, it correctly take the updated events array into account.
Scheduler parse the data in the events prop when DOM is mounted in scheduler component.
Therefore is there something I can do to get the updated array ?
Here is the App.vue:
<template>
<div id="app">
<div class="container">
<scheduler v-bind:events="events"></scheduler>
</div>
</div>
</template>
<script>
import Scheduler from './components/Scheduler.vue';
import auth from './components/auth/index'
import data from './components/data/index'
export default {
name: 'app',
components: {
Scheduler
},
data() {
return {
events: []
}
},
created() {
this.getEvents();
},
watch: {
events: function(value) {
console.log('updated');
}
},
methods: {
async getEvents() {
try {
const token = await auth.getToken(this);
const thattoken = await auth.getThatToken(this, token);
await data.getOgustData(this, token, '/calendar/events', 307310564, this.events);
} catch (e) {
console.log(e);
}
},
}
}
</script>
Here is Scheduler.vue:
<template lang="html">
<div ref="scheduler_here" class="dhx_cal_container" style='width:100%; height:700px;'>
<div class="dhx_cal_navline">
<div class="dhx_cal_prev_button"> </div>
<div class="dhx_cal_next_button"> </div>
<div class="dhx_cal_today_button"></div>
<div class="dhx_cal_date"></div>
<div class="dhx_cal_tab" name="day_tab" style="right:204px;"></div>
<div class="dhx_cal_tab" name="week_tab" style="right:140px;"></div>
<div class="dhx_cal_tab" name="month_tab" style="right:76px;"></div>
</div>
<div class="dhx_cal_header"></div>
<div class="dhx_cal_data"></div>
</div>
</template>
<script>
import 'dhtmlx-scheduler'
import 'dhtmlx-scheduler/codebase/locale/locale_fr';
import 'dhtmlx-scheduler/codebase/ext/dhtmlxscheduler_readonly.js';
export default {
name: 'scheduler',
props: {
events: {
type: Array,
default () {
return [{
id: '',
text: '',
start_date: '',
end_date: '',
}]
}
}
},
mounted() {
scheduler.config.xml_date = '%Y-%m-%d %H:%i';
// disable left buttons on lightbox
scheduler.config.buttons_left = [];
// enable cancel button on lightbox's right wing
scheduler.config.buttons_right = ['dhx_cancel_btn'];
// changing cancel button label
scheduler.locale.labels['icon_cancel'] = 'Fermer';
// hide lightbox in month view
scheduler.config.readonly_form = true;
// hide select bar in day and week views
scheduler.config.select = false;
scheduler.config.lightbox.sections = [
{
name: "description",
height: 20,
map_to: "text",
type: "textarea",
focus: true
}
];
scheduler.init(this.$refs.scheduler_here, new Date(), 'month');
scheduler.parse(this.$props.events, 'json');
},
}
</script>
<style lang="css" scoped>
#import "~dhtmlx-scheduler/codebase/dhtmlxscheduler.css";
</style>
getOgustData can't populate events in a way that Vue can observe. Since you're passing it as an argument, the array itself can be updated, but it's not a reactive array. Try
var newEvents;
await data.getOgustData(this, token, '/calendar/events', 307310564, newEvents);
this.events = newEvents;
Assigning to this.events is something Vue can notice.
Problem is solved. The issue didn't come from Vue but rather from the dhtmlx scheduler which wasn't parsing events when events was updated.
I ended up watching for any changes to events and thus, parsing it when it updates.
Thanks again for the help provided.
App.vue :
<template>
<div id="app">
<div class="container">
<scheduler v-bind:events="events"></scheduler>
</div>
</div>
</template>
<script>
import Scheduler from './components/Scheduler.vue';
import auth from './components/auth/index'
import data from './components/data/index'
import 'dhtmlx-scheduler'
export default {
name: 'app',
components: {
Scheduler
},
data() {
return {
events: []
}
},
created() {
this.getEvents();
},
watch: {
events: function(value) {
scheduler.parse(this.events, 'json');
}
},
methods: {
async getEvents() {
const token = await auth.getToken(this);
const apiToken = await auth.getApiToken(this, token);
this.events = await data.getApiData(this, apiToken, '/calendar/events', 307310564, this.events);
}
},
}
</script>

How to make components wait for Ajax changes

How can I make my components wait for an ajax call to complete before being rendered? Right now, their data objects are empty because the ajax call has not completed when they are mounted.
Additionally, I want all components to have access to this ajax data, which will be refreshed and updated every 30 seconds. What is the best way to achieve this?
//Snippet of what gets return from ajax call
{
121: {
table_info: {
id: "121",
name: "Test",
table_number: "51",
cap: "6",
seating: "OPEN",
position_x: "0.19297285",
position_y: "0.07207237",
participants_in_tables: "5",
count: 5
}
}
}
//Global
var tableData; //This gets set when the Vue ajax call is complete after being mounted
var width = $(document).width();
var height = $(document).height();
//Vue
Vue.component('tables', {
data: () => {
return {
tables: tableData
}
},
template: `
<div id="tableContain">
<div class='table' v-for="table in tables" :style="computeOffsets(table)">
{{table.table_info.name}}
</div>
</div>
`,
methods: {
computeOffsets(table) {
return {
top: (table.table_info.position_x * width) + 'px',
left: (table.table_info.position_y * height) + 'px'
}
}
});
var app = new Vue({
el: '#main',
mounted() {
$.ajax({
method: 'POST',
dataType: 'json',
url: base_url + 'users/getTableAssignments/' + event_id
}).done(data => {
tableData = data; //Set global tableData
});
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.4.2/vue.js"></script>
<div id="main">
<table></table>
</div>
It's better to use created in vue and then get the data with props in component like:
Vue.component('tables', {
props: ['tables']
// ...
});
var app = new Vue({
el: '#main',
data: { tables: [] },
created() {
axios.get('url').then( response => { this.tables = response.data } )
}
});
I prefer axios but u can use ajax if u like :D
And if u like to refresh u can just use intervals but I prefer server side solution like sockets. Im using it in my app with laravel broadcasting, redis and sockets to have a realtime dashboard :D

How to look up the element in the HTML in the .vue file that needs to be updated:

You can consider me a toddler in the vuejs worlđ
In my app i fetch some posts that are present in firebase database
Each post has a upvote and downvote button just like stack overflow where users can upvote or downvote( it's completely u to them)
Eeverything like votes getting updated to databae and rest all works great*
Here comes the problem
Firebase provides a event listener for listening whenever there is change in each child i.e post (in my case upvotes. Downvotes)
i add this listener to the created() lifecycle hook so that the votes update when there is change in them by other users
here is the simplified code below of my .vue file
<template>
<div>
<div v-for="post in posts" id="post.key" class="container">
<p id="upvotes">{{ post.up}}</p>
<p id="downvotes">{{ post.down }}</p>
</div>
</div>
</template>
<script>
export default{
created:{
const ref = this.$firebase.database().ref();
ref.child("posts").on('child_changed', function(post) {
var upvotes = post.val().up;
var downvotes = post.val().down;
//how to look up the element in the HTML above that needs to be updated:
//if it were plain javascript we would have done something like this
//var postElm = document.getElementById(post.key);
//postElm.getElementById("upvotes").innerHTML = upvotes;
//postElm.getElementById("downvotes").innerHTML = downvotes;
});
}
}
</script>
My issue:
how to look up the upvotes and downvotes element in the HTML above in the template that needs to be updated:
according to the docs we can register a reference ref to an element but there is this note saying:
because the refs themselves are created as a result of the render function, you cannot access them on the initial render - they don’t exist yet! $refs is also non-reactive, therefore you should not attempt to use it in templates for data-binding.
so how can I reference the elements to update them
Update: Here is an example where I have mocked the firebase behavior. posts should be a data item, because you control its contents. computed items are for derived values based on other data or props items. You wouldn't make a computed based on a value external to Vue, because such values are not reactive.
const payloads = [{
val() {
return {
key: 2,
up: 10,
down: 3
};
}
},
{
val() {
return {
key: 1,
up: 3,
down: 10
};
}
}
];
new Vue({
el: '#app',
components: {
postVoter: {
template: '#post-voter-template',
data() {
return {
posts: [{
key: 1,
up: 0,
down: 0
},
{
key: 2,
up: 1,
down: 0
},
{
key: 3,
up: 0,
down: 1
}
]
}
},
created() {
const i = setInterval(() => {
if (payloads.length) {
console.log("Doing");
const post = payloads.shift();
const item = this.posts.find((p) => p.key === post.val().key);
item.up = post.val().up;
item.down = post.val().down;
} else {
console.log("Done");
clearInterval(i);
}
}, 1000);
}
}
}
});
.container {
display: flex;
justify-content: space-between;
width: 10rem;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.2.6/vue.min.js"></script>
<template id="post-voter-template">
<div>
<div v-for="post in posts" class="container">
<p id="upvotes">{{ post.up}}</p>
<p id="downvotes">{{ post.down }}</p>
</div>
</div>
</template>
<post-voter id="app"></post-voter>
You should be finding which data item needs updated, rather than trying to find it in the DOM. You don't say how posts is created or populated, so I'm having to surmise what it looks like. The code should go something like this:
<template>
<div>
<div v-for="post in posts" class="container">
<p id="upvotes">{{ post.up}}</p>
<p id="downvotes">{{ post.down }}</p>
</div>
</div>
</template>
<script>
export default{
created() {
const ref = this.$firebase.database().ref();
ref.child("posts").on('child_changed', (post) => {
const item = this.posts.find((p) => p.key === post.key);
item.up = post.val().up;
item.down = post.val().down;
});
}
}
</script>