I'm follwoing a book which talks about SPA - single page web application. It is very interesting but I get blocked on a piece of code. So far I understood what I was doing so it looks very strange that this isn't woking.
I have an event that is triggered on the chat click, which is at the bottom right of the page. This event triggers the change of the URL hash but.. the hashchange event is not triggered even though I binded it to the window with $(window).bind('hashchange', onHashchange()).trigger('hashchange');
I need to refresh manually the page to see the chat changing from open to close, but this is not supposed to happen, since I want it to be triggered by onhashchange.
Can you help me spotting out what's going on?
It looks like I'm missing something, but everyone around complains about not working in compatibility with other browsers, while in this case is just not working :S
Any hint is appreciated =)
I have the following html code
<html>
<head>
<title>SPA starter</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width">
<link rel="stylesheet" href="css/spa.css" type="text/css">
<link rel="stylesheet" href="css/spa.shell.css" type="text/css">
<!-- 3rd party lib -->
<script id="jquery_js" src="js/libs/jquery/jquery.js"></script>
<script id="janchor_js" src="js/libs/jquery/jquery.uriAnchor.js"></script>
<!-- my lib -->
<script id ="spajs" src="js/spa.js"></script>
<script id="spashelljs" src="js/spa.shell.js"></script>
<script id="unloader" src="js/js_unloader.js"></script>
<script id="starter">
$(function() {
spa.initModule($('#spa'));
});
</script>
</head>
<body>
<div id="spa"></div>
</body>
</html>
and 3 scripts + jquery clearly. First script can be found here and is provided by the author of the book, the other two are the following
spa.js
var spa = (function() {
var initModule = function($container) {
spa.shell.initModule($container);
};
return {initModule: initModule};
}());
spa.shell.js
spa.shell = (function() {
//----------- BEGIN MODULE SCOPE VARIABLES ---------
var configMap = {
main_html: String() +
'<div class="spa-shell-head">' +
'<div class="spa-shell-head-logo"> </div>' +
'<div class="spa-shell-head-acct"> </div>' +
'<div class="spa-shell-head-search"> </div>' +
'</div>' +
'<div class="spa-shell-main">' +
' <div class="spa-shell-main-nav"> </div>' +
' <div class="spa-shell-main-content"> </div>' +
'</div>' +
'<div class="spa-shell-foot"></div>' +
'<div class="spa-shell-chat"></div>' +
'<div class="spa-shell-modal"></div>',
chat_extend_time: 250,
chat_retract_time: 300,
chat_extend_height: 450,
chat_retract_height: 15,
chat_extended_title: 'Click to retract',
chat_retracted_title: 'Click to extend',
anchor_schema_map: {
chat: {open: true, closed: true}
}
},
stateMap = {
$container: null,
is_chat_retracted: true,
anchor_map: {}
},
jqueryMap = {},
setJqueryMap, toogleChat, onClickChat,
copyAnchorMap, changeAnchorPart, onHashchange,
initModule;
//----------- END MODULE SCOPE VARIABLES ---------
//----------- BEGIN UTILITY METHODS ---------
//Return copy of stored anchro map; minimizes overhead
copyAnchorMap = function() {
return $.extend(true, {}, stateMap.anchor_map);
};
//----------- END UTILITY METHODS ---------
//----------- BEGIN DOM METHODS ---------
//Begin DOM method /changeAnchorPart/
changeAnchorPart = function(arg_map) {
console.log("change anchor part");
var
anchor_map_revise = copyAnchorMap(),
bool_return = true,
key_name, key_name_dep;
//BEGIN merge changes into anchor map
KEYVAL:
for (key_name in arg_map) {
if (arg_map.hasOwnProperty(key_name)) {
//console.log("key_name:= " + key_name);
//skip dependet keys during iteration
if (key_name.indexOf('_') === 0) {
console.log("key name starts with '_'");
continue KEYVAL;
}
//update independent key value
anchor_map_revise[key_name] = arg_map[key_name];
//update matching dependent key
key_name_dep = '_' + key_name;
//console.log("key_name_dep:= " + key_name_dep);
if (arg_map[key_name_dep]) {
//console.log("if");
anchor_map_revise[key_name_dep] = arg_map[key_name_dep];
}
else {
//console.log("else");
delete anchor_map_revise[key_name_dep];
delete anchor_map_revise['_s' + key_name_dep];
}
}
}
//END merge changes into anchor map
//BEGIN ateempt to update URI; revert if not successful
try {
console.log("setting anchor");
$.uriAnchor.setAnchor(anchor_map_revise);
console.log("set");
} catch (error) {
//replace URI with existing state
$.uriAnchor.setAnchor(stateMap.anchor_map, null, true);
console.log("changeAnchorPart error :=" + error);
bool_return = false;
}
//END attemp to update URI
return bool_return;
};
//END DOM method /changeAnchorPart/
//begin DOM method /setJqueryMap/
setJqueryMap = function() {
var $container = stateMap.$container;
jqueryMap = {$container: $container,
$chat: $container.find('.spa-shell-chat')
};
};
//end DOM method /setJqueryMap/
//Begin DOM method /toogleChat/
//
toogleChat = function(do_extend, callback) {
var px_chat_ht = jqueryMap.$chat.height(),
is_open = px_chat_ht === configMap.chat_extend_height,
is_closed = px_chat_ht === configMap.chat_retract_height,
is_sliding = !is_open && !is_closed;
//avoid race condition
if (is_sliding) {
console.log('avoid race condition');
return false;
}
//begin chat slider
if (do_extend) {
jqueryMap.$chat.animate({height: configMap.chat_extend_height},
configMap.chat_extend_time, function() {
jqueryMap.$chat.attr('title', configMap.chat_extended_title);
stateMap.is_chat_retracted = false;
if (callback) {
callback(jqueryMap.$chat);
}
});
return true;
}
//End extend chat slider
//Begin retract chat slider
jqueryMap.$chat.animate({height: configMap.chat_retract_height},
configMap.chat_retract_time, function() {
jqueryMap.$chat.attr('title', configMap.chat_retracted_title);
stateMap.is_chat_retracted = true;
if (callback) {
callback(jqueryMap.$chat)
}
});
return true;
//End rectract chat slider
};
//end DOM method /toogleChat/
//----------- END DOM METHODS ---------
//
//----------- BEGIN EVENT HANDLERS ---------
onClickChat = function(event) {
// console.log(stateMap.is_chat_retracted);
changeAnchorPart({
chat: (stateMap.is_chat_retracted ? 'open' : 'closed')
});
return false;
};
//
// BEGIN event handler /onHashchange/
//
onHashchange = function(event) {
console.log("on hash change");
var
anchor_map_previous = copyAnchorMap(),
anchor_map_proposed,
_s_chat_previous, _s_chat_proposed,
s_chat_proposed;
//Attempt to parse anchor
try {
anchor_map_proposed = $.uriAnchor.makeAnchorMap();
} catch (error) {
console.log("onHashchange error:= " + error)
$.uriAnchor.setAnchor(anchor_map_previous, null, true);
return false;
}
stateMap.anchor_map = anchor_map_proposed;
//convenience vars
_s_chat_previous = anchor_map_previous._s_chat;
_s_chat_proposed = anchor_map_proposed._s_chat;
//BEGIN adjust of component if changed
if (!anchor_map_previous || _s_chat_previous !== _s_chat_proposed) {
s_chat_proposed = anchor_map_proposed.chat;
console.log("adjusting components, chat:= " + s_chat_proposed);
switch (s_chat_proposed) {
case 'open':
toogleChat(true);
break;
case 'closed':
toogleChat(false);
break;
default :
toogleChat(false);
delete anchor_map_proposed.chat;
$.uriAnchor.setAnchor(anchor_map_proposed, null, true);
}
}
//END of the adjustment
return false;
};
//END event handler /onHashchange/
//----------- END EVENT HANDLERS ---------
//----------- BEGIN PUBLIC METHODS ---------
//Begin Public methods /initModule/
//
initModule = function($container) {
//load HTML and map jQuery collections
stateMap.$container = $container;
$container.html(configMap.main_html);
setJqueryMap();
//initialize chat slider and bind click handler
stateMap.is_chat_retracted = true;
jqueryMap.$chat.attr('title', configMap.chat_retracted_title)
.click(onClickChat);
//configure uriAnchor to use our schema
$.uriAnchor.configModule({
schema_map: configMap.anchor_schema_map
});
//HANDLE URI anchor change events
//
if ("onhashchange" in window) {
console.log('SUPPORTED');
}
$(window).bind('hashchange', onHashchange()).trigger('hashchange');
};
//End PUBLIC methods /initModule/
return {initModule: initModule};
//----------- END PUBLIC METHODS ---------
}());
spa.css
*{
margin : 0;
padding : 0;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
h1,h2,h3,h4,h5,h6, p{ margin-bottom: 10px;}
o1,ul,dl{list-style-position: inside;}
/** end reset */
/** begin standard selectors */
body{
font: 13px 'Trebuchet MS', Verdana, Helvetica, Arial, sans-serif;
color: #444;
background-color: #888;
}
strong{
font-weight: 800;
color:#000;
}
/** end standard selectors */
/** begin spa namespace selectors */
#spa{
position: absolute;
top:8px;
left:8px;
bottom:8px;
right:8px;
min-height: 500px;
min-width: 500px;
overflow: hidden;
border-radius: 0 8px 0 8px;
background-color: #fff;
}
/** end spa namespace selectors */
/** begin utility selectors */
.spa-x-select{}
.spa-x-clearfloat{
height: 0 !important;
float: none !important;
visibility: hidden !important;
clear: both !important;
}
/** */
spa.shell.css
.spa-shell-head, .spa-shell-head-logo, .spa-shell-head-acct, .spa-shell-head-search,
.spa-shell-main, .spa-shell-main-content, .spa-shell-main-nav, .spa-shell-foot,
.spa-shell-chat, .spa-shell-modal {
position: absolute;
}
.spa-shell-head{
top:0;
left:0;
right:0;
height: 40px;
background-color: red;
}
.spa-shell-head-logo{
top: 4px;
left: 4px;
height: 32px;
width: 128px;
background: orange;
}
.spa-shell-head-acct{
top:4px;
right:0;
width: 64px;
height: 32px;
background: green;
}
.spa-shell-head-search{
top:4px;
right:64px;
width: 248px;
height: 32px;
background: blue;
}
.spa-shell-main{
top:40px;
left:0;
bottom:40px;
right:0;
background-color: #993300;
}
.spa-shell-main-content, .spa-shell-main-nav{
top:0;
bottom:0;
}
.spa-shell-main-nav{
width:250px;
background: #eee;
}
.spa-x-closed, .spa-shell-main-nav{
width:0;
}
.spa-shell-main-content{
left:250px;
right:0;
background: #ddd;
}
.spa-x-closed .spa-shell-main-content{
left:0;
}
.spa-shell-foot{
bottom:0;
left:0;
right:0;
height:40px;
background-color: #99ffff;
}
.spa-shell-chat{
bottom:0;
right:0;
width: 300px;
height: 15px;
background: burlywood;
z-index: 1;
cursor:pointer;
border-radius: 5px 0 0 0;
}
.spa-shell-modal{
margin-top:-200px;
margin-left:-200px;
top:50%;
left:50%;
width:400px;
height:400px;
background: #fff;
border-radius: 3px;
z-index: 2;
}
Correct me if I am mistaken, but you are executing the function in your bind. You want it like this:
$(window).bind('hashchange', onHashchange).trigger('hashchange');
Without the '()' after.
Bear in mind, the support of this is limited in some browsers.
Excerpt from: jQuery - hashchange event
if (("onhashchange" in window) && !($.browser.msie)) {
window.onhashchange = function () {
alert(window.location.hash);
}
// Or $(window).bind( 'hashchange',function(e) {
// alert(window.location.hash);
// });
}
else {
var prevHash = window.location.hash;
window.setInterval(function () {
if (window.location.hash != prevHash) {
prevHash = window.location.hash;
alert(window.location.hash);
}
}, 100);
}
Adapt according to your needs.
Related
I'm relatively new to this and trying to create a pop-up. I'm not sure how to get the pop-up to be in the middle of the page and still populate the box correctly, since it ends up being inside the table. I tried setting a position in the CSS but that didn't work. Maybe I did it wrong?
PHP
foreach ($updateInfo['updates'] as $update) {
echo "<table><tr><td>";
if (isset($update['details']['newsDetails']['fldDatePosted'])) {
echo '<a class="news_popper">'.$update['details']['newsDetails']['fldDatePosted'].'</a><div class="news_pop"><p>'.$update['details']['newsDetails']['fldBody'].'</p></div>';
}
echo "</td></tr></table>";
}
CSS
.news_pop {
display:none;
position: absolute;
z-index: 99999;
padding: 10px;
background: #ffffff;
border: 1px solid #A2ADBC;
}
JS
$(function() {
$('a.news_popper').click(function() {
$(this).next(".news_pop").toggle();
});
});
I would suggest coding a popup in a more object oriented approach, that way you can call it whenever you need it throughout the page, and you can have multiple instances if needed. What I would do is create a constructor for the popup and a div that manages the pop ups.
First the pop up manager:
const popUpManager = (function popUpManager() {
const $popUpManager = document.createElement('div');
$popUpManager.className = "pop-up_manager";
document.body.append($popUpManager);
return {
createPopUp: () => {
const popup = new Popup();
const $popup = popup.getPopup();
$popUpManager.append($popup);
return popup;
}
}
})();
We create a div called pop-up_manager that will house your popup and allow you to place it where ever you need throughout the page.
Then we need to create the blueprint for what a popup is:
class Popup{
constructor() {
this.$popup = document.createElement('div');
this.$popup.className = 'pop-up';
this.$popup.innerHTML = '<div class="exit-button">X</div><div class="body"></div>';
this.$exitButton = this.$popup.querySelector('.exit-button');
this.$body = this.$popup.querySelector('.body');
this.setUpListeners();
}
getPopup() {
return this.$popup;
}
setContent($content) {
if (typeof $content === 'string') {
this.$body.innerHTML = $content;
} else {
this.$body.appendChild($content);
}
}
Every time we call new Popup(); we will generate a div that floats over the page that we can set to house anything we want by passing content to the setContent() method. This will create what is in the pop up.
Finally, the styling can be configured to whatever you want in the css:
.pop-up {
position: fixed;
height: 300px;
width: 300px;
background-color: grey;
/* Positioning */
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.body {
height: 100%;
width: 100%;
}
.exit-button {
cursor: pointer;
}
To call the popup from anywhere in you JS all you have to do is:
$('a.news_popper').click(function() {
const popup = popUpManager.createPopUp();
popup.setContent('<h1>INSERT CONTENT TO SHOW USER HERE</h1>');
});
Here is a codepen: CodePen Link
I just want to ask. I want to make the product image thumbnail in shopify disappear when I scrolled down to bottom of the page, and I want a bit of transition with it.. I really can't figure out how to do this..
Here's my code..
https://jsfiddle.net/vsLdz4qb/1/
function myFunction(screenWidth) {
if (screenWidth.matches) { // If media query matches
window.onscroll = function(ev) {
if ((window.innerHeight + window.scrollY) >= document.body.offsetHeight) {
document.getElementByClass("product-single__thumbnails").style.transition = "0.65s";
document.getElementByClass("product-single__thumbnails").style.opacity = 0;
}
};
}
}
let screenWidth = window.matchMedia("(min-width: 750px)");
myFunction(screenWidth); // Call listener function at run time
screenWidth.addListener(myFunction)
Thank you so much in advance!
The correct document method is document.getElementsByClassName and since it returns an array you need the first element of it so change this:
document.getElementByClass("product-single__thumbnails").style.transition = "0.65s";
document.getElementByClass("product-single__thumbnails").style.opacity = 0;
to:
document.getElementsByClassName("product-single__thumbnails")[0].style.transition = "0.65s";
document.getElementsByClassName("product-single__thumbnails")[0].style.opacity = 0;
You can read more about the method here
You should use getElementsByClassName in place of getElementByClass(This is not correct function)
and this will return an array like structure so you need to pass 0 index, if only one class available on page.
or you can try querySelector(".product-single__thumbnails");
and for transition, you can define that in your .product-single__thumbnails class like: transition: opacity .65s linear; - use here which property, you want to animate.
<!-- [product-image] this is for product image scroll down disappear -->
function myFunction(screenWidth) {
if (screenWidth.matches) { // If media query matches
window.onscroll = function(ev) {
if ((window.innerHeight + window.scrollY) >= document.body.offsetHeight) {
document.getElementsByClassName("product-single__thumbnails")[0].style.opacity = 0;
}
};
}
}
let screenWidth = window.matchMedia("(min-width: 350px)");
myFunction(screenWidth); // Call listener function at run time
screenWidth.addListener(myFunction)
body {
margin:0;
height: 1000px;
}
.product-single__thumbnails {
background-color: red;
color: white;
width: 50px;
height: 50px;
position: fixed;
transition: opacity .65s linear;
border-radius: 4px;
margin: 20px;
text-align: center;
}
<div class="product-single__thumbnails">
<p>red</p>
</div>
I'm using fetch API to access a JSON-object.
My questions is how can i implement a css loader before the actual data is displayed. And how do I know that the data has been displayed.
This is what I've tried:
var startSevenDaysInterval = (new Date).getTime();
var endSevenDaysInterval = startSevenDaysInterval - 604800000;
function fetchData2() {
fetch('https://vannovervakning.com/api/v1/measurements/3/' + endSevenDaysInterval + '/' + startSevenDaysInterval + '/')
.then(function (response) {
if(response === 404){
document.getElementById("loader").style.display = 'none';
}
return response.json();
})
.then(function (data) {
document.getElementById("loader").style.display = 'none';
printTemperature(data);
});
}
function printTemperature(data) {
var html = "<h5>";
html += data.data["TEMPERATURE"][0].value;
html += "</h5>";
document.getElementById('temp1').innerHTML = html;
}
setInterval(function () {
fetchData2();
}, 1000);
My loader looks like this:
.loader {
border: 4px solid #f3f3f3; /* Light grey */
border-top: 4px solid #e0e0e0;
border-radius: 30px;
width: 30px;
height: 30px;
animation: spin 1s linear infinite;
}
#keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
And my HTML looks like this:
<div class="tempVal">
<div class="loader"></div>
<div id="temp1"></div>
</div>
You are already doing that, but using getElementById for a class (it's for IDs only). When you're not sure about these things make sure you have actually got the element you want e.g. using a debugger statement or console.log(document.getElementById("loader")).
Get a class with querySelector which can handle all css selectors:
document.querySelector('.loader').style.display = 'none'
Note if you have multiple classes with that name it will get the first one. You don't need to repeat it in both .then's.
What I'm doing and what's wrong
When I click on a button, a slider shows up. (here is an example of what it looks like, do not pay attention to this code)
The slider shows via an animation. When the animation is finished I should include an HTML page I've loaded from the server. I need to apply the HTML in the slider after the animation otherwise the animation stops (the DOM is recalculated).
My algorithm
Start the request to get the HTML to display inside the slider
Start the animation
Wait the data to be ready and the transition to be finished
Why? If I apply the HTML during the animation, it stops the animation while the new HTML is added to the DOM. So I wait for both to end before step 4.
Apply the HTML inside the slider
Here is the shortened code:
// Start loading data & animate transition
var count = 0;
var data = null;
++count;
$.get(url, function (res) {
data = res;
cbSlider();
});
// Animation starts here
++count;
$(document).on('transitionend', '#' + sliderId, function () {
$(document).off('transitionend', '#' + sliderId);
cbSlider()
});
function cbSlider() {
--count;
// This condition is only correct when both GET request and animation are finished
if (count == 0) {
// Attempt to enforce the frame to finish (doesn't work)
window.requestAnimationFrame(() => { return });
$('#' + sliderId + ' .slider-content').html(data);
}
}
The detailed issue
transitionend is called too early. It makes the last animated frame a lot too long (477.2ms) and the last frame is not rendered at transitionend event.
From the Google documentation, I can tell you that the Paint and Composite step of the Pixel Pipeline is called after the Event(transitionend):
Maybe I'm overthinking this.
How should I handle this kind of animations?
How can I wait the animation to be fully finished and rendered?
I'm not sure why transitionend is fired before the last frame has rendered, but in this (very crude) test it seems that a setTimeout does help...
The first example shows how the html calculation and injection happens too early. The second example wraps the long running method in a setTimeout and doesn't seem to trigger any interuption in the animation.
Example 1: reproduction of your problem
var ended = 0;
var cb = function() {
ended += 1;
if (ended == 2) {
$(".animated").html(createLongHTMLString());
}
}
$(".load").click(function() {
$(".animated").addClass("loading");
$(".animated").on("transitionend", cb);
setTimeout(cb, 100);
});
function createLongHTMLString() {
var str = "";
for (var i = 0; i < 100000; i += 1) {
str += "<em>Test </em>";
}
return str;
};
.animated,
.target {
width: 100px;
height: 100px;
position: absolute;
text-align: center;
line-height: 100px;
overflow: hidden;
}
.target,
.animated.loading {
transform: translateX(300%);
}
.animated {
background: green;
z-index: 1;
transition: transform .2s linear;
}
.target {
background: red;
z-index: 0;
}
.wrapper {
height: 100px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="wrapper">
<div class="animated">Loading</div>
<div class="target"></div>
</div>
<button class="load">load</button>
Example 2: in which a setTimeout seems to fix it
With a setTimeout around the html injection code.
var ended = 0;
var cb = function() {
ended += 1;
if (ended == 2) {
setTimeout(function() {
$(".animated").html(createLongHTMLString());
});
}
}
$(".load").click(function() {
$(".animated").addClass("loading");
$(".animated").on("transitionend", cb);
setTimeout(cb, 100);
});
function createLongHTMLString() {
var str = "";
for (var i = 0; i < 100000; i += 1) {
str += "<em>Test </em>";
}
return str;
};
.animated,
.target {
width: 100px;
height: 100px;
position: absolute;
text-align: center;
line-height: 100px;
overflow: hidden;
}
.target,
.animated.loading {
transform: translateX(300%);
}
.animated {
background: green;
z-index: 1;
transition: transform .2s linear;
}
.target {
background: red;
z-index: 0;
}
.wrapper {
height: 100px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="wrapper">
<div class="animated">Loading</div>
<div class="target"></div>
</div>
<button class="load">load</button>
Well, if transitions are not working for you the way you want to, you can go back a few years and use jQuery animations instead?
(function(slider){
$.get(url, function (res) {
slider.animate({
// put whatever animations you need here
left: "5%",
}, 5000, function() {
// Animation complete.
slider.find('.slider-content').html(res);
});
});
}($('#' + sliderId)));
You can also start both actions at the same time, and then add the html to the document only after the animation has finished and the request is complete, but that would require a flag.
(function(slider){
// whether the animation is finished
var finished = false;
// whether the html has been added already
var added = false;
// your html data
var html = null;
function add() {
if (finished && html && !added) {
// make sure function will only add html once
added = true;
slider.find('.slider-content').html(html);
}
}
$.get(url, function (res) {
html = res;
add();
});
slider.animate({
// put whatever animations you need here
left: "5%",
}, 5000, function() {
// Animation complete.
finished = true;
add();
});
}($('#' + sliderId)));
Using regular JavaScript or JQuery I want to replace the createPopup() method like done in this post:
A universal createPopup() replacement?
but I want to use a Div instead of an iFrame. I don't need anything fancy just a simple div which can be styled.
The problem with using a Div is that I have a lot of existing code like this which I would like to remain untouched e.g.
var popup = window.createPopup();
oPopup.document.body.innerHTML = "Click outside <strong>popup</strong> to close.";
In the new createPopup() method below, is there a way to return an object that has the properties document.body.innerHTML to style the Div and the existing code can remain untouched.
if(!window.createPopup){
window.createPopup = function() {
// TODO return div object
}
}
You can use javascript setters and getters in combination with defineProperties to pull off what you are trying to do.
if(!window.createPopup){
window.createPopup = (function() {
// build our object
var o = {
document: {
body: {
_innerHTML: ''
}
}
};
// build the popup
o.document.body._outer = document.createElement('div');
o.document.body._inner = document.createElement('div');
o.document.body._outer.className = 'modal';
o.document.body._inner.className = 'inner';
// attach popup
o.document.body._outer.appendChild(o.document.body._inner);
document.body.appendChild(o.document.body._outer);
// add a property for innerHTML
Object.defineProperties(o.document.body, {
'innerHTML': {
get: function () { return this._innerHTML; },
set: function (x) {
this._innerHTML = x;
this._inner.innerHTML = this._innerHTML;
}
}
});
// return the object
return o;
});
}
var oPopup = window.createPopup();
oPopup.document.body.innerHTML = "Click outside <strong>popup</strong> to close.";
.modal {
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
background: rgba(0,0,0,0.5);
}
.modal .inner {
padding: 2em;
background: #fff;
border: 1px solid #eee;
display: inline-block;
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
}