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.
Related
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);
};
};
I know that the empty method removes all children in the DOM element.
In this example however, why does removing the empty method result in duplicate entries:
and putting it in results in a normal page:
var renderNotesList = function()
{
var dummyNotesCount = 10, note, i;
var view = $(notesListSelector);
view.empty();
var ul = $("<ul id =\"notes-list\" data-role=\"listview\"></ul>").appendTo(view);
for (i=0; i<dummyNotesCount; i++)
{
$("<li>"+ "" + "<div>Note title " + i + "</div>" + "<div class=\"list-item-narrative\">Note Narrative " + i + "</div>" + "" + "</li>").appendTo(ul);
}
ul.listview();
};
I don't know why empty() doesn't work but I found this
... so until this is sorted everyone should just use:
el.children().remove(); instead of el.empty();
( jQuery.empty() does not destroy UI widgets, whereas jQuery.remove() does (using UI 1.8.4) )
Without seeing how your JavaScript is being used in your page, I suspect that you must be calling the renderNotesList() function twice and thus generating to unordered lists.
When you use the .empty() method, you are removing the first ul list, so you only see one instance. Without the call to .empty(), you retain both.
However, I can't say where or how this is happening in you web page without seeing more, but at least you now have some idea of what to look for.
Demo Fiddle
I built a demo using your JavaScript, but I was sort of guessing as to how you are using it.
Fiddle: http://jsfiddle.net/audetwebdesign/UVymE/
Footnote
It occurred to me that the function ul.listview() may actually be appending a second copy of the ul to the DOM. You need to check the code or post it for further review.
I have a div that is dynamically populated with other divs...
<div id="wrapper">
</div>
//javascript
for(//a bunch of times){
var d = document.cloneNode('divModel');
d.foo = {//bunch of stuff };
document.getChildById('wrapper').appendChild(d) is applied later to fill in child divs
}
Now I would like to loop through the children of wrapper and do something with that foo.
When I do it via normal javascript
document.getElementById('wrapper').childNodes[x].foo
That works as I would expect
However
$('#wrapper').each(function (i, element){
console.log(element.foo); //foo here is undefined
})
results in a bunch of undefined
So... I'm guessing that jQuery is actually dealing with some internal wrapper to my divs rather than the divs directly. I'm curious how (or if it's possible) I can access the actual divs in this loop so as to access my custom foo.
EDIT
Based on all your answers so far (and thanks very much for the time) I feel like I need to clarify, cause everyone seems to be missing my actual question...
I've modified the code above to be more explicit.
I don't have a syntax error (in my actual code). jQuery cycles through the wrapper children correctly, it's just not returning the ACTUAL children (divs) it's returning its own version of them, and this custom version doesn't contain my appended foo.
So, what I need is to be able to get back to the actual dom object, not the jQuery wrapper around it. Is this possible?
You forgot the children (and the '#' as mentioned):
$('#wrapper').children().each(function (i, element)...
You're right exactly, except the selector is off. You need $("#wrapper").
$("#wrapper").each(function(i,e) { console.log(e.id) });
Edit
Confirming that it's possible to get an object back from a DOM element ...
var c = document.getElementById('container');
var el = document.createElement('div');
el.id = 'new';
el.innerText='the test element';
el.test = { hello: "world" };
c.appendChild(el);
console.log('javascript: ' + document.getElementById('new').test.hello);
$("#container div").each(function(i,e) {
if (e.test) console.log('jquery: ' + e.test.hello);
});
http://jsfiddle.net/eeYHr/3/
Some options for you:
$('#wrapper').children().each(function (i, element) { ... });
$('#wrapper > *').each(function (i, element) { ... });
Or to be more explicit, as you state you are concentrating on child DIVs only:
$('#wrapper div').each( function() {
// do some funky stuff
alert($(this).attr('id')); // displays element ID
});
For further learning, please also refer to this JQuery help page:
http://api.jquery.com/category/selectors/basic-css-selectors/
I am using Javascript(with Mootools) to dynamically build a large page using HTML "template" elements, copying the same template many times to populate the page. Within each template I use string keywords that need to be replaced to create the unique IDs. I'm having serious performance issues however in that it takes multiple seconds to perform all these replacements, especially in IE. The code looks like this:
var fieldTemplate = $$('.fieldTemplate')[0];
var fieldTr = fieldTemplate.clone(true, true);
fieldTr.removeClass('fieldTemplate');
replaceIdsHelper(fieldTr, ':FIELD_NODE_ID:', fieldNodeId);
parentTable.grab(fieldTr);
replaceIdsHelper() is the problem method according to IE9's profiler. I've tried two implementations of this method:
// Retrieve the entire HTML body of the element, replace the string and set the HTML back.
var html = rootElem.get('html').replace(new RegExp(replaceStr, 'g'), id);
rootElem.set('html', html);
and
// Load the child elements and replace just their IDs selectively
rootElem.getElements('*').each(function(elem) {
var elemId = elem.get('id');
if (elemId != null) elemId = elemId.replace(replaceStr, id);
elem.set('id', elemId)
});
However, both of these approaches are extremely slow given how many times this method gets called(about 200...). Everything else runs fine, it's only replacing these IDs which seems to be a major performance bottleneck. Does anyone know if there's a way to do this efficiently, or a reason it might be running so slow? The elements start hidden and aren't grabbed by the DOM until after they're created so there's no redrawing happening.
By the way, the reason I'm building the page this way is to keep the code clean, since we need to be able to create new elements dynamically after loading as well. Doing this from the server side would make things much more complicated.
I'm not 100% sure, but it sounds to me that the problem is with the indexing of the dom tree.
First of all, do you must use ids or can you manage with classes? since you say that the replacement of the id is the main issue.
Also, why do you clone part of the dom tree instead of just inserting a new html?
You can use the substitute method of String (when using MooTools), like so:
var template = '<div id="{ID}" class="{CLASSES}">{CONTENT}</div>';
template.substitute({ID: "id1", CLASSES: "c1 c2", CONTENT: "this is the content" });
you can read more about it here http://mootools.net/docs/core/Types/String#String:substitute
Then, just take that string and put it as html inside a container, let's say:
$("container_id").set("html", template);
I think that it might improve the efficiency since it does not clone and then index it again, but I can't be sure. give it a go and see what happens.
there are some things you can do to optimise it - and what #nizan tomer said is very good, the pseudo templating is a good pattern.
First of all.
var fieldTemplate = $$('.fieldTemplate')[0];
var fieldTr = fieldTemplate.clone(true, true);
you should do this as:
var templateHTML = somenode.getElement(".fieldTemplate").get("html"); // no need to clone it.
the template itself should/can be like suggested, eg:
<td id="{id}">{something}</td>
only read it once, no need to clone it for every item - instead, use the new Element constructor and just set the innerHTML - notice it lacks the <tr> </tr>.
if you have an object with data, eg:
var rows = [{
id: "row1",
something: "hello"
}, {
id: "row2",
something: "there"
}];
Array.each(function(obj, index) {
var newel = new Element("tr", {
html: templateHTML.substitute(obj)
});
// defer the inject so it's non-blocking of the UI thread:
newel.inject.delay(10, newel, parentTable);
// if you need to know when done, use a counter + index
// in a function and fire a ready.
});
alternatively, use document fragments:
Element.implement({
docFragment: function(){
return document.createDocumentFragment();
}
});
(function() {
var fragment = Element.docFragment();
Array.each(function(obj) {
fragment.appendChild(new Element("tr", {
html: templateHTML.substitute(obj)
}));
});
// inject all in one go, single dom access
parentTable.appendChild(fragment);
})();
I did a jsperf test on both of these methods:
http://jsperf.com/inject-vs-fragment-in-mootools
surprising win by chrome by a HUGE margin vs firefox and ie9. also surprising, in firefox individual injects are faster than fragments. perhaps the bottleneck is that it's TRs in a table, which has always been dodgy.
For templating: you can also look at using something like mustache or underscore.js templates.
Say I have a list like this:
<ul id='dom_a'>
<li>foo</li>
</ul>
I know how to insert elements in the ul tag with:
Element.insert('dom_a', {bottom:"<li>bar</li>"});
Since the string I receive contains the dom id, I need to insert the inner HTML instead of the whole element. I need a function to do this:
insert_content('dom_a', {bottom:"<ul id='dom_a'><li>bar</li></ul>"});
And obtain:
<ul id='dom_a'>
<li>foo</li>
<li>bar</li>
</ul>
How should I do this with Prototype ?
Here is the solution I have come up with, can anyone make this better ?
Zena.insert_inner = function(dom, position, content) {
dom = $(dom);
position = position.toLowerCase();
content = Object.toHTML(content);
var elem = new Element('div');
elem.innerHTML = content; // strip scripts ?
elem = elem.down();
var insertions = {};
$A(elem.childElements()).each(function(e) {
insertions[position] = e;
dom.insert(insertions);
});
}
I think you could parse the code block in your variable, then ask it for its innerHTML, and then use insert to stick that at the bottom of the actual node in the DOM.
That might look like this:
var rep_struct = "<ul id='dom_a'><li>bar</li></ul>";
var dummy_node = new Element('div'); // So we can easily access the structure
dummy_node.update(rep_struct);
$('dom_a').insert({bottom: dummy_node.childNodes[0].innerHTML});
I think you can slim down the code a bit by simply appending the innerHTML of the first child of temporary element:
Zena.insert_inner = function(dom, position, content) {
var d = document.createElement('div');
d.innerHTML = content;
var insertions = {};
insertions[position] = d.firstChild.innerHTML;
Element.insert(dom, insertions);
}
Not too much of an improvement though, example here.
I've been looking into the Prototype Documentation and I found this: update function.
By the way you described it, you could use the update function in order to find the current bottom content and then update it (just like innerHTML) by adding the desired code plus the previous stored code.
You could use regular expression to strip the outer element.
Element.Methods.insert_content = function(element, insertions) {
var regex = /^<(\w+)[^>]*>(.*)<\/\1>/;
for (key in insertions) {
insertions[key] = regex.exec(insertions[key])[2];
}
Element.insert(element, insertions);
};
Element.addMethods();
$('dom_a').insert_content({bottom:"<ul id='dom_a'><li>bar</li></ul>"});
If you are using PrototypeJS, you might also want to add script.aculo.us to your project. Builder in script.aculo.us provides a nice way to build complex DOM structures like so:
var myList = Builder.node("ul", {
id: "dom_a"
},[
Builder.node("li", "foo"),
Builder.node("li", "bar"),
]);
After this, you can insert this object which should be rendered as HTML anywhere in the DOM with any insert/update functions (of PrototypeJS) or even standard JavaScript appendChild.
$("my_div").insert({After: myList});
Note that in PrototypeJS insert comes in 4 different modes: After, Before, Top and Bottom. If you use insert without specifying a "mode" as above, the default will be Bottom. That is, the new DOM code will be appended below existing contents of the container element as innerHTML. Top will do the same thing but add it on top of the existing contents. Before and After are also cool ways to append to the DOM. If you use these, the content will be added in the DOM structure before and after the container element, not inside as innerHTML.
With Builder however, there is one thing to keep in mind, .. okay two things really:
i. You cannot enter raw HTML in the object as content... This will fail:
Builder.node("ul", "<li>foo</li>");
ii. When you specify node attributes, keep in mind that you must use className to signify HTML attribute class (and possibly also htmlFor for for attribute... although for attribute seems to be deprecated in HTML5(?), but who does not want to use it for labels)
Builder.node("ul", {
id: "dom_a",
className: "classy_list"
});
I know you are scratching your head because of point i. > What, no raw HTML, dang!
Not to worry. If you still need to add content which might contain HTML inside a Builder created DOM, just do it in the second stage using the insert({Before/After/Top/Bottom: string}). But why'd you want to do it in the first place? It would be really good practice if you wrote an once for all function that generates all kinds of DOM elements rather than stitching in all sorts of strings. The former approach would be neat and elegant. This is something like the inline style versus class type of question. Good design should after all separate content from meta content, or formatting markup / markdown.
One last thing to keep handy in your toolbox is Protype's DOM traversal in case you want to dynamically insert and delete content like a HTML Houdini. Check out the Element next, up, down, previous methods. Besides the $$ is also kinda fun to use, particularly if you know CSS3 selectors.