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.
Related
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.
I'm currently writing a library using jQuery, and I'm facing a wall I can't pass alone.
First, here is some code :
(function($) {
// default options
var defaults = {
option1: "what ever",
option2: "what ever"
};
// available methods
var methods = {
method1: function(param1) {
console.log(param1);
/* ... */
},
method2: function(param1, param2) {
console.log(param1);
console.log(param2); // gives undefined
/* ... */
}
};
// Where magic happens
$.fn.pskChart = function(params) {
if (methods[params] != undefined) {
if (this.length > 0) {
return $(this).each(function(i) {
return methods[params].apply($(this), Array.prototype.slice.call(arguments, 1));
});
}
} else {
$.error("Method " + params + " doesn't exist for pskChart");
}
}
})(jQuery);
$("#sample").pskChart("method1", "param1"); // will work
$("#sample").pskChart("method2", "param1", "param2"); // won't work
This code is working in case I provide only two parameters (method and another parameter), but won't work if I have more parameters
I understand that Array.prototype.slice.call(arguments, 1) returns only one object containing all the remaining arguments.
In the second example, method2 will be called with only one parameter that contains ["param1","param2"]
I would like to "split" this object in order to provide as many parameters I have.
I feel like methods[params].apply($(this), Array.prototype.slice.call(arguments, 1)); but I have no clue how to fix it.
Note that I could have an infinite (or at least big) number of parameters (in addition to the method, that will always be first).
Playing with Array.slice would not be a great (nor proper) solution.
I don't see why it would work even with just one parameter, as you're using the wrong arguments object.
Instead (see comments):
// Where magic happens
$.fn.pskChart = function(params) {
var args; // **Change** We'll use this below
if (methods[params] != undefined) {
if (this.length > 0) {
// **Change** Grab the arguments here, in the `pskChart` function
args = Array.prototype.slice.call(arguments, 1);
return $(this).each(function(i) {
// **Change** Use them here; you can't use `arguments` here
// because, it will be the arguments to this anonymouus
// function, not the ones to `pskChart`
return methods[params].apply($(this), args);
});
}
} else {
$.error("Method " + params + " doesn't exist for pskChart");
}
}
Side note: params is a very strange name for an argument you're using as a method name. Perhaps methodName?
Side note 2: Rather than repeatedly looking up methods[params], consider using a variable. Large sets may benefit from the reduced work.
Side note 3: This line inside your each loop is almost certainly a mistake:
return methods[params].apply($(this), args);
That will return the return value of the method to jQuery's each code. jQuery's each code will completely ignore that vallue unless it's === false, in which case it will stop the each loop. Not at all likely to be what you meant that to do.
Side note 4: Your pskChart function, being a jQuery plugin, should return this unless it has a good reason for returning something else. Right now, the return value is chaotic: If the set has no elements, you return undefined (implicitly); if the set has objects, you return a new jQuery object, not this, because of this line:
return $(this).each(function(i) {
There's no reason for using $() there, this is already a jQuery set (which is why the previous line using this.length works).
Side note 5: You're missing a semicolon from the assignment statement (e.g., there should be one after the closing } on the function expression).
Recommendation:
// Where magic happens
$.fn.pskChart = function(methodName) {
var args, method = methods[methodName];
if (!method) {
// Throws
$.error("Method " + params + " doesn't exist for pskChart");
}
if (this.length > 0) {
args = Array.prototype.slice.call(arguments, 1);
this.each(function() {
method.apply($(this), args);
});
}
return this;
};
Maybe some of you know about AOP, in some languages using AOP can lead you to be able to inject code after, before, or while a method is executing,etc.
What I want is to apply the same in Javascript, I am currently working on a massive app which has more than 300 ajax calls, and every time I need to do some change on the catch statement on them, I have to modify them one by one which is very tedious.
What I want to do is something like :
functionName.before("try {")
functionName.after("} catch(ex){
//dostuff
}")
Is it possible? I know there are things like .call, or the arguments object inside every function..which seem pretty meta-function (AOP) functionalities.
Not with before and after, but a wrap will work:
Function.prototype.wrapTry = function(handle) {
var fn = this;
return function() {
try {
return fn.apply(this, arguments);
} catch(e) {
return handle(e);
}
};
};
Then use it like
var safeFunction = functionName.wrapTry(doStuff);
In JavaScript, functions are first-class objects. That means you can manipulate or redeclare them.
Assuming that there is a "foo" function:
var originalFoo = foo;
foo = function()
{
// "before" code.
// Call the original function.
originalFoo.apply(this, arguments);
// "after" code.
};
After that, any call to foo() will call the new function: even with parameters.
Old question but you may take a look over this https://github.com/k1r0s/kaop-ts/blob/master/docs/api.md#available-join-points
import { onException } from "kaop-ts"
import handlingException from "./somewhere"
class Something {
#onException(handlingException)
method() {
// stuff that may throw an error
}
}
I also will give a late answer in order to shed some light onto this special case that every then and now pops up as JavaScript and AOP.
Firstly, cases like the very one presented by the OP always ask for modifying already existing functionality, thus targeting closed code that sometimes is not even owned by the party that sees itself challenged from modifying the control flow of such code.
Why then, not just name it like that ... JavaScript method modification or JavaScript method modifiers.
Secondly, because of already riding the horse of terminology, altering closed functionality in JavaScript has nothing to do with Aspect-oriented Programming unless an implementation that claims to be AO provides abstraction and code-reuse levels for at least Aspect, Advice and Pointcut.
Last, for what the OP is going to achieve and what also has been the accepted answer, there does exist a a whole bunch of before, after around / wrap solutions, almost always unfortunately mentioning AO(P), and in far too many cases not taking care of the context or target which is essential to method modification.
The example I do provide uses a prototypal implementation of afterThrowing. Because JavaScript already features a standardized bind, I'm firmly convinced that Function.prototype is the right place as well for some other method-modifiers
like before, after, around, afterThrowing
and afterFinally.
// OP's example pseudo code
//
// functionName.before("try {")
//
// functionName.after("} catch(ex){
// dostuff
// }")
function doStuffAfterThrowing(exception, originalArguments) {
"use strict";
var context = this;
console.log('context : ', context);
console.log('String(exception) : ', String(exception));
console.log('originalArguments : ', originalArguments);
return "safely handled exception";
}
function doFail() {
throw (new ReferenceError);
}
function oneOutOfManyAjaxCallbacks(payload) {
doFail();
}
var jsonData = {
"foo": "foo",
"bar": "bar"
};
var someModifiedAjaxCallback = oneOutOfManyAjaxCallbacks.afterThrowing(doStuffAfterThrowing, { x: 'y' });
// does fail controlled/handled.
console.log('someModifiedAjaxCallback(jsonData) : ', someModifiedAjaxCallback(jsonData));
// does fail "Uncaught".
console.log('oneOutOfManyAjaxCallbacks(jsonData) : ', oneOutOfManyAjaxCallbacks(jsonData));
.as-console-wrapper { min-height: 100%!important; top: 0; }
<script>
(function (Function) {
var
isFunction = function (type) {
return (
(typeof type == "function")
&& (typeof type.call == "function")
&& (typeof type.apply == "function")
);
},
getSanitizedTarget = function (target) {
return ((target != null) && target) || null;
}
;
Function.prototype.afterThrowing = function (handler, target) { // afterThrowing
target = getSanitizedTarget(target);
var proceed = this ;
return (isFunction(handler) && isFunction(proceed) && function () {
var ret, args = arguments;
try {
ret = proceed.apply(target, args);
} catch (exc) {
ret = handler.call(target, exc, args);
//throw exc;
}
return ret;
}) || proceed;
};
}(Function));
</script>
Having come that far one might also consider reading ...
sandwich pattern in javascript code
Can you alter a Javascript function after declaring it?
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.
In trying to make my Javascript unobtrusive, I'm using onLoads to add functionality to <input>s and such. With Dojo, this looks something like:
var coolInput = dojo.byId('cool_input');
if(coolInput) {
dojo.addOnLoad(function() {
coolInput.onkeyup = function() { ... };
});
}
Or, approximately equivalently:
dojo.addOnLoad(function() {
dojo.forEach(dojo.query('#cool_input'), function(elt) {
elt.onkeyup = function() { ... };
});
});
Has anyone written an implementation of Ruby's andand so that I could do the following?
dojo.addOnLoad(function() {
// the input's onkeyup is set iff the input exists
dojo.byId('cool_input').andand().onkeyup = function() { ... };
});
or
dojo.byId('cool_input').andand(function(elt) {
// this function gets called with elt = the input iff it exists
dojo.addOnLoad(function() {
elt.onkeyup = function() { ... };
});
});
I don't know Dojo, but shouldn't your first example read
dojo.addOnLoad(function() {
var coolInput = dojo.byId('cool_input');
if(coolInput)
coolInput.onkeyup = function() { ... };
});
Otherwise, you might end up trying to access the element before the DOM has been built.
Back to your question: In JavaScript, I'd implement andand() as
function andand(obj, func, args) {
return obj && func.apply(obj, args || []);
}
Your example could then be written as
dojo.addOnLoad(function() {
andand(dojo.byId('cool_input'), function() {
this.onkeyup = function() { ... };
});
});
which isn't really that much shorter than using the explicit if statement - so why bother?
The exact syntax you want is not possible in JavaScript. The way JavaScript executes would need to change in a pretty fundamental fashion. For example:
var name = getUserById(id).andand().name;
// ^
// |-------------------------------
// if getUserById returns null, execution MUST stop here |
// otherwise, you'll get a "null is not an object" exception
However, JavaScript doesn't work that way. It simply doesn't.
The following line performs almost exactly what you want.
var name = (var user = getUserById(id)) ? user.name : null;
But readability won't scale to larger examples. For example:
// this is what you want to see
var initial = getUserById(id).andand().name.andand()[0];
// this is the best that JavaScript can do
var initial = (var name = (var user = getUserById(id)) ? user.name : null) ? name[0] : null;
And there is the side-effect of those unnecessary variables. I use those variables to avoid the double lookup. The variables are mucking up the context, and if that's a huge deal, you can use anonymous functions:
var name = (function() {return (var user = getUserById(id)) ? user.name : null;})();
Now, the user variable is cleaned-up properly, and everybody's happy. But wow! what a lot of typing! :)
You want dojo.behavior.
dojo.behavior.add({
'#cool_input': {
onKeyUp: function(evt) { ... }
}
});
How about something like this:
function andand(elt, f) {
if (elt)
return f(elt);
return null;
}
Call like this:
andand(dojo.byId('cool_input'), function(elt) {
// this function gets called with elt = the input iff it exists
dojo.addOnLoad(function() {
elt.onkeyup = function() { ... };
});
});
As far as I know there isn't a built-in JavaScript function that has that same functionality. I think the best solution though is to query by class instead of id and use dojo.forEach(...) as you will be guaranteed a non-null element in the forEach closure.
You could always use the JavaScript equivalent:
dojo.byId('cool_input') && dojo.byId('cool_input').whateverYouWantToDo(...);
I've never used dojo, but most javascript frameworks (when dealing with the DOM) return the calling element when a method is called from the element object (poor wording, sorry). So andand() would be implicit.
dojo.addOnLoad(function() {
dojo.byId('cool_input').onkeyup(function(evt) { /*event handler code*/
});
});
For a list:
Array.prototype.andand = function(property, fn) {
if (this.filter(property).length > 0) this.map(fn);
}