Defining Javascript function with prototype property - javascript

What's wrong with this piece of code?
(function (){
'use strict';
// add hasClass function
String.prototype.hasClass = function (className) {
return this.className.search('(\\s|^)' + className + '(\\s|$)') != -1 ? true : false;
};
console.log(document.getElementById('link').hasClass('test'));
})();
I'd expect it to return true or false, but all I get is
TypeError: document.getElementById("link").hasClass is not a function**
UPD: Thanks guys. Now i get it. I should set method to Object or Element (What is more right?) not String!

document.getElementById('link') doesn't return a String, it returns a DOM element. You could try this instead:-
Element.prototype.hasClass = function (className) {
return this.className.search('(\\s|^)' + className + '(\\s|$)') != -1 ? true : false;
};
console.log(document.getElementById('link').hasClass('test'));

As far as I know, hasClass is not a method of Element, you're likely thinking of the jQuery method, as such you would have to use jQuery and select the element using a jQuery selector. Other frameworks may also have such methods, I believe YUI does as well.

The way to do this is to write a function that receives a DOM element, as the String object has nothing to do with it ;)
A simple example:
function hasClass(element, classcheck){
return element.className.indexOf(classcheck) !== -1;
}
So your code would look like:
(function (){
'use strict';
// add hasClass function
function hasClass(element, classcheck){
return element && element.className && element.className.indexOf(classcheck) !== -1;
}
console.log(hasClass(document.body,'test'));
})();
Obviously, you should be checking that the first argument is actually a DOM element too (quite a lot of different ways to achieve that), but this is the right way to go about it.

Related

How to create shorter equivalent for addEventListener

I want to create a short function for addEvenTListener like jQuery does. I have seperate functions for select and event listener. But I want to be able to use $(selector).on instead of on(event, elem, callback, capture). How can I achieve that?
function $ (selector, scope) {
scope = scope ? scope : document;
return scope.querySelector(selector);
};
var on = function (event, elem, callback, capture) {
if (typeof (elem) === 'function') {
capture = callback;
callback = elem;
elem = window;
}
capture = capture ? true : false;
elem = typeof elem === 'string' ? document.querySelector(elem) : elem;
if (!elem) return;
elem.addEventListener(event, callback, capture);
};
JQuery returns an array of elements wrapped in jQuery object that provides useful methods in its prototype. So, simplified, it works similar to the code below. The last line is the way you want to attach event listeners to elements (if I understand correctly).
function $(selector, scope){
let z = Object.create($.prototype);
z.e = (scope || document).querySelector(selector);
return z;
}
$.prototype.on = function(event, callback, capture){
return this.e.addEventListener(event, callback, capture);
}
$("button").on("click", ()=>console.log("Clicked!"));
<button>Click</button>
jQuery returns a wrapper object which exposes the methods .on(), .attr(), .prop() etc, instead of the actual HTMLElement instance. So, one way you can accomplish that is to do the same.
Another, technically feasible way is to amend the prototype of the HTMLElement class:
HTMLElement.prototype.on = function(event, callback, capture) { ... };
Note, however, that manipulating prototypes you didn't create yourself is very much not recommended. It can easily break predefined behavior, perhaps not now but in future versions of JavaScript.
Node.prototype.listen = Node.prototype.addEventListener;
creates an alias or new reference.
// example
el.listen('click', function(){
// etc.
})

jQuery .hasClass() method fails for SVG elements

I have a set of SVG elements with the classes node and link. My program should detect whether an element has the node class or the link class upon hovering over any of the SVG elements. However, for some reason, the .hasClass() doesn't seem to work:
$(".node").hover(function(evt){
console.log($(this).attr("class")); //returns "node"
console.log($(this).hasClass('node')); //returns false
}, function(){console.log("Done");});
So the element I hovered on has the class node, and jQuery detects that too, as shown by console.log($(this).attr("class"));, but for some reason the actual .hasClass() fails. Why is this? Is it failing because of the SVG?
The class attribute for HTML element doesn't have the same meaning in SVG.
$("<b></b>").addClass($(this).attr("class")).hasClass("node")
Or
/(^|\s)node(\s|$)/.test($(this).attr("class"))
for SVG elements.
EDIT .hasClass seems to work just fine (at least in IE9 and FF) http://jsfiddle.net/X6BPX/1/
So the problem could be any combination of the following: a syntax error, using an outdated browser, using an outdated version of jQuery.
As Bergi pointed out in comments, jQuery silently fails on SVG elements on account of className returning an SVGAnimatedString object instead of a normal DOMString.
See this JSFiddle for a comparison.
I was tempted to submit a pull request on this, but did a quick project search, and apparently the jQuery project stance on SVG issues is wontfix: https://github.com/jquery/jquery/pull/1511
If you're using D3, you could use d3.select(this).classed('node'). Note that D3 correctly returns for both HTML elements and SVG elements.
This is not the fastest option ever, but it is a possible solution. Instead of using jQuery's hasClass you could instead obtain the class attribute as a string and use indexOf to search through it. There are probably use cases where this will fail, so I wouldn't recommend this except for super simple projects.
Working example:
var s = $(this).attr('class');
if( s.indexOf('node')!==-1 ){
// do something
}
Remember: indexOf returns -1 when it can't find anything, not 0. 0 is returned when the substring starts at index 0.
This is a hack for addClass, removeClass, hasClass jquery methods for before jquery 3.x.x versions.
$.fn.extend({
addSVGClass: function (cls) {
return this.each(function () {
var classList = $(this).attr('class');
if (classList) {
var classListArr = classList.split(" ");
if (classListArr.indexOf(cls) === -1) {
classListArr.push(cls);
classList = classListArr.join(" ").trim();
$(this).attr('class', classList);
}
} else {
$(this).attr('class', cls);
}
});
},
removeSVGClass: function (cls) {
return this.each(function () {
var classList = $(this).attr('class');
if (classList) {
var classListArr = classList.split(" ");
if (classListArr.indexOf(cls) !== -1) {
delete classListArr[classListArr.indexOf(cls)];
classList = classListArr.join(" ").trim();
$(this).attr('class', classList);
}
}
});
},
hasSVGClass: function (cls) {
var el = this[0];
var classList = $(el).attr('class');
if (classList) {
var classListArr = classList.split(" ");
if (classListArr.indexOf(cls) !== -1) {
return true;
} else {
return false;
}
}
return false;
}
});
usage :
$('.svg-element').addSVGClass('selected');
Works. But be sure to close the function
$(".node").hover(function(evt){
console.log($(this).attr("class")); //returns "node"
console.log($(this).hasClass('node')); //returns false
}, function(){console.log("Done");});
http://jsfiddle.net/X6BPX/

Refactor simple jQuery toggle selector

I am moving my code from document.ready() to self executing anonymous function. I have already done a few bigger code pieces but I'm mostly struggling with the smaller ones. Like this one:
/**
Advanced properties toggle
**/
$('a.toggle-link').click(function (e) {
$(this).next().slideToggle('slow');
e.preventDefault();
});
How do I refactor this to be able to introduce variables for the selector a.toggle-link (so anything can be passed into the function), for the .slideToggle (so I can pass in the .slideDown, .slideUp, ...) and for the slow?
This approach uses jQuery, though I've stuck with native DOM methods for the most part:
function actOnElem(el, method, duration) {
// if no passed 'el' or 'method' return
if (!el || !method) {
return false;
}
else {
// if 'el' is an element-node, use 'el' else assume it's an id
el = el.nodeType == 1 ? el : document.getElementById(el);
// duration is used if passed, otherwise 'slow' is used as the default
duration = duration || 'slow';
// create a jQuery object from 'el',
// call the method, if it exists,
// and use the 'duration'
$(el)[method](duration);
}
}
actOnElem(document.getElementById('two'), 'slideDown', 1000);
JS Fiddle demo.
Please note that there are no sanity checks, so if the element is already visible and you call the function with slideDown then nothing will happen. Though while I think this answers your question I'm entirely unsure why you want to take this approach, rather than directly calling upon the jQuery methods.
Slightly-revised function to allow for an (incredibly simple) failure reporting:
function actOnElem(el, method, duration, debug) {
if (!el || !method) {
return false;
}
else {
el = el.nodeType == 1 ? el : document.getElementById(el);
duration = duration || 'slow';
if ($(el)[method]) {
$(el)[method](duration);
}
else if (debug) {
console.log('Did you make a typo? There seems to be no "' + method + '" method.');
}
}
}
actOnElem(document.getElementById('two'), 'slidedown', 1000, true);
// ^
// +--- typo, should be 'slideDown'
JS Fiddle demo.

jQuery Extension accepting a String not just an Object

This might be a silly question, but I can't seem to find a solution...
I just wanted to make a isNullOrWhiteSpace extension (same name as the .NET one), to determine if a string is '', '0', 0, undefined, null. Nothing crazy.
Now doing it with a typical jQuery extension, it seems it is always looking for a jQuery Object to be passed in. But for in my extension, I need it to work with a simple string, but it doesn't work at all when I do.
$.fn.isNullOrWhiteSpace = function () {
if (['', '0', 0, undefined, null].indexOf($.trim(this)) > -1) {
return false;
}
return true;
};
'testing'.isNullOrWhiteSpace(); // doesn't work
// Uncaught TypeError: Object has no method 'isNullOrWhiteSpace'
What am I missing here??
-- from answers below, turns out it should be simply:
$.isNullOrWhiteSpace, the $.fn. part makes it a jQuery-Object extension as opposed to just a regular extension (like $.isArray(), $.trim() (which I use in my own question... sigh))
If you must hook this to jQuery — and there's really no reason to beyond namespace economy — you would do this:
$.nullOrWhitespace = function(s) {
return !s || !(s.replace(/\s*/, '')); // or something like this; just an example
};
Then call it with
if ( $.nullOrWhitespace( yourString ) ) {
... whatever ...
}
Try String.prototype.isNullOrWhiteSpace = function() {...
$.fn by default sets the context of this to an array of matched elements(when used with a selector). Which you clearly don't need in this case.
$.isNullOrWhiteSpace = function (str) {
return $.inArray($.trim(str), ['', '0', 0, undefined, null]) > -1;
};
This is what most other utility methods do eg: $.inArray, $.trim like we have already used! :)
As far as I know, $.fn.isNullOrWhiteSpace extends jQuery.
If you are extending jQuery you propably need to call the method on a jQuery object.
'testing' is a string but $('testing') would be a jQuery object.
This seems to work:
$.fn.isNullOrWhiteSpace = function () {
if (['', '0', 0, undefined, null].indexOf($.trim(this.selector)) > -1) {
return false;
}
return true;
};
alert($('').isNullOrWhiteSpace());
alert($('testing').isNullOrWhiteSpace());
DEMO

Testing if value is a function

I need to test whether the value of a form's onsubmit is a function. The format is typically onsubmit="return valid();". Is there a way to tell if this is a function, and if it's callable? Using typeof just returns that it's a string, which doesn't help me much.
EDIT: Of course, I understand that "return valid();" is a string. I've replaced it down to "valid();", and even "valid()". I want to know if either of those is a function.
EDIT: Here's some code, which may help explain my problem:
$("a.button").parents("form").submit(function() {
var submit_function = $("a.button").parents("form").attr("onsubmit");
if ( submit_function && typeof( submit_function.replace(/return /,"") ) == 'function' ) {
return eval(submit_function.replace(/return /,""));
} else {
alert("onSubmit is not a function.\n\nIs the script included?"); return false;
}
} );
EDIT 2: Here's the new code. It seems that I still have to use an eval, because calling form.submit() doesn't fire existing onsubmits.
var formObj = $("a.button").parents("form");
formObj.submit(function() {
if ( formObj[0].onsubmit && typeof( formObj.onsubmit ) == 'function' ) {
return eval(formObj.attr("onsubmit").replace(/return /,""));
} else {
alert("onSubmit is not a function.\n\nIs the script included?");
return false;
}
} );
Suggestions on possibly how to do this better?
I'm replacing a submit button with an
anchor link. Since calling
form.submit() does not activate
onsubmit's, I'm finding it, and
eval()ing it myself. But I'd like to
check if the function exists before
just eval()ing what's there. – gms8994
<script type="text/javascript">
function onsubmitHandler() {
alert('running onsubmit handler');
return true;
}
function testOnsubmitAndSubmit(f) {
if (typeof f.onsubmit === 'function') {
// onsubmit is executable, test the return value
if (f.onsubmit()) {
// onsubmit returns true, submit the form
f.submit();
}
}
}
</script>
<form name="theForm" onsubmit="return onsubmitHandler();">
<a href="#" onclick="
testOnsubmitAndSubmit(document.forms['theForm']);
return false;
"></a>
</form>
EDIT : missing parameter f in function testOnsubmitAndSubmit
The above should work regardless of whether you assign the onsubmit HTML attribute or assign it in JavaScript:
document.forms['theForm'].onsubmit = onsubmitHandler;
Try
if (this.onsubmit instanceof Function) {
// do stuff;
}
You could simply use the typeof operator along with a ternary operator for short:
onsubmit="return typeof valid =='function' ? valid() : true;"
If it is a function we call it and return it's return value, otherwise just return true
Edit:
I'm not quite sure what you really want to do, but I'll try to explain what might be happening.
When you declare your onsubmit code within your html, it gets turned into a function and thus its callable from the JavaScript "world". That means that those two methods are equivalent:
HTML: <form onsubmit="return valid();" />
JavaScript: myForm.onsubmit = function() { return valid(); };
These two will be both functions and both will be callable. You can test any of those using the typeof operator which should yeld the same result: "function".
Now if you assign a string to the "onsubmit" property via JavaScript, it will remain a string, hence not callable. Notice that if you apply the typeof operator against it, you'll get "string" instead of "function".
I hope this might clarify a few things. Then again, if you want to know if such property (or any identifier for the matter) is a function and callable, the typeof operator should do the trick. Although I'm not sure if it works properly across multiple frames.
Cheers
What browser are you using?
alert(typeof document.getElementById('myform').onsubmit);
This gives me "function" in IE7 and FireFox.
using a string based variable as example and making use instanceof Function
You register the function..assign the variable...check the variable is the name of function...do pre-process... assign the function to new var...then call the function.
function callMe(){
alert('You rang?');
}
var value = 'callMe';
if (window[value] instanceof Function) {
// do pre-process stuff
// FYI the function has not actually been called yet
console.log('callable function');
//now call function
var fn = window[value];
fn();
}
Make sure you are calling typeof on the actual function, not a string literal:
function x() {
console.log("hi");
}
typeof "x"; // returns "string"
typeof x; // returns "function"
You can try modifying this technique to suit your needs:
function isFunction() {
var functionName = window.prompt('Function name: ');
var isDefined = eval('(typeof ' + functionName + '==\'function\');');
if (isDefined)
eval(functionName + '();');
else
alert('Function ' + functionName + ' does not exist');
}
function anotherFunction() {
alert('message from another function.');
}
form.onsubmit will always be a function when defined as an attribute of HTML the form element. It's some sort of anonymous function attached to an HTML element, which has the this pointer bound to that FORM element and also has a parameter named event which will contain data about the submit event.
Under these circumstances I don't understand how you got a string as a result of a typeof operation. You should give more details, better some code.
Edit (as a response to your second edit):
I believe the handler attached to the HTML attribute will execute regardless of the above code. Further more, you could try to stop it somehow, but, it appears that FF 3, IE 8, Chrome 2 and Opera 9 are executing the HTML attribute handler in the first place and then the one attached (I didn't tested with jQuery though, but with addEventListener and attachEvent). So... what are you trying to accomplish exactly?
By the way, your code isn't working because your regular expression will extract the string "valid();", which is definitely not a function.
If it's a string, you could assume / hope it's always of the form
return SomeFunction(arguments);
parse for the function name, and then see if that function is defined using
if (window[functionName]) {
// do stuff
}
Isn't typeof xxx === 'function' the best and the fastest?
I made an bench in wich you can try it out, compared to instanceof and _underscore
Its just seems to be faster than instanceof (using chrome)
It won't trow an error if the variable is not defined
Here a bench: https://jsbench.me/qnkf076cqb/1
Checking the call method on the value seems to be a good enough test. e.g., val.call && val()
> a = () => {}
[Function: a]
> function b() {}
undefined
> c = function(){}
[Function: c]
> d = 2
2
> e = []
[]
> f = {}
{}
> a.call
[Function: call]
> b.call
[Function: call]
> c.call
[Function: call]
> d.call
undefined
> e.call
undefined
> f.call
undefined
Note: Except when it's a class.
Well, "return valid();" is a string, so that's correct.
If you want to check if it has a function attached instead, you could try this:
formId.onsubmit = function (){ /* */ }
if(typeof formId.onsubmit == "function"){
alert("it's a function!");
}
You can always use one of the typeOf functions on JavaScript blogs such as Chris West's. Using a definition such as the following for the typeOf() function would work:
function typeOf(o){return {}.toString.call(o).slice(8,-1)}
This function (which is declared in the global namespace, can be used like this:
alert("onsubmit is a " + typeOf(elem.onsubmit));
If it is a function, "Function" will be returned. If it is a string, "String" will be returned. Other possible values are shown here.
I think the source of confusion is the distinction between a node's attribute and the corresponding property.
You're using:
$("a.button").parents("form").attr("onsubmit")
You're directly reading the onsubmit attribute's value (which must be a string). Instead, you should access the onsubmit property of the node:
$("a.button").parents("form").prop("onsubmit")
Here's a quick test:
<form id="form1" action="foo1.htm" onsubmit="return valid()"></form>
<script>
window.onload = function () {
var form1 = document.getElementById("form1");
function log(s) {
document.write("<div>" + s + "</div>");
}
function info(v) {
return "(" + typeof v + ") " + v;
}
log("form1 onsubmit property: " + info(form1.onsubmit));
log("form1 onsubmit attribute: " + info(form1.getAttribute("onsubmit")));
};
</script>
This yields:
form1 onsubmit property: (function) function onsubmit(event) { return valid(); }
form1 onsubmit attribute: (string) return valid()
// This should be a function, because in certain JavaScript engines (V8, for
// example, try block kills many optimizations).
function isFunction(func) {
// For some reason, function constructor doesn't accept anonymous functions.
// Also, this check finds callable objects that aren't function (such as,
// regular expressions in old WebKit versions), as according to EcmaScript
// specification, any callable object should have typeof set to function.
if (typeof func === 'function')
return true
// If the function isn't a string, it's probably good idea to return false,
// as eval cannot process values that aren't strings.
if (typeof func !== 'string')
return false
// So, the value is a string. Try creating a function, in order to detect
// syntax error.
try {
// Create a function with string func, in order to detect whatever it's
// an actual function. Unlike examples with eval, it should be actually
// safe to use with any string (provided you don't call returned value).
Function(func)
return true
}
catch (e) {
// While usually only SyntaxError could be thrown (unless somebody
// modified definition of something used in this function, like
// SyntaxError or Function, it's better to prepare for unexpected.
if (!(e instanceof SyntaxError)) {
throw e
}
return false
}
}
if ( window.onsubmit ) {
//
} else {
alert("Function does not exist.");
}
Beware that es6 class is also a function but not callable
class C {}
typeof C === "function" // true
C instanceof Function // true
C() // error
C.call() // error
new C() // okay
new C // okay
A simple check like this will let you know if it exists/defined:
if (this.onsubmit)
{
// do stuff;
}

Categories

Resources