vue3 - can't render facebook comments - javascript

I am trying to add a Facebook comments plugin (check it here) to my vue app, the problem is that div is created in DOM but it sometimes shows, sometimes not(width: 0, height 0)
Note: I am calling XFBML.parse function, my host is added to fb app
This is my current code:
<template>
<div
ref="commentContainer"
class="fb-comments"
:data-href="onUrl()"
:data-width="cwidth"
:data-numposts="numposts"
></div>
</template>
<script lang="ts">
import { defineComponent, ref, onMounted, watch } from "vue";
import router from "../../router";
export default defineComponent({
props: {
cwidth: {
type: String,
default: "100%",
},
numposts: {
type: String,
default: "2",
},
},
setup({ cwidth, numposts }) {
const commentContainer = ref(null);
const init = () => {
if (
window.FB &&
!commentContainer.value.hasAttribute("fb-xfbml-state")
) {
setTimeout(() => {
window.FB.XFBML.parse(commentContainer.value.parentElement);
}, 2000);
}
};
onMounted(() => {
setTimeout(() => {
init();
}, 1500);
});
const onUrl = () => {
return document.location.origin + document.location.pathname;
};
watch(
() => router.currentRoute.value,
() => {
init();
}
);
return { cwidth, numposts, commentContainer, onUrl };
},
});
</script>

Instead of doing setTimeout try using nextTick and not passing any params to the parse function.
E.g. in the mounted function
this.$nextTick(() => {
window.FB.XFBML.parse()
})
Are you waiting 1.5s before running init() for a reason?
The above works using Vue2, for Vue3 example see below:
import { createApp, nextTick } from 'vue'
const app = createApp({
setup() {
const init = async () => {
await nextTick()
window.FB.XFBML.parse()
}
}
})
https://v3.vuejs.org/api/global-api.html#nexttick
Also, make sure you have added the SDK script and provided fb-root div to your index.html. It would not work on mine unless I added these just before the closing </body> tag.
I also had to add the the nextTick code to the route watcher to force the window to parse FB again when a new page is navigated to. I'm unsure of the Vue 3 version but I'm sure you can figure it out from this example:
watch: {
$route (to, from) {
if (to.fullPath !== from.fullPath) {
this.$nextTick(() => {
window.FB.XFBML.parse()
})
}
}
}

Related

Vuejs plays sound periodically

This code plays a sound when I refresh the browser, but I expect it to play the sound every 15 seconds. How can I fix this problem?
When the sound is played every time I refresh, it means that fetching data from the database is working and it will play the updated sound correctly. However, the problem is that it should update periodically, not just when the user clicks refresh
Parent
<template>
<div>
<CommunicateVoice v-if="filenames.value && filenames.value[0]" :files="filenames.value[0]"/>
</div>
</template>
<script setup>
import { onMounted, ref, computed } from "vue";
import axios from "axios";
import CommunicateVoice from './CommunicateVoice.vue';
const lands = ref([]);
const filenames = ref([]);
onMounted(async () => {
const fetchData = async () => {
const res = await axios.get("https://koh-abx.com:50100/onboardlands");
lands.value = res.data;
filenames.value = computed(() => {
return lands.value.map(item => {
const digits = item.numbershow.toString().split('');
return digits.map(digit => `https://koh-abx.com/sound/${digit}.mp3`);
});
});
};
fetchData();
setInterval(fetchData, 15000);
});
</script>
Child
<template>
<div>
<audio ref="audioEl" />
</div>
</template>
<script>
import { onMounted, ref } from 'vue';
export default {
props: {
files: {
type: Array,
required: true,
},
},
setup(props) {
const audioEl = ref(null);
const currentFileIndex = ref(0);
onMounted(() => {
audioEl.value = new Audio();
audioEl.value.addEventListener("ended", playNextFile);
document.body.appendChild(audioEl.value);
audioEl.value.src = props.files[currentFileIndex.value];
audioEl.value.play();
});
function playNextFile() {
currentFileIndex.value += 1;
if (currentFileIndex.value === props.files.length) {
document.body.removeChild(audioEl.value);
return;
}
audioEl.value.src = props.files[currentFileIndex.value];
audioEl.value.play();
}
return {
audioEl,
playNextFile,
};
},
};
</script>
Try appending something like a timestamp to your endpoint, this should avoid the cache since the URL would be unique, allowing the browser to fetch updated sounds every 15s without refreshing.
https://koh-abx.com/sound/${digit}.mp3?t=${+new Date()}

Google Consent Mode Implementation using Gatsby

I am following this tutorial about implementing google consent mode to add cookies to my website !
By using Gatsby.js I am not sure how to add these codes :
<!-- The initial config of Consent Mode -->
<script type="text/javascript">
window.dataLayer = window.dataLayer || [];
function gtag() {
dataLayer.push(arguments);
}
gtag('consent', 'default', {
ad_storage: 'denied',
analytics_storage: 'denied',
wait_for_update: 1500,
});
gtag('set', 'ads_data_redaction', true);
</script>
​
<!-- Cookie Information Pop-up Script is required for the SDK -->
<script id="CookieConsent" src="https://policy.app.cookieinformation.com/uc.js" data-culture="EN" type="text/javascript"></script>
​
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=TRACKING-ID"></script>
<script type="text/javascript">
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
​
gtag('config', 'TRACKING-ID');
</script>
​
</head>
<body>
Do you have any idea how to implement this code in Gatsby , is there any library or something that will help to implement these scripts !
Thanks
This component is used as the initial screen that applies when the page loads.
import React, { useState, useEffect } from 'react';
import { useLocation } from '#reach/router';
import { initializeAndTrack } from 'gatsby-plugin-gdpr-cookies';
import Cookies from 'js-cookie';
import CookieSettings from './Settings';
const CookieBanner = () => {
const [showBanner, setShowBanner] = useState(false);
const [showSettings, setShowSettings] = useState(false);
const location = useLocation();
// showSettings -> use this state property to open a configuration
// window which may open up more information on the cookie(s) being applied
useEffect(() => {
setShowBanner(Cookies.get('gatsby-gdpr-responded') !== 'true');
}, [])
useEffect(() => {
initTracking();
}, [Cookies.get('gatsby-gdpr-responded')])
const initTracking = () => {
initializeAndTrack(location)
}
const handleAccept = () => {
Cookies.set('gatsby-gdpr-google-analytics', true, { expires: 365 })
handleCloseAll();
}
const handleDecline = () => {
Cookies.remove('gatsby-gdpr-google-analytics');
handleCloseAll();
}
const handleCloseAll = () => {
setShowSettings(false);
setShowBanner(false);
Cookies.set('gatsby-gdpr-responded', true, { expires: 365 });
}
return (
// add your component logic here
// Take not of the different functions that are available above, like handleAccept / handleDecline / handleCloseAll
// handleCloseAll -> if a user declines / closes the banner
// handleAccept -> a button to accept by default
// handleDecline -> a button to decline the cookies
)
}
export default CookieBanner
The next component is more of a Configuration screen, which provides more information on the cookies being applied, if you take note on the import of Toggle, we use a toggle to allow users to specifically toggle on or off their cookies at any point, you of course if you have many GDPR compliances, may want to either create separate functions that handle the removal of cookies or a reusable function that is passed the name of the cookie to be removed / applied.
import React, { useState } from 'react';
import Cookies from 'js-cookie';
import Button from '#components/Button';
import Toggle from '#components/Inputs/Toggle';
const CookieSettings = ({
handleAccept,
handleDecline,
initTracking,
handleCloseAll
}) => {
const [trackAnalytics, setTrackAnalytics] = useState(Cookies.get('gatsby-gdpr-google-analytics') === 'true')
const handleToggle = () => {
Cookies.set('gatsby-gdpr-responded', true, { expires: 365 });
setTrackAnalytics((prevState) => {
if (prevState) {
Cookies.remove('gatsby-gdpr-google-analytics');
} else {
Cookies.set('gatsby-gdpr-google-analytics', true, { expires: 365 })
}
return !prevState
})
initTracking();
}
return (
// your JSX code here
)
}
export default CookieSettings;
EDIT
// A some what reusable function that you can pass a cookie name too and switch over the name provided and set the required cookie.
const handleToggle = (cookieName) => {
Cookies.set('gatsby-gdpr-responded', true, { expires: 365 });
switch (cookieName) {
case 'gatsby-gdpr-google-analytics':
return setTrackAnalytics((prevState) => {
if (prevState) {
Cookies.remove(cookieName);
} else {
Cookies.set(cookieName, true, {
expires: 365
});
}
return !prevState
})
case 'gatsby-gdpr-google-tagmanager':
return setTagAnalytics((prevState) => {
if (prevState) {
Cookies.remove(cookieName);
} else {
Cookies.set(cookieName, true, {
expires: 365
});
}
return !prevState
})
case 'gatsby-gdpr-facebook-pixel':
return setFacebookAnalytics((prevState) => {
if (prevState) {
Cookies.remove(cookieName);
} else {
Cookies.set(cookieName, true, {
expires: 365
});
}
return !prevState
})
default:
break;
}
initTracking()
}
// A JSX toggle within your cookie setting
<Toggle active={trackAnalytics} toggleActive={() => handleToggle('gatsby-gdpr-google-analytics')} />
// The toggle component itself
import React from 'react';
import cx from 'classnames'
import PropTypes from 'prop-types'
import './styles.scss';
export default function Toggle({
active = false,
toggleActive,
}) {
return (
<div onClick={typeof toggleActive === 'function' && toggleActive} className={cx('toggle relative cursor-pointer', { active })} />
)
}
Toggle.propTypes = {
active: PropTypes.bool,
toggleActive: PropTypes.func.isRequired
}
Toggle.defaultProps = {
active: false,
}
Use this plugin from the Gatsby Plugin Hub
gatsby-plugin-gdpr-cookies
It will provide you what you are looking for and also you can list in the options for the plugin which cookies you are looking to track + a cookieName you wish to provide which you can then work with from a component level when creating a cookie toolbar such as:
{
resolve: `gatsby-plugin-gdpr-cookies`,
options: {
googleAnalytics: {
trackingId: process.env.UA_TAG, // your UA tag goes here
cookieName: `gatsby-gdpr-google-analytics`,
anonymize: true,
allowAdFeatures: false
},
environments: [`production`, `development`]
},
},
It elimites the usage of having to inject a script into the head of the website with React-Helmet as the plugin will handle the script injection for you.

I want to use this. $ axios with Vuex constants

What I want to come true
I use this.$axios many times, so I tried to put it in a constant, but it doesn't work.
I read the official docs but didn't understand.
Is it because this isn't available in the Nuxt.js lifecycle?
Code
url.js
export const AXIOS_POST = this.$axios.$post
export const POST_API = '/api/v1/'
export const POST_ITEMS_API = '/api/v1/post_items/'
Vuex
import * as api from './constants/url.js' // url.js in this.
export const state = () => ({
list: [],
hidden: false
})
export const mutations = {
add (state, response) {
state.list.push({
content: response.content,
status: response.status
})
},
remove (state, todo) {
state.list.splice(state.list.indexOf(todo), 1)
},
edit (state, { todo, text }) {
state.list.splice(state.list.indexOf(todo), 1, { text })
},
toggle (state, todo) {
todo.status = !todo.status
},
cancel (state, todo) {
todo.status = false
},
// アクション登録パネルフラグ
switching (state) {
state.hidden = !state.hidden
}
}
export const actions = {
post ({ commit }, text) {
//I want to use it here
this.$axios.$post(api.POST_ITEMS_API + 'posts', {
post_items: {
content: text,
status: false
}
})
.then((response) => {
commit('add', response)
})
}
}
Error
Uncaught TypeError: Cannot read property '$axios' of undefined
Since your file is located into a constants directory, you should probably use some .env file.
Here is a guide on how to achieve this in Nuxt: https://stackoverflow.com/a/67705541/8816585
If you really want to have access to it into a non .vue file, you can import it as usual with something like this
/constants/url.js
import store from '~/store/index'
export const test = () => {
// the line below depends of your store of course
return store.modules['#me'].state.email
}
PS: getters, dispatch and everything alike is available here.
Then call it in a page or .vue component like this
<script>
import { test } from '~/constants/url'
export default {
mounted() {
console.log('call the store here', test())
},
}
</script>
As for the lifecyle question, since the url.js file is not in a .vue file but a regular JS one, it has no idea about any Vue/Nuxt lifecycles.

prevent multiplied `addEventListener` in vue mounted hook injected by mixin

Minimal, Reproducible Example
bug or intentional?
It seems if you kinda render into a component so that two components share one Element leads to doubled global mixin mounts on one $el.
Vue.mixin({
mounted: function() {
//debugger;
this.$el.addEventListener("contextmenu", (w) => {
console.log("contextmenu")
w.stopPropagation();
w.preventDefault();
})
}
});
Vue.component("App", {
render(h) {
return h("div", {
style: "height: 100vh;width: 100vw;background: gray"
});
}
});
new Vue({
render: h => h("App")
}).$mount("#el");
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="el"></div>
Mindset
Currently I build a vue-electron plugin to ease up contextmenu by the usage of decorators:
#MenuItems.Copy
#Component<myComponent>({})
class myComponent extends Vue{}
That's fine and all is working.
Except for some components.
Those are triggering twice on 'contextmenu'
The part where I inject into the hooks:
import Vue, { VueConstructor } from 'vue';
import { remote } from 'electron';
const { Menu } = remote
export default function applyMixin(_Vue: VueConstructor){
_Vue.mixin({
beforeCreate: contextMenuInit,
mounted: popup
})
function popup(this: Vue){
const onContextmenu = ((e: Event) => {
const menu = new Menu();
for(let menuItem of this.$contextMenuItems){
menu.append(menuItem)
}
menu.popup()
e.preventDefault()
e.stopPropagation()
}).bind(this)
// <injection of events>
this.$el.addEventListener('contextmenu', onContextmenu, false)
this.$once('hook:beforeDestroy', () => {
this.$el.removeEventListener('contextmenu', onContextmenu, false)
})
// </injection of events>
}
function contextMenuInit(this: Vue){
const options = this.$options
if(options.contextMenuItems){
this.$contextMenuItems = options.contextMenuItems;
} else if(options.parent && options.parent.$contextMenuItems){
this.$contextMenuItems = options.parent.$contextMenuItems.slice()
}
}
}
The FIX:
as the issue lies in parent - child both calling the registered hook and it is not wanted when they share $el one can simply return from the hook
if(this.parent && this.parent.$el === this.$el) return;
function ev(w){
console.log("contextmenu")
w.stopPropagation();
w.preventDefault();
}
Vue.mixin({
mounted: function() {
//debugger;
if(this.parent && this.parent.$el === this.$el) return;
this.$el.addEventListener("contextmenu", ev)
this.$once('hook:beforeDestroy', () => {
this.$el.removeEventListener("contextmenu", ev)
})
}
});
Vue.component("App", {
render(h) {
return h("div", {
style: "height: 100vh;width: 100vw;background: gray"
});
}
});
new Vue({
render: h => h("App")
}).$mount("#el");
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="el"></div>

React- only run file once external js file loaded (gapi not defined)

I am trying to use the gmail API with React.js.
I keep getting the error 'gapi is not defined'. I believe my client.js file in the HTML is loading after my mail.js file runs?
How can I get around this?
Index.html
...
<script src="https://apis.google.com/js/client.js"></script>
Index.js
import './Mail.js';
Mail.js
import { createAction, handleActions } from 'redux-actions'
const CLIENT_ID = '1.apps.googleusercontent.com'
const SCOPES = ['https://www.googleapis.com/auth/gmail.readonly']
export const SET_GMAIL_CREDENTIALS = 'SET_GMAIL_CREDENTIALS'
export const CHANGE_LOADING = 'CHANGE_LOADING'
export const SET_GMAIL_LABELS = 'SET_GMAIL_LABELS'
export const SELECT_GMAIL_LABEL = 'SELECT_GMAIL_LABEL'
export const SET_GMAIL_EMAILS = 'SET_GMAIL_EMAILS'
let defaultState = {
profile: {
emailAddress: ''
},
loading: true,
labels: [],
currentLabel: null,
emails: []
}
export const connect = () => {
return (dispatch, getState) => {
dispatch(turnLoadingOn())
gmailAuth(false, populateCredentials(dispatch), clearCredentials(dispatch))
}
}...
I think you're right. The way I'm handling these situations is by loading the external JS file from React and using it in a promise.
So your flow should be something like this:
React app loads
React app injects your file in the HTML
Do your thing in step 2's callback or .then()
Create a helper function. Put it in a folder like helpers/load-script. Below you have all the code you should have in that file:
export default function loadScript(url, cb) {
var scr = document.createElement('script');
scr.type = 'text/javascript';
if (scr.readyState) { // IE
scr.onreadystatechange = function() {
if (scr.readyState ==`loaded' || scr.readyState ==='complete') {
scr.onreadystatechange = null;
cb();
}
};
} else { // Others
scr.onload = cb;
}
script.src = url;
document.getElementsByTagName('head')[0].appendChild(scr);
}
Next, import that function inside the component you want to use it into:
import React from 'react';
import loadScript from 'helpers/load-script';
class testComponent extends React.Component {
componentDidMount() {
loadScript('https://apis.google.com/js/client.js', () => {
// do mail api stuff here
});
}
render() {
return (
<div>hi there</div>
);
}
}
export default testComponent;
I had the same problem with GrowSurf Javascript Web API, The external script loaded after the render function, and its function was being undefined in componentDidMount().
This is a logic I used to get the GrowSurf function not to be undefined. It can also help anyone who wants to use a function of any external JS introduced in index.html.
You can use DOMSubtreeModified in componentDidMount() to check every time the DOM is modified, once the external JS is loaded(found) run your function there, and then stop the looping of DOMSubtreeModified.
componentDidMount() {
let functionDefined = false;
window.addEventListener('DOMSubtreeModified', function () {
if(!functionDefined) {
if (window.growsurf) {
console.log('a function is defined', window.growsurf.getReferrerId());
functionDefined = true;
}
}
}, false);
}
For your case, you can simply do this.
componentDidMount() {
let functionDefined = false;
window.addEventListener('DOMSubtreeModified', function () {
if(!functionDefined) {
if (window.gapi) {
// do mail api stuff here
functionDefined = true;
}
}
}, false);
}

Categories