event when adding element into document - javascript

Adding new html string into the page:
document.getElementById('container').innerHTML = '<div id="child"></div>';
Is there an event that let me know when child element is in the document?
I have a function, which return some html codeas a string. And when this html will be added in the document, I need to execute javascript function. I've tried to use inline onload event
document.getElementById('container').innerHTML = '<div id="child" onload="console.log(\'ready!\');"></div>';
but it does not seem to work.
UPDATE:
Probably, I should provide more details about the situation. I have a library function
myLibrary.getHtml()
In old version, users just call this function and append the result into the document:
document.getElementById('container').innerHTML = myLibrary.getHtml();
In new version, the result is not a plain html. Now users can interact with it after they append it in the document. So after they append html into the document, I need to go through the result DOM and attach event handlers, hide some elements and other things. That is why, I need to know when they add it in the document and execute a javascript function, which turn plain html into fancy interactive widget.

You could try using DOM Mutation Events, but they are still inconsistently implemented across browsers.

If i have not misunderstood the question, you can probably get this to work
document.getElementById('id').childNodes;

If you can use jQuery, you could write your own custom event which would call the function you need to call whenever the new html has been added:
$('#container').bind('changeHtml', function(e) {
// Execute the function you need
});
And of course instead of just adding the html to the element, you would need to wrap it up in a function which triggers your custom event. Then all you'd need to do is call this function instead of setting the innerHtml yourself.
function addHtml(html) {
$('#container').innerHTML = html;
$('#container').trigger('changeHtml');
}

Solved by myself this way:
myLibrary.getHtml = function() {
var html;
...
var funcName = 'initWidget' + ((Math.random() * 100000) | 0);
var self = this;
window[funcName] = function() { self._initWidget(); };
return html + '<img src="fake-proto://img" onerror="' + funcName + '()"';
}
The idea behind the code is that I specify incorrect url for the src attribute of the img tag and the image triggers error event and call my function. Quite simple :)

Related

Adding Click Event Listeners During Iteration?

I'm trying to add a click event listener to a bunch of newly created divs in a for loop. The issue I'm having is that only the last div keeps its event listener. I've read up about closures and read several other posts and questions and their answers, and as far as I can tell I have it set up correctly. But it still isn't working for me. Only the final div to be iterated is receiving the event listener.
function edit_entry(k) {
wrd_input.value = k;
def_input.value = lexicon[k][1];
wrd_input.onkeyup();
delete lexicon[k];
rewrite_entries();
}
function rewrite_entries(keys = null) {
if (keys === null) { keys = []; }
let sorted_keys = sort_lex_keys();
lex_body.style.color = 'rgb(200, 200, 200)';
lex_body.innerHTML = '';
sorted_keys.forEach((key) => {
if (!keys.length || keys.includes(key)) {
lex_body.innerHTML +=
`<div class='lex-entry' id=${key}><i>${key}</i>\n<p class='pronunciation'>${lexicon[key][0]}</p>${lexicon[key][1]}</div>\n`;
let entry = document.getElementById(key)
entry.addEventListener('click', edit_entry.bind(this, key) );
}
});
}
Current state of the relevant code above. If somebody knows the issue, it'd be very helpful.
If it's relevant, this code is running via Electron (17.0.0).
An attempted solution, using an anonymous arrow function instead of a bind:
entry.addEventListener('click', () => edit_entry(key) );
yields the same result.
Update:
Changing the .innerHTML attribute of something apparently strips all event listeners. So the solution was simply to create the element completely in js, add it to the container, and then add the listener. This change to the forEach loop solves the problem:
sorted_keys.forEach((key) => {
if (!keys.length || keys.includes(key)) {
let entry = document.createElement('div');
entry.className = 'lex-entry';
let word = document.createElement('p');
word.appendChild( document.createTextNode(key) );
word.style.fontStyle = 'italic';
let pron = document.createElement('p');
pron.className = 'pronunciation';
pron.appendChild( document.createTextNode(lexicon[key][0]) );
let defn = document.createTextNode(lexicon[key][1]);
entry.append(word, pron, defn);
entry.addEventListener('click', () => edit_entry(key) );
lex_body.appendChild(entry);
So a few things crosses my mind:
Firstly, edit_entry.bind(this, key);: Are you certain this is pointing to what you want? It should be pointing to the window object. Also, is there any reason to bind context for that particular function?
Secondly innerHTML: Generally it's discouraged to use innerHTML directly. I don't know how/when the browser layouts changes to innerHTML, the div might not be on the page when you call getElementById. As an alternative, you could try document.createElement("div"), set its properties accordingly and finally append it to lex_body.
Edit
From mdn:
Please note that using innerHTML to append html elements (e.g. el.innerHTML += "link") will result in the removal of any previously set event listeners. That is, after you append any HTML element that way you won't be able to listen to the previously set event listeners.
I think that binding the function every time with this is overriding the binding every time, that should be the reason that you are only getting the last one's event.
Inside of the forEach lambda, the this keyword refers to the internal class that is calling the forEach not your rewrite_entries function. Try not binding the call, like so:
entry.addEventListener('click', () => edit_entry(key) );

Best way to attach an arbitrary callback function to a dom element?

The following (vanillajs) code works fine
// library code:
let close_cb; // nasty global var...
...
let tree = document.createElement('ul');
tree.addEventListener('click', function(event) {
...
// let close_cb = tree.getAttribute('CLOSE_CB');
// let close_cb = tree.onchange;
close_cb(leaf.id, ', so there');
// user code:
function my_close_cb(id, msg) {
const footer = document.querySelector('footer');
footer.innerHTML = id + ' is closed' + msg;
}
// tree.setAttribute('CLOSE_CB',my_close_cb);
// tree.onchange = my_close_cb;
close_cb = my_close_cb;
However the commented-out s/getAttribute code fails, getAttribute puts full text of "function my_close_cb(..." in local close_cb.
The commented-out onchange hack actually works, but feels terribly dodgy to say the least, although it is certainly closer to what I'm after.
Note the "library code" is hand written and fully under my control, whereas "user code" is intended to be transpiled or otherwise machine-generated, so changing my_close_cb to accept a single event argument would be a complete non-starter.
What is the best way to attach an arbitrary callback function that accepts an arbitrary set of parameters to a dom element?
You can attach a plain json property to the DOM element.
document.body.callback = function cb(text) { console.log(text); };
document.body.callback("hello world");
Using tree.close_cb or any other property to store a function or anything else is totally fine, as the DOM is just a (persistent) tree of JavaScript objects. As such they behave like any other JS object, and properties can be added without any restrictions.

Unable to access elements in HTMLCollection from getElementsByClassName

I would like to get an element from an HTMLCollection. The return of document.getElementsByClassName is exactly what I was expecting but when I try to access any attributes of it, it looks like there is nothing there. Here is my code (this code is run in a .js file that I src into my index.html):
document.addEventListener('DOMContentLoaded', function () {
var code = document.getElementsByClassName('CodeMirror-code');
console.log(code);
console.log(code[0]); //undefined
console.log(code.length); //0
}
and here are the console logs :
HTMLCollection(1)
0: div.CodeMirror-code //this is the div I want to access
length: 1
__proto__: HTMLCollection
undefined
0
also, If I enter in the console:
var code = document.getElementsByClassName('CodeMirror-code');
code[0]
I get the return:
<div class="CodeMirror-code">...</div>
which is exactly what I am looking for, but that is not the result I get in the script.
CodeMirror adds it's various DOM elements to the DOM after the CodeMirror() constructor is called or after CodeMirror.fromTextArea() is called.
So you can't simply wait for the various DOM ready events in order to find the element you are looking for. You can either pass the constructor a function which you can then manually add the editor to the DOM and then do a search. Or setup a custom CodeMirror event listener.
CodeMirror initialization hook
CodeMirror.defineInitHook(function(cmInstance){
var codeDiv = document.querySelector('.CodeMirror-code');
console.log(codeDiv);
});
var myCodeMirror = CodeMirror(document.body);
CodeMirror manual adding
var myCodeMirror = CodeMirror(function(editor){
//add the editor to the DOM
document.body.appendChild(editor);
var codeDiv = document.querySelector('.CodeMirror-code');
//either of these will work
var codeDiv = editor.querySelector('.CodeMirror-code');
console.log(codeDiv);
});
Demo
CodeMirror.defineInitHook(function(cmInstance){
DoWork( document.querySelector('.CodeMirror-code') );
});
var myCodeMirror = CodeMirror(document.body);
function DoWork(codeDiv){
console.log(codeDiv);
}
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.38.0/codemirror.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.38.0/codemirror.min.js"></script>
You might be doing this code before the elements have been rendered on the screen. That's why when you do it in the console it works.
Here are two options:
Try changing the js code to happen onload of body. If you don't know what onload is check out this: https://www.w3schools.com/Jsref/event_onload.asp
Try changing the js code to happen when the DOMContentLoaded listener comes up. Learn about how that works here: https://developer.mozilla.org/en-US/docs/Web/Events/DOMContentLoaded

Javascript function not called on dynamically created button in C#

I have created button in C# dynamically and added client side event
but that function not getting called instead getting error as:
Uncaught ReferenceError: setPropertyLocation is not defined
Javascript:
function setPropertyLocation() {
alert('Hello');
}
C#:
btnMap.Attributes.Add("type", "button");
btnMap.Attributes.Add("title", "Map");
btnMap.UseSubmitBehavior = false;
btnMap.OnClientClick = "setPropertyLocation();return false";
btnMap.ID = "btnMap" + objPMPropTypestructure.PMFields[fieldcnt].SystemName;
btnMap.CssClass = "dataButton";
btnMap.Text = "G";
btnMap.Enabled = true;
tablecell.Controls.Add(btnMap);
//Try to use this.
document.addEventListener('DOMContentLoaded', function () {
document.getElementById ("btnMap").addEventListener ("click", setPropertyLocation, false);
});
Consider creating this button via JavaScript instead of C#. I assume you are using an update panel. Update panels are a mess. They don't update the DOM elements, they just get rid of the old wrapper and create an entire new, what sets to undefined all JavaScript references to any element in that wrapper.
Well, some times we don't have time to refactor the solution, so we must work with what we have.
You may try add an onclick attribute to the element, just like you did for type and title attribtues.

Identifying the anchor tag when href is called

How can you identify in a function whether it has been invoked by an anchor tag href?
The event is null in this case, so event.target and event.srcElement won't work.
Code
HTML
Href works here
JavaScript
function SomeFunction ()
{
// I need to get the anchor element that invoked this function
}
What about
Href works here
function SomeFunction(context) {
var callingElement = context;
}
Following what #alex suggested, can you add a script to run in the page load to change the hrefs to be what you want (adding the 'this' reference)?
Take the following script for example, this will change the href value for anchor tags with id set to SomeID or class set to SomeClass:
function changeLinks() {
var all_links = document.getElementsByTagName("a");
for (var i=0; i<all_links.length; i++){
if (all_links[i].id == 'SomeID' || all_links[i].className == 'SomeClass') {
all_links[i].href = 'SomeFunction(this);';
}
}
}
Hope this helps...
Edit:
Following your comment, you can try this:
var clickedAnchor = null;
function setClickedAnchor(obj) {
clickedAnchor = obj;
}
function changeLinks() {
var all_links = document.getElementsByTagName("a");
for (var i=0; i<all_links.length; i++){
if (all_links[i].id == 'SomeID' || all_links[i].className == 'SomeClass') {
all_links[i].href = 'setClickedAnchor(this);' + all_links[i].href;
}
}
}
As far as I know there are only two ways to accomplish this using standard Javascript. The first way has already been outlined -- pass the node as a parameter to the function. The other way would be to handle the onclick event of the anchor. This is very similar to the previous approach in that is requires that you modify the markup to pass a parameter to a function. To do this you'll need to change the markup to the following:
Href works here
function SomeFunction(event) {
var node = event.srcElement;
}
The above code would pass the event object along to the function which would give you all sorts of interesting information about the event.
If you're unable to change the markup that is sent to the browser, you might want to consider using something like JQuery or another AJAX library to search for and modify the event handlers of the nodes at runtime. Modifying the markup before it gets to the browser is obviously preferred, but sometimes you don't have a choice.
Lastly, if you cannot change the markup and don't want to modify the DOM at runtime, you can always see what Javascript engine specific features are available. For example, you might be able to make use of arguments.caller in those engines that support it. I'm not saying that it will work, but you might want to see what's available.

Categories