Javascript substring and indexOf not working in IE9 - javascript

I have this Javascript here:
function getTxt(obj) {
var first = obj.innerHTML.substring(0, obj.innerHTML.indexOf('<span class=\"item2\">'));
var second = obj.innerHTML.substring(obj.innerHTML.indexOf('<span class=\"item2\">'));
var f = first.replace(/(<([^>]+)>)/ig,'');
var s = second.replace(/(<([^>]+)>)/ig,'');
alert(first + "\n" + second + "\n" + f + "\n" + s);
}
and the HTML:
<span class="item" onclick="getTxt(this)"><span class="item1">MyName</span><span class="item2">555-555-5555</span></span>
In most browsers (FireFox, Chrome, Safari, Opera) it will alert:
<span class="item1">MyName</span>
<span class="item2">555-555-5555</span>
MyName
555-555-5555
as expected. However, in IE9 it alerts:
<span class="item1">MyName</span><span class="item2">555-555-5555</span>
MyName555-555-5555
So it puts the vars "first" and "second" together into var "first", and puts "f" and "s" together into var "f".
I would like to know if there is anyway to correct this for IE9 (and probably other version of IE also) to work as it does in the other browsers.

Pattern matching innerHTML is particularly a problem in IE and is generally a bad idea. IE often does NOT return to you the same HTML that was originally in the page. It often requotes or removes quotes, changes the order of attributes, changes case, etc... IE is clearly reconstituting the HTML rather than give you back what was originally in the page. As such, you cannot reliably pattern match innerHTML in IE. There are some specific things you can probably match (the start of tags), but you can't expect attributes to be in a specific spot or to have a specific format.
If you console.log(obj.innerHTML) in IE, you will likely see what I'm talking about. It will look different.
A more robust solution is to use the DOM functions to navigate the specific elements or CSS selectors to find specific objects and then change attributes or innerHTML on a single specific element. Let the DOM navigation find the right element for you rather than parsing the HTML yourself.
If you provide a desired before and after sample of the HTML and describe what you're trying to accomplish, folks here can probably help you get the job done with DOM manipulation rather than HTML parsing.
I don't know which selector libraries you have available to you or which browsers you're targeting, but in jQuery, you could do this like this:
function getText(obj) {
return $(obj).find(".item1").text();
}
In plain javascript, in IE8 and above and all other modern browsers, you can use this:
function getText(obj) {
return obj.querySelectorAll(".item1").innerHTML;
}
If you had to support back to IE6 or IE7, I'd suggest getting the Sizzle library and use that for your queries:
function getText(obj) {
return Sizzle(".item1", obj)[0].innerHTML;
}

It happens because of "Quirky mode" in Internet Explorer. It's a huge pain in the ass, but you can disable it in IE DevTools, or by adding this metatag to your page:
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />

Related

Looping through arrays with JavaScript on Firefox issue [duplicate]

I have some JavaScript code that works in IE containing the following:
myElement.innerText = "foo";
However, it seems that the 'innerText' property does not work in Firefox. Is there some Firefox equivalent? Or is there a more generic, cross browser property that can be used?
Update: I wrote a blog post detailing all the differences much better.
Firefox uses W3C standard Node::textContent, but its behavior differs "slightly" from that of MSHTML's proprietary innerText (copied by Opera as well, some time ago, among dozens of other MSHTML features).
First of all, textContent whitespace representation is different from innerText one. Second, and more importantly, textContent includes all of SCRIPT tag contents, whereas innerText doesn't.
Just to make things more entertaining, Opera - besides implementing standard textContent - decided to also add MSHTML's innerText but changed it to act as textContent - i.e. including SCRIPT contents (in fact, textContent and innerText in Opera seem to produce identical results, probably being just aliased to each other).
textContent is part of Node interface, whereas innerText is part of HTMLElement. This, for example, means that you can "retrieve" textContent but not innerText from text nodes:
var el = document.createElement('p');
var textNode = document.createTextNode('x');
el.textContent; // ""
el.innerText; // ""
textNode.textContent; // "x"
textNode.innerText; // undefined
Finally, Safari 2.x also has buggy innerText implementation. In Safari, innerText functions properly only if an element is
neither hidden (via style.display == "none") nor orphaned from the document. Otherwise, innerText results in an empty string.
I was playing with textContent abstraction (to work around these deficiencies), but it turned out to be rather complex.
You best bet is to first define your exact requirements and follow from there. It is often possible to simply strip tags off of innerHTML of an element, rather than deal with all of the possible textContent/innerText deviations.
Another possibility, of course, is to walk the DOM tree and collect text nodes recursively.
Firefox uses the W3C-compliant textContent property.
I'd guess Safari and Opera also support this property.
If you only need to set text content and not retrieve, here's a trivial DOM version you can use on any browser; it doesn't require either the IE innerText extension or the DOM Level 3 Core textContent property.
function setTextContent(element, text) {
while (element.firstChild!==null)
element.removeChild(element.firstChild); // remove all existing content
element.appendChild(document.createTextNode(text));
}
jQuery provides a .text() method that can be used in any browser. For example:
$('#myElement').text("Foo");
As per Prakash K's answer Firefox does not support the innerText property. So you can simply test whether the user agent supports this property and proceed accordingly as below:
function changeText(elem, changeVal) {
if (typeof elem.textContent !== "undefined") {
elem.textContent = changeVal;
} else {
elem.innerText = changeVal;
}
}
A really simple line of Javascript can get the "non-taggy" text in all main browsers...
var myElement = document.getElementById('anyElementId');
var myText = (myElement.innerText || myElement.textContent);
Note that the Element::innerText property will not contain the text which has been hidden by CSS style "display:none" in Google Chrome (as well it will drop the content that has been masked by other CSS technics (including font-size:0, color:transparent, and a few other similar effects that cause the text not to be rendered in any visible way).
Other CSS properties are also considered :
First the "display:" style of inner elements is parsed to determine if it delimits a block content (such as "display:block" which is the default of HTML block elements in the browser's builtin stylesheet, and whose behavior as not been overriden by your own CSS style); if so a newline will be inserted in the value of the innerText property. This won't happen with the textContent property.
The CSS properties that generate inline contents will also be considered : for example the inline element <br \> that generates an inline newline will also generate an newline in the value of innerText.
The "display:inline" style causes no newline either in textContent or innerText.
The "display:table" style generates newlines around the table and between table rows, but"display:table-cell" will generate a tabulation character.
The "position:absolute" property (used with display:block or display:inline, it does not matter) will also cause a line break to be inserted.
Some browsers will also include a single space separation between spans
But Element::textContent will still contain ALL contents of inner text elements independantly of the applied CSS even if they are invisible. And no extra newlines or whitespaces will be generated in textContent, which just ignores all styles and the structure and inline/block or positioned types of inner elements.
A copy/paste operation using mouse selection will discard the hidden text in the plain-text format that is put in the clipboard, so it won't contain everything in the textContent, but only what is within innerText (after whitespace/newline generation as above).
Both properties are then supported in Google Chrome, but their content may then be different. Older browsers still included in innetText everything like what textContent now contains (but their behavior in relation with then generation of whitespaces/newlines was inconsistant).
jQuery will solve these inconsistencies between browsers using the ".text()" method added to the parsed elements it returns via a $() query. Internally, it solves the difficulties by looking into the HTML DOM, working only with the "node" level. So it will return something looking more like the standard textContent.
The caveat is that that this jQuery method will not insert any extra spaces or line breaks that may be visible on screen caused by subelements (like <br />) of the content.
If you design some scripts for accessibility and your stylesheet is parsed for non-aural rendering, such as plugins used to communicate with a Braille reader, this tool should use the textContent if it must include the specific punctuation signs that are added in spans styled with "display:none" and that are typically included in pages (for example for superscripts/subscripts), otherwise the innerText will be very confusive on the Braille reader.
Texts hidden by CSS tricks are now typically ignored by major search engines (that will also parse the CSS of your HTML pages, and will also ignore texts that are not in contrasting colors on the background) using an HTML/CSS parser and the DOM property "innerText" exactly like in modern visual browsers (at least this invisible content will not be indexed so hidden text cannot be used as a trick to force the inclusion of some keywords in the page to check its content) ; but this hidden text will be stil displayed in the result page (if the page was still qualified from the index to be included in results), using the "textContent" property instead of the full HTML to strip the extra styles and scripts.
IF you assign some plain-text in any one of these two properties, this will overwrite the inner markup and styles applied to it (only the assigned element will keep its type, attributes and styles), so both properties will then contain the same content. However, some browsers will now no longer honor the write to innerText, and will only let you overwrite the textContent property (you cannot insert HTML markup when writing to these properties, as HTML special characters will be properly encoded using numeric character references to appear literally, if you then read the innerHTML property after the assignment of innerText or textContent.
myElement.innerText = myElement.textContent = "foo";
Edit (thanks to Mark Amery for the comment below): Only do it this way if you know beyond a reasonable doubt that no code will be relying on checking the existence of these properties, like (for example) jQuery does. But if you are using jQuery, you would probably just use the "text" function and do $('#myElement').text('foo') as some other answers show.
innerText has been added to Firefox and should be available in the FF45 release: https://bugzilla.mozilla.org/show_bug.cgi?id=264412
A draft spec has been written and is expected to be incorporated into the HTML living standard in the future: http://rocallahan.github.io/innerText-spec/, https://github.com/whatwg/html/issues/465
Note that currently the Firefox, Chrome and IE implementations are all incompatible. Going forward, we can probably expect Firefox, Chrome and Edge to converge while old IE remains incompatible.
See also: https://github.com/whatwg/compat/issues/5
This has been my experience with innerText, textContent, innerHTML, and value:
// elem.innerText = changeVal; // works on ie but not on ff or ch
// elem.setAttribute("innerText", changeVal); // works on ie but not ff or ch
// elem.textContent = changeVal; // works on ie but not ff or ch
// elem.setAttribute("textContent", changeVal); // does not work on ie ff or ch
// elem.innerHTML = changeVal; // ie causes error - doesn't work in ff or ch
// elem.setAttribute("innerHTML", changeVal); //ie causes error doesn't work in ff or ch
elem.value = changeVal; // works in ie and ff -- see note 2 on ch
// elem.setAttribute("value", changeVal); // ie works; see note 1 on ff and note 2 on ch
ie = internet explorer, ff = firefox, ch = google chrome.
note 1: ff works until after value is deleted with backspace - see note by Ray Vega above.
note 2: works somewhat in chrome - after update it is unchanged then you click away and click back into the field and the value appears.
The best of the lot is elem.value = changeVal; which I did not comment out above.
As in 2016 from Firefox v45, innerText works on firefox, take a look at its support: http://caniuse.com/#search=innerText
If you want it to work on previous versions of Firefox, you can use textContent, which has better support on Firefox but worse on older IE versions: http://caniuse.com/#search=textContent
What about something like this?
//$elem is the jQuery object passed along.
var $currentText = $elem.context.firstChild.data.toUpperCase();
** I needed to make mine uppercase.
Just reposting from comments under the original post. innerHTML works in all browsers. Thanks stefita.
myElement.innerHTML = "foo";
found this here:
<!--[if lte IE 8]>
<script type="text/javascript">
if (Object.defineProperty && Object.getOwnPropertyDescriptor &&
!Object.getOwnPropertyDescriptor(Element.prototype, "textContent").get)
(function() {
var innerText = Object.getOwnPropertyDescriptor(Element.prototype, "innerText");
Object.defineProperty(Element.prototype, "textContent",
{ // It won't work if you just drop in innerText.get
// and innerText.set or the whole descriptor.
get : function() {
return innerText.get.call(this)
},
set : function(x) {
return innerText.set.call(this, x)
}
}
);
})();
</script>
<![endif]-->
It's also possible to emulate innerText behavior in other browsers:
if (((typeof window.HTMLElement) !== "undefined") && ((typeof HTMLElement.prototype.__defineGetter__) !== "undefined")) {
HTMLElement.prototype.__defineGetter__("innerText", function () {
if (this.textContent) {
return this.textContent;
} else {
var r = this.ownerDocument.createRange();
r.selectNodeContents(this);
return r.toString();
}
});
HTMLElement.prototype.__defineSetter__("innerText", function (str) {
if (this.textContent) {
this.textContent = str;
} else {
this.innerHTML = str.replace(/&/g, '&').replace(/>/g, '>').replace(/</g, '<').replace(/\n/g, "<br />\n");
}
});
}

Why does this dropdown list for sorting work in Internet Explorer but not in Chrome?

I have some JavaScript for a dropdownlist to sort results on a product inventory page. In Internet Explorer the sorting works fine and the browser handles this perfect. However in Chrome it fails every time (can you believe it, something works in IE but not Chrome?)
In IE when I use the Sort By option the URL looks like this:
MyExampleSite.com/Supplies/Products/12345/MyProduct/?a=0
However when I do the Sort By option in Chrome here is what the URL looks like:
MyExampleSite.com/Supplies/Products/12345/MyProduct/?&a=0
As you can see it adds the amp in the URL, and if I keep trying to sort it just add's an additional amp everytime.
Here is the JavaScript which caused my issues:
$("[name=a]").change(function () {
window.location = '#(this.Model.SortUri)' + '#(this.Model.SortUri.IndexOf('?') == -1 ? "?" : "&")a=' + this.value;
});
My Solution was to add Html.Raw like this:
$("[name=a]").change(function () {
window.location = '#(this.Model.SortUri)' + '#Html.Raw(this.Model.SortUri.IndexOf('?') == -1 ? "?" : "&")a=' + this.value;
});
And suddenly it works fine in IE and Chrome.
My question is why did Chrome do this but not IE?
Well, you need to make sure all your stuff is encoded correctly, and I'm guessing you aren't. We would need to see much more of your page to determine that. IE is probably detecting that you've done it incorrectly and attempting to fix it for you, while Chrome isn't auto-fixing your mistake.
It gets pretty complicated as to the rules of when you do and don't need to escape stuff. It's in a HTML page, and everyone knows you need to escape & in HTML pages, but it's in a script tag, so do you or don't you need to escape it? Well that depends if you also have the script tag in a CDATA element or not.
The simple solution is to avoid doing that. Put the URL you want to switch to on the [name=a] element as a data tag like so:
<sometag name='a' data-urlprefix='#(this.Model.SortUri)#(this.Model.SortUri.IndexOf('?') == -1 ? "?" : "&")a='>stuff</sometag>
Then in your javascript:
$("[name=a]").change(function () {
window.location.href = $(this).data('urlprefix')+encodeURIComponent(this.value);
});
This also has the benefit of moving the server processing stuff to just the HTML parts, so that you can put the javascript in it's own file, which you should be doing anyhow.
Note that you should be urlencoding this.value if it isn't already as well, so I've done that. If it is already encoded, you can safely remove it. I've also changed window.location to window.location.href because window.location can do strange things sometimes as well -- encoding on some browsers, but not on others, etc.
You just need to make the entire string as part of your Html.Raw(build ou the logic outside and directly insert that variable here)
Please check this post
Why is Html.Raw escaping ampersand in anchor tag in ASP.NET MVC 4?

Why creating the elements with custom tags adds the xml namespace in the outerHTML in IE9 or 10 until the .find() method is called?

I have a jsfiddle that demonstrates the question:
http://jsfiddle.net/H6gML/8/
$(document).ready(function() {
// this seems fine in IE9 and 10
var $div = $("<div>");
console.log("In IE, this <div> is just fine: " + $div[0].outerHTML);
// this is weird in IE
var $test = $("<test>");
console.log("However, this <test> has an xml tag prepended: \n"
+ $test[0].outerHTML);
$test.find("test");
console.log("Now, it does not: \n" + $test[0].outerHTML);
console.log("Why does this behave this way?");
});
Why does this happen? It doesn't happen in Chrome or Firefox. Is there a better way to fix this than to call .find("test") on the object?
Edit
To clarify, I'm not asking why the xml tag is added, rather, I'm wondering why the .find() call get's rid of it. It doesn't make sense to me.
Why does this happen? It doesn't happen in Chrome or Firefox. Is there a better way to fix this than to call .find("test") on the object
It is the IE causing the issue while doing document.createElement on an unknown html element type. It thinks it is an XML node and adds the xml namespace prefixed <?XML:NAMESPACE PREFIX = PUBLIC NS = "URN:COMPONENT" />. Instead if you try to make it explicit to mention that it is an html element, this issue doesn't happen.
Try:
var $test = $("<html><test/></html>");
The issue no longer occurs.
To clarify, I'm not asking why the xml tag is added, rather, I'm wondering why the .find() call get's rid of it. It doesn't make sense to me.
Now, when you do a find, jquery internally uses context.getElementsByTagName or (similar based on the type whether it is a class or a tag or id etc..) which means it does this operation on the element test. So in IE when you do that it probably internally resolves the fact that you are trying to perform the operation on an html element and not an xml element and it changes the document type for the underlying context(But i don't know why it changes the parent context though rather than just returning a match). You can check this out by this simple example as well.
var $test = document.createElement("test");
console.log("However, this <test> has an xml tag prepended: \n"
+ $test.outerHTML);
$test.getElementsByTagName("test");
console.log("Now, it does not: \n" + $test.outerHTML);
Demo
Update
Here is a documented way of defining the custom elements
The custom element type identifies a custom element interface and is a sequence of characters that must match the NCName production and contain a U+002D HYPHEN-MINUS character. The custom element type must not be one of the following values:
annotation-xml,
color-profile,
font-face,
font-face-src,
font-face-uri,
font-face-format,
font-face-name,
missing-glyph
So according to this had your tag name been somename-test ex:- custom-test IE recognizes it and it works as expected.
Demo

Custom self-closing / unpaired tags in HTML?

The following code [jsfiddle]...
var div = document.createElement("div");
div.innerHTML = "<foo>This is a <bar /> test. <br> Another test.</foo>";
alert(div.innerHTML);
...shows this parsed structure:
<foo>This is a <bar> test. <br> Another test.</bar></foo>
i.e. the browser knows that <br> has no closing tag but since <bar> is an unknown tag to the browser, it assumes that it needs an closing tag.
I know that the /> (solidus) syntax is ignored in HTML5 and invalid in HTML4, but anyway would like to teach somehow the browser that <bar> does not need an ending tag and I can omit it. Is that possible?
Yes, I'm trying to (temporarily) misuse the HTML code for custom tags and I have my specific reasons to do that. After all, browsers should ignore unknown tags and treat them just like unstyled inline tags, so I should not break anything as long I can make sure the tag names won't ever be used in real HTML standards.
You'd have to use Object.defineProperty on HTMLElement.prototype to override the innerHTML setter and getter with your own innerHTML implementation that treats the elements you want as void. Look here for how innerHTML and the HTML parser is implemented by default.
Note though that Firefox sucks at inheritance when it comes to defining stuff on HTMLElement.prototype where it filters down to HTMLDivElement for example. Things should work fine in Opera though.
In other words, what elements are void depends on the HTML parser. The parser follows this list and innerHTML uses the same rules mostly.
So, in other words, unless you want to create your own innerHTML implementation in JS, you probably should just forget about this.
You can use the live DOM viewer though to show others how certain markup is parsed. You'll then probably notice that same end tags will implicitly close the open element.
I have some outdated innerHTML getter (not setter though) code here that uses a void element list. That may give you some ideas. But, writing a setter implementation might be more difficult.
On the other hand, if you use createElement() and appendChild() etc. instead of innerHTML, you shouldn't have to worry about this and the native innerHTML getter will output the unknown elements with end tags.
Note though, you can treat the unknown element as xml and use XMLSerializer() and DOMParser() to do things:
var x = document.createElement("test");
var serializer = new XMLSerializer();
alert(serializer.serializeToString(x));
var parser = new DOMParser();
var doc = parser.parseFromString("<test/>", "application/xml");
var div = document.createElement("div");
div.appendChild(document.importNode(doc.documentElement, true));
alert(serializer.serializeToString(div));
It's not exactly what you want, but something you can play with. (Test that in Opera instead of Firefox to see the difference with xmlns attributes. Also note that Chrome doesn't do like Opera and Firefox.)

XPath queries in IE use zero-based indexes but the W3C spec is one-based. How should I handle the difference?

The Problem
I am converting a relatively large piece of Javascript that currently only works on Internet Explorer in order to make it work on the other browsers as well. Since the code uses XPath extensively we made a little compatibility function to make things easier
function selectNodes(xmlDoc, xpath){
if('selectNodes' in xmlDoc){
//use IE logic
}else{
//use W3C's document.evaluate
}
}
This is mostly working fine but we just came across the limitation that positions in IE are zero-based but in the W3C model used by the other browsers they are one-based. This means that to get the first element we need to do //books[0] in IE and //books[1] in the other browsers.
My proposed solution
The first thought was using a regex to add one to all indexes that appear in the queries if we are using the document.evaluate version:
function addOne(n){ return 1 + parseInt(nStr, 10); }
xpath = xpath.replace(
/\[\s*(\d+)\s*\]/g,
function(_, nStr){ return '[' + addOne(nStr) + ']'; }
);
My question
Is this regex based solution reasonably safe?
Are there any places it will convert something it should not?
Are there any places where it will not convert something it should?
For example, it would fail to replace the index in //books[position()=1] but since IE doesn't appear to support position() and our code is not using that I think this particular case would not be a problem.
Considerations
I downloaded Sarissa to see if they have a way to solve this but after looking at the source code apparently they don't?
I want to add one to the W3C version instead of subtracting one in the IE version to ease my conversion effort.
In the end
We decided to rewrite the code to use proper XPath in IE too by setting the selection language
xmlDoc.setProperty("SelectionLanguage", "XPath");
we just came across the limitation that positions in IE are zero-based
but in the W3C model used by the other browsers they are one-based.
This means that to get the first element we need to do //books[0] in
IE and //books[1] in the other browsers.
Before doing any XPath selection, specify:
xmlDoc.setProperty("SelectionLanguage", "XPath");
MSXML3 uses a dialect of XSLT/XPath that was in use before XSLT and XPath became W3C Recommendations. The default is "XSLPattern" and this is what you see as behavior.
Read more on this topic here:
http://msdn.microsoft.com/en-us/library/windows/desktop/ms754679(v=vs.85).aspx
Why not modify the original expressions, so that this:
var expr = "books[1]";
...becomes:
var expr = "books[" + index(1) + "]";
...where index is defined as (pseudocode):
function index(i) {
return isIE ? (i - 1) : i;
}

Categories