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
};
}());
Related
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.
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.
How can I get text from a node so that it returns it with whitespace formatting like "innerText" does, but excludes descendant nodes that are hidden (style display:none)?
UPDATE: As the OP points out in comments below, even though MDN clearly states that IE introduced innerText to exclude hidden content, testing in IE indicates that is not the case. To summarize:
Chrome: innerText returns text only from visible elements.
IE: innerText returns all text, regardless of the element's visibility.
Firefox: innerText is undefined (as indicated by the W3C, and in my testing).
Add all of this up, and you have a property to avoid like the plague. Read on for the solution....
If you want cross-browser compatibility, you'll have to roll your own function. Here's one that works well:
function getVisibleText( node ) {
if( node.nodeType === Node.TEXT_NODE ) return node.textContent;
var style = getComputedStyle( node );
if( style && style.display === 'none' ) return '';
var text = '';
for( var i=0; i<node.childNodes.length; i++ )
text += getVisibleText( node.childNodes[i] );
return text;
}
If you want to get painfully clever, you can create a property on the Node object so that this feels more "natural". At first I thought this would be a clever way to polyfill the innerText property on Firefox, but that property is not created as a property on the Node object prototype, so you would really be playing with fire there. However, you can create a new property, say textContentVisible:
Object.defineProperty( Node.prototype, 'textContentVisible', {
get: function() {
return getVisibleText( this );
},
enumerable: true
});
Here's a JsFiddle demonstrating these techniques: http://jsfiddle.net/8S82d/
This is interesting, I came here because I was looking for why the text of display:none elements was omitted in Chrome.
So, this was ultimately my solution.
Basically, I clone the node and remove the classes/styling that set display:none.
How to add hidden element's innerText
function getInnerText(selector) {
let d = document.createElement('div')
d.innerHTML = document.querySelector(selector).innerHTML.replaceAll(' class="hidden"', '')
return d.innerText
}
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/
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');