Nuxt reusable dynamic custom page transition with javascript hooks? - vue.js

I have a Nuxt.js site that I'm trying to get some fancy page transitions working on. I think I understand how you're supposed to use the transition setting when it's just CSS, but how do I make it reusable with JavaScript hooks?
It seems to me we should be able to do something like this:
// In a Page.vue template
transition(to, from) {
if (!from) {
return "fade"
}
if (to.name == "directors-name-work") {
// Animate to video playing
return "to-video"
}
if (from.name == "directors-name-work") {
// Scroll to slideshow, and at same video we just came from.
return "from-video"
}
}
And then I need to be able to define what the JS hooks are for to-video and from-video in JavaScript somewhere, but I have no idea where that goes? Where does enter() and beforeEnter() hooks get defined for the separate transitions? It makes sense if we just have one transition, then I could do it in a mixin. But when it is dynamic I have no idea.
Is there a file I should be putting somewhere called transition-to-video and transition-from-video?

It's currently undocumented, but the page's transition function can return a transition object, which may include the transition JavaScript hooks. This allows you to define your shared transition objects in a common file, and import them into a page as needed:
~/transitions.js:
export default {
fade: {
name: 'fade',
mode: 'out-in',
beforeEnter(el) {
console.log('fade beforeEnter')
}
},
bounce: {
name: 'bounce',
afterEnter(el) {
console.log('bounce afterEnter')
}
},
}
~/pages/about.vue:
<script>
import transitions from '~/transitions'
export default {
transition(to, from) {
return to.query.fade ? transitions.fade : transitions.bounce
},
}
</script>

Related

Vuejs setting the data is not reactive

am completely new to Vuejs, sorry if this is a stupid question.
This is a nuxt app and am using an IntersectionObserver and depends on the element visibility am trying to change the internal state (data). but its not reactive unless i hit the refresh in vue dev tools.
so this is my approach
async mounted(){
let options = {
root: document.querySelector('#scroll-root'),
rootMargin: '0px',
threshold: 1.0
}
const testimonialStart:any = document.querySelector('#testimonial-start')
let startObserver = new IntersectionObserver(((entries,observer)=>{
entries.forEach((entry)=>{
if(entry.isIntersecting){
this.updateTesti(false)
}
else{
this.updateTesti(true)
}
})
}), options);
startObserver.observe(testimonialStart)
}
in the methods
updateTesti(st:boolean){
this.testiPrev = st
console.log(st,'state')
},
in the data
data(){
return {
testiPrev:false,
}
}
there is no issues in the intersection observer, in the console.log am getting the boolean value as expected.
what should i need to do to get reactivity here?
temporary solution:
I found that if I add testiPrev like below inside watch am getting the reactivity.
watch: {
testiPrev: function(){
}
},
this made me to ask one more question, do we need to explicitly include all the properties inside watch to achieve reactivity, if any better way please let me know.

Globally Accessible Component Instance

In our production applications with Vue 2.x, we have a toast component. This toast component is mounted once via a plugin (code below) and is then added to the Vue prototype making it accessible in every component instance.
This makes life a lot easier instead of having to add the toast to everywhere we use.
Vue 2.x plugin
export default {
install(vue: any, _: any) {
const root = new Vue({ render: (createElement) => createElement(Toast) });
root.$mount(document.body.appendChild(document.createElement("div")));
const toastInstance: Toast = root.$children[0] as Toast;
vue.prototype.$toast = {
show: (state: ToastState, text: string) => { toastInstance.show(state, text); },
hide: () => { toastInstance.hide(); }
};
}
Which can then be called in any component like:
this.$toast.show(ToastStates.SUCCESS, "Some success message");
I have recently started another project and would like to do something similar, except using Vue 3. Because we don't have access to this in the setup function, I can't use the same approach as before.
I have been looking into a few things, and have found a few ways of doing it, but none as a definitive best practice.
Provide / Inject:
This seems the most promising, where I can use
export const appInstance = createApp(App);
then
appInstance.provide("toast", toastComponentInstance)
which I can then inject in any components. The problem with this, is that to get it available in every component, it needs to be attached to the initial app instance, where it hasn't been created yet. Maybe I could manually mount it and pass it in (but that seems like a hack).
Composition:
I have also looked at this issue here: How to access root context from a composition function in Vue Composition API / Vue 3.0 + TypeScript? but didn't find that very useful and I had to do all types of hacks to actually gain access to the plugin. Gross code below..
export function useToast() {
const root = getCurrentInstance();
const openToast: (options: ToastOptions) => void = (options: ToastOptions) => {
root.ctz.$toast.open(options);
}
const closeToast: () => void = () => {
root.ctx.$toast.close();
}
return {
openToast,
closeToast
}
}
I have other ideas but they seem far fetched an hacky. Keen to hear peoples thoughts on other solutions. I just want a simple way to have 1 instance of a toast, that I can call two functions on to open / close it when and where I want.
This is roughly how I'd do it...
I'd use Composition API, because it makes passing around internals easy
(I'm using popup instead of toast for simplicity)
myPopup.vue
// internal
const popupMessage = Vue.ref('');
const popupVisible = Vue.ref(true);
// external
export const popUpShow = function(message) {
popupMessage.value = message
popupVisible.value = true
}
export const popupHide = function () {
popupVisible.value = false
}
export default {
setup(){
return {
popupMessage, popupVisible, popupHide
}
}
}
Some component, anywhere, composition or class based...
import { popUpShow } from "./myPopup";
export default {
methods: {
myTriggeredEvent() {
popUpShow("I am your Liter")
}
}
}
By exposing popUpShow, which acts as a singleton, you can import that from anywhere, and not have to worry about context.
There the drawback in using this kind of setup/architecture is that it doesn't scale well. The problem happens if your architecture reaches a certain size, and you have multiple triggers coming from various sources that the component needs to have complex logic to handle its state (not likely for this example though). In that case, a managed global store, ie. Vuex, might be a better choice.

How to remove the dependency on jquery

When I research on how to integrate Plotly with Vue, I found this example:
https://codepen.io/rhamner/pen/MXgWqJ
It meets my requirement, but it requires jquery js, I would like to remove the dependence of jquery js file.
I tried to add bellow code into the component
ref='chart_id'
and in the mounted() I change as bellow:
this.$refs.chart_id.$on('plotly_hover', this.hover);
But looks like it doesn't work.
How to change the code, I just think it should be able to code in another way to exclude jquery to save move time one page loading.
Thanks!
jQuery is only used in mounted for two bindings which can be easily rewritten:
//...
mounted() {
this.Plot();
this.$watch("data", this.Plot, { deep: true });
window.addEventListener("resize", this.onResize);
},
beforeDestroy() {
window.removeEventListener("resize", this.onResize);
},
methods: {
Plot() {
Plotly.newPlot(this.divId, this.data, this.layout);
this.$el.on('plotly_hover', this.hover);
},
//...
}
//...
Since your normal hover event has points inside event, not inside eventData, you also need to modify the hover function to look for points in both places:
hover: function (event, eventData) {
this.$emit('hover', eventData
? eventData.points
: event.points,
this.divId);
},
... to cover both cases.
See it working without jQuery here: https://codepen.io/andrei-gheorghiu/pen/XWmJbYo

Kendo grid details template and VueJS

I need to render a sub grid using Kendo UI. Here is an example from their site.
In their example, basically they construct the sub grid using jQuery, so I will lost the connection with my Vue component. In particular, that sub grid has to have some buttons to trigger other actions, like showing a modal and then removing some data.
I have a way to do this, but I want to know if there is any more "elegant" solution to my problem:
<script>
var myComponent = {
name: 'myComponent'
data() {
...
},
methods: {
test: function (data) {
// here I do something with the data
}
}
}
export default myComponent
</script>
by defining myComponent as a variable first, I can access it from my kendo commands:
$('<div />').appendTo(e.detailCell).kendoGrid({
dataSource: e.data.someArray,
columns: [
{
command: [
{
name: 'edit',
click: function (e) {
var data = this.dataItem($(e.target).closest('tr'))
templatesManager.methods.test(data) ---> HERE
}
}
]
}
]
I don't like the approach of having a reference to my component, and as I don't have much experience with Vue, I want to know if this is the right way to communicate between the grid and my component. Unfortunately, we cannot get rid of Kendo Grid. Here I found an example on how to connect between my vue component and AngularJS via events... is that a better option? this solution of having a reference to the component could cause any potential issues?

explain vue-router component as a function

I have seen in several different places the following type of route definition:
{ path : '/dashboard',
component: { render (c) { return c('router-view') }},
children:[{
path:"",
component: Dashboard
}]
},
I am trying to understand how this is different then
{ path : '/dashboard',
component: Dashboard
},
I think it is related to the optional addition of child routs (e.g. /dashboard/user) so that and the children array here just explains that the Dashboard component renders the path /dashboard whereas if I had the second piece of code then it can only render /dashboard.
What I do want to know is what exactly this does
component: { render (c) { return c('router-view') }},
I assume this is some form of a degenerated component but I don't understand what exactly does it do and how.
In Vue, a component is created using an object containing its configuration.
The simplest possible component may look something like this
componentConfig = {
template: '<div>test</div>'
};
Vue.component('test', componentConfig);
In some cases, a developer might not want to use template, and would want to create element from scratch using pure Javascript. That's where render function comes in.
Vue recommends using templates to build your HTML in the vast majority
of cases. There are situations however, where you really need the full
programmatic power of JavaScript. That’s where you can use the render
function, a closer-to-the-compiler alternative to templates.
from https://v2.vuejs.org/v2/guide/render-function.html#Basics
To change the example above to using render function:
componentConfig = {
render: function(createElement) {
return createElement('div', 'test')
}
};
Vue.component('test', componentConfig);
They would produce the exact same result:
https://codepen.io/jacobgoh101/pen/ZoKwKb?editors=1010
https://codepen.io/jacobgoh101/pen/PemVmy?editors=1010
In other words, render function is simply an alternative to using template.
{
component: {
render(c) {
return c('router-view')
}
}
}
is equal to
{
component: {
render(createElement) {
return createElement('router-view')
}
}
}
is equal to
{
component: {
template: `<router-view></router-view>`
}
}
Because render function is closer-to-the-compiler, it's faster compared to using template. That's probably why the author of your code does it this way.
I don't know the rest of your code, but it looks like this might be an implementation of the vue-router Lazy Loading functionality. Basically, Vue + Webpack are going to split your code into chunks and only load those chunks whenever the user attempts to navigate to those routes, rather than loading them all and creating a bigger bundle to download than necessary.
When building apps with a bundler, the JavaScript bundle can become quite large, and thus affect the page load time. It would be more efficient if we can split each route's components into a separate chunk, and only load them when the route is visited.
Combining Vue's async component feature and webpack's code splitting feature, it's trivially easy to lazy-load route components.