Dialog: remove v-model, add append-to-body

This commit is contained in:
Leopoldthecoder 2017-07-26 16:39:32 +08:00 committed by 杨奕
parent 41c19249ab
commit 7f70ea6726
7 changed files with 175 additions and 69 deletions

View File

@ -22,6 +22,8 @@
dialogVisible: false,
dialogTableVisible: false,
dialogFormVisible: false,
outerVisible: false,
innerVisible: false,
form: {
name: '',
region: '',
@ -86,7 +88,7 @@ Dialog pops up a dialog box, and it's quite customizable.
<el-dialog
title="Tips"
:visible.sync="dialogVisible"
size="tiny"
width="30%"
:before-close="handleClose">
<span>This is a message</span>
<span slot="footer" class="dialog-footer">
@ -116,6 +118,10 @@ Dialog pops up a dialog box, and it's quite customizable.
```
:::
:::tip
`before-close` only works when user clicks the close icon or the backdrop. If you have buttons that close the Dialog in the `footer` named slot, you can add what you would do with `before-close` in the buttons' click event handler.
:::
### Customizations
The content of Dialog can be anything, even a table or a form. This example shows how to use Element Table and Form with Dialog。
@ -196,16 +202,56 @@ The content of Dialog can be anything, even a table or a form. This example show
```
:::
### Nested Dialog
If a Dialog is nested in another Dialog, `append-to-body` is required.
:::demo Normally we do not recommend using nested Dialog. If you need multiple Dialogs rendered on the page, you can simply flat them so that they're siblings to each other. If you must nest a Dialog inside another Dialog, set `append-to-body` of the nested Dialog to true, and it will append to body instead of its parent node, so both Dialogs can be correctly rendered.
```html
<template>
<el-button type="text" @click="outerVisible = true">open the outer Dialog</el-button>
<el-dialog title="外层 Dialog" :visible.sync="outerVisible">
<el-dialog
width="30%"
title="内层 Dialog"
:visible.sync="innerVisible"
append-to-body>
</el-dialog>
<div slot="footer" class="dialog-footer">
<el-button @click="outerVisible = false">Cancel</el-button>
<el-button type="primary" @click="innerVisible = true">open the inner Dialog</el-button>
</div>
</el-dialog>
</template>
<script>
export default {
data() {
return {
outerVisible: false,
innerVisible: false
};
}
}
</script>
```
:::
:::tip
If the variable bound to `visible` is managed in Vuex store, the `.sync` can not work properly. In this case, please remove the `.sync` modifier, listen to `open` and `close` events of Dialog, and commit Vuex mutations to update the value of that variable in the event handlers.
:::
### Attributes
| Attribute | Description | Type | Accepted Values | Default |
|---------- |-------------- |---------- |-------------------------------- |-------- |
| visible | visibility of Dialog, supports the .sync modifier | boolean | — | false |
| title | title of Dialog. Can also be passed with a named slot (see the following table) | string | — | — |
| size | size of Dialog | string | tiny/small/large/full | small |
| top | value for `top` of Dialog CSS, works when `size` is not `full` | string | — | 15% |
| width | width of Dialog | string | — | 50% |
| fullscreen | whether the Dialog takes up full screen | boolean | — | false |
| top | value for `margin-top` of Dialog CSS, works when `size` is not `full` | string | — | 15vh |
| modal | whether a mask is displayed | boolean | — | true |
| modal-append-to-body | whether to append modal to body element. If false, the modal will be appended to Dialog's parent element | boolean | — | true |
| append-to-body | whether to append Dialog itself to body. A nested Dialog should have this attribute set to `true` | boolean | — | false |
| lock-scroll | whether scroll of body is disabled while Dialog is displayed | boolean | — | true |
| custom-class | custom class names for Dialog | string | — | — |
| close-on-click-modal | whether the Dialog can be closed by clicking the mask | boolean | — | true |

View File

@ -19,9 +19,11 @@
name: '王小虎',
address: '上海市普陀区金沙江路 1518 弄'
}],
dialogVisible: false,
dialogVisible: true,
dialogTableVisible: false,
dialogFormVisible: false,
outerVisible: false,
innerVisible: false,
form: {
name: '',
region: '',
@ -76,7 +78,7 @@
Dialog 弹出一个对话框,适合需要定制性更大的场景。
:::demo 需要设置`visible`属性,它接收`Boolean`,当为`true`时显示 Dialog。Dialog 分为两个部分:`body`和`footer``footer`需要具名为`footer`的`slot`。`title`属性用于定义标题,它是可选的,默认值为空。最后,本例还展示了`beforeClose`的用法。
:::demo 需要设置`visible`属性,它接收`Boolean`,当为`true`时显示 Dialog。Dialog 分为两个部分:`body`和`footer``footer`需要具名为`footer`的`slot`。`title`属性用于定义标题,它是可选的,默认值为空。最后,本例还展示了`before-close`的用法。
```html
<el-button type="text" @click="dialogVisible = true">点击打开 Dialog</el-button>
@ -84,7 +86,7 @@ Dialog 弹出一个对话框,适合需要定制性更大的场景。
<el-dialog
title="提示"
:visible.sync="dialogVisible"
size="tiny"
width="30%"
:before-close="handleClose">
<span>这是一段信息</span>
<span slot="footer" class="dialog-footer">
@ -114,6 +116,10 @@ Dialog 弹出一个对话框,适合需要定制性更大的场景。
```
:::
:::tip
`before-close` 仅当用户通过点击关闭图标或遮罩关闭 Dialog 时起效。如果你在 `footer` 具名 slot 里添加了用于关闭 Dialog 的按钮,那么可以在按钮的点击回调函数里加入 `before-close` 的相关逻辑。
:::
### 自定义内容
Dialog 组件的内容可以是任意的,甚至可以是表格或表单,下面是应用了 Element Table 和 Form 组件的两个样例。
@ -134,7 +140,7 @@ Dialog 组件的内容可以是任意的,甚至可以是表格或表单,下
<!-- Form -->
<el-button type="text" @click="dialogFormVisible = true">打开嵌套表单的 Dialog</el-button>
<el-dialog title="收货地址" :visible.sync="dialogFormVisible">
<el-dialog title="收货地址" :visible.sync="dialogFormVisible" fullscreen>
<el-form :model="form">
<el-form-item label="活动名称" :label-width="formLabelWidth">
<el-input v-model="form.name" auto-complete="off"></el-input>
@ -193,15 +199,55 @@ Dialog 组件的内容可以是任意的,甚至可以是表格或表单,下
```
:::
### 嵌套的 Dialog
如果需要在一个 Dialog 内部嵌套另一个 Dialog需要使用 `append-to-body` 属性。
:::demo 正常情况下,我们不建议使用嵌套的 Dialog如果需要在页面上同时显示多个 Dialog可以将它们平级放置。对于确实需要嵌套 Dialog 的场景,我们提供了`append-to-body`属性。将内层 Dialog 的该属性设置为 true它就会插入至 body 元素上,从而保证内外层 Dialog 和遮罩层级关系的正确。
```html
<template>
<el-button type="text" @click="outerVisible = true">点击打开外层 Dialog</el-button>
<el-dialog title="外层 Dialog" :visible.sync="outerVisible">
<el-dialog
width="30%"
title="内层 Dialog"
:visible.sync="innerVisible"
append-to-body>
</el-dialog>
<div slot="footer" class="dialog-footer">
<el-button @click="outerVisible = false">取 消</el-button>
<el-button type="primary" @click="innerVisible = true">打开内层 Dialog</el-button>
</div>
</el-dialog>
</template>
<script>
export default {
data() {
return {
outerVisible: false,
innerVisible: false
};
}
}
</script>
```
:::
:::tip
如果 `visible` 属性绑定的变量位于 Vuex 的 store 内,那么 `.sync` 不会正常工作。此时需要去除 `.sync` 修饰符,同时监听 Dialog 的 `open``close` 事件,在事件回调中执行 Vuex 中对应的 mutation 更新 `visible` 属性绑定的变量的值。
:::
### Attributes
| 参数 | 说明 | 类型 | 可选值 | 默认值 |
|---------- |-------------- |---------- |-------------------------------- |-------- |
| visible | 是否显示 Dialog支持 .sync 修饰符 | boolean | — | false |
| title | Dialog 的标题,也可通过具名 slot (见下表)传入 | string | — | — |
| size | Dialog 的大小 | string | tiny/small/large/full | small |
| top | Dialog CSS 中的 top 值(仅在 size 不为 full 时有效) | string | — | 15% |
| width | Dialog 的宽度 | string | — | 50% |
| fullscreen | 是否为全屏 Dialog | boolean | — | false |
| top | Dialog CSS 中的 margin-top 值(仅在 size 不为 full 时有效) | string | — | 15vh |
| modal | 是否需要遮罩层 | boolean | — | true |
| modal-append-to-body | 遮罩层是否插入至 body 元素上,若为 false则遮罩层会插入至 Dialog 的父元素上 | boolean | — | true |
| append-to-body | Dialog 自身是否插入至 body 元素上。嵌套的 Dialog 必须指定该属性并赋值为 true | boolean | — | false |
| lock-scroll | 是否在 Dialog 出现时将 body 滚动锁定 | boolean | — | true |
| custom-class | Dialog 的自定义类名 | string | — | — |
| close-on-click-modal | 是否可以通过点击 modal 关闭 Dialog | boolean | — | true |

View File

@ -3,15 +3,19 @@
<div class="el-dialog__wrapper" v-show="visible" @click.self="handleWrapperClick">
<div
class="el-dialog"
:class="[sizeClass, customClass]"
:class="[{ 'is-fullscreen': fullscreen }, customClass]"
ref="dialog"
:style="style">
<div class="el-dialog__header">
<slot name="title">
<span class="el-dialog__title">{{title}}</span>
<span class="el-dialog__title">{{ title }}</span>
</slot>
<button type="button" class="el-dialog__headerbtn" aria-label="Close"
v-if="showClose" @click="handleClose">
<button
type="button"
class="el-dialog__headerbtn"
aria-label="Close"
v-if="showClose"
@click="handleClose">
<i class="el-dialog__close el-icon el-icon-close"></i>
</button>
</div>
@ -43,12 +47,17 @@
type: Boolean,
default: true
},
modalAppendToBody: {
type: Boolean,
default: true
},
appendToBody: {
type: Boolean,
default: false
},
lockScroll: {
type: Boolean,
default: true
@ -69,10 +78,9 @@
default: true
},
size: {
type: String,
default: 'small'
},
width: String,
fullscreen: Boolean,
customClass: {
type: String,
@ -81,7 +89,7 @@
top: {
type: String,
default: '15%'
default: '15vh'
},
beforeClose: Function
},
@ -95,6 +103,9 @@
this.$nextTick(() => {
this.$refs.dialog.scrollTop = 0;
});
if (this.appendToBody) {
document.body.appendChild(this.$el);
}
} else {
this.$el.removeEventListener('scroll', this.updatePopper);
this.$emit('close');
@ -103,11 +114,15 @@
},
computed: {
sizeClass() {
return `el-dialog--${ this.size }`;
},
style() {
return this.size === 'full' ? {} : { 'top': this.top };
let style = {};
if (this.width) {
style.width = this.width;
}
if (!this.fullscreen) {
style.marginTop = this.top;
}
return style;
}
},
@ -126,7 +141,6 @@
hide(cancel) {
if (cancel !== false) {
this.$emit('update:visible', false);
this.$emit('visible-change', false);
}
},
updatePopper() {
@ -139,6 +153,9 @@
if (this.visible) {
this.rendered = true;
this.open();
if (this.appendToBody) {
document.body.appendChild(this.$el);
}
}
}
};

View File

@ -405,9 +405,6 @@
-------------------------- */
--dialog-background-color: var(--color-secondary);
--dialog-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
--dialog-tiny-width: 30%;
--dialog-small-width: 50%;
--dialog-large-width: 90%;
--dialog-close-color: var(--color-extra-light-silver);
--dialog-close-hover-color: var(--color-primary);
--dialog-title-font-size: 16px;

View File

@ -5,30 +5,17 @@
@component-namespace el {
@b dialog {
position: absolute;
left: 50%;
transform: translateX(-50%);
position: relative;
margin: 0 auto 50px;
background: var(--color-white);
border-radius: var(--border-radius-small);
box-shadow: var(--dialog-box-shadow);
box-sizing: border-box;
margin-bottom: 50px;
width: 50%;
@modifier tiny {
width: var(--dialog-tiny-width);
}
@modifier small {
width: var(--dialog-small-width);
}
@modifier large {
width: var(--dialog-large-width);
}
@modifier full {
@when fullscreen {
width: 100%;
top: 0;
margin-top: 0;
margin-bottom: 0;
height: 100%;
overflow: auto;

View File

@ -49,10 +49,6 @@ const getDOM = function(dom) {
};
export default {
model: {
prop: 'visible',
event: 'visible-change'
},
props: {
visible: {
type: Boolean,
@ -145,7 +141,6 @@ export default {
open(options) {
if (!this.rendered) {
this.rendered = true;
this.$emit('visible-change', true);
}
const props = merge({}, this.$props || this, options);
@ -174,8 +169,6 @@ export default {
this._opening = true;
this.$emit('visible-change', true);
const dom = getDOM(this.$el);
const modal = props.modal;
@ -245,7 +238,6 @@ export default {
},
doClose() {
this.$emit('visible-change', false);
this._closing = true;
this.onClose && this.onClose();

View File

@ -10,7 +10,7 @@ describe('Dialog', () => {
vm = createVue({
template: `
<div>
<el-dialog :title="title" v-model="visible"></el-dialog>
<el-dialog :title="title" :visible="visible"></el-dialog>
</div>
`,
@ -34,7 +34,7 @@ describe('Dialog', () => {
vm = createVue({
template: `
<div>
<el-dialog :title="title" v-model="visible">
<el-dialog :title="title" :visible="visible">
<span>这是一段信息</span>
<span slot="footer" class="dialog-footer">
<el-button @click.native="dialogVisible = false">取消</el-button>
@ -61,11 +61,33 @@ describe('Dialog', () => {
}, 100);
});
it('append to body', done => {
vm = createVue({
template: `
<div>
<el-dialog :title="title" append-to-body :visible="visible"></el-dialog>
</div>
`,
data() {
return {
title: 'dialog test',
visible: true
};
}
}, true);
const dialog = vm.$children[0];
setTimeout(() => {
expect(dialog.$el.parentNode).to.equal(document.body);
done();
}, 10);
});
it('open and close', done => {
vm = createVue({
template: `
<div>
<el-dialog :title="title" v-model="visible">
<el-dialog :title="title" :visible.sync="visible">
<span>这是一段信息</span>
</el-dialog>
</div>
@ -83,11 +105,11 @@ describe('Dialog', () => {
vm.visible = true;
setTimeout(() => {
expect(dialog.$el.style.display).to.not.equal('none');
document.querySelector('.v-modal').click();
vm.visible = false;
setTimeout(() => {
expect(vm.visible).to.equal(false);
expect(dialog.$el.style.display).to.equal('none');
done();
}, 50);
}, 400);
}, 50);
});
@ -96,7 +118,7 @@ describe('Dialog', () => {
return createVue(Object.assign({
template: `
<div>
<el-dialog ${ props } :title="title" v-model="visible">
<el-dialog ${ props } :title="title" :visible="visible">
<span>这是一段信息</span>
</el-dialog>
</div>
@ -111,14 +133,14 @@ describe('Dialog', () => {
}, options), true);
};
it('size', () => {
vm = getDialogVm('size="full"');
expect(vm.$el.querySelector('.el-dialog').classList.contains('el-dialog--full')).to.true;
it('fullscreen', () => {
vm = getDialogVm('fullscreen');
expect(vm.$el.querySelector('.el-dialog').classList.contains('is-fullscreen')).to.true;
});
it('top', () => {
vm = getDialogVm('top="100px"');
expect(vm.$el.querySelector('.el-dialog').style.top).to.equal('100px');
expect(vm.$el.querySelector('.el-dialog').style.marginTop).to.equal('100px');
});
it('custom-class', () => {
@ -127,7 +149,7 @@ describe('Dialog', () => {
});
});
it('callbacks', done => {
it('events', done => {
vm = createVue({
template: `
<div>
@ -135,7 +157,7 @@ describe('Dialog', () => {
@open="handleOpen"
@close="handleClose"
:title="title"
v-model="visible">
:visible.sync="visible">
<span>这是一段信息</span>
</el-dialog>
</div>
@ -159,11 +181,10 @@ describe('Dialog', () => {
};
}
}, true);
const dialog = vm.$children[0];
dialog.open();
vm.visible = true;
setTimeout(() => {
expect(vm.state).to.equal('open');
dialog.close();
vm.visible = false;
setTimeout(() => {
expect(vm.state).to.equal('closed');
done();