I have a existing application which uses javascript and properties like notNull, isDate etc defined within the elements in html elements like input, select, etc
For example:
<input type = 'text' notNull class='mandatoryField' name = 'abc' id='abc' isDate/>
And the javascript checks for the properties with a hasProp method, placing the code below and corresponding warning messages are displayed:
function hasProp(thisField, thisProp) {
for ( var prop in thisField) {
if (prop == thisProp)
return true;
}
return false;
}
My issue here is with using different browsers - IE, Chrome and Firefox
This particular methods are all ok for Internet Explorer. when it comes to chrome and firefox, the notNull, isDate are treated as attributes rather than properties and the above hasProp method always returns false.
I did go through many questions available here, but couldn't find any way to access both properties and attributes in a single way - I would prefer jQuery to be used, since we will be migrating to jQuery eventually.
Any pointers to this will be really helpful.
Thanks,
Reema
I think the way you use the attribute and property aren't 100% accurate, properties (.prop()) in the jQuery context are basically the value of the attribute in memory, where as .attr() reflects the value in the markup. This is only the case for HTML attributes that are "built-in".
So in your example, you're dealing with attributes all the way, just some don't happen to have any value.
The best way of detecting the presence of an attribute, cross browser using jQuery:
$('#myElement').is('[attrName]') === true
So, in your case:
$('#abc').is('[isDate]') === true
See this JS-Fiddle
As you want a jQuery solution, you can use both jQuery.attr() and jQuery.prop() methods to solve your problem.
I would prefer an pure Javascript approach:
var attributes = ['notNull', 'isDate'],
checkForAttribute = function(elem){
for(var i = 0, c = attributes.length ; i < c ; i++){
if(elem.getAttribute(attributes[i]) !== null){
console.log("attribute " + attributes[i] + " found");
}else{
console.log("attribute " + attributes[i] + " not found");
}
}
}
See an working example here.
Here is some more information on the getAttribute() method.
Related
I want to test if an element has a specific class on it. This if statement works:
if (children[i].className === siblingClass)
However, it only works if children[i] has only one class, and it is exactly the same as the value of siblingClass. I want to make the function a little more generalized, to test if children[i] has siblingClass even if it also has other classes as well.
So I tried the classList.contains function, like this:
if (children[i].classList.contains(siblingClass))
But, when I try it (in Chrome), the console log says:
Uncaught TypeError: Cannot read property 'contains' of undefined
How do I create a test to see if children[i] has siblingClass listed among one or more classes it may have?
Please note I would prefer to have a pure Javascript solution that does not require jQuery if possible.
This is the full function where the if statement appears:
function getSiblingByClass(currentObject, siblingClass)
{
var parentofSelected = currentObject.parentNode,
children = parentofSelected.childNodes,
element,
i;
for (i = 0; i < children.length; i++)
{
if (children[i].classList.contains(siblingClass))
{
element = children[i];
break;
}
}
return element;
}
This function is called from within other functions when I want to search among element siblings for an element that has a specified class. It gets the parent of the current element, loops through all children, and returns the first match. Or, at least that's the idea.
Here is some HTML which calls a function which in turn calls the above function:
<div>
<span class="fee">hello world</span>
<span class="fi">Blah blah blah</span>
<button class="foo" onclick="copySiblingDataByClass(this, 'fi');">Click here</button>
</div>
copySiblingDataByClass() would in turn call getSiblingByClass(), and pass itself and the name of the class to be selected.
Using Node.contains on a string or array is not what it was meant to do, however; taking your function you can make it work that way.
Consider the following "prototype upgrade" - place at the beginning of your JS code, it provides the functionality globally:
Object.defineProperty(String.prototype,'contains',{enumerable:false, configurable:false, writable:false, value:function(find)
{
return ((' '+this+' ').indexOf(' '+find+' ') > -1);
}});
Now you can simply use it anywhere in your code like this:
someNode.className.contains('foo'); // returns a boolean
..however, if it were me i'd simply use your function applied to all elements automatically, like so:
Object.defineProperty(Element.prototype,'hasClass',{enumerable:false, configurable:false, writable:false, value:function(name)
{
return ((' '+this.className+' ').indexOf(' '+name+' ') > -1);
}});
Now simply use it like:
someNode.hasClass('foo');
Below are some ways you can try:
var div = document.getElementById('div');
// classList method
if (div.classList.contains('world'))
console.log(true);
else
console.log(false);
// RegExp method
if (/\s|^\wworld\s/.test(div.className))
console.log(true);
else
console.log(false);
// Converting classname into array to use array method
if (div.className.split(' ').includes('world'))
console.log(true);
else
console.log(false);
// Also using array method, but more compatible
if (div.className.split(' ').indexOf('world') > -1)
console.log(true);
else
console.log(false);
// Borrowing array method to use on classList
if (Array.prototype.includes.call(div.classList, 'world'))
console.log(true);
else
console.log(false);
<div id="div" class="hello world foo bar"></div>
There is a lot more ways to do as you become more creative.
PS
From your example, it seems your children[i] is probably not an element. A non-element node doesn't have classList, hence the undefined.
One other possibility is that your Chrome browser is extremely outdated and obsolete.
It appears that your code is part of a for loop, so, consider the following:
var children=[].slice.call(document.getElementById('someID').childNodes);
var siblingClass = 'someClassName'; // for example
for(var i in children)
{
if(!children.hasOwnProperty(i)){continue}; // get rid of "undefined"
if(children[i].className.indexOf(siblingClass) > -1)
{
// you other code here
}
}
After much testing, and following suggestions here, I simply could not deduce why classList.contains() would not work for me. The MDN says it's been supported in Chrome since version 8 and I am running version 60.
However, I need to progress my code into production, so I can't devote any more time to the mystery of it. Fortunately, I found a function in this answer that will successfully select one class out of many classes attached to an object.
function hasClass(element, cls)
{
return (' ' + element.className + ' ').indexOf(' ' + cls + ' ') > -1;
}
With this function, my if statement now looks like this:
if (hasClass(children[i], siblingClass))
... and it works perfectly, so this is the solution I will be using.
If someone can show me how to get classList.contains() to work, or explain why it doesn't, I'd happily mark that answer as correct.
How may I write this jQuery function ( or rather, produce the same result ), using only JavaScript?
$(':input').serializeArray();
Function docs.
Look at jQuery's implementation:
function serializeArray() {
return this.map(function () {
// Can add propHook for "elements" to filter or add form elements
var elements = jQuery.prop(this, "elements");
return elements ? jQuery.makeArray(elements) : this;
}).filter(function () {
var type = this.type;
// Use .is(":disabled") so that fieldset[disabled] works
return this.name && !jQuery(this).is(":disabled") && rsubmittable.test(this.nodeName) && !rsubmitterTypes.test(type) && (this.checked || !manipulation_rcheckableType.test(type));
}).map(function (i, elem) {
var val = jQuery(this).val();
return val == null ? null : jQuery.isArray(val) ? jQuery.map(val, function (val) {
return {
name: elem.name,
value: val.replace(rCRLF, "\r\n")
};
}) : {
name: elem.name,
value: val.replace(rCRLF, "\r\n")
};
}).get();
}
Of course, this assumes that this is a jQuery object with methods like .filter() and .map(). These methods are also available to Arrays in ECMAScript 5, so if you don't need to support IE < 9, this code might work where this is an Array of HTMLElements — after removing or rewriting the corner cases that jQuery handles. If you need to support old browsers, then you probably should just use jQuery.
The jQuery :input selector is equivalent to:
var elements = document.querySelectorAll('input, button, textarea, select');
elements will be a static collection of the matched elements. A similar array could be built using getElementsByTagName using each different tag name.
.serializeArray creates an array of ojbects like:
[{name: value},{name:value},...];
To serialise elements, follow the algorithm in the HTML5 Spec §4.10.22 Form submission. Note that jQuery does not submit the submit button (which is counter to the W3C specification and browser behaviour), you may wish to emulate that (or not).
There is a good link in SajithNair's comment, it's not perfect but is a pretty good start (say 98% of the way there).
The basic strategy is to loop over the members and deal with each different type, creating objects from successful controls (i.e. those that have a name and value and aren't disabled, and are checked for radio and checkbox inputs and selected for options in selects, remembering to deal with multiple selects) whose sole member is the element name with a value of the element value.
Where there are multiple controls with the same, there will be multiple objects with a same named property but likely different value (or not).
The code to do that is not difficult, but it is a bit long and tedious to write and test thoroughly. Have a go and ask for help when you get stuck.
here is a fairly simple method that works for most forms:
[].slice.call(document.querySelectorAll('input[name]:not([disabled]), textarea[name]:not([disabled]), select[name]:not([disabled])')).reduce(function(a,b,i){
var val= String({checkbox:1,radio:1}[b.type] ? b.checked : b.value).replace(/\r?\n/g, "\r\n");
a[b.name]= a[b.name] ? a[b.name].concat(val) :val;
return a;
}, {});
it doesn't handle named submits or image types like a real form, but those input's applicability are really only known at time of clicking, and thus don't make sense to a manual serializer. You can use a couple of onclicks to emulate the traditional functionality for image inputs and named submits if that's really a need for you, which it likely isn't...
this version uses a flat object of key:value pairs, or key:[value1,value2] arrays for repeats.
I was looking at the PrototypeJS code and found this check -
var div = document.createElement('div'),
form = document.createElement('form'),
isSupported = false;
if (div['__proto__'] && (div['__proto__'] !== form['__proto__'])) {
isSupported = true;
}
I would like to know what are the cases where this condition returns false -
div['__proto__'] !== form['__proto__']
Note: The function's comment says - "Used internally to detect if the browser supports extending html element prototypes". Not sure how this check helps for that.
This test allows PrototypeJS to figure out if it can add specific methods to specific types of element prototypes.
For example you would not want the method getInputs() (which returns an array of all of the form's elements) on a <div> element because it would only make sense being used on a <form> element.
HTML
<div id='mydiv'></div>
<form id='myform'></form>
JS
$('mydiv').addClassName('newclass'); //does not error
$('myform').addClassName('newclass'); //does not error
$('myform').getInputs(); //does not error
$('mydiv').getInputs(); //throws error 'has no method getInputs()'
Example on JSFiddle http://jsfiddle.net/C39gu/
When I don't have access to JQuery for whatever reason, I usually perform attribute selection manually using element.hasAttribute or element.getAttribute.
However, there seems to be some complication here because older browsers (IE <= 8) don't support hasAttribute. So if you want to check whether an element has a certain attribute, you need to use getAttribute and check the return value.
if ((element.hasAttribute && element.hasAttribute("foo"))
|| (element.getAttribute("foo") != null))
{
....
}
This makes me think you may as well just forget about using hasAttribute at all, and just always use getAttribute. The problem is that I can't find consistent documentation on the return value of getAttribute. In practice, it returns null on most browsers if the attribute doesn't exist - but it also might return empty string, because that is what it's supposed to do according to the DOM 3 specification.
Unfortunately, returning an empty string leaves us with no way to disambiguate between:
<div data-my-attribute = ""></div>
and
<div></div>
So, in practice - it seems the most portable thing to do is to first check if the browser supports hasAttribute, and if not, use getAttribute - since IE 6-8 implement getAttribute so that it returns null (instead of empty string) if the attribute doesn't exist.
Is this really the best way to go about doing this? Or is there a better way to write cross-browser attribute detection in plain Javascript?
The following works well in IE6-10 (tested it in IETester), Firefox, Chrome and Safari:
function hasAttrib(element, attributeName) {
return (typeof element.attributes[attributeName] != 'undefined');
}
Here are jsfiddle and its standalone result page (for testing in older browsers).
This will probably need some testing, but would not the length of the String describing the Element change if you tried to setAttribute an attribute it doesn't already have vs remain the same if you tried for one it does have?
var hasAttribute = (function () {
if (HTMLElement && HTMLElement.prototype
&& HTMLElement.prototype.hasAttribute)
return function (node, attrib) { // use if available
return node.hasAttribute(attrib);
};
return function (node, attrib) {
var d = document.createElement('div'), // node for innerHTML
e = node.cloneNode(false), // id attribute will be lost here
i;
if (attrib.toLowerCase() === 'id') return !!node.getAttribute('id');
d.appendChild(e);
i = d.innerHTML.length; // get original length
e.setAttribute(attrib, e.getAttribute(attrib)); // set attrib to test
return d.innerHTML.length === i; // see if length changed
};
}());
We are hoping on trimming some fat from our custom library we use across our products.
One commonly used action is changing of object styles.
Normally, we do this via:
document.getElementById('object').style.property='value';
I just tested the following in chromes console, and it worked:
function objStyle(o,p,v){
document.getElementById(o).style[p]=v;
}
objStyle('object','property','value');
Is this a valid way of doing things?
Any pitfalls one can think of when using this way of doing things? Crossbrowser compatability?
Yes, that is perfectly valid. A property that you access by .name can also be access by ['name'].
That works for any property in any object, for example:
window['alert']('Hello world.');
document['getElementById']('object')['style']['color'] = '#fff';
Your code is fine.
One thing I would consider though is whether you want to keep calling document.getElementById() (inside the function) if there is a situation where you need to perform multiple changes to the same element. What I'm about to suggest is overkill for the sake of showing you more options, but consider that you can pass the Id to your function, or pass a reference to the element directly, or have a function that accepts a string or an element reference and figures it out from the type of the parameter:
function objStyleById(oId,p,v){
document.getElementById(oId).style[p]=v;
}
function objStyle(o,p,v) {
o.style[p] = v;
}
function objStyleAuto(o,p,v) {
if (typeof o === "string")
o = document.getElementById("o");
// else not a string so assume o is element reference
o.style[p] = v;
}
objStyleById('object','property','value');
var myEl = document.getElementById("someElement");
objStyle(myEl,"prop","val");
objStyle(myEl,"prop2","val");
// some other non-style operation on myEl, e.g.,
myEl.className = "something";
myEl.innerHTML = "something";
objStyle(myEl.parentNode,"prop","value");
objStyleAuto('object','property','value');
objStyleAuto(myEl,'property','value');