JavaScript Object Literal and jQuery $(classes) - javascript

I have an object that contains a list of browser widths
breakpoints {
smallMobile: 0,
mobile: 480,
tablet: 672,
desktop: 868,
largeDesktop: 1050
}
I want to add a class to the body of the document based on whether the browser width falls between two values in the breakpoints object.
What I have so far
var key;
for (key in breakpoints) {
if (breakpoints.hasOwnProperty(key))
if (winWidth >= breakpoints[key]) {
$('body').removeClass().addClass(key);
} else {
$('body').removeClass(key);
}
}
}
Now this works, however it also removes ALL classes from the body. Instead I would like to add only one class, and not have to remove anything unless the breakpoint no longer matches.
I'm sure there has to be a way. Any help would be appreciated :)
EDIT
Current output at various widths
>= 1050: <body class="smallMobile mobile tablet desktop largeDesktop">
>= 868: <body class="smallMobile mobile tablet desktop">
>= 672: <body class="smallMobile mobile tablet">
Ideal output
>= 1050: <body class="largeDesktop">
>= 868: <body class="desktop">
>= 672: <body class="tablet">
(For the record I use Media Queries, but I need access to classnames for an edge case)

I've slightly modified your code and saved the class thats the highest applicable one. Then I remove every class and apply the applicable one.
// create a variable to store the only class you want to apply
var apply = "";
for (var key in breakpoints) {
// keep overwriting the apply var if the new key is applicable
if (winWidth >= breakpoints[key]) {
apply = key;
}
// remove any existing classes with that keyname
$('body').removeClass(key);
}
// add the key to the body as a class
$('body').addClass(apply);
Also, you can remove breakpoints.hasOwnProperty(key) as the for-loop only loops through keys that actually exist anyway, so you are doing an unnecessary check.
Update
At #juvian's note, I'll add a way to make sure that the order in which you make your object makes no difference:
var apply = "";
var check = 0;
for (var key in breakpoints) {
// Also check if the value is higher than the last set value
if (winWidth >= breakpoints[key] && breakpoints[key] >= check) {
apply = key;
check = breakpoints[key];
}
$('body').removeClass(key);
}
$('body').addClass(apply);

It's because you're using removeClass() without any parameter in it.
If a class name is included as a parameter, then only that class will
be removed from the set of matched elements. If no class names are
specified in the parameter, all classes will be removed.
Source: http://api.jquery.com/removeClass/
So, you should find the right class to remove and then call this function with a param.

Consider to store your breakpoints in sorted array instead of map:
var dimensions = [];
for (var key in breakpoints) {
dimensions.push({ name: key, width: breakpoints[key] })
}
dimensions.sort(function(a, b) { return b.width - a.width });
Now dimensions is
[
{"name":"largeDesktop","width":1050},
{"name":"desktop","width":868},
{"name":"tablet","width":672},
{"name":"mobile","width":480},
{"name":"smallMobile","width":0}
]
Of course you can hardcode it in this structure and not grep/sort each time.
Finally, find highest width with
for (var i = 0; i < dimensions.length; i++) {
if (winWidth >= dimensions[i].width) {
$("body").addClass(dimensions[i].name);
break;
}
}
However, if you want to create responsive layout, media queries is the way to go. Media queries also will take windows resize/orientation change events into account.

Here is a way you can get the one that matches with the highest value:
var wid = 868;
var max = 0;
var size = null;
Object.keys(breakpoints).map(function(a){
size = wid >= breakpoints[a] ? a : size;
max = wid >= breakpoints[a] ? Math.max(max,breakpoints[a]) : max;
})
console.log(max, size) // 868, 'desktop'

if you use .removeClass() without parameter then all class on selected element will remove as like prabu said, but if you want, i give some example so you will not confused how to remove class if window width less than breakpoints
var key;
for (key in breakpoints) {
if (breakpoints.hasOwnProperty(key)) {
$("body").addClass(key);
if (winWidth<breakpoints[key]) {
$("body").removeClass(key);
}
}
}

Related

Looping causes forced reflow while the same code doesn't cause reflow without loop

I found a weird case of forced reflow when we use loop. Example here
In the first case, I have looped through 200 elements and change its class using for loop.
function resize1(){
let children = parent1.children;
clicked2 = !clicked2;
for(let i=0;i<200;i++){
let child = children[i];
let { width } = window.getComputedStyle(child);
isLarge = clicked2 ? i % 2 === 0 : i % 2=== 1;
if(isLarge){
child.classList.add('large');
child.classList.remove('small');
}
else{
child.classList.add('small');
child.classList.remove('large');
}
// size = window.getComputedStyle(child).margin;
}
}
Instead of looping I again went through each child elements and changed the same class.
let width, isLarge, i =0, child;
child = parent.children[i];
width = window.getComputedStyle(child);
isLarge = clicked ? i % 2 === 0 : i % 2=== 1;
if(isLarge){
child.classList.add('large');
child.classList.remove('small');
}
else{
child.classList.add('small');
child.classList.remove('large');
}
i++;
child = parent.children[i];
width = window.getComputedStyle(child);
isLarge = clicked ? i % 2 === 0 : i % 2=== 1;
if(isLarge){
child.classList.add('large');
child.classList.remove('small');
}
else{
child.classList.add('small');
child.classList.remove('large');
}
i++;
(*200 elements)
I know this case is weird but when I checked for performance the second case takes less amount of time in chrome dev tools and reflow also occurs in looping case only.
The answer has absolutely nothing to do with looping vs not looping, and everything to do with how you're using window.getComputedStyle().
This handy gist outlines which JavaScript properties and methods will force layout/reflow.
window.getComputedStyle() will force layout in one of 3 conditions:
The element is in a shadow tree
There are media queries (viewport-related ones). Specifically, one of the following:
The property requested is one of the following:
height, width
Now, in the looping version of your code, you have this:
let { width } = window.getComputedStyle(child);
However in the non-looping version of your code, you have this:
width = window.getComputedStyle(child);
What is the difference? width = window.getComputedStyle(child) creates a reference to the computed styles object, but it does not access the width property. You have (perhaps erroneously) created a variable width that does not request the width property but instead the computed style object itself, which in and of itself is not enough to force a layout.
However, let { width } = window.getComputedStyle(child) adds the extra step of destructuring the width property to a variable declaration, effectively accessing that property and forcing a layout for every iteration of your for loop.
You can see the extra forced layout that let { width } = window.getComputedStyle(child) causes in the performance timeline (Safari):
If you modify the non-looping version of your code to access the width property of getComputedStyle() for all 200 cases, you get the same forced layout in the non-looping portion:
Or, you could simply remove all uses of window.getComputedStyle(), since in the current version of your code it is not used for anything, and eliminate the forced layout entirely.

Reference to an attribute instead of getters and setters

Question: is it possible to have object1 providing an attribute of object2 in a way, that no getters and setters are needed? So I could do a = object1.attribute and object1.attribute = a.
Example: I am implementing support for dynamic aligning of the absolutely positioned HTML objects on the page. All kinds of aligns should be supported - left, right, top, bottom, horizontal center, vertical center or even evenly spaced. To reduce the amount of duplicated code, I came up with the solution to implement class Direction, which can be horizontal and vertical and works in the Facade-ish mode with regards to the Element. It this is passed as an attribute to the aligning function. Similarly, I am handling the left/right/middle and distribute evenly distinction, but to keep it simple, let's ignore it here.
Here is the class.
Direction = function(selector) {
this.selector = selector;
}
Direction.prototype.get = function(element) {
return parseInt(element.style[this.selector]);
}
Direction.prototype.set = function(element, value) {
element.style[this.selector] = value + 'px';
}
Here are the "constants" available to the client.
Direction.VERTICAL = new Direction('left');
Direction.HORIZONTAL = new Direction('top');
Here is the public method performing the "minimal" align (left or top).
alignMin = function(elements, direction) {
var min = Number.MAX_VALUE;
for (var i = 0; i < elements.length; i++) {
min = Math.min(min, direction.get(elements[i]));
}
for (var i = 0; i < elements.length; i++) {
direction.set(elements[i], min);
}
}
And the client here demonstrates the intended use.
alignDivsToLeft = function() {
alignMin(document.getElementsByTagName("div"), Direction.VERTICAL);
}
Working example on a JSFiddle: http://jsfiddle.net/7Tpam/
Question again: All this works, I was just wondering, if instead of Direction.get() and Direction.set() methods I could do something (a reference to a value?) to directly access the "proxified" attribute, so the alignMin function could use just
...
min = Math.min(min, direction.value); // possibly with parseInt()
...
and
...
direction.value = min; // possibly with + 'px'
...
If there is a simple solution for this, it would be a shorter, clearer and more elegant way, especially in cases with more attributes (position, dimension, ...). Plus there is the factor of the curiosity (still learning magic of JavaScript, so..).
Javascript in some browsers supports getters and setters as described in this article
Using your objects:
Direction = function(selector) {
this._selector = selector;
}
Direction.prototype = {
get selector(){
return this._selector;
},
set selector(selector){
this._selector = selector
}
}
Usage:
var direction = new Direction("left");
console.log(direction.selector); // getter
direction.selector = 'right' //setter
JSFiddle: http://jsfiddle.net/7Tpam/1/
This would seem in the first instance to be what you're describing, but be aware it does not have global support
Firefox
Safari 3+ (Brand New)
Opera 9.5 (Coming Soon)
Source: above-linked article

CSS/JavaScript: Make element top-most z-index/top-most modal element

I would like to make an element (e.g. a <div>) be the top-most layer on the page.
My assumption is that the only way I can do this is to specify that the element has a style="z-index:" value that is the maximum the browser allows (int32?).
Is this correct?
Instead, would it be possible to somehow get the element's z-index whose is highest, and make this <div>'s z-index the [highest element's value] + 1? For example:
$myDiv.css("z-index", $(document.body).highestZIndex() + 1);
How do modal JavaScript "windows" work?
Here's how to do it :
var elements = document.getElementsByTagName("*");
var highest_index = 0;
for (var i = 0; i < elements.length - 1; i++) {
if (parseInt(elements[i].style.zIndex) > highest_index) {
highest_index = parseInt(elements[i].style.zIndex;
}
}
highest_index now contains the highest z-index on the page... just add 1 to that value and apply it wherever you want. You can apply it like so :
your_element.style.zIndex = highest_index + 1;
Here's another way of achieving the same thing using jQuery :
var highest_index = 0;
$("[z-index]").each(function() {
if ($(this).attr("z-index") > highest_index) {
highest_index = $(this).attr("z-index");
}
});
Again, same way to apply the new index to an element :
$("your_element").attr("z-index", highest_index + 1);
What about stacking context? It is not always true that: On a document highest z-index will be on top. See: http://philipwalton.com/articles/what-no-one-told-you-about-z-index/. If you do not take stacking context into account, setting a billion may not be enough to make your element on the top-most.
http://abcoder.com/javascript/a-better-process-to-find-maximum-z-index-within-a-page/ -> find the max z-index and assign +1 to it.
Sheavi's jQuery solution doesn't work because z-index is a css style, not an attribute.
Try this instead:
raiseToHighestZindex = function(elem) {
var highest_index = 0;
$("*").each(function() {
var cur_zindex= $(this).css("z-index");
if (cur_zindex > highest_index) {
highest_index = cur_zindex;
$(elem).css("z-index", cur_zindex + 1);
}
});
return highest_index;
};
Return value may not be what you expect due to Javascript's async nature, but calling the function on any element will work fine.

How can I optimize my routine to build HTML tiles from an array?

As my last question was closed for being "too vague" - here it is again, with better wording.
I have a "grid" of li's that are loaded dynamically (through JavaScript/jQuery), the Array isn't huge but seems to take forever loading.
So, SO people - my question is:
Am I being stupid or is this code taking longer than it should to execute?
Live demo: http://jsfiddle.net/PrPvM/
(very slow, may appear to hang your browser)
Full code (download): http://www.mediafire.com/?xvd9tz07h2u644t
Snippet (from the actual array loop):
var gridContainer = $('#container');
var gridArray = [
2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
2,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,2,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,2,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
];
function loadMap() {
var i = 0;
while (i <= gridArray.length) {
var gridHTML = $(gridContainer).html();
$(gridContainer).html(gridHTML+'<li class="node"></li>');
i++;
}
$('li.node').each(function() {
$(gridArray).each(function (i, val) {
if (val == '0') { gridTile = 'grass.jpg' };
if (val == '1') { gridTile = 'mud.jpg' };
if (val == '2') { gridTile = 'sand.gif' };
$($('ul#container :nth-child('+i+')'))
.css({ 'background-image': 'url(img/tiles/'+gridTile });
});
});
}
The loop where you set the background images is the real problem. Look at it: you're looping through all the <li> elements that you just got finished building from the "grid". Then, inside that loop — that is, for each <li> element — you go through the entire "grid" array and reset the background. Each node will end up being set to the exact same thing: the background corresponding to the last thing in the array over and over again to the exact same background.
The way you build the HTML is also very inefficient. You should loop through the grid and build up a string array with an <li> element in each array slot. Actually, now that I think of it, you really should be doing the first and second loops at the same time.
function loadMap() {
var html = [], bg = ['grass', 'mud', 'sand'];
for (var i = 0, len = gridArray.length; i < len; ++i) {
html.push("<li class='node " + bg[gridArray[i]] + "'></li>");
}
$(gridContainer).html(html.join(''));
}
Now you'll also need some CSS rules:
li.grass { background-image: url(grass.jpg); }
li.mud { background-image: url(mud.jpg); }
li.sand { background-image: url(sand.gif); }
It'd probably be farm more efficient to build up the complete HTML for the array and then assign it to the .html property of the container, rather than assigning each individual li:
var gridHTML = $(gridContainer).html();
while (i <= gridArray.length) {
gridHTML = gridHTML+'<li class="node"></li>';
i++;
}
$(gridContainer).html();
Next, why are you looping over both of these? The outer loop is probably completely unnecessary, because your inner loop already uses nth-child to select the proper node.
$('li.node').each(function() {
$(gridArray).each(function (i, val) {

Get a CSS value from external style sheet with Javascript/jQuery

Is it possible to get a value from the external CSS of a page if the element that the style refers to has not been generated yet? (the element is to be generated dynamically).
The jQuery method I've seen is $('element').css('property');, but this relies on element being on the page. Is there a way of finding out what the property is set to within the CSS rather than the computed style of an element?
Will I have to do something ugly like add a hidden copy of the element to my page so that I can access its style attributes?
With jQuery:
// Scoping function just to avoid creating a global
(function() {
var $p = $("<p></p>").hide().appendTo("body");
console.log($p.css("color"));
$p.remove();
})();
p {color: blue}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
Using the DOM directly:
// Scoping function just to avoid creating a global
(function() {
var p = document.createElement('p');
document.body.appendChild(p);
console.log(getComputedStyle(p).color);
document.body.removeChild(p);
})();
p {color: blue}
Note: In both cases, if you're loading external style sheets, you'll want to wait for them to load in order to see their effect on the element. Neither jQuery's ready nor the DOM's DOMContentLoaded event does that, you'd have to ensure it by watching for them to load.
Normally you should be let the browser apply all the rules and then ask the browser for the results, but for the rare case where you really need to get the value out of the style sheet you can use this: (JSFiddle)
function getStyleSheetPropertyValue(selectorText, propertyName) {
// search backwards because the last match is more likely the right one
for (var s= document.styleSheets.length - 1; s >= 0; s--) {
var cssRules = document.styleSheets[s].cssRules ||
document.styleSheets[s].rules || []; // IE support
for (var c=0; c < cssRules.length; c++) {
if (cssRules[c].selectorText === selectorText)
return cssRules[c].style[propertyName];
}
}
return null;
}
alert(getStyleSheetPropertyValue("p", "color"));
Note that this is pretty fragile, as you have to supply the full selector text that matches the rule you are looking up (it is not parsed) and it does not handle duplicate entries or any kind of precedence rules. It's hard for me to think of a case when using this would be a good idea, but here it is just as an example.
In response to Karim79, I just thought I'd toss out my function version of that answer. I've had to do it several times so this is what I wrote:
function getClassStyles(parentElem, selector, style){
elemstr = '<div '+ selector +'></div>';
var $elem = $(elemstr).hide().appendTo(parentElem);
val = $elem.css(style);
$elem.remove();
return val;
}
val = getClassStyles('.container:first', 'class="title"', 'margin-top');
console.warn(val);
This example assumes you have and element with class="container" and you're looking for the margin-top style of the title class in that element. Of course change up to fit your needs.
In the stylesheet:
.container .title{ margin-top:num; }
Let me know what you think - Would you modify it, and if so how? Thanks!
I have written a helper function that accepts an object with the css attributes to be retrieved from the given css class and fills in the actual css attribute values.
Example is included.
function getStyleSheetValues(colScheme) {
var tags='';
var obj= colScheme;
// enumerate css classes from object
for (var prop in obj) {
if (obj.hasOwnProperty(prop) && typeof obj[prop]=="object") {
tags+= '<span class="'+prop+'"></span>';
}
}
// generate an object that uses the given classes
tags= $('<div>'+tags+'</div>').hide().appendTo("body");
// read the class properties from the generated object
var idx= 0;
for (var prop in obj) {
if (obj.hasOwnProperty(prop) && typeof obj[prop]=="object") {
var nobj= obj[prop];
for (var nprop in nobj) {
if (nobj.hasOwnProperty(nprop) && typeof(nobj[nprop])=="string") {
nobj[nprop]= tags.find("span:eq("+idx+")").css(nobj[nprop]);
}
}
idx++;
}
}
tags.remove();
}
// build an object with css class names where each class name contains one
// or more properties with an arbitrary name and the css attribute name as its value.
// This value will be replaced by the actual css value for the respective class.
var colorScheme= { chart_wall: {wallColor:'background-color',wallGrid:'color'}, chart_line1: { color:'color'} };
$(document).ready(function() {
getStyleSheetValues(colorScheme);
// debug: write the property values to the console;
if (window.console) {
var obj= colorScheme;
for (var prop in obj) {
if (obj.hasOwnProperty(prop) && typeof obj[prop]=="object") {
var nobj= obj[prop];
for (var nprop in nobj) {
if (nobj.hasOwnProperty(nprop)) {
console.log(prop+'.'+nprop +':'+ nobj[nprop]);
}
}
}
}
// example of how to read an individual css attribute value
console.log('css value for chart_wall.wallGrid: '+colorScheme.chart_wall.wallGrid);
}
});
I wrote this js function, seems to be working for nested classes as well:
usage:
var style = get_css_property('.container-class .sub-container-class .child-class', 'margin');
console.log('style');
function get_css_property(class_name, property_name){
class_names = class_name.split(/\s+/);
var container = false;
var child_element = false;
for (var i = class_names.length - 1; i >= 0; i--) {
if(class_names[i].startsWith('.'))
class_names[i] = class_names[i].substring(1);
var new_element = $.parseHTML('<div class="' + class_names[i] + '"></div>');
if(!child_element)
child_element = new_element;
if(container)
$(new_element).append(container);
container = new_element;
}
$(container).hide().appendTo('body');
var style = $(child_element).css(property_name);
$(container).remove();
return style;
}

Categories