Coder Social home page Coder Social logo

vue-modal-dialogs's Introduction

Guide & Demo | 2.x docs | 1.x docs

PROJECT DISCONTINUED

This project will be no longer maintained. Its features are stable enough and works perfectly in Vue 2.x.

For those who are looking for a Vue 3.x version, try https://github.com/rlemaigre/vue3-promise-dialog

Introduction

Promisify dialogs!

A typical and essential type of user interaction is dialogs. Dialogs are somehow similar to Promise. A dialog will eventually close. A Promise will eventually resolve. A dialog returns some data when it closes. So does Promise. It is time to put them together.

vue-modal-dialogs has the magic to turn dialogs into Promises. Developers can build and control dialogs a lot easier in Vue.js applications. Especially in some complicated situations like controlling multiple dialogs, nested dialogs, etc.

Feel free to open an issue or PR if you have any questions, problems, or new ideas.

Installation

Install via npm or yarn

# Use npm
npm install vue-modal-dialogs --save

# Use yarn
yarn add vue-modal-dialogs

Then import and install vue-modal-dialogs as a Vue plugin:

import * as ModalDialogs from 'vue-modal-dialogs'
Vue.use(ModalDialogs)               // No options

Guide & Demo

Guide & demo is here!

API

ModalDialogs.create

ModalDialogs.create creates a dialog function. A dialog function is nothing but a function that returns a dialog promise. The dialog function's arguments depends on the options you passed in the ModalDialogs.create function.

Declaration

function create (component: VueComponent, ...props: string[]): DialogFunction
function create (options: DialogOptions): DialogFunction

Options

component: VueConstructor | Promise<VueConstructor>

The component option is the Vue component constructor for the dialog component. It can be a Promise resolves with a Vue component constructor for supporting async component.

props: string[]

The props option maps the arguments of the dialog function to the properties of the dialog component. For example, if you call the dialog function like this:

// The props option is ['title', 'content']
const dialogFunction = create(SomeComponent, 'title', 'content')
dialogFunction('This is title', 'This is content', 'Extra argument')

The dialog component will receive these properties:

{
  title: 'This is title',       // 1st argument
  content: 'This is content',   // 2nd argument

  // Stores all arguments
  arguments: ['This is title', 'This is content', 'Extra argument']
}

If you just leave the props option empty, the dialog function's first argument will become a 'data object' stores all the properties to pass into the dialog component.

// The props option is [] (an empty array)
const dialogFunction = create(SomeComponent)
dialogFunction({
  title: 'This is title',
  content: 'This is content'
}, 'Extra argument')

The dialog component will receive these properties:

{
  title: 'This is title',       // Data from the first argument
  content: 'This is content',

  // Stores all arguments
  arguments: [{ title: 'This is title', content: 'This is content'}, 'Extra argument']
}

This is useful when you have lots of properties. It is better to use an object instead of a long list of arguments. Remember to define all the properties in the dialog component! Otherwise no property will be received.

wrapper: string

The wrapper option specifies which dialogs wrapper to render the dialog component. The default value is 'default'. In most cases you do not need to set this option.

The dialog promise will reject when the specified wrapper is not found.

<dialogs-wrapper> component

A dialogs wrapper is nothing but a slightly modified <transition-group> component.

<dialogs-wrapper> component should be placed into the root component of your project (typically App.vue), in order to make sure the component will never be destroied.

It have two main properties:

  1. name: The name of the wrapper. It MUST be unique throughout the project. The default value is default.

  2. transition-name: Alias to the name property of the <transition-group> component.

Everything other than these two properties, including event listeners, will be directly passed into the underlying <transition-group> component. You are free to use full-featured <transition-group> component.

You can create multiply wrappers at the same time. Dialogs will go into the wrapper you specifies when calling create function (see the following section). Usually one wrapper with a default name is enough for most cases.

Dialog component

Dialog components will be rendered into the dialogs wrapper when you call the dialog function.

Optional property definitions

Property definitions are optional since you have defined them in ModalDialogs.create, unless you want to validate these properties. Just define them inside the dialog component like other components.

However, if you would like to use data objects to pass properties, you must manually define all props in the dialog component.

Injections

There are some additional properties and methods available in the dialog component:

  1. $close(): A 'callback'. Call this method when it is time to close the dialog, with the data you want to send back. It will resolve the dialog promise.
  2. $error(): It is the same to $close but it rejects the Promise.
  3. arguments: An array contains everything you passed in the dialog function.

Dialog promise

A dialog promise is a promise with some additional methods for controlling the dialog outside.

  1. close(), error(): The same to the $close() and $error() in the dialog component.
  2. transition(): Returns a promise that resolves after the dialog's transition with the same value as the dialog promise.
  3. getInstance(): Returns a promise that resolves with the dialog component instance.

Integrate with TypeScript

vue-modal-dialogs have partial support for TypeScript. Vue.js 2.5 or above is required if you are using TypeScript.

ModalDialogs.create

Specify argument types and the return type through generic:

import SomeComponent from './comp.vue'

interface ConfirmData {
  title: string,
  content: string
}

// (prop1: string, prop2: string) => Promise<any>
create<string, string>(SomeComponent, 'title', 'content')

// (prop1: string, prop2: string) => Promise<boolean>
create<string, string, boolean>(SomeComponent, 'title', 'content')

// (data: ConfirmData) => Promise<any>
create<ConfirmData>(SomeComponent)

// (data: ConfirmData) => Promise<boolean>
create<ConfirmData, boolean>(SomeComponent)

Dialog component

Dialog components can be defined by extending the base class DialogComponent<ReturnType> with the help of vue-property-decorator.

The ReturnType generic argument indicates the type that the promise resolves. TypeScript can infer this generic argument in ModalDialogs.create.

import { Prop, Component } from 'vue-property-decorator'
import { create, DialogComponent } from 'vue-modal-dialogs'

@Component
export default class SomeComponent extends DialogComponent<boolean> {
  @Prop() title: string
  @Prop() content: string

  ok () {
    this.$close(true)
  }

  render (h) {
    // ...
  }
}

// (data: ConfirmData) => Promise<boolean>
create<ConfirmData>(SomeComponent)

Migration

from 2.x

  1. Instead of using the default export (import ModalDialogs from "vue-modal-dialogs";) with Vue.use use the import all syntax (import * as ModalDialogs from "vue-modal-dialogs";)
  2. makeDialog is renamed to create.
  3. The dialogId property is removed and kept internally. You might need to find another way to implement your requirement without dialogId.

from 1.x

Here are two major breaking changes:

  1. An HTML element is inserted into the DOM automatically in 1.x. Then I create a new root Vue instance on it. This causes critical problem when using vue-modal-dialogs with vuex, vue-i18n, etc.

    Remove all options in Vue.use(ModalDialogs, ...) and add a <dialogs-wrapper> component into the root component, typically App.vue, of your project.

  2. CSS z-index control are completely removed. If you need to control it, do it yourself. The dialogId prop can be used as an auto-increment z-index value. Bind it to your dialog component.

vue-modal-dialogs's People

Contributors

elpete avatar hjkcai avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

vue-modal-dialogs's Issues

TypeError: _vm.$error is not a function

Sometimes when I invoke the error or close functions I get the error below.

Seem like that happens after vue hot reload when I change code during development, but I'm concerned if the same may happen in production:

vue.esm.js?a026:1887 TypeError: _vm.$error is not a function
    at click (eval at ./node_modules/cache-loader/dist/cjs.js?{"cacheDirectory":"node_modules/.cache/vue-loader","cacheIdentifier":"c25e115a-vue-loader-template"}!./node_modules/vue-loader/lib/loaders/templateLoader.js?!./node_modules/cache-loader/dist/cjs.js?!./node_modules/vue-loader/lib/index.js?!./src/view/pages/coach/Programs/CreateTaskDialog.vue?vue&type=template&id=a5df12d0& (app.js:6069), <anonymous>:286:34)
    at invokeWithErrorHandling (vue.esm.js?a026:1856)
    at VueComponent.invoker (vue.esm.js?a026:2178)
    at invokeWithErrorHandling (vue.esm.js?a026:1856)
    at VueComponent.Vue.$emit (vue.esm.js?a026:3850)
    at VueComponent.handleClick (element-ui.common.js?5c96:9074)
    at invokeWithErrorHandling (vue.esm.js?a026:1856)
    at HTMLButtonElement.invoker (vue.esm.js?a026:2178)
    at HTMLButtonElement.original._wrapper (vue.esm.js?a026:7503)

Any ideas?

Pass event listeners to dialog?

Hi, is there a way to pass event listeners to dialog component?

I'm using create and I know I can pass a function to prop as a workaround but for consistency with my other code I'm looking for a way to pass event listeners so they are attached to the dialog's $listeners

Would appreciate any input on that.

Fails on IE and minified vue

Following code fails on IE. The reason is that IE does not support function.name property. Also this fails, when one uses minified version of vue, because minifier rewrites the "VueComponent" function.

export function isVueComponent (obj) {
  return (
    obj != null &&
    (typeof obj === 'object' || typeof obj === 'function')
  ) && (
    // must not a Vue instance
    Object.getPrototypeOf(obj).constructor.name !== 'VueComponent'
  ) && (
    // result of Vue.extend
    (typeof obj === 'function' && obj.name === 'VueComponent') ||

    // import from a .vue file
    Array.isArray(obj.staticRenderFns) ||

    // has a render function
    typeof obj.render === 'function'
  )
}

I would suggest that you use following code.

import VueComponent from "vue";
.
.
.
export function isVueComponent (obj) {
  return obj && obj.super == VueComponent;
}

Unexpected token import

Hi @hjkcai, I get this error when trying importing this plugin.

I looked into node_modules directory and the code still use es6 import, it seem.... it doesn't compile correctly?

for now I copied this plugin manually into my project folder and make webpack compiled it. this way the plugin work flawless. anyway I love your plugin.

Webpack Devserver

I have a project using Vue CLI 2, and it looks like things fail when a hot reload signal is emitted to the browser. I'm opening this issue here as I think it would be the best place to start.

The error received is:

Uncaught TypeError: this.$close is not a function

Somehow, that method is being destroyed or unlinked. If this has been seen before, please do point me in the right direction. At present, I'm having to reload the page for it to work.

Using v3 of the package, if that helps.

Thanks ๐Ÿ‘

Is there a way to have a createModal function which would insert the component into a predefined component?

Hey love the addon, i just have a question, let's say for dialogs i use a base component where i would like to slot in the components. i've got around it in a hacky way:

Confirm.vue

<template>
  <modal-layout :close="$close" :title="title" :text="text">
    <template v-slot="{ close }">
      <div class="flex items-center justify-center">
        <o-button type="secondary" @click="onDismiss(close)" :disabled="isLoading">{{ cancel }}</o-button>
        <o-button @click="onSubmit(close)" :loading="isLoading" class="ml-4">{{ ok }}</o-button>
      </div>
    </template>
  </modal-layout>
</template>
// some scripts
// props to pass onto modal-layout

ModalLayout.vue

  <div class="overlay-confirm z-20" @click.stop="close(false)">
    <div @click.stop class="modal-confirm">
      <h2 class="header text-center mb-5">{{ title }}</h2>
      <div v-if="text" class="mb-10">
        <p v-html="text"></p>
      </div>
      <slot v-bind:close="close"></slot>
    </div>
  </div>
// props to and pass in slot

and then i can do this:

export const confirm: (props: ConfirmProps) => DialogPromise<any> = create(Confirm);

could i do something like a hoc which would setup the base conponent, so i would need to repeat the boilerplate in all sub-components?:

export const confirm: (props) => create(<ModalLayout><Confirm/></ModalLayout>);

How to add dialogs-wrapper to my app.js in Vue?

When I try to add the dialogs wrapper into my files, it does not register properly. I am getting this error message:
Unknown custom element: - did you register the component correctly? For recursive components, make sure to provide the "name" option
I added to my app.js, which does not work.
What am I missing?

Not sure how to use $error instead of $close

First of all, thanks for this awesome extension. I really like the way it can be included in our application flow, instead of registering all sorts of modals that have their individual ok/cancel actions. It keeps the application flow clear and concise.

A common use-case is to ask for confirmation or additional data. E.g. showing an input field with a OK/Cancel button. Now we return an object on $close { confirmed: true| false; value: inputValue }. All good.
It would be awesome, however, if also a $cancel method could be provided. Usually nothing needs to happen here, just closing the modal on clicking the close button in the top-right corner or using the Cancel button. And then the $close method only needs to provide the input value, instead of including a confirmed property

Probably we could also use $error for this, but I'm not really sure about it's intended behaviour and how to handle this? Can you add an example for this in the guide?

TypeScript compilation error with strictNullChecks = true

Compiling with strict "strictNullChecks": true (or "strict": true since TypeScript 2.3) causes a compilation error in the typings file:

'Interface 'WrapperRenderOptions' incorrectly extends interface 'VNodeData'.
  Types of property 'on' are incompatible.
    Type '{ beforeEnter?: TransitionEventHandler | undefined; enter?: TransitionEventHandler | undefined; a...' is not assignable to type '{ [key: string]: Function | Function[]; } | undefined'.
      Type '{ beforeEnter?: TransitionEventHandler | undefined; enter?: TransitionEventHandler | undefined; a...' is not assignable to type '{ [key: string]: Function | Function[]; } | undefined'.
        Type '{ beforeEnter?: TransitionEventHandler | undefined; enter?: TransitionEventHandler | undefined; a...' is not assignable to type '{ [key: string]: Function | Function[]; }'.
          Property 'beforeEnter' is incompatible with index signature.
            Type 'TransitionEventHandler | undefined' is not assignable to type 'Function | Function[]'.
              Type 'undefined' is not assignable to type 'Function | Function[]'.'

It's because Vue.VNodeData specifies on must be of type { [key: string]: Function | Function[] }. With strict null checks, this means it cannot be undefined, which, for example, beforeEnter?: TransitionEventHandler (which is equivalent to beforeEnter: TransitionEventHandler | undefined) is specifying.

I like the fact that you get type safety when specifying the wrapper render options - it's useful for a developer. There's a way to solve the issue - to keep typings for the on events and satisfy the TypeScript compiler without changing the core vue typings. It's possible to have an intermediate type which widens the on property, so

interface VNodeData extends Vue.VNodeData {
  on?: any
}

and then have WrapperRenderOptions extend this new interface.

Happy to put together a PR.

Nested component creation

Hi,

I tried to create a ConfirmDialog that is generic and looks like this :

<template>
[...]
</template>

<script>
export default {
  props: {
    title: {
      type: String,
      required: true,
      default: null
    },
    message: {
      type: String,
      required: true,
      default: null
    }
  },
  data() {
    return {
      isOpen: true
    }
  },
  methods: {
    cancel() {
      this.close(false)
    },
    async close(returnValue) {
      this.isOpen = false
      this.$close(returnValue)
    }
  }
}
</script>

And then another one called called ConfirmDeleteDialog :

<template>
  <ConfirmDialog
    :title="title"
    :message="message"/>
</template>

<script>
import ConfirmDialog from '~/confirm-dialog.vue'

export default {
  components: {
    ConfirmDialog
  },
  props: {
    title: {
      type: String,
      required: true
    },
    message: {
      type: String,
      default: 'error'
    }
}
</script>

But when in my code I'm doing

const dialog = create(
        ConfirmDeleteDialog,
        'title'
      )
      await dialog(
        'Confirm removing ?'
      )

Then when I close the modal I have the error

Uncaught (in promise) TypeError: this.$close is not a function

Probably because this.$close is called in the generic modal and not in the one I actually create, but I'd rather keep it this way ! Does anyone know how to fix this issue ?

"[modal-dialogs] The wrapper 'default' is already exist." error with NUXT

After initial load I constantly get this warning on reload.

[modal-dialogs] The wrapper 'default' is already exist. Please make sure that every wrapper has a unique name.

I think there are some issues with NUXT SSR, not sure if the warning is a bug or if there are some problems underneath as it seems to work.

Move vue to be a dev dependency in package.json

Firstly, thanks for creating vue-modal-dialogs - it's a nice library to use. I noticed that I receive TypeScript compilation errors when using a version of vue different to the one specified in the package.json file.

I believe that the vue dependency should be moved to the devDependencies section and the TypeScript definition file should be updated. You can see how vue-router do it:
in package.json and in the definition file

This means when installing vue-modal-dialogs it won't pull in a new version of vue, and the TypeScript compiler will use the typings of whatever version of vue is installed in the project.

Happy to create a PR if you are OK with the suggestion.

Reactivity of properties of modals

I'm trying to create a modal window with reactive components, but I failed.
so, i create modalsService wrapper as so:
import { create as createModal } from 'vue-modal-dialogs';
export const modalsService = {
createModal,
toast: createModal(Toast, 'options', 'longRunningJob'),
};

then in my main component i have something like this to show modal:
modalsService.toast({message:'some text'}, this.isProcessing)

so, in modal(toast) i have this property passed, but it is not reactive.
Is there any way to detect if property that i passed have changed?

dialog not show up

my-project.zip

Hi, here is a newbee to Vue.js

I follow the instruction at https://hjkcai.github.io/vue-modal-dialogs/ but the dialog didn't show up as expected.

HELP!

main.js
import Vue from 'vue'
import App from './App'
import Element from 'element-ui'
import * as ModalDialogs from 'vue-modal-dialogs'

Vue.config.productionTip = false
Vue.use(ModalDialogs)
Vue.use(Element)

/* eslint-disable no-new */
new Vue({
el: '#app',
render: h => h(App)
})

App.vue

<script> import helloworld from './components/helloWorld' export default { name: 'App', components: { helloworld } } </script> <style> #app { font-family: 'Avenir', Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; margin-top: 60px; } </style>

Integrating it to bootstrap 4 modal UI

boot strap 4 users data-toggle to activate the modal that's is not shown by default
<button type="button" class="btn btn-primary" data-toggle="modal" data-target="#exampleModal"> Launch demo modal </button>
how would you fire toggle on makeDialog activate this toggle on click
i have messageDialog component with bootstrap modal
<template><div><div class="modal fade" id="exampleModal" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel" aria-hidden="true"><div class="modal-dialog" role="document"></div></template>

vue-modal-dialogs with Vue 2.5 and TypeScript

Vue 2.5's better TypeScript support (components as classes, @component and @prop decorators via vue-class-component") the TypeScript bindings of vue-modal-dialogs got broken:

ERROR in C:\projects\webpanel\vsmWebPanel.Ui\node_modules\vue-modal-dialogs-ts\vue-modal-dialogs.d.ts
(20,29): error TS2694: Namespace '"C:/projects/webpanel/vsmWebPanel.Ui/node_modules/vue-modal-dialogs-ts/vue-modal-dialogs"' has no exported member 'Vue'.
ERROR in C:\projects\webpanel\vsmWebPanel.Ui\node_modules\vue-modal-dialogs-ts\vue-modal-dialogs.d.ts
(25,29): error TS2702: 'Vue' only refers to a type, but is being used as a namespace here.

Can this be fixed by correcting just the bindings in vue-modal-dialogs.d.ts or are there larger issues?
Has anyone already solved that problem and can post a fix in this forum, please ?

Unable to pass options to the DialogsWrapper component

I ran into an issue when using the i18n library with vue-modal-dialogs.

The i18n library expects the component to have an i18n property on the root component. This is easy for components in my app as it is passed to the root vue component like:

new Vue({
    i18n: i18n,
    el: ...
    store: ...
    router: ...
});

But, since components shown as a dialog (after being created by the makeDialog function) are childred of the DialogsWrapper component, when trying to translate a value, the i18n library is looking for an i18n property on the DialogsWrapper component, which doesn't exist as this is created by the vue-modal-dialogs library and there are no options which will pass this to the component's constructor.

I tried modifying the line here to pass options, like

dialogsWrapper = new DialogsWrapper(options.wrapperComponentOptions)

and use it by doing

Vue.use(ModalDialogs, { wrapperComponentOptions: { i18n: i18n } });

and this caused the i18n library to work as expected.

I've not tested it, but there may be similar issues if trying to access for example, a vuex store in a modal dialog.

Would it be possible to add a configuration option to pass such options to the DialogsWrapper component?

Again, I'm happy to help put together a PR.

Port to Vue 3 (vue-next)

Hey,

thanks for this plugin. I was wondering if there are any plans on porting this project to Vue3?

Cheers.

Issue with Object.assign in IE11

IE11 complains about the missing Object.assign even though babel-polyfill are imported the top of the app.

"devDependencies": {
    "axios": "^0.18",
    "cross-env": "^5.1",
    "laravel-mix": "^2.1",
    "laravel-mix-purgecss": "^2.2.3",
    "laravel-mix-tailwind": "^0.1.0",
    "lodash": "^4.17.11",
    "tailwindcss": "^0.6.6",
    "vue": "^2.5.17"
},
"dependencies": {
    "babel-polyfill": "^6.26.0",
    "laravel-echo": "^1.4.0",
    "moment": "^2.22.2",
    "moment-timezone": "^0.5.17",
    "pusher-js": "^4.3.0",
    "vue-modal-dialogs": "^3.0.0",
    "vue-template-compiler": "^2.5.17"
}

How could this be fixed ?

Move development dependencies to devDependencies in package.json

I noticed when I installed vue-modal-dialogs, that it pulled babel-preset-env and element-ui into the project too. It seems that these are actually dev dependencies and can be moved in package.json to the devDependencies section.

Happy to create a PR if you like?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.