DEV Community

loading...
Cover image for Route based modals with Laravel, Inertia.js and Vue.js

Route based modals with Laravel, Inertia.js and Vue.js

Lars Klopstra ⚡
Software Engineer — Creator of Nebula, Laravel enthusiast & blogger.
Originally published at larsklopstra.nl ・3 min read

Available options

Working with Vue.js gives us more flexibility. We can easily take advantage of existing libraries such as vue-js-modal or creating our own client side one. However, this comes with some downsides. We now rely on the client side state which can look like:

<script>
// Our View.vue

export default {
  data() {
    return {
      createModalIsOpen: false
    }
  },

  mounted() {
    if (location.hash === 'createModal') {
      this.createModalIsOpen = true
    }
  }
}
</script>
Enter fullscreen mode Exit fullscreen mode

We already see some problems arise. We're relying on a location hash example.app/resource#createModal which can't be read from the back-end. Outsmarting the system with a query parameter such as example.app/resource?modal=createModal solves that. But we still have a problem. How are we going to pass along modal specific data?

My approach

Okay, let's take a few steps back. What we actually want is example.app/resource/create and none of that query or hash nonsense. So, how do we achieve that?

// CompanyUserController.php

class CompanyUserController
{
    public function index(Company $company)
    {
        return inertia('Companies/Users/Index', [
            'company' => $company,
            'users' => $company
                ->users()
                ->orderBy('created_at', 'desc')
                ->paginate(),
        ]);
    }

    public function create(Company $company)
    {
        inertia()->modal('Companies/Users/CreateModal');

        return $this->index($company);
    }
}
Enter fullscreen mode Exit fullscreen mode

Looking good, but... Where did we get the modal() method from? And how do we handle this in the front-end? Slow down a bit. First, let's have a look at this method.

In the AppServiceProvider boot() method I've created a macro for the Inertia response factory:

// AppServiceProvider.php boot()

ResponseFactory::macro('modal', function ($modal) { 
    inertia()->share(['modal' => $modal]); 
});
Enter fullscreen mode Exit fullscreen mode

This simply passes the modal's path as prop to the front-end which we can take care off. Now we're going to see how we catch this on the front-end. Because this project is based on Vue 2, we're going to use a mixin:

// UseModal.js

const useModal = { 
  computed: { 
    modalComponent() { 
      return this.$page.props.modal 
        ? () => import(`@/Pages/${this.$page.props.modal}`) 
        : false 
    }
  }
}

export { useModal }
Enter fullscreen mode Exit fullscreen mode

What this mixin basically does is checking if there's a modal component set. If there's one we're going to dynamically import the modal's Vue component, otherwise we return false and don't render anything. The @ symbol is an alias for ./resources/js you can achieve that easily using Laravel Mix.

The mixin does nothing on its own. We have to use it in our global Vue instance which will look like this:

new Vue({ 
  mixins: [useModal], 
  render: h => h(App, {
    props: {
      initialPage: JSON.parse(el.dataset.page),
      resolveComponent: name => import(`./Pages/${name}`).then(module => module.default), 
    }, 
  }),
}).$mount(el)
Enter fullscreen mode Exit fullscreen mode

Alright, cool. We're almost set. There's only a few steps left. How are we going to render the actual component? Because we have the mixin we can easily obtain the component and render it inside our app layout so we can make use of the modal everywhere:

<Component 
  v-bind="$page.props" 
  v-if="$root.modalComponent" 
  :is="$root.modalComponent"
/>
Enter fullscreen mode Exit fullscreen mode

This is a dynamic Vue component, we can tell it what to render by passing a component name or path to the :is="<component>" attribute. Also, notice how we check if there's a modal and how we pass the data. The modal has access to the page props, just like a regular Inertia.js view.

You forgot something?

On the first glance, everything looks fine. However, if you were to build an edit modal you're now probably wondering: What to do if my create method has additional data such as roles, users or companies I can select from?

No problem, this we can easily solve by allowing the index method to have an additional parameter:

// CompanyUserController.php

class CompanyUserController
{
    public function index(Company $company, array $modalProps = [])
    {
        return inertia('Companies/Users/Index', array_merge([
            'company' => $company,
            'users' => $company
                ->users()
                ->orderBy('created_at', 'desc')
                ->paginate(),
        ], $modalProps));
    }

    public function create(Company $company)
    {
        inertia()->modal('Companies/Users/CreateModal');

        return $this->index($company, [
            'roles' => Role::all(),
            'moreOptions' => ['...', '...'],
        ]);
    }
}
Enter fullscreen mode Exit fullscreen mode

Awesome, we now have a route based modal with Inertia.js and Vue.js. I hope this will be useful for your projects (for now). I say for now because Jonathan and the Inertia team is working on modals as well. So until that ships, feel free to use this implementation.

...

This article was originally posted on Larsklopstra.nl - if you want to stay up-to-date follow me on Twitter

Discussion (0)

Forem Open with the Forem app