Popup won't close when i click close button, i tried debugging with console.log and it looks like closeButton.onclick function doesn't run at all for some reason.
When running close() function manually from the console everything works fine.
class Popup {
constructor(content){
this.div = document.createElement("div");
this.div.className = "block";
//tried positioning popup into the center of the screen, doesn't work yet
this.div.style.position = "fixed";
this.div.style.margin = "auto auto";
//caption
this.caption = document.createElement("div");
this.caption.style.textAlign = "right";
//closeButton
this.closeButton = document.createElement("button");
this.closeButton.textContent = "X";
this.closeButton.onclick = this.close;
document.body.appendChild(this.div);
this.div.appendChild(this.caption);
this.caption.appendChild(this.closeButton);
this.div.innerHTML += content;
}
close(){
this.div.parentNode.removeChild(this.div);
delete this;
}
}
new Popup("close me");
That's how it looks like:
var popup = new Popup("hm hello");
SOLUTION:
The issue was happening because:
I was appending content of the popup right into main div using +=. That made DOM refresh and onclick trigger reset.
this.closeButton.onclick = this.close; here onclick trigger will execute close function and also will overwrite this keyword, so it contains a button that called trigger, not the Popup object. I decided to put Popup into a variable that is visible to onclick function. Now everything works fine.
class Popup {
constructor(content){
this.div = document.createElement("div");
this.div.className = "block";
this.div.style.position = "fixed";
this.div.style.margin = "auto auto";
//делоем капшон
this.caption = document.createElement("div");
this.caption.style.textAlign = "right";
//кнопка закрытия
this.closeButton = document.createElement("button");
this.closeButton.textContent = "X";
let popup = this;
this.closeButton.onclick = function(){popup.close()};
this.content = document.createElement("div");
this.content.innerHTML = content;
this.caption.appendChild(this.closeButton);
this.div.appendChild(this.caption);
this.div.appendChild(this.content);
document.body.appendChild(this.div);
}
close(){
this.div.parentNode.removeChild(this.div);
delete this;
}
}
new Popup("hello guys");
The issue is that right here:
this.div.innerHTML += content;
When you assign a value to .innerHTML, the entire previous value is overwritten with the new value. Even if the new value contains the same HTML string as the previous value, any DOM event bindings on elements in the original HTML will have been lost. The solution is to not use .innerHTML and instead use .appendChild. To accomplish this in your case (so that you don't lose the existing content), you can create a "dummy" element that you could use .innerHTML on, but because of performance issues with .innerHTML, it's better to set non-HTML content up with the .textContent property of a DOM object.
You were also going to have troubles inside close() locating the correct parentNode and node to remove, so I've updated that.
class Popup {
constructor(content){
this.div = document.createElement("div");
this.div.className = "block";
this.div.style.position = "fixed";
this.div.style.margin = "auto auto";
//caption
this.caption = document.createElement("div");
this.caption.style.textAlign = "right";
//closeButton
this.closeButton = document.createElement("button");
this.closeButton.textContent = "X";
this.closeButton.addEventListener("click", this.close);
this.caption.appendChild(this.closeButton);
this.div.appendChild(this.caption);
// Create a "dummy" wrapper that we can place content into
var dummy = document.createElement("div");
dummy.textContent = content;
// Then append the wrapper to the existing element (which won't kill
// any event bindings on DOM elements already present).
this.div.appendChild(dummy);
document.body.appendChild(this.div);
}
close() {
var currentPopup = document.querySelector(".block");
currentPopup.parentNode.removeChild(currentPopup);
delete this;
}
}
var popup = new Popup("hm hello");
I've finally found a final solution.
As Scott Marcus mentioned in his answer, i will have troubles inside close function, so i decided to put Popup object into a variable that is visible to close function. Everything works fine without applying classes. Though it may look like a bad code.
class Popup {
constructor(content){
this.div = document.createElement("div");
this.div.className = "block";
this.div.style.position = "fixed";
this.div.style.margin = "auto auto";
//делоем капшон
this.caption = document.createElement("div");
this.caption.style.textAlign = "right";
//кнопка закрытия
this.closeButton = document.createElement("button");
this.closeButton.textContent = "X";
let popup = this;
this.closeButton.onclick = function(){popup.close()};
this.content = document.createElement("div");
this.content.innerHTML = content;
this.caption.appendChild(this.closeButton);
this.div.appendChild(this.caption);
this.div.appendChild(this.content);
document.body.appendChild(this.div);
}
close(){
this.div.parentNode.removeChild(this.div);
delete this;
}
}
new Popup("hello guys")
P.S.
What's the point of this restriction?
Related
I want clicking on an "expando" to toggle between its states: expanded and collapsed.
I'm still pretty new to DOM/JS, so my style here is probably awful; If you have any style guidelines let me know, but for right now I want to get the code working. I've tried a few different ways, like setting the expand or collapse behavior in dom's onclick (and changing it in the expand and collapse functions), but if I do that, then for some reason clicking doesn't trigger a collapse, but it will trigger an expand.
The problem with the code below is that I can expand an expando, but when I click on it, it also triggers the collapse, so it expands and then immediately collapses back.
var expandos = document.getElementsByTagName("expando");
var uid = 0;
for(var i=0; i<expandos.length; ++i) {
var dom = expandos[i];
dom.id = "expando_"+uid++;
var iframe = document.createElement("iframe");
iframe.src = dom.innerHTML;
iframe.name = dom.id +".big";
iframe.id = iframe.name;
iframe.scrolling = "no";
iframe.style.display = "inline";
iframe.onclick = collapse(dom);
var p = document.createElement("p");
var text = document.createTextNode(dom.innerHTML);
p.id = dom.id+".small";
p.style.display = "inline";
p.appendChild(text);
p.onclick = expand(dom);
dom.innerHTML = "";
/* We have to clear the innerHTML to prevent the original text from
showing up in addition to the text added by p.
*/
dom.appendChild(iframe);
dom.appendChild(p);
/* We have to append iframe and p **after** we clear innerHTML
because otherwise clearing innerHTML will clear the appended
children.
*/
function expand(dom) {
return function() {
alert("Expanding "+dom.id);
var iframe = document.getElementById(dom.id+".big");
var p = document.getElementById(dom.id+".small");
p.style.display = "none";
iframe.style.display = "initial";
dom.onclick = collapse(dom);
}
}
function collapse(dom) {
return function() {
alert("Collapsing "+dom.id);
var iframe = document.getElementById(dom.id+".big");
var p = document.getElementById(dom.id+".small");
p.style.display = "initial";
iframe.style.display = "none";
dom.onclick = expand(dom);
}
}
collapse(dom)();
}
The sample HTML I'm testing on:
<body>
<expando>The quick brown</expando> fox jumps over <expando>the lazy dog</expando>.
<script src="loadExpandos.js"></script>
</body>
In the same directory, I have files named "The quick brown" and "the lazy dog", and they expand properly.
A quick fix for to get the basic functionality you want is to combine your expand and collapse into a single function and have an if/else block that checks the state. Not 100% on what caused your original issue, but I'd guess it has something to do with your onClick events not being cleared.
function clickHandler(dom) {
return function() {
var iframe = document.getElementById(dom.id+".big");
var p = document.getElementById(dom.id+".small");
if(p.style.display === "initial"){
p.style.display = "none";
iframe.style.display = "initial";
} else {
p.style.display = "initial";
iframe.style.display = "none";
}
I have a link that is attached with an "onclick" function. When pressed it attaches an img element into a separate div called "mediaBox". The problem I'm having is that if it's pressed multiple times then it attaches more instances of the img. How can I control this. I'm still new to JavaScript and I prefer to receive this answer in pure Javascript not jQuery, as I will cross that bridge after I have a full understanding of Javascript.
var rkf = document.getElementById("submenulinks").getElementsByTagName("li")[0];
rkf.onclick = function(){
var client = document.getElementById('client');
var description2 = document.getElementById('description2');
var role = document.getElementById('role');
var mediaBox = document.getElementById('mediaBox');
var thumb = document.getElementById("thumb");
var client2 = document.getElementById("client2");
var newImage = document.createElement("img");
client2.innerHTML = "Role - Applications";
client.innerHTML = "RKF Real Estate";
client2.innerHTML = "Role - Applications";
description2.innerHTML = "Quarterly Catalog of Exclusive Listings managed by RKF";
role.innerHTML = "Custom designed Cover and listings content. Tables were also utilized within Indesign. <br><br><b><i> Photoshop and Indesign</i></b>";
newImage.setAttribute("src", "../images/rkf_cover.jpg");
newImage.setAttribute("height", "500px");
newImage.setAttribute("width", "387px");
newImage.setAttribute("alt", "rkf");
newImage.setAttribute("href", "#");
mediaBox.style.backgroundImage = "none";
document.getElementById("mediaBox").appendChild(newImage);
newImage.style.display = "block";
newImage.style.marginLeft = "auto";
newImage.style.marginRight = "auto";
newImage.style.marginTop = "25px";
}
rkf.onclick = function(){
var client = document.getElementById('client');
...
...
...
// Remove the handler after it ran once.
this.onclick = null; // <<<<<========================
}
Since you do want to use jQuery in the future, it's equal to:
$('#submenulinks li:first').one('click', handler);
EDIT 2 - I decided to create a simple example with jsfiddle.
http://jsfiddle.net/VqA9g/61/
As you can see, I am trying to reference the new div.
EDIT - d/t negative votes and unclear question
I have a linked-list like so:
var struct_list = function () {
this.id = 0;
this.name = 0;
this._head = null;
};
struct_list.prototype = {
// .. adding code , delete code ...
list_contents: function () {
var current = this._head;
while ( current != null ) {
var div = document.createElement("div");
div.style.width = "100px";
div.style.height = "100px";
div.style.background = "white";
div.style.color = "black";
div.style.top = "0px";
div.style.left = "0px";
div.style.margin = "400px 1000px auto";
div.style.cursor = "pointer";
div.innerHTML = current.name;
div.onclick = function ( v ) { var d = document.getElementById('div'); alert(d)};
document.body.appendChild(div);
current = current.next;
}
return null;
},};
I want to be able to display this linked list, and each item displayed be able to interact with an "onclick".
Example:
struct_list.add ( 0 , "Zero" );
struct_list.add ( 1 , "One" );
struct_list.list_contents();
_________________________________________________________________________
| |
| <clickable> "Zero" that does a function(id) that passes over its ID(0) |
|________________________________________________________________________|
| |
| <clickable> "One" <same as above> |
|________________________________________________________________________|
Sorry if I was unclear. Will reedit if still unclear. My apologies.
I have a linked-list struct that I hold data in (it changes data frequently) and I have a setInterval to refresh it. My question is how can I list the struct's contents while still being able to click the exposed content, I have it set up right now that each content in the linked-list contains an id. Also , how can I make sure that overflow is automatic for the y axis? I am guessing I have to place it into a div that has that enabled.
But my real question is how to expose the linked-lists elements while also being able to interact with them via an onclick.
I also do not want to use anything other than pure javascript.
Example (in my mind) would maybe be something like:
<div id="PopUp">
<script>
setInterval(function() {
if ( struct_qued_list ) {
struct_qued_list = false;
main.struct_list.list_contents(); // the linked list
}
}, 100);
</script>
</div>
list_contents: function () {
var current = this._head;
while ( current != null ) {
var div = document.createElement("div");
div.style.width = "100px";
div.style.height = "100px";
div.style.background = "white";
div.style.color = "black";
div.style.top = "0px";
div.style.left = "0px";
div.style.margin = "400px 1000px auto";
div.style.cursor = "pointer";
div.innerHTML = current.name;
div.onclick = function ( v ) { var d = document.getElementById('div'); alert(d)};
document.body.appendChild(div);
current = current.next;
}
return null;
},
Any help or logical way to do this would be appreciated.
This is mainly a scope problem, in your Edit 2 fiddle, the alert gives undefined because your i got the value 2 in order to leave the loop.
Here is a possible solution : Live demo (jsfiddle)
!function(){
var index = i; // Make it independant of i
div.onclick = function () { alert(list[index]); };
}();
You could also use attributes to store any value, and using this in the function to retrieve it.
Or export the whole process to another function to obtain something like this :
for ( var i = 0; i < 2 ; i++ ) {
doSomething(i);
}
When you add new content to the DOM JavaScript sometimes has a hard time picking that up. You may need to use a DOM mutation event (like DOMNodeInserted) to add the event listeners to your text nodes.
document.addEventListener('DOMNodeInserted', function(){
document.getElementById('thing').addEventListener('click', function(){
alert('yey!');
});
});
you may need to name your functions so you can remove them as well, if nodes are going to be inserted without removing all the old ones. Yes this is pure javascript.
EDIT: for your overflow issue, you could assign a class to each node as you insert it and style the class via CSS
.classname {
overflow: auto;
}
I am in the process of making a bookmarklet that pops up a div with various things in it... when you click on the link to open the bookmarklet twice, two bookmarklets pop up. how do I prevent this from happening?
index.html:
<html>
<head>
<title>Bookmarklet Home Page</title>
<link rel="shortcut icon" href="favicon.ico" />
</head>
<body>
click here
</body>
</html>
code.js:
function toggle_bookmarklet() {
bookmarklet = document.getElementById("bookmarklet");
if (bookmarklet.style.display == "none") {
bookmarklet.style.display = "";
}
else {
bookmarklet.style.display = "none";
}
}
div = document.createElement("div");
div.id = "bookmarklet";
div.style.margin = "auto";
div.style.position = "fixed";
content = "";
content += "<a href='javascript:void(0);'><div id='xbutton' onClick='javascript:toggle_bookmarklet();'>x</div></a>";
div.innerHTML = content;
document.body.appendChild(div);
Just check for the existence of div before creating it.
var div = document.getElementById("bookmarklet");
if (!div)
{
div = document.createElement("div");
div.id = "bookmarklet";
div.style.margin = "auto";
div.style.position = "fixed";
}
Also, since you already have a global reference to div, you don't need to search for it by id in toggle_bookmarklet. You can just reference div. I'd try to choose a more unique name though, so as to avoid running in to naming collisions.
Edit: For that matter, if you are going to use a global variable, you can simplify further. Don't even bother giving it an id, just use the global reference:
function toggle_bookmarklet() {
bookmarkletEl.style.display = bookmarkletEl.style.display == "none" ? "" : "none";
}
if (!window.bookmarkletEl) {
var bookmarkletEl = ddocument.createElement("div");
bookmarkletEl.style.margin = "auto";
bookmarkletEl.style.position = "fixed";
}
Yes, I've searched high and low on Stack Overflow and seen some great solutions to this problem that's been solved time and time again with things like SimpleModal, jQuery.confirm and the like.
Problem is, I am developing for this low level device that doesn't allow for a JS framework to be utilized AND I am having to shoehorn this modal confirm into existing JS.
There is an existing script that I am at liberty to edit (but not rewrite) that does a few things like validate, concatenate a few inputs into a single variable, and more.
The script was written to:
Take some session variables and assign new variable names to them and format accordingly
Present a confirm to the user to see whether they want to use those variables to pre-populate the form on the page
Get some functions ready to validate inputs.
other stuff, like offer an abandonment scenario, among other things
Now, all was good when the "confirm" was in place as the script would pause until an OK or Cancel was provided. I am now presenting a modal on the page that I want to mock this behavior and the only way I can think of doing it is to remove that reliance on the line that goes through the confirm thing and NOT run the script until the user interacts with the modal.
Does anyone have an idea how to take what's in place and "wrap" it in a "listening" if/else scenario for each of the YES or NO possibilities?
Sorry if this is jumbled... my brain is all blended up at the moment, too.
As far as I know there is - so far - no way to halt scripts like the Browser specific alert() or confirm() Dialog does.
Frameworks like dojo for example try to mock this behaviour by putting a transparent DIV over the whole window to prevent clicks or other input while the Dialog is showing.
This is quite tricky as I have experienced, since Keyboard-Input may be able to activate Input Fields or Buttons behind this curtain. Keyboard Shortcuts or Field-Tabbing for example.
One sollution is to disable active Elements manually, which works quite well with me in most cases.
One or more function is passed to this "mock" Dialog to execute when an option was chosen.
Escpecially with ajax background activity the responsibilty to stop conflicting function calls while the Dialog is open lies with the developer.
Here is an example I came up with:
<html>
<head>
<title>Modal Dialog example</title>
<script type="text/javascript">
<!--
var ModalDialog = function(text,choices){
this._text = text;
this._choices = choices;
this._panel = null;
this._modalDialog = null;
this._disableElements = function(tag){
var elements = document.getElementsByTagName(tag);
for(i=0; i < elements.length; i++){
elements[i].disabled = true;
}
};
this._enableElements = function(tag){
var elements = document.getElementsByTagName(tag);
for(i=0; i < elements.length; i++){
elements[i].disabled = false;
}
};
this._disableBackground = function(){
if(this._panel){
this._panel.style.display = 'block';
}
else{
// lower the curtain
this._panel = document.createElement('div');
this._panel.style.position = 'fixed';
this._panel.style.top = 0;
this._panel.style.left = 0;
this._panel.style.backgroundColor = 'gray';
this._panel.style.opacity = '0.2';
this._panel.style.zIndex = 99; // make sure the curtain is in front existing Elements
this._panel.style.width = '100%';
this._panel.style.height = '100%';
document.body.appendChild(this._panel);
// Disable active Elements behind the curtain
this._disableElements('INPUT');
this._disableElements('BUTTON');
this._disableElements('SELECT');
this._disableElements('TEXTAREA');
}
};
this.close = function(){
// Hide Curtain
this._panel.style.display = 'none';
// Hide Dialog for later reuse - could also be removed completely
this._modalDialog.style.display = 'none';
// reactivate disabled Elements
this._enableElements('INPUT');
this._enableElements('BUTTON');
this._enableElements('SELECT');
this._enableElements('TEXTAREA');
};
this.open = function(){
var _this = this;
this._disableBackground();
if(this._modalDialog){
this._modalDialog.style.display = 'block';
}
else{
// create the Dialog
this._modalDialog = document.createElement('div');
this._modalDialog.style.position = 'absolute';
this._modalDialog.style.backgroundColor = 'white';
this._modalDialog.style.border = '1px solid black';
this._modalDialog.style.padding = '10px';
this._modalDialog.style.top = '40%';
this._modalDialog.style.left = '30%';
this._modalDialog.style.zIndex = 100; // make sure the Dialog is in front of the curtain
var dialogText = document.createElement('div');
dialogText.appendChild(document.createTextNode(this._text));
// add Choice Buttons to the Dialog
var dialogChoices = document.createElement('div');
for(i = 0; i < this._choices.length; i++){
var choiceButton = document.createElement('button');
choiceButton.innerHTML = this._choices[i].label;
var choiceAction = _this._choices[i].action
var clickAction = function(){
_this.close();
if(choiceAction)choiceAction();
};
choiceButton.onclick = clickAction;
dialogChoices.appendChild(choiceButton);
}
this._modalDialog.appendChild(dialogText);
this._modalDialog.appendChild(dialogChoices);
document.body.appendChild(this._modalDialog);
}
};
};
var myConfirm = function(text,okAction){
var dialog = new ModalDialog(text,[
{
label:'ok',
action : function(){
console.log('ok')
okAction();
}
},
{
label:'cancel'
}
]);
dialog.open();
};
-->
</script>
</head>
<body>
<form name="identity" action="saveIdentity.do">
<label>Firstname</label><input name="name" type="text"><br>
<label>Lastname</label><input name="name" type="text"><br>
<input type="button"
value="submit"
onclick="if(myConfirm('Do you really want to Commit?',function(){ document.forms['identity'].submit();}));">
</form>
</body>
</html>
In this code there is still an error concerning the availability of the stored choice-function (undefined) at execution time. The function variable is no longer available in the closure. If anyone has a sollution for this you are welcome to add to it.
Hope that comes near to what you need to know.
Updated version: fixed choiceAction undefined, added IE compatibility. Internet Explorer is one main reason to use this, since confirm() is now blocked by default.
<!doctype html>
<html><head>
<title>Modal Dialog example</title>
<script type="text/javascript"><!-- //http://stackoverflow.com/questions/4739740/yet-another-confirm-replacement-quesiton
var ModalDialog = function(text,choices) {
this._text = text;
this._choices = choices;
this._panel = null;
this._modalDialog = null;
this._disableElements = function(tag) {
var elements = document.getElementsByTagName(tag);
for(i=0; i < elements.length; i++) {
elements[i].disabled = true;
}
};
this._enableElements = function(tag) {
var elements = document.getElementsByTagName(tag);
for(i=0; i < elements.length; i++) {
elements[i].disabled = false;
}
};
this._disableBackground = function() {
if(this._panel) {
this._panel.style.display = 'block';
}
else {
// lower the curtain
this._panel = document.createElement('div');
this._panel.style.position = 'fixed';
this._panel.style.top = 0;
this._panel.style.left = 0;
this._panel.style.backgroundColor = '#000';
this._panel.style.opacity = '0.3';
this._panel.style.filter = 'alpha(opacity=30)'; //ie7+
this._panel.style.zIndex = 99; // make sure the curtain is in front existing Elements
this._panel.style.width = '100%';
this._panel.style.height = '100%';
document.body.appendChild(this._panel);
// Disable active Elements behind the curtain
this._disableElements('INPUT');
this._disableElements('BUTTON');
this._disableElements('SELECT');
this._disableElements('TEXTAREA');
}
};
this.close = function() {
// Hide Curtain
this._panel.style.display = 'none';
// Hide Dialog for later reuse - could also be removed completely
this._modalDialog.style.display = 'none';
// reactivate disabled Elements
this._enableElements('INPUT');
this._enableElements('BUTTON');
this._enableElements('SELECT');
this._enableElements('TEXTAREA');
};
this.open = function() {
var _this = this;
this._disableBackground();
if(this._modalDialog) {
this._modalDialog.style.display = 'block';
}
else {
// create the Dialog
this._modalDialog = document.createElement('div');
this._modalDialog.style.position = 'absolute';
this._modalDialog.style.backgroundColor = 'white';
this._modalDialog.style.border = '1px solid black';
this._modalDialog.style.padding = '16px';
this._modalDialog.style.top = '35%';
this._modalDialog.style.left = '30%';
this._modalDialog.style.zIndex = 100; // make sure the Dialog is in front of the curtain
var dialogText = document.createElement('div');
dialogText.style.padding = '0 10px 10px 0';
dialogText.style.fontFamily = 'Arial,sans-serif';
dialogText.appendChild(document.createTextNode(this._text));
// add Choice Buttons to the Dialog
var dialogChoices = document.createElement('div');
for(i = 0; i < this._choices.length; i++) {
var choiceButton = document.createElement('button');
choiceButton.style.marginRight = '8px';
choiceButton.name = i;
choiceButton.innerHTML = this._choices[i].label;
var clickAction = function() {
_this.close();
if(_this._choices[this.name].action) _this._choices[this.name].action();
};
choiceButton.onclick = clickAction;
dialogChoices.appendChild(choiceButton);
}
this._modalDialog.appendChild(dialogText);
this._modalDialog.appendChild(dialogChoices);
document.body.appendChild(this._modalDialog);
}
};
};
var myConfirm = function(text,okAction){
var dialog = new ModalDialog(text,[
{
label : 'OK',
action : function() {
console.log('ok');
okAction();
}
},
{
label : 'Cancel'
}
]);
dialog.open();
};
-->
</script>
</head>
<body>
<form name="identity" action="saveIdentity.do">
<label>Firstname</label><input name="name" type="text"><br>
<label>Lastname</label><input name="name" type="text"><br>
<input type="button" value="submit"
onclick="if(myConfirm('Do you really want to Commit?',function(){ alert('submitted') }));">
<!-- document.forms['identity'].submit(); -->
</form>
</body>
</html>