dynamically create element with onclick - javascript

I'm obviously missing something, but I haven't been able to find what I am doing wrong and I have been staring at this for entirely too long
function message(options) {
...
options.onclose = options.onclose || null;
...
this.gui = document.createElement('div');
this.msg = document.createElement('div');
...
if (options.onclose != null) {
var close = document.createElement('i');
close.innerHTML = 'close';
close.className = 'material-icons close';
close.onclick = options.onclose;
console.log(close.onclick);
this.msg.append(close);
}
this.msg.innerHTML += options.msg;
this.gui.append(this.msg);
...
return this.gui;
}
msgContainer.append(new message({
class: 'update',
sticky: true,
icon: 'mic',
msg: 'You are in a call',
onclose: () => { console.log('click'); }
}));
from the developer console document.querySelector('.close').onclick is null, but if I add an on click document.querySelector('.close').onclick = () => { console.log('click'); }; it works?

Why it wont work is because on click is a function:
document.querySelector('.close').onclick
doesn't do anything so why call it.
document.querySelector('.close').onclick = () {
alert("did something");
}
so the real question is what do you want to do when clicked? create a new link or div.. look below. I would start using jQuery.
jQuery answer:
$(document).ready(function(){
$(".myclass").click(function(){
$(".container_div").append("<a href='test.php'>test link</a>");
// also .prepend, .html are good too
});
});

Here is working example. I changed your code a little bit. You can add more events by passing it to an array. I used addEventListener.
var msgContainer = document.getElementById('msgContainer');
function message(options) {
options.onclose = options.onclose || null;
this.gui = document.createElement('div');
this.msg = document.createElement('div');
if (options.onclose != null) {
var close = document.createElement('i');
close.innerHTML = 'closepp';
close.className = 'material-icons close';
close.dataset.action = 'close';
this.msg.append(close);
}
this.msg.innerHTML += options.msg;
this.gui.append(this.msg);
// Create listeners dynamically later on
events = [
{ selector: close.dataset.action, eventType: 'click', event: options.onclose }
];
renderElement(this.gui, events);
}
function renderElement(element, events) {
msgContainer.append(element);
for (i = 0; i < events.length; i++) {
var currentEvent = events[i];
var selector = element.querySelector('[data-action="' + currentEvent['selector'] + '"]');
selector.addEventListener(currentEvent['eventType'], currentEvent['event'].bind(this), false);
}
}
new message({
class: 'update',
sticky: true,
icon: 'mic',
msg: 'You are in a call',
onclose: () => { console.log('click'); }
});
<div id="msgContainer">
</div>

I finally figured it out! setting innerHTML makes chrome rebuild the dom and in the process it loses the onclick event, onclick works fine if I use textContent instead of innerHTML. In the below example if you comment out the last line of JS the onclick works, here's the same thing in jsFiddle
var blah = document.getElementById('blah');
var div = document.createElement('button');
div.style['background-color'] = 'black';
div.style.padding = '20px;';
div.style.innerHTML = 'a';
div.onclick = () => { alert('wtf');};
blah.appendChild(div);
// Uncomment this to make onclick stop working
blah.innerHTML += ' this is the culprit';
<div id="blah">
</div>

Related

How to prevent modal event listener from affecting other items?

I'm currently working on a Library app where a user can track unread and read books. I have it setup where when you click a book a modal pops up with the title, author, and buttons allowing you to mark the book as read or completely delete it.
I'm trying to fix a problem where opening and closing more than one modal and then clicking the delete button will delete all the items you previously clicked.
Here's the delete function -
Book.prototype.delete = function() {
myLibrary = myLibrary.filter((e) => {
return e !== this;
});
};
Here's how I'm opening each modal -
const render = () => {
const booksUnreadList = document.getElementById('unread');
const booksReadList = document.getElementById('read');
booksUnreadList.innerHTML = 'Unread';
booksReadList.innerHTML = 'Read';
myLibrary.forEach((book) => {
const li = document.createElement('li');
li.className = 'book';
book.read === 'Read'
? booksReadList.appendChild(li)
: booksUnreadList.appendChild(li);
li.innerHTML = book.info();
li.addEventListener('click', function handler() {
openBookModal(book);
});
});
And then the modal itself -
function openBookModal(book) {
document
.getElementById('book-modal-mark-complete')
.removeEventListener('click', markReadHandler);
document
.getElementById('book-modal-delete')
.removeEventListener('click', deleteHandler);
bookForm.style.display = 'none';
toggleForm.style.backgroundColor = '#978de0';
toggleForm.innerHTML = 'Add Book';
const bookModal = document.getElementById('book-modal');
bookModal.style.display = 'grid';
document.getElementById('book-modal-title').innerHTML = book.title;
document.getElementById('book-modal-author').innerHTML = 'By ' + book.author;
document
.getElementById('book-modal-mark-complete')
.addEventListener('click', markReadHandler);
function markReadHandler() {
book.read = 'Read';
render();
bookModal.style.display = 'none';
}
document
.getElementById('book-modal-delete')
.addEventListener('click', deleteHandler);
function deleteHandler() {
book.delete();
render();
bookModal.style.display = 'none';
}
document.getElementById('book-modal-close').addEventListener('click', () => {
bookModal.style.display = 'none';
});
}
Here's a jsfiddle of everything, to recreate the problem just open and close 2+ books and then delete 1 of them.
https://jsfiddle.net/Spawn_Bot/w6j4b8Lh/4/
Thanks!
You could store the currently selected book in the currentBook variable, move the modal event handlers outside the openBookModal() function and in the event handlers you could delete the currentBook:
let currentBook = {};
...
function openBookModal(book) {
currentBook = book;
...
}
document
.getElementById('book-modal-mark-complete')
.addEventListener('click', markReadHandler);
function markReadHandler() {
currentBook.read = 'Read';
render();
bookModal.style.display = 'none';
}
document
.getElementById('book-modal-delete')
.addEventListener('click', deleteHandler);
function deleteHandler() {
currentBook.delete();
render();
bookModal.style.display = 'none';
}
Here's the demo: https://jsfiddle.net/k6z08m2q/

Remove dynamically created elements by class name Javascript

So, in plain terms I am creating a Chrome Extension that so far can only save links from the internet but not delete them. What I want to add is a "remove" button for deleting unwanted links. So far I haven't got that to work.
The buttons I want to remove are added using JavaScript. Each new block of HTML features a "remove" button but clicking that button does nothing. I have tried binding listeners to each element using a for loop but that doesn't seem to work.
The code runs without errors and I'm certain that the issue is a slight oversight but I have only just started using JavaScript so I'm lost for solutions at the moment.
I have included all the code because I don't want to leave out anything that might be imperative to finding a solution.
It starts with the code for adding a link, followed by removing a single link and then removing all links at once. Thank you all for any help, really want to get this working.
https://github.com/mmmamer/Drop Repository for the rest of the code. Mainly popup.html and popup.css.
var urlList = [];
var i = 0;
document.addEventListener('DOMContentLoaded', function() {
getUrlListAndRestoreInDom();
// event listener for the button inside popup window
document.getElementById('save').addEventListener('click', addLink);
});
function addLink() {
var url = document.getElementById("saveLink").value;
addUrlToListAndSave(url);
addUrlToDom(url);
}
function getUrlListAndRestoreInDom() {
chrome.storage.local.get({
urlList: []
}, function(data) {
urlList = data.urlList;
urlList.forEach(function(url) {
addUrlToDom(url);
});
});
}
function addUrlToDom(url) {
// change the text message
document.getElementById("saved-pages").innerHTML = "<h2>Saved pages</h2>";
var newEntry = document.createElement('li');
var newLink = document.createElement('a');
var removeButton = document.createElement('button');
removeButton.textContent = "Remove";
//removeButton.createElement('button');
removeButton.type = "button";
removeButton.className = "remove";
newLink.textContent = url;
newLink.setAttribute('href', url);
newLink.setAttribute('target', '_blank');
newEntry.appendChild(newLink)
newEntry.appendChild(removeButton);
newEntry.className = "listItem";
document.getElementById("list").appendChild(newEntry);
}
function addUrlToListAndSave(url) {
urlList.push(url);
saveUrlList();
//}
}
function saveUrlList(callback) {
chrome.storage.local.set({
urlList
}, function() {
if (typeof callback === 'function') {
//If there was no callback provided, don't try to call it.
callback();
}
});
}
// remove a single bookmark item
document.addEventListener('DOMContentLoaded', function() {
getUrlListAndRestoreInDom();
var allButtons = document.getElementsByClassName('remove');
function listenI(i) {
allButtons[i].addEventListener('click', () => removeMe(i));
}
for (var i = 0; i < allButtons.length; i++) {
listenI(i);
}
});
function removeMe(i) {
var fullList = documents.getElementsByClassName('listItem');
listItem[i].parentNode.removeChild(listItem[i]);
}
//remove all button
document.addEventListener('DOMContentLoaded', function() {
document.getElementById("remove-all").addEventListener('click', function() {
var removeList = document.getElementsByClassName("listItem");
while(removeList[0]) {
removeList[0].parentNode.removeChild(removeList[0]);
}
})
});
chrome.storage.local.get() is asynchronous. So when you try to add the event listeners to the Remove buttons, they're not in the DOM yet.
You can add the listener in the addUrlToDom() function instead. That way you'll also add the event listener when you create new buttons.
function addUrlToDom(url) {
// change the text message
document.getElementById("saved-pages").innerHTML = "<h2>Saved pages</h2>";
var newEntry = document.createElement('li');
var newLink = document.createElement('a');
var removeButton = document.createElement('button');
removeButton.textContent = "Remove";
//removeButton.createElement('button');
removeButton.type = "button";
removeButton.className = "remove";
newLink.textContent = url;
newLink.setAttribute('href', url);
newLink.setAttribute('target', '_blank');
newEntry.appendChild(newLink)
newEntry.appendChild(removeButton);
removeButton.addEventListener("click", function() {
var anchor = this.previousElementSibling;
var url = anchor.getAttribute("href");
removeUrlAndSave(url);
this.parentNode.remove();
});
newEntry.className = "listItem";
document.getElementById("list").appendChild(newEntry);
}
function removeUrlAndSave(url) {
var index = urlList.indexOf(url);
if (index != -1) {
urlList.splice(index, 1);
saveUrlList();
}
}

JavaScript Namespaces + Setter

Well, I'm using object orientation in JavaScript, but instead of using new, I just call the method from the namespace. In the moment, I have the following code:
var Component = {
Button: function(_text, use_image) {
button = $.createElement('button')
if (use_image != false)
{
button.innerHTML = _text;
}
else
{
button.innerHTML = _text
}
var text = _text
return button
}
}
When I want to return a button, I do:
x = Component.Button("Click me")
And after I can use x. But if I want to change the text of x, I must use x.textContent. I'd like to instantiate this and use a setter to apply its text, this way:
x = new Component.Button("Click me")
x.text = "Don't click me"
document.getElementsByTagName("body")[0].appendChild(x)
And if I try to apply the setter text, it becomes global, I want to have a unique for each button. Mix namespaces with get/set.
Thank you in advance
One way can be like, I am not sure if this is the best approach. Looking for more answers
var Component = {
Button: function(_text, use_image) {
var button = {
node : document.createElement('button'),
setText : function(txt){this.node.innerHTML = txt;}
};
if(_text){button.setText(_text);}
return button;
}
};
x = Component.Button("Click me");
x.setText("Don't click me");
document.getElementsByTagName("body")[0].appendChild(x.node);
y = Component.Button("Click me2");
y.setText("Don't click me2");
document.getElementsByTagName("body")[0].appendChild(y.node);
http://jsfiddle.net/2LCyn/
How about something like this:
var Component = (function($){
var buttonCount = 0;
function Button(text, use_image){
this.id = ++buttonCount;
this.text = text;
this.use_image = use_image;
this.$element = $('<button/>').text(text);
this.bindEvents();
}
Button.prototype.bindEvents = function(){
var that = this;
this.$element.on('click', function(){
console.log('hello, I\'m button ' + that.id);
});
};
Button.prototype.setText = function(text){
this.text = text;
this.$element.text(text);
};
return {
Button: Button
}
})(jQuery);
var button1 = new Component.Button('hello', false);
button1.setText('helloooo');
$('body').append(button1.$element);
var button2 = new Component.Button('world', true);
$('body').append(button2.$element);
http://jsfiddle.net/ATmAx/1/

Copy/Paste element with jQuery

I have a div that I'm appending to another div when a button is clicked. I'm also calling a bunch of functions on the div that gets created.
HTML
<a onClick="drawRect();">Rect</a>
JS
function drawRect(){
var elemRect = document.createElement('div');
elemRect.className = 'elem elemRect';
elemRect.style.position = "absolute";
elemRect.style.background = "#ecf0f1";
elemRect.style.width = "100%";
elemRect.style.height = "100%";
elemRect.style.opacity = "100";
renderUIObject(elemRect);
$('.elemContainer').draggableParent();
$('.elemContainer').resizableParent();
makeDeselectable();
handleDblClick();
}
var createDefaultElement = function() {
..
..
};
var handleDblClick = function() {
..
..
};
var renderUIObject = function(object) {
..
..
};
var makeDeselectable = function() {
..
..
};
I could clone the element when the browser detects a keydown event
$(window).keydown(function(e) {
if (e.keyCode == 77) {
$('.ui-selected').clone();
return false;
}
});
then append it to #canvas. But the problem is, none of the functions I mentioned above get called with this method.
How can I copy/paste an element (by pressing CMD+C then CMD+V) and call those above functions on the cloned element?
The jQuery.clone method returns the cloned node. So you could adjust your code to do something like this:
var myNodes = $('.ui-selected').clone();
myNodes.each(function () {
createDefaultElement(this);
appendResizeHandles(this);
appendOutline(this);
});

NotFoundError: DOM Exception 8 when substituting innerHTML

I'm new to js-development. I have the following code:
<html>
<body>
<div><span id="inline">Click here to start editing</span></div>
<script>
var inline = document.getElementById("inline");
inline.onclick = function() {
if (!inline.editable) {
var text = inline.innerText;
inline.innerHTML = "<input type='text' id='inline-editable'>";
inline.editable = true;
var inline_editable = document.getElementById("inline-editable");
inline_editable.value = text;
inline_editable.onblur = function() {
var value = inline_editable.value;
inline.editable = false;
inline.innerHTML = value;
}
inline_editable.onkeypress = function(event) {
if (event.keyCode == 13) {
inline_editable.onblur();
}
}
}
}
</script>
</body>
</html>
Which shows some text inside span and allows inline editing. When I finish editing within just onblur event it work perfectly fine. But if I want to terminate editing by Enter and use the same hander I get an error NotFoundError: DOM Exception 8 in this line:
inline.innerHTML = value;
Nevertheless everything works as I expect. Can anyone help me to avoid this error?
I assume that is happened because I destroy inline-editable element while event handling is not finished and it wants to invoke onchange maybe. Should I have 2 controls all the time an switch their visibility instead?
Problem here is the onblur is triggered twice, the second time, the element is not there which causes the problem. Kill the events
var inline = document.getElementById("inline");
inline.onclick = function() {
if (!inline.editable) {
var text = inline.innerText;
inline.innerHTML = "<input type='text' id='inline-editable'>";
inline.editable = true;
var inline_editable = document.getElementById("inline-editable");
inline_editable.value = text;
inline_editable.onblur = function() {
this.onblur = function(){};
var value = this.value;
inline.editable = false;
inline.innerHTML = value;
}
inline_editable.onkeypress = function(event) {
if (event.keyCode == 13) {
this.onblur();
}
}
}
}

Categories

Resources