.test {
width:80px;
height:50px;
background-color:#808080;
margin:20px;
}
HTML -
<div class="test">Click Here</div>
In JavaScript i want to get margin:20px
For modern browsers you can use getComputedStyle:
var elem,
style;
elem = document.querySelector('.test');
style = getComputedStyle(elem);
style.marginTop; //`20px`
style.marginRight; //`20px`
style.marginBottom; //`20px`
style.marginLeft; //`20px`
margin is a composite style, and not reliable cross-browser. Each of -top -right, -bottom, and -left should be accessed individually.
fiddle
The accepted answer is the best way to get the computed values. I personally needed the pre-computed value. Say for instance 'height' being set to a 'calc()' value. I wrote the following jQuery function to access the value from the style sheet. This script handles nested 'media' and 'supports' queries, CORS errors, and should provide the final cascaded precomputed value for accessible properties.
$.fn.cssStyle = function() {
var sheets = document.styleSheets, ret = [];
var el = this.get(0);
var q = function(rules){
for (var r in rules) {
var rule = rules[r];
if(rule instanceof CSSMediaRule && window.matchMedia(rule.conditionText).matches){
ret.concat(q(rule.rules || rule.cssRules));
} else if(rule instanceof CSSSupportsRule){
try{
if(CSS.supports(rule.conditionText)){
ret.concat(q(rule.rules || rule.cssRules));
}
} catch (e) {
console.error(e);
}
} else if(rule instanceof CSSStyleRule){
try{
if(el.matches(rule.selectorText)){
ret.push(rule.style);
}
} catch(e){
console.error(e);
}
}
}
};
for (var i in sheets) {
try{
q(sheets[i].rules || sheets[i].cssRules);
} catch(e){
console.error(e);
}
}
return ret.pop();
};
// Your element
console.log($('body').cssStyle().height);
Using jQuery:
$('.class').css( "backgroundColor" );
Another approach (still experimental) could be computedStyleMap:
const computedStyleMap = document.getElementById('my-id').computedStyleMap();
computedStyleMap.get('overflow-x'); // {value: 'scroll'}
computedStyleMap.has('padding-right'); // false
computedStyleMap.entries(); // Iterator {}
I've just released an npm package for this purpose exactly. You can find it here on npm or github:
npm: https://www.npmjs.com/package/stylerjs
github: https://github.com/tjcafferkey/stylerjs
you would use it like so
var styles = styler('.class-name').get(['height', 'width']);
and styles would equal
{height: "50px", width: "50px"}
So you could just get the values like so
var height = styles.height;
Related
I'm working on a project in which I use es6 code with babel.
I use the following code:
let result= xmlDocument.querySelector("xmlNodeSelector");
for (let child of result.children) { /* do something */ }
The problem it doens't work on IE11 since no children property.
I create the following polyfill but it didn't help:
if(Element.prototype.hasOwnProperty('children')){
return;
}
Object.defineProperty(Element.prototype, 'children', {
get: function(){
let children = new HTMLCollection();
for(let i=0; i < this.childNodes.length; i++){
let item = this.childNodes[i];
if(item.nodeName !== '#text'){
children.push(item);
}
}
return children;
}
});
When I debug IE11 I can see the prototype is Element but the property is not added. In addition when using:
selectorResult instanceof Element
selectorResult instanceof Node
I get false on both.
At the moment I use a method to extract children rather then adding to the prototype which is what i prefer.
Any suggestions?
Thanks in advance
The following code adds the property children to all HTML,XML and SVG elements - just tested it under IE11:
//make sure we have Node.children and Element.children available
(function (constructor) {
if (constructor &&
constructor.prototype &&
constructor.prototype.children == null) {
Object.defineProperty(constructor.prototype, 'children', {
get: function () {
var i = 0, node, nodes = this.childNodes, children = [];
//iterate all childNodes
while (node = nodes[i++]) {
//remenber those, that are Node.ELEMENT_NODE (1)
if (node.nodeType === 1) { children.push(node); }
}
return children;
}
});
}
//apply the fix to all HTMLElements (window.Element) and to SVG/XML (window.Node)
})(window.Node || window.Element);
I found that polyfill on MDN.
This polyfill will return an array, instead of an HTMLCollection, but you can still make use of Node.children.length and Node.children[index].
Using this polyfill you could iterate your result like this:
var resChildren = result.children
var index, maxindex;
for (index=0, maxindex=resChildren.length; index<maxindex; index++)
{
/* do_something_with(resChildren[index]); */
}
I am trying to create a static array. But I found that it can increase the item in the array at run time. How I can achieve static array in JavaScript? Why array is mutable in JavaScript?
var a = [];
//var a = new Array(3);
for (var i = 0; i < 5; i++) {
//a.push(i);
a[i] = i;
}
document.write(a);
You can freeze the array with Object.freeze:
"use strict";
var a = Object.freeze([0, 1, 2]);
console.log(a);
try {
a.push(3); // Error
} catch (e) {
console.error(e);
}
try {
a[0] = "zero"; // Error
} catch (e) {
console.error(e);
}
console.log(a);
That disallows
Changing existing properties, either their values or their other features like whether they're extensible, etc.
Adding or removing properties
See the link for full details. If you just want to keep the size fixed but do want to allow changes to the values of entries, just use Object.seal instead.
Note that whether attempts to change existing properties result in an error (as opposed to silent failure) depends on whether you're in strict mode.
freeze and seal were introduced in ES5 (June 2009), and so should be present in any vaguely up-to-date browser. Obsolete browsers will not have them.
Using the latest JS syntax (polyfill required for older browsers):
var list = Object.seal([1,2,3])
Object.seal prevents further changes to an Object.
It sounds like you want an immutable array.
Most languages have immutable collections, either built in or from a library. JS does not include them normally, but Facebook provides a library that holds all the typical immutable collection types.
While immutablejs does not have an array type as such, it does have the more traditional List:
const staticArray = List([0, 1, 2, 3, 4]);
You can use Object.defineProperties() to set writable:false, configurable:false at property descriptors, Object.preventExtensions() to prevent new property being added to array or object.
"use strict";
const ro = obj => {
const props = {};
const length = obj.length || Object.keys(obj).length;
const entries = Array.isArray(obj)
? [...obj, length].entries()
: Array.from(Object.keys(obj), prop => [prop, obj[prop]]);
for (let [key, value] of entries) {
props[key === length ? `length` : key] = {
value: value,
configurable: false,
writable: false
}
}
return Object.preventExtensions(Object.defineProperties(obj, props));
};
let arr = ro([1,2,3]);
try {
console.log(`arr[0]:${arr[0]}`);
console.log(`try to set arr[0] to ${arr[0] = 4}`);
} catch (e) {
console.error(e)
}
try {
console.log(`try to .push() to arr ${arr.push(4)}`);
} catch (e) {
console.error(e)
}
try {
console.log(`try to set .length of arr ${arr.length = 4}`);
console.log(arr.length);
} catch (e) {
console.error(e)
}
try {
console.log(`try to delete of arr[1] ${delete arr[1]}`);
console.log(arr.length);
} catch (e) {
console.error(e)
}
console.log(JSON.stringify(arr, null, 2));
let obj = ro({a:1, b:2, c:3});
try {
console.log(`obj["c"]:${obj["c"]}`);
console.log(`try to assign 4 to obj["c"]:${obj["c"] = 4}`);
} catch (e) {
console.error(e)
}
try {
console.log(`try to assign property "d" to obj:${obj["d"] = 4}`);
} catch (e) {
console.error(e)
}
try {
console.log(`try to delete property "b" of obj:${delete obj["b"]}`);
} catch (e) {
console.error(e)
}
console.log(JSON.stringify(obj, null, 2));
I am looking for a way to get all the attributes of an element that begins with "on" using jQuery or Vanilla JS. I am currently getting all attributes and then looping through them to get the ones I want using the method proposed by #primvdb on this post: Get all attributes of an element using jQuery.
My code looks like this:
/* Expanding .attr as proposed by #primvdb */
(function(old) {
$.fn.attr = function() {
if(arguments.length === 0) {
if(this.length === 0) {
return null;
}
var obj = {};
$.each(this[0].attributes, function() {
if(this.specified) {
obj[this.name] = this.value;
}
});
return obj;
}
return old.apply(this, arguments);
};
})($.fn.attr);
/* And then my function */
$.fn.attrThatBeginWith = function(begins){
var attributes = this.attr();
var attrThatBegin = {};
for(var attr in attributes){
if(attr.indexOf(begins)==0){
attrThatBegin[attr] = attributes[attr];
}
}
return attrThatBegin;
};
/* Usage */
var onAttributes = $("#MyElement").attrThatBeginWith("on");
And this works but is very "dirty". It's seems like with all the vast features of jQuery there should be a better "cleaner" way to do this. Does anybody have any suggestions?
You can get all attributes attached to an element with element.attributes.
The native attributes object can be converted to an array and then filtered based on the given string.
A plugin that does the above would look like
$.fn.attrThatBeginWith = function(begins){
return [].slice.call(this.get(0).attributes).filter(function(attr) {
return attr && attr.name && attr.name.indexOf(begins) === 0
});
};
FIDDLE
I want to serve different javascript files depending on if browser supports CSS3 transition or not. Is there a better way to detect transition support than my code below?
window.onload = function () {
var b = document.body.style;
if(b.MozTransition=='' || b.WebkitTransition=='' || b.OTransition=='' || b.transition=='') {
alert('supported');
} else {
alert('NOT supported')
}
}
I also think including Modernizr is an overkill. The function below should work for any feature.
function detectCSSFeature(featurename){
var feature = false,
domPrefixes = 'Webkit Moz ms O'.split(' '),
elm = document.createElement('div'),
featurenameCapital = null;
featurename = featurename.toLowerCase();
if( elm.style[featurename] !== undefined ) { feature = true; }
if( feature === false ) {
featurenameCapital = featurename.charAt(0).toUpperCase() + featurename.substr(1);
for( var i = 0; i < domPrefixes.length; i++ ) {
if( elm.style[domPrefixes[i] + featurenameCapital ] !== undefined ) {
feature = true;
break;
}
}
}
return feature;
}
var hasCssTransitionSupport = detectCSSFeature("transition");
Inspired by https://developer.mozilla.org/en-US/docs/CSS/Tutorials/Using_CSS_animations/Detecting_CSS_animation_support
Modernizr will detect this for you. Use this link to create a custom download build that only contains CSS3 2D and/or 3D transitions.
Once it's run, you can either test for the csstransitions class on the html tag (CSS), or in JavaScript, test if Modernizr.csstransitions is true.
More docs: http://modernizr.com/docs/#csstransitions
Here is another testing code. Maybe it is an overkill, but the function tries to set the CSS property to DOM object and then read back from it.
Never tested this code on large amount of exotic browsers, but it is safer than just checking for the CSS property availability. Ah, yes, it can distinguish 2D transform support from 3D transform support! Just pass CSS property values you want to test!
The plus of this code is that it detects the vendor prefix supported (if any). Possible return values:
false, when feature unsupported, or
{
vendor: 'moz',
cssStyle: '-moz-transition',
jsStyle: 'MozTransition'
}
when feature supported
/**
* Test for CSS3 feature support. Single-word properties only by now.
* This function is not generic, but it works well for transition and transform at least
*/
testCSSSupport: function (feature, cssTestValue/* optional for transition and transform */) {
var testDiv,
featureCapital = feature.charAt(0).toUpperCase() + feature.substr(1),
vendors = ['', 'webkit', 'moz', 'ms', 'o'],
jsPrefixes = ['', 'Webkit', 'Moz', 'ms', 'O'],
defaultTestValues = {
transition: 'left 2s ease 1s',
transform: 'rotateX(-180deg) translateZ(.5em) scale(0.5)'
// This will test for 3D transform support
// Use other values if you need to test for 2D support only
},
testFunctions = {
transition: function (jsProperty, computed) {
return computed[jsProperty + 'Delay'] === '1s' && computed[jsProperty + 'Duration'] === '2s' && computed[jsProperty + 'Property'] === 'left';
},
transform: function (jsProperty, computed) {
return computed[jsProperty].substr(0, 9) === 'matrix3d(';
}
};
/* test given vendor prefix */
function isStyleSupported(feature, jsPrefixedProperty) {
if (jsPrefixedProperty in testDiv.style) {
var testVal = cssTestValue || defaultTestValues[feature],
testFn = testFunctions[feature];
if (!testVal) {
return false;
}
testDiv.style[jsPrefixedProperty] = testVal;
var computed = window.getComputedStyle(testDiv);
if (testFn) {
return testFn(jsPrefixedProperty, computed);
}
else {
return computed[jsPrefixedProperty] === testVal;
}
}
return false;
}
//Assume browser without getComputedStyle is either IE8 or something even more poor
if (!window.getComputedStyle) {
return false;
}
//Create a div for tests and remove it afterwards
if (!testDiv) {
testDiv = document.createElement('div');
document.body.appendChild(testDiv);
setTimeout(function () {
document.body.removeChild(testDiv);
testDiv = null;
}, 0);
}
var cssPrefixedProperty,
jsPrefixedProperty;
for (var i = 0; i < vendors.length; i++) {
if (i === 0) {
cssPrefixedProperty = feature; //todo: this code now works for single-word features only!
jsPrefixedProperty = feature; //therefore box-sizing -> boxSizing won't work here
}
else {
cssPrefixedProperty = '-' + vendors[i] + '-' + feature;
jsPrefixedProperty = jsPrefixes[i] + featureCapital;
}
if (isStyleSupported(feature, jsPrefixedProperty)) {
return {
vendor: vendors[i],
cssStyle: cssPrefixedProperty,
jsStyle: jsPrefixedProperty
};
}
}
return false;
}
Github: https://github.com/easy-one/CSS3test
if (window.TransitionEvent){
}
With Modernizr 3.0 (alpha), you can generate custom builds locally. This may resolve the aforementioned "overkill" concern - although i'm not entirely clear on that concern in the first place (but i'm assuming it's size). The new api provides a 'build' method, to which you can pass json containing the tests that you would like to include in the build.
I use something like this in my gulp file but gulp is not needed - a simple node script will do.
gulp.task('js:modernizr', function() {
var modConfig = JSON.parse(fs.readFileSync('modernizr-config.json', {
encoding: 'utf8'
}));
modernizr.build(modConfig, function(res) {
fs.writeFileSync('modernizr.js', res);
return true;
});
});
And an example of the 'modernizr-config.json' file would be
{
"classPrefix": "",
"options": [
"addTest",
"atRule",
"domPrefixes",
"hasEvent",
"html5shiv",
"html5printshiv",
"load",
"mq",
"prefixed",
"prefixes",
"prefixedCSS",
"setClasses",
"testAllProps",
"testProp",
"testStyles"
],
"feature-detects": [
"css/transforms",
"css/transforms3d",
"css/transformstylepreserve3d",
"css/transitions",
"touchevents",
"workers/webworkers",
"history"
]
}
The full config file is included in the Modernizr package.
With this approach, you can take advantage of the well maintained Modernizr test suite via package installers and easily add/remove tests as needed. Less tests, smaller file obviously.
The 'setClasses' option will add the related test class to your html but you can also take advantage of the 3.0 async events like so:
Modernizr.on('csstransitions', function(bool) {
if (bool === true) // do transition stuffs
}
I've got these functions to create elements and change their attributes. Could you give me an advice on how to modify them?
function create(elem) {
return document.createElementNS ? document.createElementNS("http://www.w3.org/1999/ xhtml", elem) : document.createElement(elem);
}
function attr(elem, name, value) {
if (!name || name.constructor != String) return "";
name = {"for": "htmlFor", "class": "className"}[name] || name;
if (typeof value != "undefined") {
elem[name] = value;
if (elem.setAttribute) elem.setAttribute(name, value);
}
return elem[name] || elem.getAttribute(name) || "";
}
I want to get something like this create('div', {'id': 'test', 'class': 'smth'});
function create(elem, attr) {
if (!attr) return document.createElementNS ? document.createElementNS("http://www.w3.org/1999/xhtml", elem) : document.createElement(elem);
if (attr) {
var el = document.createElementNS ? document.createElementNS("http://www.w3.org/1999/xhtml", elem) : document.createElement(elem);
for (var i = 0; i < attr.length; i++) {
attr(el, name[i], value[i]);
}
return el;
}
}
Please help =]
You did pretty good but I have a solution for you that you should try that worked for me and it is quick and easier. It for the creating a element and sets attributes function.
as you mentioned:
I want to get something like this create('div', {'id': 'test', 'class': 'smth'});
here is the solution:
function create(ele, attrs) {
//create the element with a specified string:
var element = document.createElement(ele);
//create a for...in loop set attributes:
for (let val in attrs) {
//for support in the setAttrubute() method:
if (element.setAttribute) {
if (element[val] in element) {
element.setAttribute(val, attrs[val]);
} else {
element[val] = attrs[val];
}
} else {
element[val] = attrs[val];
}
}
//return the element with the set attributes:
return element;
}
This also works with custom attributes and it property's like innerHTML too.
If you also want to be sure that I know this works I have tested it and logged it on the console and seeing it on the HTML page. I tested this on Firefox.
Here's a Demo
You can't iterate through an object like that:
for (var k in attrs) {
if (attr.hasOwnProperty(k))
attr(el, k, attrs[k]);
}
Note that I changed your "attr" variable to "attrs" so that it doesn't hide the "attr" function you've created. Also, up in your "attr" function, change the "undefined" test:
if (typeof value !== undefined)
to be a little safer. Comparisons with "==" and "!=" attempt a type conversion, which is unnecessary if you're just checking undefined.
I would recommend a javascript framework like jQuery. They already have this functionality implemented.
$("<div/>", {
"class": "test",
text: "Click me!",
click: function(){
$(this).toggleClass("test");
}
}).appendTo("body");
A word of advice: I personally prefer the jquery way because you can add the css and events to the element directly, and refer to objects by a var name instead of the id, but... There are issues when using this method to create input elements, ie7 & ie8 don't allow you to set the type property so beware when creating a button, textbox, etc for example, jquery will throw a "type property can't be changed" error.
If the code is to be used in a browser before ie9, best use: document.createElement instead to increase compatibility.
export function Element(name, object = {}) {
const element = document.createElement(name);
for (const key in object) {
element[key] = object[key];
}
return element;
}
export function Anchor(object) {
return Element('a', object);
}
Use it like:
const anchor = Anchor({href: 'test'});