Skip to content

Instantly share code, notes, and snippets.

@humphreyja
Created January 29, 2017 23:22
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save humphreyja/eb8256ada8a78d6dcd1dfb1329d1f0e8 to your computer and use it in GitHub Desktop.
Save humphreyja/eb8256ada8a78d6dcd1dfb1329d1f0e8 to your computer and use it in GitHub Desktop.
Info Message Box for Websites.
<div id="vue-info">
<div class="info-message info-message-1" :style="{top: html.top, left: html.left, right: html.right, bottom: html.bottom, position: html.position, height: html.height, width: html.width, maxHeight: html.maxHeight, maxWidth: html.maxWidth, display: containerOpen ? 'block' : 'none'}">
<svg class="container" viewbox="0 0 800 700" :style="{top: 0, left: 0, height: svgHeight, width: svgWidth, display: mobile ? 'none' : 'block'}" :class="[svgOpen ? 'open' : '']">
<defs>
<filter id="filter-dropshadow" x="0" y="0" width="140%" height="140%">
<feOffset result="offOut" in="SourceAlpha" dx="6" dy="3" />
<feColorMatrix result="matrixOut" in="offOut" type="matrix"
values="0 0 0 0 0
0 0 0 0 0
0 0 0 0 0
0 0 0 0.7 0"/>
<feGaussianBlur result="blurOut" in="matrixOut" stdDeviation="3" />
<feBlend in="SourceGraphic" in2="blurOut" mode="normal" />
</filter>
</defs>
<path :d="[svgStart, svgTopLeftCurve, svgLeft, svgBottomLeftCurve, svgBottom, svgBottomRightCurve, svgRight, svgTopRightCurve, svgTop]" stroke-width="1" filter="url(#filter-dropshadow)" />
</svg>
<div class="message-contents" :class="[svgOpen ? 'open' : '']" :style="{margin: messageMargin, padding: messagePadding, borderLeft: messageBorderLeft, borderTop: messageBorderTop, boxShadow: messageShadow}">
{{{ messageHTML }}}
</div>
</div>
</div>
// Opens an info message by passing in a JQuery event object, a node, and an optional message to the `open` function.
// If no message if found, it will parse the node and any of its parents until if finds a 'data-info-message' attribute
// and will use the value for that attribute as its message. If it doesn't find that attribute, the message will not open.
// This is also mobile friendly and will change to a popup from the bottom if the window height is less than 800 or it will
// change to a popup from the right side if window width is less than 800.
(function() {
"use strict";
App.register('component').enter(function() {
var infoMessage = new Vue({
el: '#vue-info',
data: function() {
return {
messageHTML: "",
open1: false,
open2: false,
container: '#vue-info .info-message-1',
clickX: 0,
clickY: 0,
windowX: 0,
windowY: 0,
windowWidth: 0,
windowHeight: 0,
windowPadding: 10,
svgPadding: 6,
svgArrowPadding: 20,
svgHeight: 350,
svgWidth: 400,
svgCurve: 5,
svgRes: 2,
svgOpen: false,
containerOpen: false,
windowHeightBreakPoint: 800,
windowWidthBreakPoint: 800
}
},
ready: function() {
var self = this;
// Track window size changes
this.windowHeight = $(window).height();
this.windowWidth = $(window).width();
$(window).resize(function(){
self.windowHeight = $(window).height();
self.windowWidth = $(window).width();
self.close();
});
// Detect clicks inside of container and stops the container from closing
$(this.container).click(function(e) {
var offset = $(this).offset();
var posY = e.pageY - offset.top;
var posX = e.pageX - offset.left;
if (posY >= self.svg.container.top && posY <= self.svg.container.bottom && posX >= self.svg.container.left && posX <= self.svg.container.right){
e.stopPropagation();
return;
}
});
},
computed: {
mobile: function() {
// Checks if message should be displayed as mobile
if (this.direction === 'bottompanel' || this.direction === 'sidepanel') {
return true;
}else{
return false;
}
},
html: function() {
// HTML inline styles
var top = this.top + 'px',
bottom = 'auto',
right = 'auto',
left = this.left + 'px',
height = this.svgHeight + 'px',
width = this.svgWidth + 'px',
position = 'absolute',
maxHeight = 'auto',
maxWidth = 'auto';
if (this.direction === 'sidepanel'){
position = 'fixed';
top = '0px';
bottom = '0px';
left = 'auto';
right = '0px';
height = 'auto';
maxWidth = width;
width = '40%';
}else if (this.direction === 'bottompanel') {
position = 'fixed';
top = 'auto';
bottom = '0px';
left = '0px';
right = '0px';
width = 'auto';
maxHeight = height;
height = '40%';
}
return {
top: top,
bottom: bottom,
right: right,
left: left,
height: height,
width: width,
position: position
}
},
svg: function() {
// The SVG specs
var arrowHeight = 14;
var top = this.svgPadding;
var bottom = this.svgHeight - (this.svgPadding);
var right = this.svgWidth - (this.svgPadding);
var left = this.svgPadding;
if (this.direction === 'down') {
top = arrowHeight * this.svgRes;
}
if (this.direction === 'up') {
bottom = this.svgHeight - (arrowHeight * this.svgRes);
}
return {
height: this.svgHeight * this.svgRes,
width: this.svgWidth * this.svgRes,
curve: this.svgCurve * this.svgRes,
padding: this.svgPadding * this.svgRes,
container: {
top: top,
bottom: bottom,
right: right,
left: left
},
arrow: {
base: 14 * this.svgRes,
top: 4 * this.svgRes,
height: arrowHeight * this.svgRes,
point: 20 * this.svgRes,
mid: 30 * this.svgRes,
padding: this.svgArrowPadding * this.svgRes
}
}
},
direction: function() {
// The direction the info message container shows up in respects to click position
if (this.windowWidth <= this.windowWidthBreakPoint) {
return 'bottompanel';
}
if (this.windowHeight <= this.windowHeightBreakPoint) {
return 'sidepanel';
}
if (this.windowY <= (this.windowHeight / 2)) {
return 'down';
}else{
return 'up';
}
},
top: function() {
// The Top position of the info svg container
if (this.direction === 'down'){
return this.clickY + (this.svg.arrow.height / this.svgRes);
}else{
return this.clickY - (this.svg.arrow.height / this.svgRes) - this.svgHeight;
}
},
left: function() {
// The Left position of the info svg container
var left = this.clickX - (this.svgWidth / 2);
if (left < 0) {
return 0;
}else if ((left + this.svgWidth) >= this.documentWidth()){
return this.documentWidth() - this.svgWidth;
}else{
return left;
}
},
svgTopArrowPadding: function() {
// The amount of space above the container for the arrow
if (this.direction === 'down') {
return this.svg.arrow.padding;
}else{
return 0;
}
},
svgBottomArrowPadding: function() {
// The amount of space below the container for the arrow
if (this.direction === 'up') {
return this.svg.arrow.padding;
}else{
return 0;
}
},
svgStart: function() {
// SVG Drawing: Move to start position
var x = this.svg.curve + this.svg.padding;
var y = 0 + this.svg.padding + this.svgTopArrowPadding;
return "M" + x + ' ' + y;
},
svgTopLeftCurve: function(){
// SVG Drawing: Draw Quadratic curve for top left corner
var cx = 0 + this.svg.padding;
var cy = 0 + this.svg.padding + this.svgTopArrowPadding;
var x = 0 + this.svg.padding;
var y = 0 + this.svg.curve + this.svg.padding + this.svgTopArrowPadding;
return "Q" + cx + ' ' + cy + ' ' + x + ' ' + y;
},
svgBottomLeftCurve: function(){
// SVG Drawing: Draw Quadratic curve for bottom left corner
var cx = 0 + this.svg.padding;
var cy = this.svg.height - this.svg.padding - this.svgBottomArrowPadding;
var x = 0 + this.svg.curve + this.svg.padding;
var y = this.svg.height - this.svg.padding - this.svgBottomArrowPadding;
return "Q" + cx + ' ' + cy + ' ' + x + ' ' + y;
},
svgBottomRightCurve: function(){
// SVG Drawing: Draw Quadratic curve for bottom right corner
var cx = this.svg.width - this.svg.padding;
var cy = this.svg.height - this.svg.padding - this.svgBottomArrowPadding;
var x = this.svg.width - this.svg.padding;
var y = this.svg.height - this.svg.curve - this.svg.padding - this.svgBottomArrowPadding;
return "Q" + cx + ' ' + cy + ' ' + x + ' ' + y;
},
svgTopRightCurve: function(){
// SVG Drawing: Draw Quadratic curve for top right corner
var cx = this.svg.width - this.svg.padding;
var cy = 0 + this.svg.padding + this.svgTopArrowPadding;
var x = this.svg.width - this.svg.curve - this.svg.padding;
var y = 0 + this.svg.padding + this.svgTopArrowPadding;
return "Q" + cx + ' ' + cy + ' ' + x + ' ' + y;
},
svgLeft: function() {
// SVG Drawing: Draw Line on left side
var sx = 0 + this.svg.padding;
var sy = this.svg.height - this.svg.curve - this.svg.padding - this.svgBottomArrowPadding;
return 'L' + sx + ' ' + sy;
},
svgBottom: function() {
// SVG Drawing: Draw Line on bottom side, if the container is 'up' in terms of the click position
// insert an arrow with respects to the position of the click
if (this.direction === 'up') {
var midPoint = (this.clickX - this.left) * this.svgRes;
var sx = midPoint - this.svg.arrow.mid;
var sy = this.svg.height - this.svg.padding - this.svgBottomArrowPadding;
var mid = sx + this.svg.arrow.mid;
var a1cx = mid - this.svg.arrow.base;
var a1cy = sy;
var a1x = mid - this.svg.arrow.top;
var a1y = sy + this.svg.arrow.height;
var a2cx = mid;
var a2cy = this.svg.height - this.svg.padding - this.svgBottomArrowPadding + this.svg.arrow.point;
var a2x = mid + this.svg.arrow.top;
var a2y = sy + this.svg.arrow.height;
var a3x = mid + this.svg.arrow.mid;
var a3y = sy;
var a3cx = mid + this.svg.arrow.base;
var a3cy = sy;
var ex = this.svg.width - this.svg.curve - this.svg.padding;
var ey = this.svg.height - this.svg.padding - this.svgBottomArrowPadding;
return 'L' + sx + ' ' + sy + ' Q' + a1cx + ' ' + a1cy + ' ' + a1x + ' ' + a1y + ' Q' + a2cx + ' ' + a2cy + ' ' + a2x + ' ' + a2y + ' Q' + a3cx + ' ' + a3cy + ' ' + a3x + ' ' + a3y + ' L' + ex + ' ' + ey;
}else{
var sx = this.svg.width - this.svg.curve - this.svg.padding;
var sy = this.svg.height - this.svg.padding - this.svgBottomArrowPadding;
return 'L' + sx + ' ' + sy;
}
},
svgRight: function() {
// SVG Drawing: Draw Line on right side
var sx = this.svg.width - this.svg.padding;
var sy = 0 + this.svg.curve + this.svg.padding + this.svgTopArrowPadding;
return 'L' + sx + ' ' + sy;
},
svgTop: function() {
// SVG Drawing: Draw Line on top side, if the container is 'down' in terms of the click position
// insert an arrow with respects to the position of the click
if (this.direction === 'down') {
var midPoint = (this.clickX - this.left) * this.svgRes;
var sx = midPoint + this.svg.arrow.mid;
var sy = 0 + this.svg.padding + this.svgTopArrowPadding;
var mid = sx - this.svg.arrow.mid;
var a1cx = mid + this.svg.arrow.base;
var a1cy = sy;
var a1x = mid + this.svg.arrow.top;
var a1y = sy - this.svg.arrow.height;
var a2cx = mid;
var a2cy = this.svgTopArrowPadding - this.svg.arrow.point + this.svg.padding;
var a2x = mid - this.svg.arrow.top;
var a2y = sy - this.svg.arrow.height;
var a3x = mid - this.svg.arrow.mid;
var a3y = sy;
var a3cx = mid - this.svg.arrow.base;
var a3cy = sy;
var ex = this.svg.curve + this.svg.padding;
var ey = 0 + this.svg.padding + this.svgTopArrowPadding;
return 'L' + sx + ' ' + sy + ' Q' + a1cx + ' ' + a1cy + ' ' + a1x + ' ' + a1y + ' Q' + a2cx + ' ' + a2cy + ' ' + a2x + ' ' + a2y + ' Q' + a3cx + ' ' + a3cy + ' ' + a3x + ' ' + a3y + ' L' + ex + ' ' + ey;
}else{
var sx = this.svg.curve + this.svg.padding + this.svgTopArrowPadding;
var sy = 0 + this.svg.padding + this.svgTopArrowPadding;
return 'L' + sx + ' ' + sy;
}
},
messageMargin: function(){
// Margin for the contents of the container. If NOT mobile, calculate the margin based on SVG
if (this.mobile) {
return 0;
}
var top = this.svg.padding + (this.svgTopArrowPadding / this.svgRes);
var bottom = this.svg.padding + (this.svgBottomArrowPadding / this.svgRes);
var right = this.svg.padding;
var left = this.svg.padding;
return top + 'px ' + left + 'px ' + bottom + 'px ' + right + 'px';
},
messagePadding: function(){
// Padding for the contents of the container. If mobile, use the padding for the svg so its consistent
if (this.mobile) {
return this.svg.padding + 'px';
}else{
return 0;
}
},
messageBorderLeft: function(){
// Left Border for the container on mobile
if (this.direction === 'sidepanel') {
return '1px solid';
}else{
return 0;
}
},
messageBorderTop: function(){
// Top Border for the container on mobile
if (this.direction === 'bottompanel') {
return '1px solid';
}else{
return 0;
}
},
messageShadow: function(){
// box shadow for the container on mobile
if (this.mobile) {
return '0 0 6px #000';
}else{
return 'none';
}
}
},
methods: {
documentHeight: function(){
// Fetch document height
return $(document).height();
},
documentWidth: function(){
// Fetch document width
return $(document).width();
},
findMessage: function(node) {
// recursively fetches a message from the first 'data-info-message' attribute on any parent node of the clicked node
if ($(node).data('info-message') === undefined) {
if ($(node).parent().is('html')) {
return false;
}else{
return this.findMessage($(node).parent());
}
}else{
return $(node).data('info-message');
}
},
open: function(args) {
// Opens the info message if the event, node, and a message is found
if (args.event === undefined || args.node === undefined) {
return;
}
var $event = args.event;
var node = args.node;
var msg = args.message || this.findMessage(node);
if (msg == false) {
return;
}else{
this.messageHTML = msg;
this.clickX = $event.pageX;
this.clickY = $event.pageY;
this.windowX = $event.clientX;
this.windowY = $event.clientY;
this.containerOpen = true;
var self = this;
// Opens the container, makes sure events are in order
setTimeout(function () {
self.svgOpen = true;
// On the first click of the window, close the message (unless clicked in the container)
setTimeout(function () {
$(window).one('click', function() {
self.close();
});
}, 10);
}, 10);
}
},
close: function() {
// Closes the message
this.svgOpen = false;
var node = $(this.container);
var self = this;
// hides the container after fade out
setTimeout(function () {
self.containerOpen = false;
}, 200);
}
}
});
$("section").on('dblclick', function(event) {
infoMessage.open({event: event, node: this});
});
});
})();
$background-color: #eeeeee;
$border-color: #aaaaaa;
#vue-info {
svg.container {
@include transform(translateY(-20px) scale(0.8));
@include transition(all, 0.2s);
opacity: 0;
position: absolute;
z-index: -1;
path {
fill: $background-color;
stroke: $border-color;
}
&.open {
@include transform(none);
opacity: 1;
@include transition(all, 0.2s);
}
}
.info-message {
position: absolute;
z-index: 100000000;
.message-contents {
overflow: scroll;
top: 0;
bottom: 0;
right: 0;
left: 0;
position: absolute;
opacity: 0;
background: $background-color;
border-color: $border-color !important;
&.open {
@include transform(none);
opacity: 1;
@include transition(all, 0.4s);
@include transition-delay(0.2s);
}
}
}
}
@humphreyja
Copy link
Author

Preview of what it looks like by default

screen shot 2017-01-29 at 5 23 32 pm

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment