Permalink
Browse files

feat(tooltip+popover): Create mixin for common props and methods (#1021)

* feat(tooltip+popover): Create mixin for common props and methods

* make mixin available

* ESLint

* Update toolpop.js

* Update toolpop.js

* [tooltip.vue] use toolpop mixin

* [popover.vue] Use toolpop mixin
  • Loading branch information...
tmorehouse committed Sep 8, 2017
1 parent d73ff01 commit edc7b207a1ec6701aee39cf1ea2d916642cfe581
Showing with 218 additions and 305 deletions.
  1. +9 −157 lib/components/popover.vue
  2. +9 −147 lib/components/tooltip.vue
  3. +3 −1 lib/mixins/index.js
  4. +197 −0 lib/mixins/toolpop.js
@@ -8,22 +8,14 @@
<script>
import PopOver from '../classes/popover';
import { isArray } from '../utils/array';
import { assign } from '../utils/object';
import { isElement } from '../utils/dom';
import { observeDom, warn } from '../utils';
import { toolpopMixin } from '../mixins';
export default {
mixins: [ toolpopMixin ],
data() {
return {
popOver: null
}
return {};
},
props: {
target: {
// ID of element, or a reference to an element or component
type: [String, Object]
},
title: {
type: String,
default: ''
@@ -39,158 +31,18 @@
placement: {
type: String,
default: 'right'
},
delay: {
type: Number,
default: 0
},
offset: {
type: [Number, String],
default: 0
},
noFade: {
type: Boolean,
default: false
},
container: {
// String ID of container, if null body is used (default)
type: String,
default: null
}
},
mounted() {
// We do this in a $nextTick in hopes that the target element is in the DOM
// And that our children have rendered
this.$nextTick(() => {
methods: {
createToolpop() {
// getTarget is in toolpop mixin
const target = this.getTarget();
if (target) {
// Instaniate popover on target
this.popOver = new PopOver(target, this.getConfig(), this.$root);
// Listen to close signals from others
this.$on('close', this.onClose);
// Observe content Child changes so we can notify popper of possible size change
observeDom(this.$refs.content, this.updatePosition.bind(this), {
subtree: true,
childList: true,
attributes: true,
attributeFilter: ['class', 'style']
});
this._toolpop = new PopOver(target, this.getConfig(), this.$root);
} else {
warn("b-popover: 'target' element not found!");
}
});
},
deactivated() {
// Called when component is inside a <keep-alive> and component taken offline
if (this.popOver) {
this.popOver.hide();
}
},
updated() {
// If content changes, etc
if (this.popOver) {
this.popOver.updateConfig(this.getConfig());
}
},
beforeDestroyed() {
this.$off('close', this.onClose);
if (this.popOver) {
// Destroy the popover
this.popOver.destroy();
this.popOver = null;
}
// Bring our stuff back if necessary
this.bringItBack();
},
computed: {
baseConfig() {
const cont = this.container;
return {
title: this.title.trim() || '',
content: this.content.trim() || '',
placement: this.placement || 'top',
// container currently needs to be an ID with '#' prepended, or false for body
container: cont ? (/^#/.test(cont) ? cont : `#${cont}`) : false,
delay: parseInt(this.delay, 10) || 0,
// offset can be a css distance string. if no units provided then pixels are assumed
offset: this.offset || 0,
animation: Boolean(this.noFade),
trigger: isArray(this.triggers) ? this.triggers.join(' ') : this.triggers
};
}
},
methods: {
onClose(callback) {
if (this.popOver) {
this.popOver.hide(callback);
} else if (typeof callback === 'function') {
callback();
}
},
updatePosition() {
if (this.popOver) {
// Instruct popper to reposition popover if necessary
this.popOver.update();
}
},
getConfig() {
const cfg = assign({}, this.baseConfig);
if (this.$refs.title.innerHTML.trim()) {
// We pass the DOM element to preserve components
cfg.title = this.$refs.title;
cfg.html = true;
}
if (this.$refs.content.innerHTML.trim()) {
// We pass the DOM element to preserve components
cfg.content = this.$refs.content;
cfg.html = true;
}
cfg.callbacks = {
show: this.onShow,
shown: this.onShown,
hide: this.onHide,
hidden: this.onHidden
};
return cfg;
},
getTarget() {
const target = this.target;
if (typeof target === 'string') {
// Assume ID of element
return document.getElementById(/^#/.test(target) ? target.slice(1) : target) || null;
} else if (typeof target === 'object' && target.$el) {
// Component reference
return target.$el;
} else if (typeof target === 'object' && isElement(target)) {
// Element reference
return target;
}
return null;
},
onShow(evt) {
this.$emit('show', evt);
},
onShown(evt) {
this.$emit('shown', evt);
},
onHide(evt) {
this.$emit('hide', evt);
},
onHidden(evt) {
// Bring our content back if needed to keep Vue happy
// PopOver class will move it back to $tip when shown again
this.bringItBack();
this.$emit('hidden', evt);
},
bringItBack() {
if (this.$el) {
if (this.$refs.title) {
this.$el.appendChild(this.$refs.title);
}
if (this.$refs.content) {
this.$el.appendChild(this.$refs.content);
}
this._toolpop = null;
}
return this._toolpop;
}
}
};
@@ -7,22 +7,14 @@
<script>
import ToolTip from '../classes/tooltip';
import { isArray } from '../utils/array';
import { assign } from '../utils/object';
import { isElement } from '../utils/dom';
import { observeDom, warn } from '../utils';
import { toolpopMixin } from '../mixins';
export default {
mixins: [ toolpopMixin ],
data() {
return {
toolTip: null
}
return {};
},
props: {
target: {
// String ID of element, or element/component reference
type: [String, Object]
},
title: {
type: String,
default: ''
@@ -34,148 +26,18 @@
placement: {
type: String,
default: 'top'
},
delay: {
type: Number,
default: 0
},
offset: {
type: [Number, String],
default: 0
},
noFade: {
type: Boolean,
default: false
},
container: {
// String ID of container, if null body is used (default)
type: String,
default: null
}
},
mounted() {
// We do this in a $nextTick in hopes that the target element is in the DOM
// And that our children have rendered
this.$nextTick(() => {
methods: {
createToolpop() {
// getTarget is in toolpop mixin
const target = this.getTarget();
if (target) {
// Instantiate ToolTip on target
this.toolTip = new ToolTip(target, this.getConfig(), this.$root);
// Listen to close signals from others
this.$on('close', this.onClose);
// Observe content Child changes so we can notify popper of possible size change
observeDom(this.$refs.title, this.updatePosition.bind(this), {
subtree: true,
childList: true,
attributes: true,
attributeFilter: ['class', 'style']
});
this._toolpop = new ToolTip(target, this.getConfig(), this.$root);
} else {
warn("b-tooltip: 'target' element not found!");
}
});
},
deactivated() {
// Called when component is inside a <keep-alive> and component taken offline
if (this.toolTip) {
this.toolTip.hide();
}
},
updated() {
// If content/props changes, etc
if (this.toolTip) {
this.toolTip.updateConfig(this.getConfig());
}
},
beforeDestroy() {
this.$off('close', this.onClose);
if (this.toolTip) {
this.toolTip.destroy();
this.tooltip = null;
}
// bring our content back if needed
this.bringItBack();
},
computed: {
baseConfig() {
const cont = this.container;
return {
title: this.title.trim() || '',
placement: this.placement || 'top',
// Container curently needs to be an ID with '#' prepended, if null then body is used
container: cont ? (/^#/.test(cont) ? cont : `#${cont}`) : false,
delay: parseInt(this.delay, 10) || 0,
// Offset can be css distance. if no units, pixels are assumed
offset: this.offset || 0,
animation: Boolean(this.noFade),
trigger: isArray(this.triggers) ? this.triggers.join(' ') : this.triggers
};
}
},
methods: {
onClose(callback) {
if (this.toolTip) {
this.toolTip.hide(callback);
} else if (typeof callback === 'function') {
callback();
}
},
updatePosition() {
if (this.toolTip) {
// Instruct popper to reposition popover if necessary
this.toolTip.update();
}
},
getConfig() {
const cfg = assign({}, this.baseConfig);
if (this.$refs.title.innerHTML.trim()) {
// If slot has content, it overrides 'title' prop
// We use the DOM node as content to allow components!
cfg.title = this.$refs.title;
cfg.html = true;
}
// Callbacks so we can trigger events on component
cfg.callbacks = {
show: this.onShow,
shown: this.onShown,
hide: this.onHide,
hidden: this.onHidden
};
return cfg;
},
getTarget() {
const target = this.target;
if (typeof target === 'string') {
// Assume ID of element
return document.getElementById(/^#/.test(target) ? target.slice(1) : target) || null;
} else if (typeof target === 'object' && target.$el) {
// Component reference
return target.$el;
} else if (typeof target === 'object' && isElement(target)) {
// Element reference
return target;
}
return null;
},
onShow(evt) {
this.$emit('show', evt);
},
onShown(evt) {
this.$emit('shown', evt);
},
onHide(evt) {
this.$emit('hide', evt)
},
onHidden(evt) {
// bring our content back if needed to keep Vue happy
// Tooltip class will move it back to tip when shown again
this.bringItBack();
this.$emit('hidden', evt);
},
bringItBack() {
if (this.$el && this.$refs.title) {
this.$el.appendChild(this.$refs.title);
this._toolpop = null;
}
return this._toolpop;
}
}
};
@@ -10,6 +10,7 @@ import formStateMixin from "./form-state";
import idMixin from "./id";
import linkMixin from "./link";
import listenOnRootMixin from "./listen-on-root";
import toolpopMixin from "./toolpop";
export {
cardMixin,
@@ -23,5 +24,6 @@ export {
formStateMixin,
idMixin,
linkMixin,
listenOnRootMixin
listenOnRootMixin,
toolpopMixin
};
Oops, something went wrong.

0 comments on commit edc7b20

Please sign in to comment.