Skip to content
This repository has been archived by the owner on Jul 17, 2023. It is now read-only.

protonemedia/inertia-vue-modal-poc

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

18 Commits
 
 
 
 
 
 
 
 

Repository files navigation

Warning: Like the Tables package, I've decided to archive this repository as I've moved away from Inertia.js to focus on Splade and Eddy 🔥

Inertia Vue Modal POC

I've copied the default Laravel Jetstream modal component for this demo.

Blog post

Proof of Concept: Load any route into a modal with Inertia.js using Laravel and Vue.js

Limitations

This POC is a very early draft, so these limitations will probably be fixed soon.

  • No support for nested modals (modal in modal)
  • Only tested on Vue 2.6 + Laravel 8
  • No access to the parent from within the modal
  • No way to close the modal from the parent
  • It doesn't use browser history navigation

Installation

Client-side installation

npm i @protonemedia/inertia-vue-modal-poc

The only dependency for this POC to work is vue-portal.

In your main JavaScript file, register the Modalable and ToModal components:

import Vue from "vue";
import { Modalable, ToModal } from "@protonemedia/inertia-vue-modal-poc"
import PortalVue from "portal-vue";

Vue.component("Modalable", Modalable);
Vue.component("ToModal", ToModal);
Vue.use(PortalVue)

In your root layout, you need to add the ComponentModal as the last component of your template:

<template>
  <div class="min-h-screen">
    <nav></nav>

    <!-- Page Content -->
    <main>
      <slot />
    </main>

    <ComponentModal />
  </div>
</template>

<script>
import { ComponentModal } from "@protonemedia/inertia-vue-modal-poc"

export default {
  components: {
    ComponentModal,
  },
};
</script>

Server-side installation

In your Laravel application, you only need to add a few lines of code to the HandleInertiaRequests middleware.

  1. Add the handle method to the HandleInertiaRequests middleware.
  2. Add the isModal property to the shared data array.
<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Inertia\Inertia;
use Inertia\Middleware;
use Symfony\Component\HttpFoundation\RedirectResponse;

class HandleInertiaRequests extends Middleware
{
    public function handle(Request $request, Closure $next)
    {
        $response = parent::handle($request, $next);

        if ($response instanceof RedirectResponse && (bool) $request->header('X-Inertia-Modal-Redirect-Back')) {
            return back(303);
        }

        if (Inertia::getShared('isModal')) {
            $response->headers->set('X-Inertia-Modal', true);
        }

        return $response;
    }

    public function share(Request $request)
    {
        return array_merge(parent::share($request), [
            'isModal' => (bool) $request->header('X-Inertia-Modal'),
        ]);
    }
}

Usage

Since we added the ComponentModal component, the global $inertia object now has a visitInModal method. This allows you to make an Inertia visit that loads into the modal.

You can use this method, for example, in the @click handler of a button:

<button @click="$inertia.visitInModal('/user/create')">Load in modal</button>

Instead of using the method in your template, you can also use it in your script:

<script>
export default {
  methods: {
    openModal() {
      this.$inertia.visitInModal('/user/create');
    },
  },
};
</script>

Update the page you want to load into a modal

In most cases, the /user/create endpoint renders a form that's wrapped into a template, maybe with other components and components to style the form. Here's a simple example of what the UserCreate.vue component might look like:

<template>
  <!-- app-layout provides the sidebar navigation and footer -->
  <app-layout>
    <!-- form-panel provides a nice padding and shadow -->
    <form-panel>
      <form @submit.prevent="form.post('/user/store')">
        <input type="text" v-model="form.name">
        <input type="email" v-model="form.email">

        <button type="submit">Login</button>
      </form>
    </form-panel>
  </app-layout>
</template>

<script>
export default {
  data() {
    return {
      form: this.$inertia.form({
        name: "",
        email: "",
      }),
    };
  },
};
</script>

To load this form into a modal, we don't want the sidebar, footer, and styling from the form-panel component. We want just the form itself!

To accomplish this, you need to do three things:

  1. Add the IsModalable mixin to your component.
  2. Wrap your whole component into the Modalable component.
  3. Move the form to a separate #toModal template and replace it with a ToModal component.
<template>
  <!-- the new Modalable root component -->
  <Modalable :is-modal="isModal">
    <!-- the 'old' root component -->
    <app-layout>
      <form-panel>
        <!-- the previous location of the form, replaced by the ToModal component -->
        <ToModal />
      </form-panel>
    </app-layout>

    <template #toModal>
      <!-- the 'new' location of the form -->
      <form @submit.prevent="form.post('/user.store')">
        <input type="text" v-model="form.name">
        <input type="email" v-model="form.email">

        <button type="submit">Login</button>
      </form>
    </template>
  </Modalable>
</template>

<script>
import { IsModalable } from "@protonemedia/inertia-vue-modal-poc"

export default {
  mixins: [IsModalable],

  components: {
    Modalable
  },

  data() {
    return {
      form: this.$inertia.form({
        name: "",
        email: "",
      }),
    };
  },
};
</script>

Now when you visit /user/create, nothing has changed! You still have your layout and form-panel styling. But when you load this component into a modal, it will only render the form.

Handling redirects

By default, redirects are handled as any other Inertia request. For example you're visiting /user, you open /user/create in a modal, and after a successful submit, you redirect the user to the detail page of the newly created user:

public function store(UserStoreRequest $request)
{
    $user = User::create(...);

    return redirect()->route('user.show', $user);
}

You might not always want to route to the detail page. Luckily, you don't have to update your server-side implementation.

The visitInModal method accepts a second argument that can either a Boolean or a callback. Instead of redirecting the user, the user stays on the same page, and you can manually handle the event with the callback. This callback is executed after a successful request, for example, when the new user is stored in the database.

this.$inertia.visitInModal('/user/create', (event) => {
  // do something
});