I'm using jQuery in a website has a polyfill for the built-in String.trim(). Site used to run in IE 8 a lot and needed the polyfill, but it doesn't anymore. Unfortunately I can't remove the polyfill from the page -- I don't have permissions to touch that and there is no possible way for me to remove it -- so this bit of code runs before anything I can control:
String.prototype.trim = function() {
return this.replace(/^\s\s*/, "").replace(/\s\s*$/, "")
}
Then jQuery comes along and does this, not realizing that the native String.trim has already by messed with:
// Use native String.trim function wherever possible
trim: core_trim && !core_trim.call("\uFEFF\xA0") ?
function( text ) {
return text == null ?
"" :
core_trim.call( text );
} :
// Otherwise use our own trimming functionality
function( text ) {
return text == null ?
"" :
( text + "" ).replace( rtrim, "" );
},
Up to now this hasn't really been much of a problem, but I'm using the Datatables plugin for jQuery and it has many places where it calls $.trim() on data that isn't a string, like number or arrays. The native code in IE 11 and Chrome (the browsers we target) knows to just return the value of $.trim(6), but the polyfill doesn't.
I tried redefining the the prototype with a function that should work:
String.prototype.trim = function(){
if(typeof this.valueOf(this) === 'string'){
return this.replace(/^\s\s*/, "").replace(/\s\s*$/, "");
} else {
return this.valueOf(this);
}
}
But that didn't work because jQuery has already extend using the polyfill and further changes to the prototype won't change what jQuery is using.
I tried following this thread to redefine $.trim(), but that didn't work.
Is there a way to return String.prototype.trim() to its native code?
Is there a way to redefine $.trim()?
Some other idea I haven't thought of?
You can override jQuery core methods
(function(){
var string = " TRIM ME ";
console.log(jQuery.trim(string));
// Define overriding method.
jQuery.trim = function(string){
return string;
}
console.log(jQuery.trim(string));
})();
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
Simply override jQuery's $.trim() using String.prototype.trim(), then override String.prototype.trim() with your function:
var trimText = " Some text ";
String.prototype.trim = function() {
return this.replace(/^\s\s*/, "").replace(/\s\s*$/, "")
}
$.trim = function(string) {
return String.prototype.trim(string);
}
trimText = trimText.trim();
console.log(trimText);
<script src="https://code.jquery.com/jquery-3.3.1.js"></script>
Based on Jack Bashford's answer I came up with this.
String.prototype.trim = function(){
if(typeof this.valueOf(this) === 'string'){
return this.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, '');
} else {
return this.valueOf(this);
}
};
$.trim = function(e){
return String.prototype.trim.call(e);
};
Part of the original problem was that I needed to fix it so that if $.trim(number) or $.trim(array) was called it wouldn't throw and error.
Related
I am trying to write a JQuery plugin to support $ in element id, my code is:
DollarSignPlugin.js:
function escapeJQueryElementName(elementName) {
elementName = elementName.replace(/\\\$/g, "$");
return elementName.replace(/\$/g, "\\$");
}
var jQueryInit = $.fn.init;
$.fn.init = function(arg1, arg2, rootjQuery){
arg2 = arg2 || window.document;
if (arg1 && arg1.replace) {
var newArg1 = escapeJQueryElementName(arg1);
return new jQueryInit(newArg1, arg2, rootjQuery);
}
return new jQueryInit(arg1, arg2, rootjQuery);
};
It is working great, but i faced one issue with current line:
var $anyselector = $("#");
JQuery throw error "Syntax error, unrecognized expression: #".
This line is from bootstrap when clicking on any tab with href="#".
This error doesn't appear when removing my plugin, also if i copy paste the replace function to directly the jquery file it works fine.
So is there a better way to override the selector or i have some issue with my code, please help?
I would strongly recommend you don't do this (but if you want to, keep reading, I do have a fix below), because:
It means you're using invalid selectors in your code, which is a maintenance issue — at some point, someone doing maintenance won't understand what's going on, or you'll hit an edge condition, etc.
Your current implementation will mess up things like
$("<div>$12</div>").appendTo(document.body);
(by putting a backslash in front of the dollar sign). Who knows what else it's messing up?
Instead, I'd just have a simple:
function $gid(id) {
var e = document.getElementById(id);
if (!e) {
return $();
}
if (e.id === id) {
return $(e);
}
// Fallback processing for old IE that matches name attributes
return $('[id="' + id.replace(/"/g, '\\"') + '"]').filter(function() {
return this.id === id;
}).first();
}
Example:
// Plugin
function $gid(id) {
var e = document.getElementById(id);
if (!e) {
return $();
}
if (e.id === id) {
return $(e);
}
// Fallback processing for old IE that matches name attributes
return $('[id="' + id.replace(/"/g, '\\"') + '"]').filter(function() {
return this.id === id;
}).first();
}
// Use
$gid("a$b").css("color", "blue");
$gid("$c").css("color", "green");
<div id="a$b">a$b</div>
<div id="$c">$c</div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
However: If you're going to do it, you have to be careful to tread as softly as possible. That means calling the original with exactly the same number of arguments, the same thisvalue, etc.; and making sure that $.fn.init.prototype has the same value after you replace $.fn.init as before.
Doing both of those things seems to solve the problem:
// Plugin
(function () {
function escapeJQueryElementName(elementName) {
elementName = elementName.replace(/\\\$/g, "$");
return elementName.replace(/\$/g, "\\$");
}
var slice = Array.prototype.slice;
var jQueryInit = $.fn.init;
$.fn.init = function (arg1) {
var args = slice.call(arguments, 0);
if (arg1 && arg1.replace) {
args[0] = escapeJQueryElementName(arg1);
}
return jQueryInit.apply(this, args);
};
$.fn.init.prototype = $.fn;
})();
// Use
$("#a$b").css("color", "blue"); // Using special version that handles $ without \\
$("#$c").css("color", "green"); // Using special version that handles $ without \\
$("<div>Checking '#' selector, should get 0: " + $("#").length + "</div>").appendTo(document.body);
<div id="a$b">a$b</div>
<div id="$c">$c</div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
Instead of trying to attack this problem by overriding something that should really only be used internally, a nicer solution is a function that will create a new alias of jQuery with support for escaping selectors.
Btw, keep in mind that escaping should not be done for things that are HTML, e.g. $('<span>give me $$$</span>'), so I've made a crude check for that.
(function(jQuery) {
jQuery.withSelectorEscaping = function() {
function escapeJQueryElementName(elementName) {
elementName = elementName.replace(/\\\$/g, "$");
return elementName.replace(/\$/g, "\\$");
}
return function(selector, context) {
// avoid doing this for HTML
if (selector && selector.replace && selector[0] !== '<') {
selector = escapeJQueryElementName(selector);
}
return jQuery(selector, context);
};
}
}(jQuery));
// create new alias here
$ = jQuery.withSelectorEscaping();
// use new alias
console.log($('#'));
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
This code snippet adds a new function to jQuery itself called jQuery.withEscaping; it returns a function which you can then assign to an alias of your choosing, I picked $ here but you could also do:
$foo = jQuery.withSelectorEscaping();
It leaves the behaviour of jQuery itself alone and only exposes your escaping feature for things that need it.
I'm building a very simple Validation plugin that uses part of the Constraint validation API. I'm using 2 features of the API, valueMissing and patternMismatch like so:
var el = $(this)[0];
if ( el.validity.valueMissing || el.validity.patternMismatch ) {
$errMsg.show();
} else {
$errMsg.hide();
}
I'd like to write my own polyfill so these features work in older browsers as opposed to using an entire HTML5 validation library or plugin.
So I guess what I'm trying to achieve is something like this:
if (!typeof document.createElement( 'input' ).checkValidity == 'function'){
validity.valueMissing = function(){
// check for value
}
}
However, I can't figure out how to go about this, nor how the element is used as the first part of the function:
el.validity.valueMissing
Thanks in advance for any help or suggestions!
There would be a bit of work involved for this poly, but what you need to do, is alter HTMLInputElement's prototype, so each new instance has those methods and propertyes (validity, checkValidity and so on).
Something along these lines:
HTMLInputElement.prototype.testMethod = function (e) { console.log(e) };
HTMLInputElement.prototype.testProp = 'foo';
var input = document.createElement('input');
input.testMethod(input.testProp);
So I finally figured this out with some help from Poelinca's answer...
if ( typeof document.createElement( 'input' ).checkValidity !== 'function' ) {
Object.defineProperty( Element.prototype, 'validity', {
get: function() {
return this;
}
});
Object.defineProperty( Element.prototype.validity, 'valueMissing', {
get: function() {
return this.value !== "";
}
});
Object.defineProperty( Element.prototype.validity, 'patternMismatch', {
get: function() {
var pattern = this.getAttribute('pattern'),
regEx = pattern !== null ? eval( "{/" + pattern + "/}" ) : "";
value = this.value;
if ( regEx !== "" ) {
return regEx.test( value );
} else {
return true;
}
return false;
}
});
}
I used Object.defineProperty so I could call the function without using the parenthesis, which is how the native Constraint validation API works:
if ( el.validity.valueMissing || el.validity.patternMismatch ) { //...
From my research I learned that extending the DOM like this isn't really recommended as you can run into a lot of issues. For details read this article. The comments are particularly helpful as this article is quite dated.
In my case what I'm trying to do is quite simple and I only need to support IE8+. Maybe a little overkill but I like it.
Please feel free to improve / critique my answer!
Thanks!
Question
I'd like to know the best way I can wrap the jQuery function while retaining all functionality. Essentially I want to call $('#someId') but have it operate as $('#' + id + 'someId') by wrapping the function, modifying the arguments, and passing it through to the original jQuery function.
Motivation
I have a section of JS that will reuse the same variable winId which is concatenated and passed to jQuery. Instead of writing
$('#' + winId + 'someId').html();
$('#' + winId + 'someOtherId').css();
...
$('#' + winId + 'someThirdId').text();
throughout the whole file, I want to wrap the jQuery function so I can just call
$('#someId').html();
$('#someOtherId').css();
...
$('#someThirdId').text();
and and have winId added in before passing through to $.
My attempt
Here's what I'm thinking as a wrapper:
(function() {
var fn = $;
return $ = function() {
for ( var i = 0; i < arguments.length; i++ ) {
if ( typeof arguments[i] == 'string') {
arguments[i] = /* code to add in winId, omitted */
}
}
return fn.apply( this, arguments );
}
})();
This works great, except that obviously none of the methods like $.ajax are available:
Uncaught TypeError: Object function () {
for ( var i = 0; i < arguments.length; i++ ) {
if ( typeof arguments[i] == 'string' ) {
arguments[i] = /* code to add in winId, omitted */
}
}
return fn.apply( this, arguments );
} has no method 'ajax'
Note: I know I could copy the object over using jQuery.extend($, jQuery), but I'm interested in a more elegant solution than that if possible.
Here's a different implementation:
DEMO
(jQuery.fn.init = (function (init) {
return function (selector) {
if (typeof selector === 'string' && selector[0] === '#') {
arguments[0] = selector.replace('#', '#prefix_');
}
return init.apply(this, arguments);
};
})(jQuery.fn.init)).prototype = jQuery.fn;
$(function () {
console.log($('#test').length);
console.log($.ajax);
});
EDIT: Followup question: How can I apply this only within a closure? For example, within an object.
Perhaps with functions that allows to add named decorators and remove them, something like:
HTML
<div id="prefix_test"></div>
JS
var decJQ = (function (decJQ, $) {
var decorators = {},
init = $.fn.init;
($.fn.init = function () {
for (var k in decorators) {
if (decorators.hasOwnProperty(k)) {
arguments = decorators[k].apply(this, arguments);
}
}
return init.apply(this, arguments);
}).prototype = $.fn;
return $.extend(decJQ, {
decorate: function (name, fn) {
decorators[name] = fn;
},
undecorate: function (name) {
delete decorators[name];
}
});
})(window.decJQ || {}, jQuery);
decJQ.decorate('idPrefix', function (selector) {
if (typeof selector === 'string' && selector[0] === '#') {
arguments[0] = selector.replace('#', '#prefix_');
}
return arguments;
});
$(function () {
console.log($('#test').length); //1
decJQ.undecorate('idPrefix');
console.log($('#test').length); //0
});
EDIT 2:
You could also go for something extremely simple, such as:
(function ($) {
//use $ which has been wrapped
})(function () {
//do some manipulations
return jQuery.apply(this, arguments);
});
Following the suggestion by Bergi and the post he links to here, this is one way to go:
$.fn.extend({
initCore: $.fn.init,
init: function (selector, context, rootjQuery) {
if (typeof selector === 'string' && selector[0] === '#') {
selector = selector.replace('#', '#' + winId);
}
return $.fn.initCore(selector, context, rootjQuery);
}
});
$.fn.init.prototype = $.fn;
I've tested $('#foo') will find a div that has a winId prefixed to the id value, like this <div id="1foo"></div>.
For example: http://jsfiddle.net/MfdJS/1/
Add class="winID" to your elements.
Use $(".winID").find('#someId").css(...) to access CSS attributes of specific element.
Use $(".winID").css(...) to access CSS attribues to all winID tagged elements.
ok well i just tested
$('.con'+'tainer')
and
$('d'+'iv');
and
var s = 't';
$('.con'+s+'ainer');
and the console is returning the correct values
i belive that you are calling a function jQuery() with a string parameter, so as long as you use the normal syntax for building/appending/constructing a string with the plus signs, i think you're golden. im glad you asked this question because now i know too
That's a pretty strange thing to do. Why don't you just create a CSS selector string for winId and save it as a variable?
var foo = '#' + winId;
Now you can do:
$(foo + ', #bar').html("add some content");
What you're proposing to do will leave any programmer working on this project -- including you six months from now -- completely flummoxed when they use $('#bar') and it's actually selecting #foo and #bar.
I'm using the NicEdit WYSIWYG plugin on my site.
It's come to my attention that when NicEdit is instantiated in Chrome, the following Javascript error is generated:
Uncaught TypeError: Object has no method 'createRange'
This doesn't stop the plugin from working, but I would like to prevent this if possible. Here is the offending method:
getRng : function() {
var s = this.getSel();
if(!s) { return null; }
return (s.rangeCount > 0) ? s.getRangeAt(0) : s.createRange();
}
NicEdit seems to be pretty much dead as a project, which is why I am asking this question here instead of over at the NicEdit forums. I am hoping that someone knows of a 'quickfix' to this problem. In all other respects NicEdit works well for me, so I am reluctant to change over to a different WYISWYG plugin just yet...
Thanks (in advance) for your help.
The problem is that the implementation of the selection object for Webkit does not define a createRange( ) method. That method seems to be specific to Internet Explorer. For Webkit and Gecko DOM implementations, the createRange( ) method is defined on the document object. With this knowledge, the fix for getRng( ) becomes:
getRng : function() {
var s = this.getSel();
var rng;
if(!s) { return null; }
if (s.rangeCount > 0) {
rng = s.getRangeAt(0);
} else if ( typeof s.createRange === 'undefined' ) {
rng = document.createRange();
} else {
rng = s.createRange();
}
return rng;
},
I encountered this as I was evaluating a number of rich text editors for an upcoming project and had to create a sample page with nicEdit.
The version at https://github.com/danishkhan/NicEdit contains this and other bugfixes.
This particular fix: https://github.com/danishkhan/NicEdit/commit/efa6a1e8867b745b841157e919a0055cb626d2c4
Same code, written in nicEdit current design:
getRng : function() {
var s = this.getSel();
if(!s) { return null; }
return (s.rangeCount > 0) ? s.getRangeAt(0) : (typeof s.createRange == 'undefined') ? document.createRange() : s.createRange();
},
How to improve the following if-else structure in JavaScript?
if(isIE())
if(termin != "")
TargetElement.onclick = function() {merkzettelRemove(this, id, termin)};
else
TargetElement.onclick = function() {merkzettelRemove(this, id)};
else
if(termin != "")
TargetElement.setAttribute("onclick","merkzettelRemove(this, " + id + ",
'" + termin + "')");
else
TargetElement.setAttribute("onclick","merkzettelRemove(this, " + id + ")");
// get a cross-browser function for adding events
var on = (function(){
if (window.addEventListener) {
return function(target, type, listener){
target.addEventListener(type, listener, false);
};
}
else {
return function(object, sEvent, fpNotify){
object.attachEvent("on" + sEvent, fpNotify);
};
}
}());
// add the event listener
on(TargetElement, "click", function(){
// if termin is empty we pass undefined, this is the same as not passing it at all
merkzettelRemove(this, id, (termin) ? termin : undefined);
});
First things first, put some more braces in. It'll make it clearer what's going on, as well as saving untold heartache when you come to edit this in future.
Yes, you can get away without the braces if they wrap a single statement. When you come along three months from now and add something else into one of those blocks, your code will break unless you remember to wrap the whole block in braces. Get into the habit of doing it from the beginning.
First, use { } everywhere, it will make your loops more readable.
Second: I think this does the same
if(isIE()) {
TargetElement.onclick = function() {
merkzettelRemove(this, id, termin || null);
};
} else {
TargetElement.setAttribute("onclick",
"merkzettelRemove(this, " + id + ",'" + termin || null + "')");
}
Third, but you could try using unobtrusive javascript to add handlers to TargetElement
Firstly, lose the browser-sniffing. onclick= function... works everywhere; setAttribute is not only broken in IE, but also ugly. JavaScript-in-a-string is a big code smell; avoid. setAttribute on HTML attributes has IE problems and is less readable than simple DOM Level 1 HTML properties anyway; avoid.
Secondly, it would be ideal to make merkzettelRemove accept an out-of-band value (null, undefined, or even '' itself) as well as an omitted argument. It is possible it already allows undefined, depending on what mechanism it is using to support optional arguments. If so you could say simply:
TargetElement.onclick= function() {
merkzettelRemove(this, id, termin || undefined);
};
If you must completely omit the argument and you don't want to use an if...else, there's another way around although IMO the clarity is worse:
TargetElement.onclick= function() {
merkzettelRemove.apply(null, termin==''? [this, id] : [this, id, termin]);
};
I think using a Javascript framework is the best solution, but you can try something like this:
var src="merkzettelRemove(this, " + id + (termin && (",'" + termin + "'")) + ")";
if (isIE()) {
TargetElement.onclick = new Function(src);
} else {
TargetElement.setAttribute("onclick", src);
}
I know this is not an answer for your question, but if you don't have any reason for not doing so you should definitely use a library that cares about compatibility issues for you, such as jQuery. Then you could write something like this:
var el = $(TargetElement);
if (termin != "")
el.click(function() {merkzettelRemove(this, id, termin)});
else
el.click(function() {merkzettelRemove(this, id)});
how about using short circuiting operators. So the following code
if(A)
{
if(B) {
C; }
else{
D;}
else{
if(E)
F;
else
G;
}
}
Will become
A && ((B&&C) || D || ((E&&F) || G));