getComputedStyle like javascript function for IE8 - javascript

I'm trying to write a Javascript function inside a Java GWT code that gets the value of the following styles
"direction", "fontFamily", "fontSize", "fontSizeAdjust", "fontStyle", "fontWeight", "letterSpacing", "lineHeight", "padding", "textAlign", "textDecoration", "textTransform", "wordSpacing"
The getComputedStyle was useful in all browsers except IE8 which doesn't support such function as I understand
I looked at the posts about smiler subject here but all of them failed to get one of the above styles
smiler subject posts 1, 2.
Here is my initial solution without the IE8 special case
public static native String getStyleProperty(Element element, String style) /*-{
if (element.currentStyle) {
return element.currentStyle[style];
} else if (window.getComputedStyle) {
return window.getComputedStyle(element, null).getPropertyValue(
style);
}
}-*/;
Any suggestions for a good getComputedStyle replacement function for IE8 ?

Look over here: http://snipplr.com/view/13523/
The code:
if (!window.getComputedStyle) {
window.getComputedStyle = function(el, pseudo) {
this.el = el;
this.getPropertyValue = function(prop) {
var re = /(\-([a-z]){1})/g;
if (prop == 'float') prop = 'styleFloat';
if (re.test(prop)) {
prop = prop.replace(re, function () {
return arguments[2].toUpperCase();
});
}
return el.currentStyle[prop] ? el.currentStyle[prop] : null;
}
return this;
}
}
Example:
window.onload = function() {
var compStyle = window.getComputedStyle(document.getElementById('test'), "");
alert(compStyle.getPropertyValue("color"));
alert(compStyle.getPropertyValue("float"));
alert(compStyle.getPropertyValue("background-color"));
}

Here is the solution. It is based on this Trick, but then I've expanded it in case to resolve two problems.
First problem is that borderTopWidth (left,bottom,right) in el.currentStyle returns as adjective - 'thin', 'medium', 'thick' - or 'none'. The Trick will return exception.
And second problem - some values will not be calculated correctly. Such as opacity and some else. You can check it by yourself by applying this Trick-method to all the properties:
var _style = el.currentStyle;
for (var key in _style) {
/// here is the Trick.....
}
At last, here is my solution, based on assumption, that I know all the properties I want to get by this function:
if (!window.getComputedStyle) window.getComputedStyle = function(el){
var __style = el.currentStyle,
_style = {};
for (var i in __style) {
_style[i] = __style[i];
}
// IE8 returns border widths as adjectives
if (style.indexOf("border") === 0)
switch (_style[style]) {
case "thin":
_style[style] = 2;
break;
case "medium":
_style[style] = 4;
break;
case "thick":
_style[style] = 6;
break;
default:
_style[style] = 0;
}
// based on http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291
var leftCopy = el.style.left;
var runtimeLeftCopy = el.runtimeStyle.left;
// some properties, that I want to use
_styleParams = {
left : 1,
right : 1,
top : 1,
bottom : 1,
width : 1,
height : 1,
borderLeftWidth : 1,
borderRightWidth : 1,
borderTopWidth : 1,
borderBottomWidth : 1,
paddingLeft : 1,
paddingRight : 1,
paddingTop : 1,
paddingBottom : 1,
marginLeft : 1,
marginRight : 1,
marginTop : 1,
marginBottom : 1
}
for (var key in _styleParams) {
el.runtimeStyle.left = el.currentStyle.left;
el.style.left = _style[key];
_style[key] = el.style.pixelLeft;
el.style.left = leftCopy;
el.runtimeStyle.left = runtimeLeftCopy;
}
// opacity for IE8
if (_style.filter.match('alpha')) {
_style.opacity = _style.filter.substr(14);
_style.opacity = parseInt(_style.opacity.substring(0, _style.opacity.length-1)) / 100;
} else {
_style.opacity = 1;
}}

Here is more complete polyfill for IE8/getComputedStyle which should handle all cases:
https://github.com/jonathantneal/Polyfills-for-IE8/blob/master/getComputedStyle.js

I used a similar method to my original solution with an additional case to handle inline styles, also the way to check if the current document support the getComputedStyle is a bit different it checks in the document.defaultView instead of the window itself, here is the full function
public static native String getStyleProperty(Element el, String prop) /*-{
var computedStyle;
if (document.defaultView && document.defaultView.getComputedStyle) { // standard (includes ie9)
computedStyle = document.defaultView.getComputedStyle(el, null)[prop];
} else if (el.currentStyle) { // IE older
computedStyle = el.currentStyle[prop];
} else { // inline style
computedStyle = el.style[prop];
}
return computedStyle;
}-*/;
source

The best solution I've found so far was from another answer here: https://stackoverflow.com/a/17890142/3672465. It's a standalone version of the jQuery curCSS() code; you may need to adjust it to suit your needs (as Maksim notes in his answer). Here's a compact version of the IE 8 portion, if you just want something to drop in.
if( !window.getComputedStyle && document.documentElement.currentStyle ){
function getStyles( elem ){ return elem.currentStyle; };
function curCSS( elem, name, computed ){
var rnum = /^([+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|))(?!px)[a-z%]+$/i;
var t = 'left', l, rs, rsL, c = computed || getStyles( elem ),
r = c ? c[ name ] : undefined, s = elem.style;
if( r == null && s && s[ name ] ){ r = s[ name ]; }
if( rnum.test( r ) && !/^(top|right|bottom|left)$/.test( name ) ){
l = s[t]; rs = elem.runtimeStyle; rsL = rs && rs[t];
if( rsL ){ rs[t] = elem.currentStyle[t]; }
s[t] = name === 'fontSize' ? '1em' : r; r = s.pixelLeft + 'px';
s[t] = l; if( rsL ){ rs[t] = rsL; }
}
return r === '' ? 'auto' : r;
};
}

Related

How do I get the neighboring keys and their value of a key from an object in javascript?

I have this object:
var registered_screens = {
handle_1 : 'step_1',
handle_2 : 'step_2',
handle_3 : 'step_3'
};
And here's what I tried to get its neighbors:
function get_neighbors( current_step ) {
var steps = {
next: '',
previous: ''
};
for( var i in registered_screens ) {
if( current_step == registered_screens[i] ) {
if(registered_screens.hasOwnProperty(registered_screens[i])) {
steps.next = registered_screens[i+1];
}
if(registered_screens.hasOwnProperty(registered_screens[i-1])) {
steps.previous = registered_screens[i-1];
}
}
}
return steps;
}
Obviously, this is a no-go because an object can't be parsed the same as an array, but just wanted to show what I tried.
What I'd want to get is, if I call get_neighbors('handle_2'), return:
steps { prev : 'handle_1' , next : 'handle_3' }
Or, for get_neighbors('handle_3'):
steps { prev : 'handle_2', next : null }
I've also attempted:
var registered_screens = {
handle_one : 'step_1',
handle_2 : 'step_2',
handle_3 : 'step_3'
};
var key_values = Object.keys(registered_screens);
for( var i = 0; i < key_values.length; i++ ) {
console.log(registered_screens.key_values[i]);
}
But this throws:
main.js?ver=4.9.6:18 Uncaught TypeError: Cannot read property '0' of undefined
main.js?ver=4.9.6:18
main.js?ver=4.9.6:270
Funnily enough, I checked what the values for key_values[i] are and they're the right handles.
It seems JS has a hard time building variables out of strings?
I don't like it much and would look at a restructure to make registered_screens an array. I also would not trust this code due to object order can not be guaranteed.
That said, this will work with my browser.
Edit: Added an array version, which I would trust but would expand for blank (undefined) results.
// array version
var aScreens = [];
aScreens['handle_one'] = 'step_1';
aScreens['handle_2'] = 'step_2';
aScreens['handle_3'] = 'step_3';
function getArrayPrevNext(a,current) {
var x,p = '',n = '',found = false;
for(x in a) {
if (found) {
n = x;
break;
} else if (x == current) {
found = true;
} else {
p = x;
}
}
return {prev:p,next:n};
}
var aSteps = getArrayPrevNext(aScreens,'handle_3');
console.log('array prev['+ aSteps.prev +'], next['+ aSteps.next +']');
var p = aSteps.prev, n = aSteps.next;
console.log('handle prev['+ aScreens[p] +'], next['+ aScreens[n] +']');
console.log('handle alt prev['+ aScreens[aSteps.prev] +'], next['+ aScreens[aSteps.next] +']');
// Object version
var registered_screens = {
handle_one : 'step_1',
handle_2 : 'step_2',
handle_3 : 'step_3'
};
function getPreviousNext(obj,current) {
var prev = '', nxt = '', found = false;
Object.keys(obj).forEach(function(key) {
if (! nxt) {
if (found) {
nxt = key;
} else if (key == current) {
found = true;
} else {
prev = key;
}
}
});
return {prev:prev,next:nxt};
}
var steps = getPreviousNext(registered_screens,'handle_3');
console.log('Object steps:['+ steps.prev +']['+ steps.next +']');
Your second attempt is what I thought as well, it should work fine if you correct the key access like below
var registered_screens = {
handle_one : 'step_1',
handle_2 : 'step_2',
handle_3 : 'step_3'
};
var key_values = Object.keys(registered_screens);
for( var i = 0; i < key_values.length; i++ ) {
console.log(registered_screens[key_values[i]]);
}
As far as I know, the keys in the object are un-ordered and we should not rely on that order, like others have mentioned, it's order will not be the same as when you created, but your requirement seems like it can make use of Object.keys to iterate and find next and prev keys to some extent
To your question, why this registered_screens[key_values[i]] works and not registered_screens.key_values[i], the dot notation will not work for dynamic keys, i.e key_values[i] is not a key, it's a variable holding the key, in such cases you have to access it like an array like Object[keyNameHolder]
This script does a very basic parsing of object keys - it assumes it is always in the format of handle_{n} - based on that it creates an array that holds the keys in proper order, which then is searched for and uses n-1 and n+1 to return prev and next (if possible, else null). And yes i know, most browsers would sort it correctly nonetheless so that in most scenarios you would get the proper order (included a console output for comparison)
var screens = {
handle_1 : 'step_1',
handle_2 : 'step_2',
handle_3 : 'step_3',
handle_4 : 'step_4',
handle_5 : 'step_5',
handle_6 : 'step_6',
handle_7 : 'step_7',
handle_8 : 'step_8',
handle_9 : 'step_9',
handle_10 : 'step_10',
handle_11 : 'step_11',
},
keyParser = (key) => parseInt(key.replace('handle_', '')),
keySorter = (a, b) => keyParser(a) - keyParser(b),
handleKeys = Object.keys(screens).sort(keySorter);
// Compare key ordering in your browser
// It will be most likely identic, since most modern browsers understand that
// it should sort by {str}_{int} and not by {str}_{str} if an {int} is present
// but chances are that for instance IE9 would do the latter, so it could be that
// with browser ordering handle_10 and handle_11 come after handle_1
console.log('browser ordering:', Object.keys(screens));
console.log('parsed ordering:', handleKeys);
function getSteps(handle) {
var pos = handleKeys.indexOf(handle);
if(pos === -1) throw(`Can't find handle ${handle} in screens`);
return {
current: screens[handleKeys[pos]],
prev: pos > 0 ? screens[handleKeys[pos-1]] : null,
next: pos < handleKeys.length-1 ? screens[handleKeys[pos+1]] : null
}
}
console.log(
getSteps('handle_1'),
getSteps('handle_2'),
getSteps('handle_6'),
getSteps('handle_10'),
getSteps('handle_11')
);
Also a good read: https://hackernoon.com/out-of-order-keys-in-es6-objects-d5cede7dc92e
As far as I know the answer is there is no direct way, but you can play around this in many ways.
One idea is that you can play with the naming convention of your data, for example,
call the object items as handle_1 instead of "handle_one" and so on, that
way you can loop around the array using index ['handle_' + i] but notice that you can't do ['handle_' + i + 1] or else you will have a wrong index value because the string conversion will happen before the summation.
I hope this helps.
I made this work:
var registered_screens = {
handle_1 : 'step_1',
handle_2 : 'step_2',
handle_3 : 'step_3'
};
function get_neighbors( current_step ) {
var steps = {
next: '',
previous: ''
};
var key_values = Object.keys(registered_screens);
for( var i = 0; i < key_values.length; i++ ) {
if( current_step == registered_screens[key_values[i]]) {
if( !(registered_screens[key_values[i-1]] == null) ) {
steps.previous = registered_screens[key_values[i-1]];
}
if( !(registered_screens[key_values[i+1]] == null) ) {
steps.next = registered_screens[key_values[i+1]];
}
}
}
return steps;
}
And so, get_neighbors('step_2') reliably (in my tests) returns:
steps : { next : 'step_3', previous: 'step_1' };

Change variable value if document.URL contains array element

I have a javascript/jquery function that displays a notification. I want to make this function display a different mode if they aren't on x, y and z page.
This is how I tried to achieve this:
function display_alert(message, type, delay, mode)
{
type = (typeof type === "undefined") ? "danger" : type;
delay = (typeof delay === "undefined") ? 3000 : delay;
mode = (typeof mode === "undefined") ? 'normal' : mode;
var current_location = document.URL;
var home_locations = ['home', 'remote', 'zip'];
for (var i = 0; i < home_locations.length; i++)
{
if (current_location.toString().indexOf(home_locations[i]) == -1)
{
// alert(home_locations[i]); return;
mode = 'top';
break;
}
}
...
So if document.URL doesn't contain one of the array elements, then I want the mode variable to become top.
I think this is a simple problem, but I just can't see how to fix it.
You could use a regular expression built with your home_locations array :
var home_regex = new RegExp('('+home_locations.join('|')+')');
// home_regex = /(home|remote|zip)/;
if (!home_regex.test(document.URL)) mode = 'top';
You need to reverse the logic in your loop...
var current_location = document.URL;
var home_locations = ['home', 'remote', 'zip'];
var found = false;
for (var i = 0; i < home_locations.length; i++)
{
if (current_location.toString().indexOf(home_locations[i]) >= 0)
{
found = true;
break;
}
}
if (!found) {
mode = 'top';
}

using replace with browser prefixes js/css

I am making a javascript script that wants to do something like this:
elementObject.style.transform.replace('...','...');
elementObject.style.webkitTransform.replace('...','...');
And just to clear this up, yes, I have set both of those styles earlier in the script. However, this causes errors because in Chrome, for example, it does not recognize the transform style. Therefore, replace fails because elementObject.style.transform is undefined, and undefined has no method replace. Is there any way to do this without causing these errors? I just want the webkit transform, i do not need moz, o, or any other prefix.
var st = elementObject.style;
if (st.transform !== undefined) st.transform = st.transform.replace('...','...');
// ...
or more general:
function replaceStyle(element, style, text, replacement) {
var vendors = ['webkit', 'moz', 'o', 'ms'];
var st = element.style;
if (st[style] !== undefined) st[style] = st[style].replace(text, replacement);
style = style.charAt(0).toUpperCase()+style.substring(1);
for (var i=0, l=vendors.length; i<l; i++) {
var vendorStyle = vendors[i]+style;
if (st[vendorStyle] !== undefined)
st[vendorStyle] = st[vendorStyle].replace(text, replacement);
}
}
// sample call (setting width from "40px" to "100%")
replaceStyle( myElementObject, 'with', '40px', '100%' );
You must use the Adapter design pattern.
For example:
function transformStyle() {
var tempEl = document.createElement('div'),
elStyle = tempEl.style;
if (typeof elStyle.transform !== 'undefined') {
transformStyle = function (el, val) {
val != null && (el.style.transform = val);
return el.style.transform;
};
} else if (typeof elStyle.webkitTransform !== 'undefined') {
transformStyle = function (el, val) {
val != null && (el.style.webkitTransform = val);
return el.style.webkitTransform;
};
} else {
transformStyle = function () { return ''; }; //ignore when not supported
}
tempEl = null;
elStyle = null;
return transformStyle.apply(this, arguments);
}
var el = document.createElement('div');
transformStyle(el, 'rotate(-2deg)'); //set style
transformStyle(el); //rotate(-2deg)
Obviously you could write something more generic such as a style method that allows accessing or modifying any styles. You could use a list of vendor prefixes to make the code more DRY as well. The previous code was just an example.

Get margin top with JavaScript

I need to get margin-top of an input with JavaScript. This is the jQuery code which works fine:
alert($("#input").css('margin-top'))
But I need it in pure Javascript, I have tried the following code with no luck
alert(document.getElementById('input').style.marginTop)
How can I make it work in pure JavaScript?
I just found a solution:
var style = window.getComputedStyle(document.getElementById('input'));
var marginTop = style.getPropertyValue('margin-top');
alert(marginTop);
Here is a stand-alone version of curCSS from jQuery. Please note the edit that I made to keep the code size down. It hasn't caused me any problems thus far.
//Get current CSS - from jQuery-1.9.0
var curCSS;
(function(){
/*!
* Copyright 2005, 2012 jQuery Foundation, Inc. and other contributors
* Released under the MIT license
* http://jquery.org/license
*/
var getStyles, core_pnum = /[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source,
rmargin = /^margin/, rnumnonpx = new RegExp( "^(" + core_pnum + ")(?!px)[a-z%]+$", "i" );
if(window.getComputedStyle){
getStyles = function(elem){return window.getComputedStyle( elem, null )};
curCSS = function( elem, name, _computed ){
var width, minWidth, maxWidth, computed = _computed || getStyles( elem ),
ret = computed ? computed.getPropertyValue( name ) || computed[ name ] : undefined,
style = elem.style;
if( computed ){
/* Edit - removed edge case as requires lots more jQuery code
if ( ret === "" && !jQuery.contains( elem.ownerDocument, elem ) ) {ret = jQuery.style( elem, name )}*/
if( rnumnonpx.test( ret ) && rmargin.test( name )){
width = style.width; minWidth = style.minWidth; maxWidth = style.maxWidth;
style.minWidth = style.maxWidth = style.width = ret; ret = computed.width;
style.width = width; style.minWidth = minWidth; style.maxWidth = maxWidth}}
return ret;
}
}
else if (document.documentElement.currentStyle){
getStyles = function( elem ){return elem.currentStyle};
curCSS = function( elem, name, _computed ){
try{
var left, rs, rsLeft, computed = _computed || getStyles( elem ),
ret = computed ? computed[ name ] : undefined, style = elem.style;
if( ret == null && style && style[ name ] ) {ret = style[ name ]}
if( rnumnonpx.test( ret ) && !rposition.test( name ) ) {
left = style.left; rs = elem.runtimeStyle;rsLeft = rs && rs.left;
if ( rsLeft ) {rs.left = elem.currentStyle.left}
style.left = name === "fontSize" ? "1em" : ret; ret = style.pixelLeft + "px";
style.left = left; if ( rsLeft ) {rs.left = rsLeft}}
return ret === "" ? "auto" : ret
}
catch(e){};
}
}
})();

Javascript array index error

I am trying to access to a simple nested Array, doing this:
var currMenu = 1;
while ( currMenu < menu.length ) {
alert(currMenu);
alert(menu[0][currMenu].text);
currMenu++;
}
Despite alerts are throwing the correct values, I am getting this error on firebug: TypeError: menu[0][currMenu] is undefined.
What is happening?
Thanks!
Edit: Sorry, I was rushing, here you have the "menu" structure:
menu[0] = new Array();
menu[0][0] = new Menu(false, '', 15, 50, 20, '','' , 'navlink', 'navlink');
menu[0][1] = new Item('someText', '#', '', 100, 10, 1);
And the object Item:
function Item(text, href, frame, length, spacing, target) {
this.text = text;
if (href == '#') {
this.href = '#';
} else if (href.indexOf('http') == 0) {
this.href = href;
} else this.href = href;
this.frame = frame;
this.length = length;
this.spacing = spacing;
this.target = target;
// Reference to the object's style properties (set later).
this.ref = null;
this.showLoadingBar = false;
}
Assuming your menu is coherent with the [0][currMenu], you should access it like this :
while ( currMenu < menu[0].length ) {
alert(currMenu);
alert(menu[0][currMenu].text);
You're looking at the length of the "menu" array, but you're accessing the array at the zero-th index of that array (which may or may not be an array; I can't tell from the code you've posted).

Categories

Resources