Notification: add position

pull/6325/head
Leopoldthecoder 2017-08-02 17:36:10 +08:00 committed by 杨奕
parent 7d69d11ead
commit c340ed13e4
6 changed files with 393 additions and 56 deletions

View File

@ -49,13 +49,52 @@
},
open7() {
this.$notify.success({
this.$notify({
title: 'Custom Position',
message: 'I\'m at the top right corner'
});
},
open8() {
this.$notify({
title: 'Custom Position',
message: 'I\'m at the bottom right corner',
position: 'bottom-right'
});
},
open9() {
this.$notify({
title: 'Custom Position',
message: 'I\'m at the bottom left corner',
position: 'bottom-left'
});
},
open10() {
this.$notify({
title: 'Custom Position',
message: 'I\'m at the top left corner',
position: 'top-left'
});
},
open11() {
this.$notify({
title: 'Success',
message: 'This is a success message',
offset: 100
});
},
open12() {
this.$notify({
title: 'HTML String',
dangerouslyUseHTMLString: true,
message: '<strong>This is <i>HTML</i> string</strong>'
});
},
onClose() {
console.log('Notification is closed');
}
@ -65,7 +104,7 @@
## Notification
Displays a global notification message at the upper right corner of the page.
Displays a global notification message at a corner of the page.
### Basic usage
@ -177,17 +216,32 @@ We provide four types: success, warning, info and error.
```
:::
### With offset
### Custom Position
Customize Notification's offset from the top edge of the screen
Notification can emerge from any corner you like.
::: demo Set the `offset` attribute to customize Notification's offset from the top edge of the screen. Note that every Notification instance of the same moment should have the same offset.
::: demo The `position` attribute defines which corner Notification slides in. It can be `top-right`, `top-left`, `bottom-right` or `bottom-left`. Defaults to `top-right`.
```html
<template>
<el-button
plain
@click="open7">
Notification with offset
plain
@click="open7">
Top Right
</el-button>
<el-button
plain
@click="open8">
Bottom Right
</el-button>
<el-button
plain
@click="open9">
Bottom Left
</el-button>
<el-button
plain
@click="open10">
Top Left
</el-button>
</template>
@ -195,7 +249,60 @@ Customize Notification's offset from the top edge of the screen
export default {
methods: {
open7() {
this.$notify.success({
this.$notify({
title: 'Custom Position',
message: 'I\'m at the top right corner'
});
},
open8() {
this.$notify({
title: 'Custom Position',
message: 'I\'m at the bottom right corner',
position: 'bottom-right'
});
},
open9() {
this.$notify({
title: 'Custom Position',
message: 'I\'m at the bottom left corner',
position: 'bottom-left'
});
},
open10() {
this.$notify({
title: 'Custom Position',
message: 'I\'m at the top left corner',
position: 'top-left'
});
}
}
}
</script>
```
:::
### With offset
Customize Notification's offset from the edge of the screen.
::: demo Set the `offset` attribute to customize Notification's offset from the edge of the screen. Note that every Notification instance of the same moment should have the same offset.
```html
<template>
<el-button
plain
@click="open11">
Notification with offset
</el-button>
</template>
<script>
export default {
methods: {
open11() {
this.$notify({
title: 'Success',
message: 'This is a success message',
offset: 100
@ -207,6 +314,39 @@ Customize Notification's offset from the top edge of the screen
```
:::
### Use HTML String
`message` supports HTML string.
::: demo Set `dangerouslyUseHTMLString` to true and `message` will be treated as an HTML string.
```html
<template>
<el-button
plain
@click="open12">
Use HTML String
</el-button>
</template>
<script>
export default {
methods: {
open12() {
this.$notify({
title: 'HTML String',
dangerouslyUseHTMLString: true,
message: '<strong>This is <i>HTML</i> string</strong>'
});
}
}
}
</script>
```
:::
:::warning
Although `message` property supports HTML strings, dynamically rendering arbitrary HTML on your website can be very dangerous because it can easily lead to [XSS attacks](https://en.wikipedia.org/wiki/Cross-site_scripting). So when `dangerouslyUseHTMLString` is on, please make sure the content of `message` is trusted, and **never** assign `message` to user-provided content.
:::
### Global method
Element has added a global method `$notify` for Vue.prototype. So in a vue instance you can call `Notification` like what we did in this page.
@ -226,6 +366,7 @@ In this case you should call `Notification(options)`. We have also registered me
|---------- |-------------- |---------- |-------------------------------- |-------- |
| title | title | string | — | — |
| message | description text | string/Vue.VNode | — | — |
| dangerouslyUseHTMLString | whether `message` is treated as HTML string | boolean | — | false |
| type | notification type | string | success/warning/info/error | — |
| iconClass | custom icon's class. It will be overridden by `type` | string | — | — |
| customClass | custom class name for Notification | string | — | — |

View File

@ -49,13 +49,52 @@
},
open7() {
this.$notify.success({
title: '成功',
message: '这是一条成功的提示消息',
this.$notify({
title: '自定义位置',
message: '右上角弹出的消息'
});
},
open8() {
this.$notify({
title: '自定义位置',
message: '右下角弹出的消息',
position: 'bottom-right'
});
},
open9() {
this.$notify({
title: '自定义位置',
message: '左下角弹出的消息',
position: 'bottom-left'
});
},
open10() {
this.$notify({
title: '自定义位置',
message: '左上角弹出的消息',
position: 'top-left'
});
},
open11() {
this.$notify({
title: '偏移',
message: '这是一条带有偏移的提示消息',
offset: 100
});
},
open12() {
this.$notify({
title: 'HTML 片段',
dangerouslyUseHTMLString: true,
message: '<strong>这是 <i>HTML</i> 片段</strong>'
});
},
onClose() {
console.log('Notification 已关闭');
}
@ -65,7 +104,7 @@
## Notification 通知
悬浮出现在页面右上角,显示全局的通知提醒消息。
悬浮出现在页面角,显示全局的通知提醒消息。
### 基本用法
@ -178,17 +217,32 @@
```
:::
### 带有偏移
### 自定义弹出位置
让 Notification 偏移一些位置
可以让 Notification 从屏幕四角中的任意一角弹出
::: demo Notification 提供设置偏移量的功能,通过设置 `offset` 字段,可以使弹出的消息距屏幕顶部偏移一段距离。注意在同一时刻,所有的 Notification 实例应当具有一个相同的偏移量
::: demo 使用`position`属性定义 Notification 的弹出位置,支持四个选项:`top-right`、`top-left`、`bottom-right`、`bottom-left`,默认为`top-right`
```html
<template>
<el-button
plain
@click="open7">
偏移的消息
右上角
</el-button>
<el-button
plain
@click="open8">
右下角
</el-button>
<el-button
plain
@click="open9">
左下角
</el-button>
<el-button
plain
@click="open10">
左上角
</el-button>
</template>
@ -196,9 +250,62 @@
export default {
methods: {
open7() {
this.$notify.success({
title: '成功',
message: '这是一条成功的提示消息',
this.$notify({
title: '自定义位置',
message: '右上角弹出的消息'
});
},
open8() {
this.$notify({
title: '自定义位置',
message: '右下角弹出的消息',
position: 'bottom-right'
});
},
open9() {
this.$notify({
title: '自定义位置',
message: '左下角弹出的消息',
position: 'bottom-left'
});
},
open10() {
this.$notify({
title: '自定义位置',
message: '左上角弹出的消息',
position: 'top-left'
});
}
}
}
</script>
```
:::
### 带有偏移
让 Notification 偏移一些位置
::: demo Notification 提供设置偏移量的功能,通过设置 `offset` 字段,可以使弹出的消息距屏幕边缘偏移一段距离。注意在同一时刻,所有的 Notification 实例应当具有一个相同的偏移量。
```html
<template>
<el-button
plain
@click="open11">
偏移的消息
</el-button>
</template>
<script>
export default {
methods: {
open11() {
this.$notify({
title: '偏移',
message: '这是一条带有偏移的提示消息',
offset: 100
});
}
@ -208,6 +315,39 @@
```
:::
### 使用 HTML 片段
`message` 属性支持传入 HTML 片段
::: demo 将`dangerouslyUseHTMLString`属性设置为 true`message` 就会被当作 HTML 片段处理。
```html
<template>
<el-button
plain
@click="open12">
使用 HTML 片段
</el-button>
</template>
<script>
export default {
methods: {
open12() {
this.$notify({
title: 'HTML 片段',
dangerouslyUseHTMLString: true,
message: '<strong>这是 <i>HTML</i> 片段</strong>'
});
}
}
}
</script>
```
:::
:::warning
`message` 属性虽然支持传入 HTML 片段,但是在网站上动态渲染任意 HTML 是非常危险的,因为容易导致 [XSS 攻击](https://en.wikipedia.org/wiki/Cross-site_scripting)。因此在 `dangerouslyUseHTMLString` 打开的情况下,请确保 `message` 的内容是可信的,**永远不要**将用户提交的内容赋值给 `message` 属性。
:::
### 全局方法
Element 为 `Vue.prototype` 添加了全局方法 `$notify`。因此在 vue instance 中可以采用本页面中的方式调用 Notification。
@ -227,6 +367,7 @@ import { Notification } from 'element-ui';
|---------- |-------------- |---------- |-------------------------------- |-------- |
| title | 标题 | string | — | — |
| message | 说明文字 | string/Vue.VNode | — | — |
| dangerouslyUseHTMLString | 是否将 message 属性作为 HTML 片段处理 | boolean | — | false |
| type | 主题样式,如果不在可选值内将被忽略 | string | success/warning/info/error | — |
| iconClass | 自定义图标的类名。若设置了 `type`,则 `iconClass` 会被覆盖 | string | — | — |
| customClass | 自定义类名 | string | — | — |

View File

@ -1,17 +1,18 @@
import Vue from 'vue';
import { PopupManager } from 'element-ui/src/utils/popup';
import { isVNode } from 'element-ui/src/utils/vdom';
let NotificationConstructor = Vue.extend(require('./main.vue'));
const NotificationConstructor = Vue.extend(require('./main.vue'));
let instance;
let instances = [];
let seed = 1;
var Notification = function(options) {
const Notification = function(options) {
if (Vue.prototype.$isServer) return;
options = options || {};
let userOnClose = options.onClose;
let id = 'notification_' + seed++;
const userOnClose = options.onClose;
const id = 'notification_' + seed++;
const position = options.position || 'top-right';
options.onClose = function() {
Notification.close(id, userOnClose);
@ -32,13 +33,12 @@ var Notification = function(options) {
instance.dom = instance.vm.$el;
instance.dom.style.zIndex = PopupManager.nextZIndex();
const offset = options.offset || 0;
let topDist = offset;
for (let i = 0, len = instances.length; i < len; i++) {
topDist += instances[i].$el.offsetHeight + 16;
}
topDist += 16;
instance.top = topDist;
let verticalOffset = options.offset || 0;
instances.filter(item => item.position === position).forEach(item => {
verticalOffset += item.$el.offsetHeight + 16;
});
verticalOffset += 16;
instance.verticalOffset = verticalOffset;
instances.push(instance);
return instance.vm;
};
@ -56,23 +56,29 @@ var Notification = function(options) {
});
Notification.close = function(id, userOnClose) {
let index;
let removedHeight;
for (var i = 0, len = instances.length; i < len; i++) {
if (id === instances[i].id) {
if (typeof userOnClose === 'function') {
userOnClose(instances[i]);
}
let index = -1;
const len = instances.length;
const instance = instances.filter((instance, i) => {
if (instance.id === id) {
index = i;
removedHeight = instances[i].dom.offsetHeight;
instances.splice(i, 1);
break;
return true;
}
}
return false;
})[0];
if (!instance) return;
if (len > 1) {
for (i = index; i < len - 1 ; i++) {
instances[i].dom.style.top = parseInt(instances[i].dom.style.top, 10) - removedHeight - 16 + 'px';
if (typeof userOnClose === 'function') {
userOnClose(instance);
}
instances.splice(index, 1);
if (len <= 1) return;
const position = instance.position;
const removedHeight = instance.dom.offsetHeight;
for (let i = index; i < len - 1 ; i++) {
if (instances[i].position === position) {
instances[i].dom.style[instance.verticalProperty] =
parseInt(instances[i].dom.style[instance.verticalProperty], 10) - removedHeight - 16 + 'px';
}
}
};

View File

@ -1,10 +1,9 @@
<template>
<transition name="el-notification-fade">
<div
class="el-notification"
:class="customClass"
:class="['el-notification', customClass, horizontalClass]"
v-show="visible"
:style="{ top: top ? top + 'px' : 'auto' }"
:style="positionStyle"
@mouseenter="clearTimer()"
@mouseleave="startTimer()"
@click="click">
@ -15,7 +14,12 @@
</i>
<div class="el-notification__group" :class="{ 'is-with-icon': typeClass || iconClass }">
<h2 class="el-notification__title" v-text="title"></h2>
<div class="el-notification__content"><slot>{{ message }}</slot></div>
<div class="el-notification__content">
<slot>
<p v-if="!dangerouslyUseHTMLString">{{ message }}</p>
<p v-else v-html="message"></p>
</slot>
</div>
<div class="el-notification__closeBtn el-icon-close" @click.stop="close"></div>
</div>
</div>
@ -43,14 +47,30 @@
onClose: null,
onClick: null,
closed: false,
top: null,
timer: null
verticalOffset: 0,
timer: null,
dangerouslyUseHTMLString: false,
position: 'top-right'
};
},
computed: {
typeClass() {
return this.type && typeMap[this.type] ? `el-icon-${ typeMap[this.type] }` : '';
},
horizontalClass() {
return this.position.indexOf('right') > -1 ? 'right' : 'left';
},
verticalProperty() {
return /^top-/.test(this.position) ? 'top' : 'bottom';
},
positionStyle() {
return {
[this.verticalProperty]: `${ this.verticalOffset }px`
};
}
},

View File

@ -9,12 +9,19 @@
box-sizing: border-box;
border-radius: var(--border-radius-small);
position: fixed;
right: 16px;
background-color: var(--color-white);
box-shadow: var(--notification-shadow);
transition: opacity 0.3s, transform .3s, right .3s, top 0.4s;
transition: opacity .3s, transform .3s, left .3s, right .3s, top 0.4s, bottom .3s;
overflow: hidden;
&.right {
right: 16px;
}
&.left {
left: 16px;
}
@e group {
margin-left: 0;
@when with-icon {
@ -35,6 +42,10 @@
margin: 10px 0 0 0;
color: var(--notification-color);
text-align: justify;
p {
margin: 0;
}
}
@e icon {
@ -74,8 +85,15 @@
}
.el-notification-fade-enter {
transform: translateX(100%);
right: 0;
&.right {
right: 0;
transform: translateX(100%);
}
&.left {
left: 0;
transform: translateX(-100%);
}
}
.el-notification-fade-leave-active {

View File

@ -47,12 +47,23 @@ describe('Notification', () => {
});
const group = document.querySelector('.el-notification__group');
const title = group.querySelector('.el-notification__title');
const message = group.querySelector('.el-notification__content');
const message = group.querySelector('.el-notification__content p');
expect(document.querySelector('.el-notification')).to.exist;
expect(title.textContent).to.equal('狮子');
expect(message.textContent).to.equal('狮鹫');
});
it('html string as message', () => {
Notification({
title: '狮子',
message: '<strong>狮鹫</strong>',
dangerouslyUseHTMLString: true,
duration: 0
});
const message = document.querySelector('.el-notification__content strong');
expect(message.textContent).to.equal('狮鹫');
});
it('create by vnode', () => {
const fakeVM = new Vue();
const h = fakeVM.$createElement;