A weird regex problem - javascript

The following code results in "undefined" for lastIndex:
var a = /cat/g;
var l = "A test sentence containing cat and dog.";
var r = a.exec(l);
document.write(r.lastIndex);
However, it works perfectly for r.index (and r.input).
I am using Firefox. Does anybody have a clue?
EDIT: OK, the above code works perfectly in IE! Further, in Firefox, it works perfectly if instead of calling r.lastIndex on line 5, a.lastIndex is called. It appears that Firefox does not return lastIndex property in the result - rather sets the property for the pattern invoking the exec() only. Interesting that IE sets both.

This is one of those places where Microsoft decided to add some stuff to the language and act as if it was supposed to be there. Thankfully, they are now cleaning up their act and documenting such nonsense.
To be clear: Firefox is correct according to the ECMAScript Language Specification 3rd Edition (PDF, 705KB).
IE is not correct; its behaviour is a proprietary extension. There is no reason to believe that this IE-specific behaviour will ever be supported by any other browser. It certainly isn't at the moment. See JScript Deviations from ES3 (PDF, 580KB, by Pratap Lakshman of Microsoft Corporation) Section 4.6 for more on this particular deviation from the spec, including tests showing no support on other browsers.
Note also that this may not even be supported in the future by IE: a number of proprietary IE CSS-related mechanisms are disabled by default on IE8-in-IE8-mode, and future implementations of JScript may find a reason to similarly disable this extension to the language.

lastIndex is a property of a RegExp object. So try this:
a.lastIndex

To avoid all the weird, try this
var a = /cat/g;
var l = "A test sentence containing cat and dog.";
var r = a.exec(l);
var lastIndex = (r!=null) ? l.indexOf(r[0])+r[0].length : 0;
It is used here: http://www.pagecolumn.com/tool/regtest.htm

Related

Why is String.toLowerCase undefined in Chrome but not Firefox?

In the Chrome console, String.toLowerCase returns undefined. However in Firefox, it does not.
What's the reason for the difference?
var body = $("body");
body.append(new String(String.toLowerCase).toString());
body.append("<br>");
body.append(String.prototype.toLowerCase.toString());
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
String.toLowerCase is one of the Generics that Firefox has allowed for String, Array (and possibly other). They are not defined in any ECMAScript standard and are considered deprecated by Firefox and will be removed.
Extracted from the MDN docs:
String generics are non-standard, deprecated and will get removed near future. Note that you can not rely on them cross-browser without using the shim that is provided below.
See also:
Introduction of Generics in Firefox's JS 1.6
Deprecation announcement (Not sure how much support the site has from Mozilla, so take this one with a grain of salt.)
Deprecation warning implementation ticket
Is this what you are trying to do? Either of these work fine in chrome for me.
let body = $("body");
let str = 'STRING TO LOWER';
body.append(str.toLowerCase());
body.append("<br>");
body.append("ALL CAPS".toLowerCase());
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

When a string is not a string? Unicode normalization weirdness in Javascript

I have run into what is, to me, some serious weirdness with string behavior in Firefox when using the .normalize() Unicode normalization function.
Here is a demo, view the console in Firefox to see the problem.
Suppose I have a button with an id of "NFKC":
<button id="NFKC">NFKC</button>
Get a reference to that, easy enough:
document.querySelector('#NFKC')
// <button id="NFKC">
Now, since this button has an id of NFKC, which we can get at that string as follows:
document.body.querySelector('#NFKC').id
// "NFKC"
Stick that string in a variable:
var s1 = document.body.querySelector('#NFKC').id
By way of comparison, assign the very same string to a variable directly:
var s2 = 'NFKC'
So of course:
s1 === s2
// true
And:
s1 == s2
// true
Now’s the part where my head explodes.
To normalize a string, you pass one of NFC, NFD, NFKC, or NFKD to .normalize(), like this:
'á'.normalize('NFKC')
// "á"
Of course, depending on the normalization form you choose, you get different codepoints, but whatever.
'á'.normalize('NFC').length == 1
// true
'á'.normalize('NFD').length == 2
// true
But whatever. The point is, pass one of four strings corresponding to normalization forms to .normalize(), and you'll get a normalized string back.
Since we know that s1 (the string we retrieved from the DOM) and s2 are THE SAME STRING (s1 === s2 is true), then obviously we can use either to normalize a string:
'á'.normalize(s2)
"á"
// well yeah, because s2 IS 'NFKC'.
Naturally, s1 will behave exactly the same way, right?
'á'.normalize(s1)
// RangeError: form must be one of 'NFC', 'NFD', 'NFKC', or 'NFKD'
Nope.
So the question is: why does it appear that s1 is not equal to s2 as far as .normalize() is concerned, when s1 === s2 is true?
This doesn’t happen in Chrome, the only other browser I’ve tested so far.
UPDATE
This was a bug in Firefox and has been fixed.
I'm not sure if this will help, but the documentation states that
This is an experimental technology, part of the Harmony (ECMAScript 6) proposal.
Because this technology's specification has not stabilized, check the compatibility table for usage in various browsers. Also note that the syntax and behavior of an experimental technology is subject to change in future version of browsers as the spec changes.
And the compatibility table is
Feature Chrome Firefox (Gecko) Internet Explorer Opera Safari
Basic support 34 31 (31) 11 on Windows 10 Preview (Yes) Not supported
However, the last update to this page was Nov 18, 2014.

Making a short alias for document.querySelectorAll

I'm running document.querySelectorAll() frequently, and would like a short alias for it.
var queryAll = document.querySelectorAll
queryAll('body')
TypeError: Illegal invocation
Doesn't work. Whereas:
document.querySelectorAll('body')
Still does. How can I make the alias work?
This seems to work:
const queryAll = document.querySelectorAll.bind(document);
bind returns a new function which works identically to the querySelectorAll function, where the value of this inside the querySelectorAll method is bound to the document object.
The bind function is only supported in IE9+ (and all the other browsers) - https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Function/bind
Update: In fact you could create shortcuts to a whole range of document methods like this:
const query = document.querySelector.bind(document);
const queryAll = document.querySelectorAll.bind(document);
const fromId = document.getElementById.bind(document);
const fromClass = document.getElementsByClassName.bind(document);
const fromTag = document.getElementsByTagName.bind(document);
A common answer is to use $ and $$ for querySelector and querySelectorAll. This alias mimics jQuery's one.
Example:
const $ = document.querySelector.bind(document)
const $$ = document.querySelectorAll.bind(document)
$('div').style.color = 'blue'
$$('div').forEach(div => div.style.background = 'orange')
div {
margin: 2px;
}
<div>
test
</div>
<section>
<div>
hello
</div>
<div>
foo
</div>
</section>
The JavaScript interpreter throws an error because querySelectorAll() should be invoked in document context.
The same error is thrown when you are trying to call console.log() aliased.
So you need to wrap it like this:
function x(selector) {
return document.querySelectorAll(selector);
}
My solution covers the four following use cases:
document.querySelector(...)
document.querySelectorAll(...)
element.querySelector(...)
element.querySelectorAll(...)
The code:
let doc=document,
qsa=(s,o=doc)=>o.querySelectorAll(s),
qs=(s,o=doc)=>o.querySelector(s);
In terms of parameters, the selector s is required, but the container element object o is optional.
Usage:
qs("div"): Queries the whole document for the first div, returns that element
qsa("div"): Queries the whole document for all divs, returns a nodeList of all those elements
qs("div", myContainer): Queries just within the myContainer element for the first div, returns that element
qsa("div", myContainer): Queries just within the myContainer element for all divs, returns a nodeList of all those elements
To make the code slightly shorter (but not quite as efficient), the qs code could be written as follows:
let qs=(s,o=doc)=>qsa(s,o)[0];
The code above uses ES6 features (let, arrow functions and default parameter values). An ES5 equivalent is:
var doc=document,
qsa=function(s,o){return(o||doc).querySelectorAll(s);},
qs=function(s,o){return(o||doc).querySelector(s);};
or the equivalent shorter but less efficient ES5 version of qs:
var qs=function(s,o){return qsa(s,o)[0];};
Below is a working demo. To ensure it works on all browsers, it uses the ES5 version, but if you're going to use this idea, remember that the ES6 version is shorter:
var doc = document;
var qs=function(s,o){return(o||doc).querySelector(s);},
qsa=function(s,o){return(o||doc).querySelectorAll(s);}
var show=function(s){doc.body.appendChild(doc.createElement("p")).innerHTML=s;}
// ____demo____ _____long equivalent______ __check return___ _expect__
// | | | | | | | |
let one = qs("div"); /* doc.querySelector ("#one") */ show(one .id ); // "one"
let oneN = qs("div",one); /* one.querySelector ("div") */ show(oneN .id ); // "oneNested"
let many = qsa("div"); /* doc.querySelectorAll("div") */ show(many .length); // 3
let manyN = qsa("div",one); /* one.querySelectorAll("div") */ show(manyN.length); // 2
<h3>Expecting "one", "oneNested", 3 and 2...</h3>
<div id="one">
<div id="oneNested"></div>
<div></div>
</div>
This would work, you need to invoke the alias using call() or apply() with the appropriate context.
func.call(context, arg1, arg2, ...)
func.apply(context, [args])
var x = document.querySelectorAll;
x.call(document, 'body');
x.apply(document, ['body']);
I took #David Muller's approach and one-lined it using a lambda
let $ = (selector) => document.querySelector(selector);
let $all = (selector) => document.querySelectorAll(selector);
Example:
$('body');
// <body>...</body>
function x(expr)
{
return document.querySelectorAll(expr);
}
If you don't care about supporting ancient, awful browsers that nobody should be using anymore, then you can just do this:
const $ = (sel, parent = document) => parent.querySelector(sel);
const $$ = (sel, parent = document) => Array.from(parent.querySelectorAll(sel));
Here's some examples of usage:
// find specific element by id
console.log($("#someid"));
// find every element by class, within other element
// NOTE: This is a contrived example to demonstrate the parent search feature.
// If you don't already have the parent in a JavaScript variable, you should
// query via $$("#someparent .someclass") for better performance instead.
console.log($$(".someclass", $("#someparent")));
// find every h1 element
console.log($$("h1"));
// find every h1 element, within other element
console.log($$("h1", $("#exampleparent")));
// alternative way of finding every h1 element within other element
console.log($$("#exampleparent h1"));
// example of finding an element and then checking if it contains another element
console.log($("#exampleparent").contains($("#otherelement")));
// example of finding a bunch of elements and then further filtering them by criteria
// that aren't supported by pure CSS, such as their text content
// NOTE: There WAS a ":contains(text)" selector in CSS3 but it was deprecated from the
// spec because it violated the separation between stylesheets and text content, and you
// can't rely on that CSS selector, which is why you should never use it and should
// instead re-implement it yourself like we do in this example.
// ALSO NOTE: This is just a demonstration of .filter(). I don't recommend using
// "textContent" as a filter. If you need to find specific elements, use their way
// more reliable id/class to find them instead of some arbitrary text content search.
console.log($$("#exampleparent h1").filter(el => el.textContent === "Hello World!"));
The functions I provided use a ton of modern JS features:
const to make sure the function variable can't be overwritten.
functions defined as an arrow function aka lambda: (args) => code with implied return statement
default parameters (not supported by browsers before the year 2016)
no {} or return, since those can be skipped if there's just 1 statement in the function body.
The modern function Array.from() is used, which converts the querySelectorAll result (which is always a NodeList, or empty NodeList), into an Array, which is basically what every developer wants. Because that's how you get access to .filter() and other Array functions that allow you to process the discovered nodes further, using clean, short code. And Array.from() creates a shallow copy of all elements which means that it's blazingly fast (it just copies the memory references/pointers to each Node DOM element from the original NodeList). It's a major API enhancer.
If you care about ancient browsers, you can still use my functions but use Babel to convert the modern JS to old ES5 when you release your website.
My suggestion would be to write your entire site in ES6 or higher and then use Babel if you care about visitors from Windows XP and other dead operating systems, or just random people who haven't updated their browsers in 5+ years.
But I wouldn't recommend using Babel. Stop worrying about people who have old browsers. It is their problem, not yours.
The modern "app" world is incredibly deeply based on web browsers and JavaScript and modern CSS, and most of your visitors these days have modern, auto-updated browsers. You basically can't live modern life without a modern browser, since so many websites demand it now.
In my opinion, the days of expecting web designers to waste their time and sanity trying to make a site work on browsers from 1993 are over. It's time the laziest customers/visitors update their old browsers instead. It's not difficult. Even old and dead operating systems usually have ways to install new versions of browsers on them. And people who don't have an updated browser are only a tiny fraction of a percent these days.
For example, the Bootstrap framework, the world's most popular framework for mobile/responsive sites, only cares about supporting the 2 most recent major versions of all major browsers (at least 0.5% market share). Here's their list as of this moment:
0.5% market share or higher
last 2 major versions only
not a dead browser (not discontinued)
Chrome >= 60
Firefox >= 60
Firefox ESR
iOS >= 12
Safari >= 12
not Explorer <= 11 (meaning no versions of Internet Explorer at all)
And I completely agree with this. I was a web developer in the early 2000s and it was absolute hell. Being expected to make it work on some random, stupid user's ancient browser was hell. It took all the fun out of web development. It made me hate and quit web development. Because 90% of my time was wasted on browser compatibility. That's not how life should be. It's not your fault that some customers/visitors are lazy. And these days, visitors have no more excuses to stay lazy.
Instead, you should only target users who have modern browsers. Which is basically everybody these days. There is no excuse for anyone to use an old browser. And if they use an old browser, your site should show a big, fat banner saying "Please, join the modern world for your own sake. Download a new browser. How are you even able to live your normal life with such an old browser? Are you a time traveler from caveman times?".
People have no excuses to have old browsers anymore:
Linux: Ships with the latest versions of Firefox by default.
Mac: Ships with Safari, which is a modern browser. But some older macOS versions won't get newer versions of Safari, and older machines often can't install the latest macOS version either. Well, tough luck for those visitors. They are gonna have trouble on more than just your website. It's up to them to install a modern browser (such as Chrome), which they are able to do even on old versions of macOS. So they have no excuses. Don't waste your life catering to people on very old, buggy Safari versions.
Windows: Windows 10 ships with Edge, which is based on Chromium and is as compatible with websites as Chrome is. People have no excuse to have an old browser. And most Windows users use Chrome. As for very old, discontinued versions of Windows (XP, Vista, 7, 8), well, we yet again arrive at the same question as before: Do you care about 0.0000001% stupid visitors who use a dead OS and an old Internet Explorer version? The whole freaking web will be broken for them anyway, so who cares if your site is broken for them too? They should stop being lazy and just upgrade their OS to Windows 10, or at least install Chrome or Firefox on their current OS. They have no excuses.
iOS: If you're stuck on a super old iOS device, then you can't use the modern web. Tough luck. A lot of the web is gonna be broken for you. Get a new device. Even frameworks like Bootstrap, the world's #1 mobile web framework, doesn't support iOS 11 or earlier. It's not our problem. It's the cheapskate visitor's problem if they still hang onto such an old device. They can literally get a newer iOS device second-hand for almost no money at all and fix all of their problems with visiting the modern web. And they'll need to buy that anyway since most apps (even banking/important apps) require modern iOS versions.
Android: The browser is independently updated from the OS, and can even be sideloaded, so even if you're stuck on old Android versions, you have access to modern browsers. So you have no excuses.
Most people these days have browsers that are completely up-to-date and auto-updated. That's the fact.
So yeah... the days of website designers suffering through hell just for catering to old browsers are over. Therefore I suggest that people use ES6 and CSS3 for their websites, to make web designing a joy for the first time.
Hope you enjoy the ES6 functions I provided!
Here is my take on it. If the selector has multiple matches, return like querySelectorAll. If ony one match is found return like querySelector.
function $(selector) {
let all = document.querySelectorAll(selector);
if(all.length == 1) return all[0];
return all;
}
let one = $('#my-single-element');
let many = $('#multiple-elements li');
2019 update
Today I made a new take on the problem. In this version you can also use a base like this:
let base = document.querySelectorAll('ul');
$$('li'); // All li
$$('li', base); // All li within ul
Functions
function $(selector, base = null) {
base = (base === null) ? document : base;
return base.querySelector(selector);
}
function $$(selector, base = null) {
base = (base === null) ? document : base;
return base.querySelectorAll(selector);
}
The highly invasive version:
<script>
for(const c of [HTMLDocument, Element, DocumentFragment]) {
c.prototype.$ = c.prototype.querySelector;
c.prototype.$$ = c.prototype.querySelectorAll;
}
const $ = document.$.bind(document); // For inline events
const $$ = document.$$.bind(document);
window.$ = $; // For JS files
window.$$ = $$;
</script>
So you can chain $('nav').$$('a') like jQuery allows (I think). Since ShadowRoot extends DocumentFragment you can even inspect (open) Shadow DOMs:
const styles = $('x-button').shadowRoot.$$('style');
function $(selector, base = null) {
base = (base === null) ? document : base;
return base.querySelector(selector);
}
function $$(selector, base = null) {
base = (base === null) ? document : base;
return base.querySelectorAll(selector);
}
Why not simplier ??? :
let $ = (selector, base = document) => {
let elements = base.querySelectorAll(selector);
return (elements.length == 1) ? elements[0] : elements;
}
you dont need to add any dot before class name you can modify this also by using getEle by class or id tag etc
function $(selector){
var dot = ".";
var newSelector = dot.concat(selector);
var Check = document.querySelectorAll(newSelector);
if(Check && Check.length>0 && Check.length < 2){
return document.querySelector(newSelector);
}
else {
return document.querySelectorAll(newSelector);
}
}
$("answercell")[0].remove();

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;
}

IE Javascript error "Object doesn't support this property or method" within jQuery

For some reason, I am getting the following Javascript error in Internet Explorer 8 on line 3156 of jquery.js (version 1.4.3, non-compressed version): Object doesn't support this property or method. No error occurs in Firefox and Google Chrome.
This is the line the error occurs on:
if ( (match = Expr.leftMatch[ type ].exec( expr )) != null && match[2] ) {
Investigation (console.log(Expr.leftMatch[type])) produces the following interesting result: In Google Chrome, it outputs
/(^(?:.|\r|\n)*?):((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\3\))?(?![^\[]*\])(?![^\(]*\))/
However in Internet Explorer this is the output:
function() {
var p = this;
do p = p.previousSibling;
while (p && p.nodeType != 1);
return p;
}
On which exec cannot be called (it is undefined). The quoted function is not present within jquery.js. Does anyone have any clue why this happens, or what I can do to solve it?
I have, unfortunately, not yet been able to create a simple script to reproduce the problem, although I did find this post of someone having the same problem, but it does not offer a solution (the last post suggests the page should be run in Standards Mode, but mine already is).
As it turns out, I managed to figure it out by myself after several painful hours. It appears the Sizzle selector engine breaks in this unexpected way (and only in Internet Explorer), if you have defined Object.prototype.previousObject elsewhere.
Removing that declaration, or renaming previousObject to something else fixes the problem.
The funny thing is, I even put that code there myself (the Object.prototype.previousObject = [the function in my question]), but I did not recognize the code.
Well, that's another day full of development potential wasted.
I have discovered the same behaviour occurs if you attempt to add a method called "inherited" to the Object.prototype, ie Object.prototype.inherited = <some func>
It affects IE6, 7 & 8 but seems to be fixed in IE9 (beta)
May be to late to respond but I had the same problem and solved with selecting elements with plain java script rather then jquery!
var div = document.getElementById("myDiv");
var rect = div.getBoundingClientRect();
This works some how!

Categories