Why this javascript is not working in IE? - javascript

I am using the following javascript to dynamically add rows in a table:-
var trObj = document.createElement('tr');
trObj.setAttribute('name', 'dynamicTR');
var tdObjEmpty = document.createElement('td');
tdObjEmpty.setAttribute('colspan', '2');
tdObjEmpty.innerHTML = ' '
trObj.appendChild ( tdObjEmpty );
var tdObj = document.createElement('td');
tdObj.setAttribute('colspan', '15');
tdObj.innerHTML = postingDivObj.innerHTML; // <-- copy the innerHTML
trObj.appendChild ( tdObj );
parentObj = approvedDisapprovedTableObj.getElementsByTagName('tbody')[0];
targetElementObj = getNthTr ( parentObj, rowIndex1 - extraTr ); // <-- it will just return the trObject,
if ( targetElementObj ){
parentObj.insertBefore(trObj, targetElementObj.nextSibling )
}else{
//alert ( 'targetElementObj is null' );
}
This is working in FF as well as in IE, [ but, i guess, in case of IE name and colspan attribute is not set using setAttribute. but not sure ] .
Now, when i have to remove all rows which are dynamically created i use:-
dynamicTRObjs = document.getElementsByName('dynamicTR');
if ( dynamicTRObjs ){
parentObj = approvedDisapprovedTableObj.getElementsByTagName('tbody')[0];
for ( i = 0 ; i < dynamicTRObjs.length; i++ ){
parentObj.removeChild ( dynamicTRObjs[i] );
extraTr++;
}
}
This code removes all dynamically created TRs. and it works fine in FF, but not in IE.
Also in case of IE dynamicTRObjs.length is always 0,whereas in FF dynamicTRObjs.length it gives correct number of rows. Please tell me what i am missing here.

The HTML4 spec list of attributes lists elements that the name attribute can be set on. Tables and table elements are not on the list. The most obvious option is one of,
Keep references to all TRs you create so you don't have to find them in the DOM
Set a className on your TRs and use selectors to find them
That Firefox uses getElementsByName 'correctly' and IE does not is something others have run into too. I'd just avoid using name here altogether.

http://www.quirksmode.org/dom/w3c_core.html
getElementsByName() is not working well in IE6-8
I would suggest that you some other way of identifying that element if you want cross browser usability.

I know, it's a bit off-topic, but let me give you a small advice on using getElementsByName functionality in a browser. It will not help you to solve the current problem (which is because TR can not have Name attribute ), but it will definitely help you to prevent future problems which you will met.
getElementsByName returns you collection, which always keeps itself up-to-date with the DOM tree. This means, that at the moment when you remove ONE item with removeChild, the SIZE of collection will be decreased. So, if you will removing nodes and keep relying on the length of the collection, not all nodes will be removed.
Check this example of your for loop:
Collection length is 3, Your i var is 0, i < length
You remove child,
collection length is 2, your i var is 1, i < length
you remove child,
collection length is 1 and your i var i 2.
Condition i< length == true that means that for loop will stop, BUT some of the elements will still be presented in the DOM.
Choose any solution you like to fix this, but try to avoid relying on the length of the Collection which is returned by getElementsByTagName.
Good luck

since I'm not the only one to suggest avoidance of low-level DOM manipulation, here's an example: an untested implementation with jquery. not exactly an answer to your question, but a comment would lose the formatting.
var mkTd = function (colspan, html)
{
return $('<td />')
.attr('colspan', colspan)
.html(html)
;
}
var addRow = function (rowNr)
{
var target = $('#approvedDisapprovedTableObj tbody tr:eq('+rowNr+')');
if (!target.length) {
//alert ( 'target is null' );
return;
}
target.after(
$('<tr />')
.addClass('dynamicTR')
.append(mkTd(2, ' ')
.append(mkTd(15, $('#postingDivObj').html()))
);
}
var dropRows = function ()
{
$('.dynamicTR').remove();
}
notice that the expression $('.dynamicTR').remove() achieves the same as your
dynamicTRObjs = document.getElementsByName('dynamicTR');
if ( dynamicTRObjs ){
parentObj = approvedDisapprovedTableObj.getElementsByTagName('tbody')[0];
for ( i = 0 ; i < dynamicTRObjs.length; i++ ){
parentObj.removeChild ( dynamicTRObjs[i] );
extraTr++;
}
}
IMO it's obvious that the benefits are huge.

Related

Remove an item from datalist

I have a working Ajax call and function that populates a datalist. The datalist is used to select from a finite list an addition to an UL element on the page. Once I add one from the datalist to the actual list, I want to remove that option from the datalist. The brute force method I'm using is to clear the datalist and repopulate it.
function populate_datalist_from_array(list_id, list_str)
{
clearChildren( list_id );
var arr = eval ( list_str );
for (var i = 0; i < arr.length; i++) {
var opt = document.createElement('option');
opt.innerHTML = arr[i];
opt.value = arr[i];
document.getElementById(list_id).appendChild(opt);
}
}
function clearChildren( parent_id ) {
var childArray = document.getElementById( parent_id ).children;
if ( childArray.length > 0 ) {
document.getElementById( parent_id ).removeChild( childArray[ 0 ] );
clearChildren( parent_id );
}
}
I've verified that the list_str object is correct. i.e., it contains only the options not already in the current list. But after calling populate_datalist_from_array with that new list, the datalist in the dropdown doesn't change. Is this because the browser has essentially compiled all of the values that were there (like it were a normal, browser-based autocomplete) and doesn't 'forget' the values that I want removed?
Teemu's JsFiddle works fine. However, it's normally better to avoid recursion, and multiple DOM queries when not required.
Here is an edit that only requires a single DOM query, and is iterative. (Note decrement before index because this is a zero based list)
clearChildren = function (parent_id) {
var parent = document.getElementById(parent_id);
var childArray = parent.children;
var cL = childArray.length;
while(cL > 0) {
cL--;
parent.removeChild(childArray[cL]);
}
};
(In JSFiddle on MacBookPro I saved 10 ms - from 15 ms total - on a list of 500 elements, but could be more dramatic with larger DOM's on mobile).
If list_str really is OK, your code works. You can check it in action at jsFiddle.
The most general reason for a behaviour you've described, is that your code refreshes the page. If you remove type="button" from the "Change options" button in the linked fiddle, you'll get an error (due to the fiddle itself). In your page you probably have something similar invoking populate_datalist_from_array(). Notice, that also hitting Enter on an active text input will do submit/refresh.

Remove all child nodes but leave the text content of the node in javascript (no framework)

I'm trying to remove all child elements from a node but leave the actual text content of the node. I.e. go from this:
<h3>
MY TEXT
<a href='...'>Link</a>
<a href='...'>Link</a>
<select>
<option>Value</option>
<option>Value</option>
</select>
</h3>
to this:
<h3>
MY TEXT
</h3>
I know that there are a million easy ways to do this in jQuery, but it's not an option for this project... I've got to use plain old javascript.
This:
var obj = document.getElementById("myID");
if ( obj.hasChildNodes() ){
while ( obj.childNodes){
obj.removeChild( obj.firstChild );
}
}
obviously results in just <h3></h3>, and when I tried:
var h3 = content_block.getElementsByTagName('h3')[0];
var h3_children = h3.getElementsByTagName('*');
for(var i=0;i<h3_children.length;i++){
h3_children[i].parentNode.removeChild(h3_children[i]);
}
It gets hung up part way through. I figured it was having trouble removing the options, but altering the for loop to skip removal unless h3_children[i].parentNode==h3 (i.e. only remove first-level child-elements) stops after removing the first <a> element.
I'm sure I'm missing something super obvious here, but perhaps it's time to turn to the crowd. How can I remove all child elements but leave the first-level textNodes alone? And why doesn't the above approach work?
EDITS
There are a couple of working solutions posted, which is great, but I'm still a little mystified as to why looping through and removing h3.getElementsByTagName('*') doesn't work. A similar approach(adapted from Blender) likewise does not complete the process of removing child nodes. Any thoughts as to why this would be?
var h3=content_block.getElementsByTagName("h3")[0];
for(var i=0;i<h3.childNodes.length;i++)
{
if(h3.childNodes[i].nodeType==3)//TEXT_NODE
{
continue;
}
else
{
h3.removeChild(h3.childNodes[i]);
i--;
}
}
JSFiddle demo
Edit:
Combined the i-- to make it look shorter:
var h3=content_block.getElementsByTagName("h3")[0];
for(var i=0;i<h3.childNodes.length;i++)
{
if(h3.childNodes[i].nodeType==3)//TEXT_NODE
continue;
else
h3.removeChild(h3.childNodes[i--]);
}
Edit #2:
Pointed out by #SomeGuy, make it even shorter:
var h3=content_block.getElementsByTagName("h3")[0];
for(var i=0;i<h3.childNodes.length;i++)
{
if(h3.childNodes[i].nodeType!=3)//not TEXT_NODE
h3.removeChild(h3.childNodes[i--]);
}
The brackets can be removed too, but that would be "less readable" and "confusing", so I keep it there.
You can check properties .nodeType or .nodeName for each node.
Text nodes have these properties set to:
.nodeType == 3
.nodeName == '#text'`
For instance:
var e = obj.firstChild
while (e) {
if (e.nodeType == 3) {
e = e.nextSibling
} else {
var n = e.nextSibling
obj.removeChild(e)
e = n
}
}
try this. I am assuming you will keep ant text here.
var h3 = document.getElementsByTagName('h3')[0];
if (h3.hasChildNodes()) {
for (var i = h3.childNodes.length - 1; i >= 0; i--) {
if (h3.childNodes[i].nodeName != "#text")
h3.removeChild(h3.childNodes[i]);
}
}
Hope it will work.
Well, The thing I used (inspired from the answers here) is somewhat like:
var h3 = document.getElementsByTagName("h3")[0];
Array.protoype.filter.call(h3.childNodes, function(child){
if (child.nodeType != 3) {
h3.removeChild(child);
}
});

Need help optimizing script that hides table rows

I made this Greasemonkey script:
var maxpi = 250;
var p1 = "/html/body/div/div[2]/div/div[2]/table[2]/tbody/tr[1]/td[11]";
var p2 = "/html/body/div/div[2]/div/div[2]/table[2]/tbody/tr[2]/td[11]";
..
var p25 = "/html/body/div/div[2]/div/div[2]/table[2]/tbody/tr[25]/td[11]";
var r1 = "/html/body/div/div[2]/div/div[2]/table[2]/tbody/tr[1]";
var r2 = "/html/body/div/div[2]/div/div[2]/table[2]/tbody/tr[2]";
..
var r25 = "/html/body/div/div[2]/div/div[2]/table[2]/tbody/tr[25]";
var xpathPI1 = document.evaluate(p1, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null);
..
var xpathPI25 = document.evaluate(p25, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null);
var xpathrow1 = document.evaluate(r1, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null);
..
var xpathrow25 = document.evaluate(r25, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null);
if (xpathPI1.singleNodeValue.textContent >maxpi ){
xpathrow1.singleNodeValue.style.display='none';}
..
if (xpathPI25.singleNodeValue.textContent >maxpi ){
xpathrow25.singleNodeValue.style.display='none';}
Basically, it checks a table row's 11th field and if its contents > than 250 it hides the row.
With my limited javascript knowledge took quite some time get this working.
The problem is that I have to rewrite every single line if I want to check-hide another row.
I want to make it more usable so I can use it on similar tables without rewriting the whole thing.
Maybe I need to use a different XPath type or use some kind of changing variable?
Of course, there are more ways to improve your script.
Firstly, you need to thoroughly think through WHAT exactly you want to look for. Is is every row and column? Is it rows/columns with some text, class, any other attribute? You can even select only those nodes that have their text value greater than your maxpi!
Read something about XPath, the possibly best resource is the official one.
Some random thoughts on what could be useful regarding XPath:
//table//tr[5]/td[2] ... the double slash is the deal here
//table//tr/td[number(text()) > 250] ... the number() and text() functions
When talking about JavaScript, that would be a little tougher, because there are so many things you could use!
Just for starters - you can create dynamically changing xpath expressions by String concatenation and For loop, like this:
for (var i = 1; i <= maxNumberOfRows; i++) {
var p1 = "//table/tbody/tr[" + i + "]";
// more work goes here...
}
Also, you could use arrays to store multiple nodes returned by your XPath expressions and work on them all with just a single command.
For more JavaScript, I would recommend the first chapters of some JavaScript tutorial, that will boost your productivity by a lot.
Use a loop and functions. Here's one way:
hideRowsWithLargeCellValue (
"/html/body/div/div[2]/div/div[2]/table[2]/tbody/tr[",
25,
"]/td[11]",
250
);
function hideRowsWithLargeCellValue (xpathPre, maxRows, xpathPost, maxpi) {
for (var J = maxRows; J >= 1; --J) {
var srchRez = document.evaluate (
xpathPre + J + xpathPost,
document,
null,
XPathResult.FIRST_ORDERED_NODE_TYPE,
null
);
if (srchRez.singleNodeValue && srchRez.singleNodeValue.textContent > maxpi) {
var rowToHide = srchRez.singleNodeValue.parentNode;
rowToHide.style.display='none';
}
}
}
Then read "Dont Repeat Yourself" (sic).

infinite-loop via prepend element in DOM

Not Looking for a Use Framework XXX Answer
This question is not intended for finding a practical solution via a framework. Answering with use framework XXX, or this is so easy in framework XXX, or why not use this framework XXX??? doesn't answer the question.
I have a function meant to run after a page has been loaded: performShim. This function iterates over all elements in the DOM that are span tags, checks if they have a className of shim and if so, calls shim passing to it a reference of the matched element.
My goal was to prepend another span that contains an iframe to the element that is passed to shim.
With the code I wrote so far, I am able to append to the element's parent just fine. However, if I comment out the append line and instead try the prepend line the browser hangs in presumably an infinite-loop.
It's not readily obvious to me why this is the case.
function shim( element ) {
var iframe = document.createElement('iframe');
iframe.setAttribute( 'frameborder', '0' );
iframe.setAttribute( 'scrolling', 'no' );
iframe.setAttribute( 'align', 'bottom' );
iframe.setAttribute( 'marginheight', '0' );
iframe.setAttribute( 'marginwidth', '0' );
iframe.setAttribute( 'src', "javascript:'';" );
var span = document.createElement('span');
span.appendChild(iframe);
//element.parentNode.insertBefore(span,element); //causes infinite loop?
element.parentNode.appendChild(span); //this line works OK
var els = element.style;
var originalVisibility = els.visibility;
var originalPosition = els.position;
var originalDisplay = els.display;
els.visibility = 'hidden';
els.position = 'absolute';
els.display = 'inline';
var width = element.offsetWidth;
var height = element.offsetHeight;
els.display = originalDisplay;
els.position = originalPosition;
els.visibility = originalVisibility;
iframe.style.width = (width-6) + 'px';
iframe.style.height = (height-6) + 'px';
}
function performShim() {
var children = document.getElementsByTagName("span");
for( var i = 0; i < children.length; i++ ) {
if( children[i].className == "shim" ) {
shim(children[i]);
}
}
}
A NodeList (such as the one returned by document.getElementsByTagName) is typically a live list -- changes you make to the DOM show up in it as well. So each time you add a span before the current one, you're extending the list by one element and moving the current element over by one, and the next iteration puts you right back at the node you just finished.
You have a couple of easy workarounds for that...
Bump the counter when you add a node. (Ugly, and if you ever end up adding something instead of a span, you'll end up skipping nodes and it won't be obvious why.)
Copy the list to an array and iterate over the array. You could do this with something like
children = [].slice.call(children, 0); (more common) or
children = Array.apply(window, children);.
Use document.querySelectorAll, which returns you a NodeList that's not live. (And even if it were live, in this case you could select 'span.shim' and the inserted spans wouldn't show up in it anyway.)
Iterate backwards (from children.length - 1 to 0).

Sorting Divs in jQuery by Custom Sort Order

I'm trying to re-sort the child elements of the tag input by comparing
their category attribute to the category order in the Javascript
variable category_sort_order. Then I need to remove divs whose category attribute
does not appear in category_sort_order.
The expected result should be:
any
product1
product2
download
The code:
<div id="input">
<div category="download">download</div>
<div category="video">video1</div>
<div category="video">video2</div>
<div category="product">product1</div>
<div category="any">any</div>
<div category="product">product2</div>
</div>
<script type="text/javascript">
var category_sort_order = ['any', 'product', 'download'];
</script>
I really don't even know where to begin with this task but if you could please provide any assistance whatsoever I would be extremely grateful.
I wrote a jQuery plugin to do this kind of thing that can be easily adapted for your use case.
The original plugin is here
Here's a revamp for you question
(function($) {
$.fn.reOrder = function(array) {
return this.each(function() {
if (array) {
for(var i=0; i < array.length; i++)
array[i] = $('div[category="' + array[i] + '"]');
$(this).empty();
for(var i=0; i < array.length; i++)
$(this).append(array[i]);
}
});
}
})(jQuery);
and use like so
var category_sort_order = ['any', 'product', 'download'];
$('#input').reOrder(category_sort_order);
This happens to get the right order for the products this time as product1 appears before product2 in the original list, but it could be changed easily to sort categories first before putting into the array and appending to the DOM. Also, if using this for a number of elements, it could be improved by appending all elements in the array in one go instead of iterating over the array and appending one at a time. This would probably be a good case for DocumentFragments.
Just note,
Since there is jQuery 1.3.2 sorting is simple without any plugin like:
$('#input div').sort(CustomSort).appendTo('#input');
function CustomSort( a ,b ){
//your custom sort function returning -1 or 1
//where a , b are $('#input div') elements
}
This will sort all div that are childs of element with id="input" .
Here is how to do it. I used this SO question as a reference.
I tested this code and it works properly for your example:
$(document).ready(function() {
var categories = new Array();
var content = new Array();
//Get Divs
$('#input > [category]').each(function(i) {
//Add to local array
categories[i] = $(this).attr('category');
content[i] = $(this).html();
});
$('#input').empty();
//Sort Divs
var category_sort_order = ['any', 'product', 'download'];
for(i = 0; i < category_sort_order.length; i++) {
//Grab all divs in this category and add them back to the form
for(j = 0; j < categories.length; j++) {
if(categories[j] == category_sort_order[i]) {
$('#input').append('<div category="' +
category_sort_order[i] + '">'
+ content[j] + '</div>');
}
};
}
});
How it works
First of all, this code requires the JQuery library. If you're not currently using it, I highly recommend it.
The code starts by getting all the child divs of the input div that contain a category attribute. Then it saves their html content and their category to two separate arrays (but in the same location.
Next it clears out all the divs under the input div.
Finally, it goes through your categories in the order you specify in the array and appends the matching child divs in the correct order.
The For loop section
#eyelidlessness does a good job of explaining for loops, but I'll also take a whack at it. in the context of this code.
The first line:
for(i = 0; i < category_sort_order.length; i++) {
Means that the code which follows (everything within the curly brackets { code }) will be repeated a number of times. Though the format looks archaic (and sorta is) it says:
Create a number variable called i and set it equal to zero
If that variable is less than the number of items in the category_sort_order array, then do whats in the brackets
When the brackets finish, add one to the variable i (i++ means add one)
Then it repeats step two and three until i is finally bigger than the number of categories in that array.
A.K.A whatever is in the brackets will be run once for every category.
Moving on... for each category, another loop is called. This one:
for(j = 0; j < categories.length; j++) {
loops through all of the categories of the divs that we just deleted from the screen.
Within this loop, the if statement checks if any of the divs from the screen match the current category. If so, they are appending, if not the loop continues searching till it goes through every div.
Appending (or prepending) the DOM nodes again will actually sort them in the order you want.
Using jQuery, you just have to select them in the order you want and append (or prepend) them to their container again.
$(['any', 'product', 'video'])
.map(function(index, category)
{
return $('[category='+category+']');
})
.prependTo('#input');
Sorry, missed that you wanted to remove nodes not in your category list. Here is the corrected version:
// Create a jQuery from our array of category names,
// it won't be usable in the DOM but still some
// jQuery methods can be used
var divs = $(['any', 'product', 'video'])
// Replace each category name in our array by the
// actual DOM nodes selected using the attribute selector
// syntax of jQuery.
.map(function(index, category)
{
// Here we need to do .get() to return an array of DOM nodes
return $('[category='+category+']').get();
});
// Remove everything in #input and replace them by our DOM nodes.
$('#input').empty().append(divs);
// The trick here is that DOM nodes are selected
// in the order we want them in the end.
// So when we append them again to the document,
// they will be appended in the order we want.
I thought this was a really interesting problem, here is an easy, but not incredibly performant sorting solution that I came up with.
You can view the test page on jsbin here: http://jsbin.com/ocuta
function compare(x, y, context){
if($.inArray(x, context) > $.inArray(y, context)) return 1;
}
function dom_sort(selector, order_list) {
$items = $(selector);
var dirty = false;
for(var i = 0; i < ($items.length - 1); i++) {
if (compare($items.eq(i).attr('category'), $items.eq(i+1).attr('category'), order_list)) {
dirty = true;
$items.eq(i).before($items.eq(i+1).remove());
}
}
if (dirty) setTimeout(function(){ dom_sort(selector, order_list); }, 0);
};
dom_sort('#input div[category]', category_sort_order);
Note that the setTimeout might not be necessary, but it just feels safer. Your call.
You could probably clean up some performance by storing a reference to the parent and just getting children each time, instead of re-running the selector. I was going for simplicity though. You have to call the selector each time, because the order changes in a sort, and I'm not storing a reference to the parent anywhere.
It's seems fairly direct to use the sort method for this one:
var category_sort_order = ['any', 'product', 'download'];
// select your categories
$('#input > div')
// filter the selection down to wanted items
.filter(function(){
// get the categories index in the sort order list ("weight")
var w = $.inArray( $(this).attr('category'), category_sort_order );
// in the sort order list?
if ( w > -1 ) {
// this item should be sorted, we'll store it's sorting index, and keep it
$( this ).data( 'sortindex', w );
return true;
}
else {
// remove the item from the DOM and the selection
$( this ).remove();
return false;
}
})
// sort the remainder of the items
.sort(function(a, b){
// use the previously defined values to compare who goes first
return $( a ).data( 'sortindex' ) -
$( b ).data( 'sortindex' );
})
// reappend the selection into it's parent node to "apply" it
.appendTo( '#input' );
If you happen to be using an old version of jQuery (1.2) that doesn't have the sort method, you can add it with this:
jQuery.fn.sort = Array.prototype.sort;

Categories

Resources