Javascript: assign onclick inside class - javascript

I created a constructor that will handle a custom list control. I created a method in order to allow the user to add elements to the list, and I need to assign event handlers to the click events of the list elements (divs).
A simplified version of the code is here. The list elements are created using the innerHTML property and a string template upon which I substitute specific parts. Later I get the element by it's id and assign it a function in closure:
function prueba(){
var plantilla = '<div id="«id»">«texto»</div>';
var f = function(nombre){
return function(){console.log('mi nombre es ' + nombre)};
};
this.agregar = function(id, texto){
var tmp = plantilla.replace('«id»', id);
tmp = tmp.replace('«texto»', texto);
document.body.innerHTML += tmp;
document.getElementById(id).onclick = f(id);
};
};
The problem is that, apparently, the event handler is unasigned to previous created divs, so is only retained by the last one, as it can be tested with the following code:
var p = new prueba;
p.agregar('i1', 'texto1');
console.log(document.getElementById('i1').onclick.toString());//shows the function code
p.agregar('i2', 'texto2');
console.log(document.getElementById('i2').onclick.toString());//shows the function code
console.log(document.getElementById('i1').onclick.toString());//returns 'null' error
p.agregar('i3', 'texto3');
console.log(document.getElementById('i3').onclick.toString());//shows the function code
console.log(document.getElementById('i2').onclick.toString());//returns 'null' error
This happens in Iceweasel as well as in Chromium. It does NOT happen when I add 'onclick = f(«id»)' in the template (which I cannot do here because of the assigned function scope), and neither happens if I use document.createElement. What am I doing wrong?

You destroy elements previously created when you do this:
document.body.innerHTML += tmp;
Instead use insertAdjacentHMTL() if you want to append using HTML markup.
document.body.insertAdjacentHTML("beforeend", tmp);
Now instead of going through this destructive process...
serialize the existing DOM nodes to HTML
concatenate the new HTML fragment to the serialized nodes
destroy the old nodes
recreate the nodes with the new nodes
...it simply creates the new content and places it before the close of the body element.
Basically, remove element.innerHTML += ... from your coding practices. It's never necessary, it's inefficient and it causes problems like what you've described.
FYI, the .insertAdjacentHTML() method receives 4 different string possibilities as the first argument. Each one designates a position relative to the element on which you're calling it.
The strings are...
"beforebegin"
"afterbegin"
"beforeend"
"afterend"
The labels are pretty self-explanatory. They position the new content before the current element, inside the current element at the beginning, inside the current element at the end, or after the current element, respectively.
Your full code will look like this, which I shortened a bit too since the tmp really isn't needed here:
function prueba(){
var plantilla = '<div id="«id»">«texto»</div>';
var f = function(nombre){
return function(){console.log('mi nombre es ' + nombre)};
};
this.agregar = function(id, texto){
document.body.insertAdjacentHTML("beforeend",
plantilla.replace('«id»', id)
.replace('«texto»', texto));
document.getElementById(id).onclick = f(id);
};
};

Related

How do I copy an HTML document and edit the copy based upon the selection, without altering the original document?

I have an HTML document, and I would like to remove some of the tags from it dynamically using Javascript, based on whether the tags are within the current selection or not. However, I do not want to update the actual document on the page, I want to make a copy of the whole page's HTML and edit that copy. The problem is that the Range object I get from selection.getRangeAt(0) still points to the original document, as far as I can see.
I've managed to get editing the original document in place with this code:
var node = window.getSelection().getRangeAt(0).commonAncestorContainer;
var allWithinRangeOfParent = node.getElementsByTagName("*");
for (var i=0, el; el = allWithinRangeParent[i]; i++) {
// The second parameter says to include the element
// even if it's not fully selected
if (selection.containsNode(el, true) ) {
el.remove();
}
}
But what I want to do is to somehow perform the same operation with removing elements, but remove them from a copy of the original HTML. I've made the copy like this: var fullDocument = $('html').clone(); How could I accomplish this?
Either dynamically add a class or data attribute to all your elements on load before you clone so that you have a point of reference then grab the class or data attribute on the common ancestor and remove it from the clone. I can give an example if you like? Along these lines - http://jsfiddle.net/9s9hpc2v/ isn't properly working exactly right but you get the gist.
$('*').each(function(i){
$(this).attr('data-uniqueId', i);
});
var theclone = $('#foo').clone();
function laa(){
var node = window.getSelection().getRangeAt(0).commonAncestorContainer;
if(node.getElementsByTagName){
var allWithinRangeOfParent = $(node).find('*');
console.log(allWithinRangeOfParent, $(allWithinRangeOfParent).attr('data-uniqueId'));
$.each(allWithinRangeOfParent, function(){
theclone.find('[data-uniqueId="'+$(this).attr('data-uniqueId')+'"]').remove();
});
console.log(theclone.html());
}
}
$('button').click(laa);

Find DOM element in text content using javascript

Please push me towards a duplicate of this question if possible. I've looked everywhere but can't seem to find it.
How do I do a getElementById on text content?
var test = '<div id="thisDiv">HI</div>';
How do I select thisDiv if it's not a part of the DOM?
Create an element, set your string as its innerHTML and then search inside that ...
Something like
var test = '<div id="thisDiv">HI</div>';
var element = document.createElement('DIV');
element.innerHTML = test;
alert(element.textContent);
(changed the initial outerHTML as you can only maintain a reference to the originaly created element, and not the new one that is created by the new html)
For getting the text value inside your tags, use RegEx:
var re = new RegExp("<(.|\n)*?>", "g");
var content = test.replace(re,"");
You could create a temporary DIV and populate it with your string. Even then, your ability to access it would be limited. Add it to the DOM to access it using getElementById.
var div = document.createElement('div');
div.innerHTML = '<div id="thisDiv">HI</div>';
alert(div.firstChild);
To avoid creating that extra element, we could just add it to the body...
var test = '<div id="thisDiv">HI</div>';
document.querySelector('body').innerHTML = test;
console.log(document.getElementById('thisDiv'));
Obligatory Fiddle
Getting just the text...
console.log(document.getElementById('thisDiv').textContent); // returns HI

Why does innerHTML not change src of an image?

I have to set one src to an image object. Then I change it.
But if I add something to the element (content of element), such as
meaning.innerHTML += ")";
(where meaning is parent element of image), then if change the src of object it won't affect the document.
Example: http://jsfiddle.net/WcnCB/3/
Could you explain me why it happens, and how to fix it?
meaning.innerHTML += ')'; does more than you think. Visually it just appends a ) character, but behind the scenes what happens is:
meaning.innerHTML = meaning.innerHTML + ')';
So, you're first converting the DOM to a string representation (HTML), then adding a ) character, and finally have convert it back from HTML to the DOM. All elements the HTML represents are created again, and meaning is replaced by those new elements. So your old one is distroyed.
The simplest solution is to use createTextNode: http://jsfiddle.net/WcnCB/4/.
meaning.appendChild(document.createTextNode(")"));
By writing innerHTML += ... you are overwriting the previous HTML and destroying every reference to it - including the actual_button variable.
Why are you using innerHTML += ... anyway? You should be doing:
meaning.appendChild(document.createTextNode("(Something"));
When you do the greatest sin of all, that is .innerHTML += (specifically innerHTML combined with +=, neither of them are bad alone), what happens is:
Serialize the element's DOM subtree into a html string.
Concatenate some stuff into that html string
Remove all elements from the target element
Parse the html resulted above into a new DOM subtree. This means all the elements are new.
Append that into the target element
So given this, actual_button refers to a detached dom element. Not to the another img element created from parsing html.
Works if you set the image ID and get it after changing innerHTML :
var meaning = document.getElementById('meaning');
meaning.innerHTML += 'Something ...';
var actual_button = document.createElement('img');
actual_button.id = 'actual_button';
actual_button.src = 'http://www.pawelbrewczynski.tk/images/add.png';
actual_button.className = 'add_word';
meaning.appendChild(actual_button);
meaning.innerHTML += " ... and another.";
var actual_button= document.getElementById('actual_button');
actual_button.src = 'http://www.pawelbrewczynski.tk/images/loading.gif';
http://jsfiddle.net/j8yEG/1/

how to get children elements of an html element

I have a var saved in my JS custom object (code below is from inside the class):
var toolbar;
this.create_toolbar = function() {
self.toolbar = document.createElement('div');
$(self.toolbar)
.html('<ul>' +
'<li id="insert_bold"></li>' +
'<li id="insert_em"></li>' +
'<li id="insert_hyperlink"></li>' +
'<li id="insert_code"></li>' +
'<li id="insert_image"></li>' +
'</ul>')
.insertBefore(self.editTextarea); // just a textarea on the page
}
The toolbar gets created and placed successfully, and self.toolbar now holds the object for the new div. However, when trying to bind the <li>'s to clicks I can't seem to get anything to happen:
$(self.toolbar).children("li#insert_bold").click(function() {
alert("just checking");
});
To be clear, after the above, nothing is happening when I click <li id="insert_bold">
Is there something that I am missing? I am fairly new to jQuery....am I allow to put a variable that holds an [object object] in the $()? If not, how should I do this?
You need to use .find() here, like this:
$(self.toolbar).find("li#insert_bold").click(function() {
alert("just checking");
});
.children() only looks at immediate children, <li> is beneath the <ul> though, so it's not an immediate child.
Be aware though if you intend on having multiple instances of this on a page (I'm guessing this is likely the case), you should use classes instead (IDs need to be unique), then use a class selector, for example: $(self.toolbar).find("li.insert_bold").
While Nick has solved your question, I don't think this is a good idea, especially if you have a large number of HTML elements binding.
When I have a rather small number, I do gather the items in an object inside the main constructor function
this.el = {
$button : $('.button','#div'),
$submit : $('.submit','#div')
};
Then when I need an element, I just call it with (this), assuming you are using prototype functions.
this.el.$button.click(function () {
});
If you many elements to deal with, 20 or more, I'll recommend that you opt for a JavaScript FrameWork. Backbone Js is a good one.

How can I inject a string of HTML into an element?

Using Mootools, we can inject an element into another element:
$('childID').inject($('parentID'), 'top');
The second parameter allows me to control the location and can either be 'top' or 'bottom' to inject it into a parent object or 'before' or 'after' to inject it as a sibling.
We can also set the HTML of an element from a string:
var foo = "<p>Some text</p>";
$('parentID').set('html', foo);
My problem is that I want to have the same flexibility with strings as I do with elements. I can't, for example, put a string at the top of an element using set() as this overwrites the HTML rather than appending it at a specific location. Similarly, I can't append HTML after or before a sibling element.
Is there a function that will allow me to inject strings in the same way as I inject elements?
Insert at bottom:
foo.innerHTML = foo.innerHTML + 'string';
Insert at top:
foo.innerHTML = 'string' + foo.innerHTML;
Best Solution
The inject method will look like this:
inject: function(element, location) {
var el = Elements.from(this);
if($type(el) === 'array') var el = el.reverse();
return el.inject(element, location);
}
Let's break this into parts.
1) Elements.from(this) will take whatever the method is applied to and convert it into elements:
var foo = "<p>Some text</p>";
var el = Elements.from(foo);
//el is equal to a p element.
var bar = "<div>First div</div><div>Second div</div>";
var el = Elements.from(bar);
//el is equal to an array containing 2 div elements
2) if($type(el) === 'array') checks if el is an array. If it is then it applies .reverse() to el. This is necessary to inject the elements in the correct order. Otherwise they would inject with, for example, the second div first and the first div second. Obviously if el is just a single element, we don't need to change its order.
3) Finally, we just use the original inject method to inject el into the element specified in the element parameter to the location specified in the location parameter. If el is an array of elements, they will all get injected just fine.
To be able to use this function, we have to add it as a method on string objects. To do this you have to use implement():
String.implement({
inject: function(element, location) {
var el = Elements.from(this);
if($type(el) === 'array') var el = el.reverse();
return el.inject(element, location);
}
});
This will allow you to use the inject function on any variable containing a string. Make sure you don't put this inside the domready event i.e. Before window.addEvent('domready', function() { ... });
Now the inject function itself will look like this:
var foo = "<p>Some text</p>";
foo.inject($('parentID'), 'top');
This will create the p element and inject it at the top of parentID.
Alternative Solution
If you just wish to use inject with the 'top' and 'bottom' locations, you can use this inject method instead:
inject: function(element, location) {
var html = element.get('html')
if(location === 'top') return element.set('html', this + html);
else if (location === 'bottom') return element.set('html', html + this);
}
This method will get the innerHTML of the element you need to convert and either concatenate the string with that HTML or the HTML with that string, placing the string at the top or the bottom of the element respectively. The element's innerHTML is then set to this value.
The advantage of this method is that as long as the innerHTML of the element isn't too great, this is likely to be faster as we don't need to create new elements which could be time-consuming if the string contains many top-level sibling elements. Obviously if this situation were reversed (few top-level siblings and small innerHTML), the speed advantage would also be reversed (I haven't tested the speed difference so this is just an educated guess and might be negligible).
The disadvantage, however, is that we can't easily use it with the 'after' and 'before' locations.
You're looking for appendText. Example similar to the Mootools docs:
http://mootools.net/docs/core/Element/Element#Element:appendText
HTML
<div id="myElement">partner.</div>
JavaScript
$('myElement').appendText('Howdy, ', 'top');
The second (where) argument defaults to 'bottom' but also accepts 'top', 'bottom', 'before' and 'after'.
Resulting HTML
<div id="myElement">Howdy, partner.</div>
Working example:
http://jsfiddle.net/hq5Gr/
Try this:
var foo = "<p>Some text</p>"
$('parentID').set('html', foo + $('parentID').get('html')); // prepend/top
$('parentID').set('html', $('parentID').get('html') + foo)); // append/bottom
couple of things you ought to look at that may help.
First off, slick and mootools 1.3 offer a "nicer" new Element constructor which can add and configure elements from pseudo string markup very nicely:
http://www.jsfiddle.net/dimitar/aQvpb/
new Element('div#myId.myClass.myOtherClass[title=Mouseover Title][text=Dimitar Was Here]').injectAfter(document.id("foo"));
new Element("input#someID.someClass1.someClass2[disabled=true]");
second of all, element.injectAfter(previousEl) and element.injectBefore(followingEl) can also be helpful in injecting somewhere after or before a particular node.
totally do NOT append html by rewriting old html or any events the elements have that are not delegated will be gone (new UIDs)
and you can use Slick with older versions of mootools as well although I can't find the gist for that atm, post here if you're interested. the currently nightly is fairly stable but 1.3 release is due shortly.
you can use insertAdjacentHTML()
const divInString = '<div class="todo">Stuff</div>'
const parentOfDiv = document.querySelector(".parent")
parentOfDiv.insertAdjacentHTML("beforeEnd",divInString)
// "afterEnd , "beforeBegin","afterBegin"
// I was looking for a solution for this problem as well and
// this is what solved my issue.
You want to use text nodes.
To append text to an element:
var yourTextNode = element.appendChild(document.createTextNode("some text"))
To prepend text to an element:
var yourTextNode = element.parentNode.insertBefore(document.createTextNode("some text"), element)
To change the value of the text node, you'd do yourTextNode.nodeValue = "new value here".
#shanebo's answer is close but appendText escapes HTML.
Try:
http://mootools.net/core/docs/1.5.1/Element/Element#Element:appendHTML
$('myElement').appendHTML('<div>Hello world</div>', 'top');
Fiddle: http://jsfiddle.net/m0ez1t50/1/

Categories