In Nuxt/VueJs, how can I get SendInBlue Chat Widget to see route changes? - vue.js

I'm using SendInBlue chat widget on a Nuxt/VueJs website. Through the
SendInBlue Conversations UI,
the widget reports the visitor's page current location (url and title).
However, the location does not change when the internal route changes (no hard browser location change).
I want to see the location changes.
How can I notify the SendInBlue Chat Widget that the route is changing?

The
SendInBlue Chat Widget API
includes a pageView method that can be called to alert the widget to a route change.
SibConversations('pageView')
This Answer about watching a route
provides the basic framework. Note that in my case I added the code to the /layouts/default.vue since it renders most of my pages. (Add to all necessary layouts.)
<script>
export default {
watch: {
$route() {
// on route change, notify SendInBlue Chat Widget so it updates visitor location
if (process.client) {
// delay 0.5 seconds to be sure route has actually changed ($nexttick does show correct page title)
window.setTimeout(() => {
window.SibConversations('pageView')
}, 500)
}
},
},
// ... and data, methods, etc., as needed
}
</script>
So now when the route changes, we notify the widget.
Note: If we trigger window.SibConversations('pageView') without a delay, I found that the title is not correct. I'm assuming the page is not yet rendered. I tried using this.$nexttick, but the title is still not present. So, I use window.setTimeout() to provide a short delay. I found that 100ms works for me, but I used a longer time in case a slower computer took longer. (And we aren't worried about half-second accuracy.)
I also wrapped the call to ensure it's only called on client side. No nasty "windows undefined" errors.

Related

With Vue and the Nuxt store, how do I fetch from API on build, but not ever by the client?

I'm using Vue and Nuxt and want to pull data into my build that is mostly static. Each time we build the site, we want to get the latest data and then never call the API again. The data does not need to be updated dynamically, it just needs to be available for the app and can be built in.
One of the problems is that the call to get the API data is long (>5 seconds) and we don't want the customer waiting.
Our thinking was to
Call an API at some point in the build process to collect the data
Save the data in the store so other components can access it.
Not call the API to get the data again.
How might I do this? I really appreciate any help, I'm pretty new to Vue and especially Nuxt, but really enjoy both.
What I've tried
My understanding is that using fetch() in a component will call both on the server before the initial page is rendered and on the client some time after the component is mounted.
From the documentation on Nuxt
fetch is a hook called during server-side rendering after the
component instance is created, and on the client when navigating. The
fetch hook should return a promise (whether explicitly, or implicitly
using async/await) that will be resolved:
On the server before the initial page is rendered On the client some
time after the component is mounted
I've also tried this approach (and am currently using it) from Stackoverflow
export const actions = {
async nuxtServerInit ({ state }, { req }) {
let response = await axios.get("some/path/...");
state.data = response.data;
}
}
But there is a caveat in the answer:
nuxtServerInit will fire always on first page load no matter on what page you are. So you should navigate first to store/index.js.
Anyway, I could use a hand figuring out how to do this.

Vuejs detect the change and update the page without user's involvement

I've got the VueJS single page application (SPA) and when users successfully register in my app it redirects them to the Home page. This Home page shows a bar (alert) asking users to confirm the email they have provided during the registration. Note: this bar will we be constantly shown until they confirm it!
Currently when a user confirms the email it opens the confirmation page in a new tab saying that Email have been successfully confirmed! and the bar disappears but when user goes to the original tab (Home page) the bar is still shown.
Is there any way you could listen on the background for a change of the user's state (user.isVerified) and hide the bar without user's involvement (refresh the page/click on any links)?
To get the user's data I'm using an API call:
getUser({ commit }) {
return new Promise((resolve, reject) => {
axios
.get('http://localhost:8000/api/user')
.then(response => {
commit('setData', response.data.data);
resolve(response);
})
.catch(errors => {
reject(errors.response);
})
})
}
JSON response:
{
"data": {
"id": 2,
"name": "John Doe",
"email": "john#email.com",
"isVerified": true,
"createdAt": "2020-04-17T13:17:07.000000Z",
"updatedAt": "2020-04-26T23:15:24.000000Z"
}
}
Vue page:
<template>
<v-content>
<v-alert
v-if="user.isVerified === false"
tile
dense
color="warning"
dark
border="left"
>
<div class="text-center">
<span>
Hey {{ user.name }}! We need you to confirm your email address.
</span>
</div>
</v-alert>
<v-content>
<router-view></router-view>
</v-content>
</v-content>
</template>
To be clear, when you say "listen for state" - each Vue instance has its own state, so your website open in another tab does not naturally share state with the original tab. It sounds like you want your Vue app to listen for changes in your database, so that you can tell when your API has marked your user as 'confirmed'. This is a great opportunity for Websockets.
Essentially, your app has a 'listener' for a 'user confirmed' message. When your API confirms a user, it sends the message. When the listener receives that message, it updates the user confirmation in state and refreshes (or, better, re-requests the user from the API). This is the same technology that powers push notifications, chat apps, etc.
You have to set user.isVerified = true somewhere in the conformation component, e.g. in the mounted hook:
ConformationPage.vue
mounted () {
this.user.isVerified = true
},
If the "user" object is stored in Vuex store, then dispatch or commit change to the store instead:
ConformationPage.vue
mounted () {
this.$store.commit('userIsVerified', true)
}
store.js
mutations: {
userIsVerified (state, value) {
state.user.isVerified = value
}
}
Keep in mind, if that ConformationPage.vue component is a page (route) and not just a component, make sure that users cannot just verify themselves simply by visiting that page before even getting the email.
And also make sure you lazy-load that component, because otherwise it might be rendered when the user opens the website, which will immidiatelly verify them.
tldr;
This is the job for Websockets.
Long answer below
Why do you need Websockets?
The idea behind it is that your application needs to know whether the user has truly confirmed his email. And for it to be successfully confirmed it has to access the backend server which connects to your user database.
So now, you need your backend server to update the data in the frontend application right? Yup! If you are able to answer this, you already halfway through.
However, this is what your frontend application can't do. For the example in the question right now you used axios which uses HTTP Protocol to ask the backend server for data.
So what's up with the HTTP Protocol?
The problem with HTTP Protocol is that it only allows you to request one way to the back end server. So your interaction is only REQUEST and RESPONSE. Ok, so what? Isn't it fine? I can do some sort of setInterval until I get the response I wanted right? Well, that was my thought and technically you could.
However, it's a bad practice and you shouldn't even think about it.
Ok, so how do I know when the database is updated?
This is where the Websocket comes in.
MDN interpretation of Websocket
The WebSocket API is an advanced technology that makes it possible to open a two-way interactive communication session between the user's browser and a server. With this API, you can send messages to a server and receive event-driven responses without having to poll the server for a reply.
In a simple way, it allows your backend server to talk to your frontend application. Do I need to replace my HTTP backend server? No, WebSocket is actually made to be together with HTTP, act like it was some sort of an extension to HTTP Protocol.
Now I would say, this is where the job is hard. To learn about websocket, and create a backend server that supports your needs. Also, your frontend application has to adapt to use WebSocket as well.
If you're really interested in getting some live data and is not interested (or doesn't have time) to create a backend server, you should check out Firebase. It's free, and you can have fun with it.
I hope that can help you get started. Thank you!

Is there a way to get the url of router.back in vue?

Problem
I have an electron app with vue. As the user has no UI to navigate back I created a back button.
This works so far so good. But being logged in and on the index page the user shouldn't be able to navigate back any more. As he would navigate back to the login but would get redirected back to the index page. And this wouldn't make much sense. For that I want to disable the back button.
Tested
I have added a global variable to get the previous route in my components. But somehow this gets messed up when clicking twice or more often. It looks like that:
router.beforeEach((to, from, next) => {
Vue.prototype.$prevRoute = from;
return next();
});
I think it is because when navigating back the previous route changed to the route you were coming from. And not the history anymore.
There seems to be a github feature request to add the referrer but it was opened in 2016 and I am not sure if it will be again the same problem as above.
https://github.com/vuejs/vue-router/issues/883
In-component guards
I also tested beforeRouteLeave and beforeRouteUpdate but as the back-button is in my layout these methods are not called.
Application-design
I am having a login-page with authentication. After that the user-information is stored in the local-storage and I am using a middleware concept for redirecting (https://markus.oberlehner.net/blog/implementing-a-simple-middleware-with-vue-router/)
To go back I am using this method: this.$router.back()
Expectation
My expected result would be to be able to check in the back-button-component if the back-route will be the login or if there the going-back-stack has only one value left. In that case I would disable my back-button.

node express multer fast-csv pug file upload

I trying to upload a file using pug, multer and express.
The pug form looks like this
form(method='POST' enctype="multipart/form-data")
div.form-group
input#uploaddata.form-control(type='file', name='uploaddata' )
br
button.btn.btn-primary(type='submit' name='uploaddata') Upload
The server code looks like this (taken out of context)
.post('/uploaddata', function(req, res, next) {
upload.single('uploaddata',function(err) {
if(err){
throw err;
} else {
res.json({success : "File upload sucessfully.", status : 200});
}
});
})
My issue is that while the file uploads successfully, the success message is not shown on the same page, ie: a new page is loaded showing
{success : "File upload sucessfully.", status : 200}
As an example for other elements (link clicks) the message is displayed via such javascript:
$("#importdata").on('click', function(){
$.get( "/import", function( data ) {
$("#message").show().html(data['success']);
});
});
I tried doing a pure javascript in order to workaround the default form behaviour but no luck.
Your issue has to do with mixing form submissions and AJAX concepts. To be specific, you are submitting a form then returning a value appropriate to an AJAX API. You need to choose one or the other for this to work properly.
If you choose to submit this as a form you can't use res.json, you need to switch to res.render or res.redirect instead to render the page again. You are seeing exactly what you are telling node/express to do with res.json - JSON output. Rendering or redirecting is what you want to do here.
Here is the MDN primer on forms and also a tutorial specific to express.js.
Alternatively, if you choose to handle this with an AJAX API, you need to use jquery, fetch, axios, or similar in the browser to send the request and handle the response. This won't cause the page to reload, but you do need to handle the response somehow and modify the page, otherwise the user will just sit there wondering what has happened.
MDN has a great primer on AJAX that will help you get started there. If you are going down this path also make sure you read up on restful API design.
Neither one is inherently a better strategy, both methods are used in large-scale production applications. However, you do need to choose one or the other and not mix them as you have above.

Prevent offline iphone webapp from opening link in Safari

I’m developing a website that will work with mobile safari in offline mode. I'm able to bookmark it to the home screen and load it from there. But, once opened from the home screen, clicking on certain links will jump out of the app and open in mobile safari – despite the fact that I preventDefault() on all link clicks!
The app binds an onclick event handler at the <body> level. Using event delegation, it catches any click on any link, looks at its href (eg 'help' or 'review'), and dynamically calls a javascript template and update the pages. The event handler calls preventDefault() on the event object – for some of the links this works, and the page is updated with the template output. However, for the links that result in a hit against the local database before outputting the results of the template, the links are opened in mobile safari.
In desktop safari, all the links work even when i’m offline – something is happening that’s mobile safari specific.
Any thoughts on why some links would work offline, but not others? None of the link URLs in question are listed in the manifest file, but they don’t (shouldn't) need to be since the link action is prevented.
a couple extra oddities:
* once I click on a a link that loads in mobile safari, even if I'm offline, those same links now work, and the templates populated with data from the db work properly. in other words: the links fail when opened from the home screen, but not from within mobile safari offline
* changing the link to remove the database hit (populating the template with a mock db result) solves the problem, and the links can be clicked in the app from the home screen.
You may want to take a look at this: https://gist.github.com/1042026
// by https://github.com/irae
(function(document,navigator,standalone) {
// prevents links from apps from oppening in mobile safari
// this javascript must be the first script in your <head>
if ((standalone in navigator) && navigator[standalone]) {
var curnode, location=document.location, stop=/^(a|html)$/i;
document.addEventListener('click', function(e) {
curnode=e.target;
while (!(stop).test(curnode.nodeName)) {
curnode=curnode.parentNode;
}
// Condidions to do this only on links to your own app
// if you want all links, use if('href' in curnode) instead.
if('href' in curnode && ( curnode.href.indexOf('http') || ~curnode.href.indexOf(location.host) ) ) {
e.preventDefault();
location.href = curnode.href;
}
},false);
}
})(document,window.navigator,'standalone');
I got it to work, the problem was due to an unseen error in the event handler code (unrelated to stopping the link from being followed). If you bind an event handler for click events to the body tag, and call preventDefault(), then the link will not be followed and mobile safari will not open, and you can define you own logic for updating the page based on that link url.
You should be sure that you call preventDefault() before any errors could possibly occur - the problem in my case was that an error was occurring in the event handler before preventDefault() was called, but of course I couldn't see that error in the console because the link had already been followed.
Here's the code I'm using (it assumes DOM standard events and would fail in IE):
bodyOnClickHandler = function(e) {
var target = e.target;
if (target.tagName == 'A') {
e.preventDefault();
var targetUrl = target.getAttribute("href");
//show the page for targetUrl
}
}