Best way to make a single multi-use modal component for CRUD/REST?


#1

So I’ve got an REST API and I’ve got a Vue app I’m using to interact with it. I want to manage certain endpoints with a modal for CREATE, EDIT, and DELETE. I ideally want to make it a single component with options that determine what it should/shouldn’t be doing.

This is proving to be a little difficult to figure out as it feels very complex. For the DELETE aspect, it’s easy since I can just have a prop with the delete link and a static modal that basically never changes. But for edit and update, it’s tricky because the input for 1 endpoint wont be the same as it is for another. I could use slots, but then since a majority of the modal will be defined inside the slot, it feels like that almost defeats the purpose of the component.

Currently the best solution I’ve thought up is to have 3 different components;

  • DeleteModal.vue
  • EditModal.vue
  • CreateModal.vue

Then inside my view, define all of them with v-if’s and base it on if it’s been clicked or not.

I’m aware this is a REALLY bad solution, but it’s the best one I’ve got so far and is one of the reasons I’m posting here to try and get some help on improving it and making a more composite Modal component so that I can just define 1 component inside my view instead of 3 with a bunch of v-ifs.

Any help is appreciated!


#2

Why not? For many cases this can be a good solution.

However if your data records get larger then your “single component” gets even more complex and much more difficult to maintain. In many cases (dependent on your application) you will also have more than one “EditModal” for different parts of your record (i.e. one for order header data, one for order adress data, one for order lines etc… )

So here is the way I solved it (works good for my application):

  • I created a CommonModal component (with a title header, a body and a footer) which always returns one of the following Results when it closes: OK, CANCEL, YES, NO, IGNORE, RETRY
  • Every CreateModal and EditModal are built on top of CommonModal, all the fields you need can be placed in the default slot
  • For any DeleteModal I created a simple MessageModal which is also built on top of CommonModal. It includes only an icon and a custom text message in its default slot (“Do you really want to delete …?”)

Now to keep the main component clean I have written a small plugin function which mounts any of the Modals with $mount, see API. So for example in your main order component you can then just use the plugin like “Vue.$modals.open(OrderEditModal, options, fnResult)”. All of the edit logic happens in the OrderEditModal.

Another advantage is that you can call your Modals from everywhere, not only in your main component.

But this is only the way I do it with my application. Maybe you find some useful ideas for structuring your Modals. Good luck.


#3

Hey twp,

Thanks for this. This sounds like a great solution.

Is it possible you could elaborate a bit more on the “Modal Plugin” part you mentioned towards the end?

Thanks!


#4

Hi,

nothing too special about this, it is simply a small plugin to mount a component.
I use typescript but you can easily convert the class into a plain JS-function. Please note that I skipped some code to keep it simple, but I think you can get the core ideas from the code below…

import { VueConstructor } from 'vue'

export enum ModalResult {
    ok,
    cancel,
    yes,
    no,
    retry,
    ignore
}

export class Modal {
    private modal: VueConstructor;
    private propsData?: object;
    private fnResult?: (result: ModalResult) => void;
    
    constructor(modal: VueConstructor, propsData?: Object, fnResult?: (result: ModalResult) => void) {
        this.modal = modal;
        this.propsData = propsData;
        this.fnResult = fnResult; 
    }

    public show() {
        // get DOM insert point
        var el = document.createElement('div');
        document.getElementsByClassName('v-baseview')[0].appendChild(el);
        
        // setup modal instance
        var vModal = Vue.extend(this.modal);
        var vModalInstance = new vModal({ propsData: this.propsData, el: el });
        
        // add close event handler to modal
        vModalInstance.$on('close', (e: ModalResult) => this.fnResult && this.fnResult(e));
    }
}

function show(modal: VueConstructor, propsData?: object, fnResult?: (result: ModalResult) => void) {
    new Modal(modal, propsData, fnResult).show(); 
}

/**
 * Register modal plugin
 */
const ModalPlugin = {
    install(vue: any, options?: any) {
        vue.$modal = {
            show: show,
        }
    }
};

export default ModalPlugin; 

Do not forget to install the plugin in your app:

import ModalPlugin from "./gui/..../ModalPlugin";
...
Vue.use(ModalPlugin);

Then you can simply call your Modal from everywhere insige a component:

addRole() {
    Vue.$modal.show(
        vCreateRoleModal, 
        undefined, 
        (r: ModalResult) => {
            if (r === ModalResult.Ok) {
                this.getRoles();
            }
        }
    );
}