Applying state changes that apply to many elements in a block - javascript

I like BEM a lot, and I usually use a variant of BEM where I use state classes to toggle sub-items on/off, leading to easy to understand rules in my SASS like:
.my-block{
&__element {
color : blue;
// I prefer state-classes over modifiers for state
&.is-selected { color : red; }
&--small { height: 50%; }
}
}
The problem I wonder how to solve in the most BEM-ish way is how to deal with a state change in my javascript that should apply to a lot of elements in a block. For instance, say I have a component that should hide or show different elements depending on whether you are in step1, step2 or step3 of a process.
The BEM bits are easy enough if I should just apply state classes on each of the elements, but that is just the problem. If I have 10 elements where 7 needs to be hidden on step 2 then that is 7x as much javascript to add as if I had just added a rules on the not so BEM-pure form of
&__element {
display: none;
// -- this --
.my-block.is-step4 & { display : block }
}
I could then activate all of those rules with one myBlock.classList.toggle("is-step4"), as opposed to one for each element.
My solution was a pragmatic middle point of developer convenience and pure BEM, but I wonder if there is a "pure" BEM solution that is also friendly in terms of line of codes needed in the javascript to update the elements?

Just use nested selectors for this case. So you'll have step modifiers on the parent block which contains all the rest of blocks you need to affect (and don't be afraid if such parent is the whole page).
See https://en.bem.info/methodology/faq/#can-i-use-nested-selectors — it's recommended solution.

Related

How to make an element's CSS NOT inherit from others?

I want the element to use only css that are in the "A" section and ignore the "B" section.
Is it possible?If javascript can do this, How?
Thanks you.
You can not do that with the example you've provided. The C in CSS stands for Cascading, the styling rules cascade down the DOM tree.
You have to reset the styling of the element to what you want with a more specific selector, e.g. #Examplewrapper input{}. By using a more specific selector, it'll overwrite/suplement the previous styling, without the need for !important.
Alternatively, you can set the most upper selector more specific, e.g. #content input{}. This way, when you place a form in the #footer, it will not have the styling, as #content doesn't have a #footer in it (it cant cascade).
I do recommend to define a general input as you have. This way, all forms have the same font, size and styling throughout your website. If you want another color border, you only have to change that one settings. This is the way many (profesional) sites work, because it is the most efficient.
This is how the inheritance works. You can only overwrite styles if others are set globally (i.e. for all input elements).
You can always limit the global styles of input with some classname, like input.myStyle so the raw input will have no styles set.

jQuery Add & Remove Class Div Width

How can i add/remove class according as div width ? I tried some codes but I have no idea about jquery codes. I'd like add div class according as antoher div width. Just i need add class like that. If container is smaller than 600px "add class" to content div else "remove class" from content div. These are my codes;
<div class="container">
<div class="content"></div>
</div>
$(window).resizeboxes(function() {
if ($(".container").width < 600){
$( ".content" ).addClass( ".content_600" );
}
});
else{
removeClass('.content_600')
}
$(window).trigger('resizeboxes');
This works, though the code is changed slightly. There were some problems with the syntax also, so I've corrected those (for instance the else statement was slightly misplaced). Here is a working example:
https://jsfiddle.net/vt0nbx36/3/
Here is the code:
var resizeboxes = function() {
if ($(".container").width() < 600)
{
$(".content").addClass("content_600");
}
else
{
$(".content").removeClass("content_600")
}
};
resizeboxes();
$(window).resize(function(){
resizeboxes();
});
For this need exactly, you have jQuery's .toggleClass() function. It takes the class name as a first parameter, and optional second boolean parameter that states wether the class name should be added or removed. You can find the documentation here
$(".content").toggleClass("content_600", ($(".container").width() < 600));
Even tho your question is a JS related question, CSS as a matter of fact can handle this like no other beast can (mostly)!
CSS allows you to use media-queries to resize your content based on the width of the viewport.
The upside of this is that the browser will handle this for you within the rendering engine rather than having JS between your change and the rendering engine.
The major downside is that you can't define the width of element A based on element B but are unfortunately locked to using the viewport as an indicator.
Before I explain why you'd want to use CSS I'd like to point out why you don't want to use JS for this if possible.
The jQuery.resize eventhandler fires inconsistently across browsers and it fires alot of times usually.
This causes your scrolling to clog up and make your webpage feel laggy.
If there's anything your users will dislike it's the fact that scrolling is controlled by something they don't even know of which is slowing you down.
As for a CSS solution, a media query looks like this:
.my-selector {
width: 900px;
}
#media all and (max-width: 600px) {
.my-selector {
width: 600px;
...
}
}
You wrap your code in a sort-of conditional that allows you to be very flexible with manipulating elements on the page.
What happens in the above piece of code is that when the parser reads the CSS it sees the first selector not in a media query so it applies width: 900px; then it sees a media query and sees the other rule for my-selector however it will only apply that rule when the screen is at that width we defined in the #media ... rule. When you resize CSS handles things differently behind the scenes so it's much faster than JS in that case.
I'm not sure if it actually applies to your situation but if your container is sized by the viewport rather than parent elements this should be possible and I thought it'd be nice atleast to show you a good way of playing with element dimensions.
Also, you can use #media to for instance make a webpage print friendly by changing the all to print for example and setting the background-color: transparent for an element - saves ink ^.^ which is an additional extra on top of the general awesomeness of media queries.
Hope it helps, good luck if you wish to make your webpage 5 times faster ;)

Remove CSS property completely

I have a button, which when clicked loads an additional CSS file, that overrides a great part of the basic CSS files. (this is for accessibility purposes if you wonder)
Let's say I have a background and background-color properties used in multiple selectors for input[type='text']. I want to reset/delete those. I DON'T want to set a new value for those background properties, I want to remove them, so that the browser will render everyting as it would by default.
The reason for this is because in high contrast mode with black background color to the body in Firefox, any background set to input or button will override it with a value equal to the text color which will make the value of the input or the button unreadable. But that's another story...
EDIT: Since everybody so far is telling me to set some new property to those, I'm writing it in bold big letters - I DON'T NEED TO SET NEW PROPERTY FOR background. :) The reason behind that if that property is present Firefox defaults it to black if the background set in the high contrast mode is black as well. To test this, go to Preferences -> Content -> Colors and check Allow pages to choose their own colors, instead of my selections above. Here's how my options look.
You can remove the original stylesheet. Just assign it an id and use jQuery.remove(...).
The alternate solution is to alter the first stylesheet to use some kind of namespace+, for example:
/* these are the rules that you want to be removed */
.stylesheet1 { }
.stylesheet1 h1 { }
.stylesheet1 p { }
.stylesheet1 a { }
.stylesheet1 input { }
/* these rules can co-exist with the next stylesheet */
nav { }
article { }
aside { }
section { }
Inside your HTML add the stylesheet1 class to body. When you load the other CSS file (presumably via JavaScript) then you remove this class. All namespaced rules will become ineffective.
* CSS preprocessors e.g. SASS and LESS make it easier for you to manage these rules.
Do a css reset/normalize at the beginning in your first css file. Then at the beginning of the second one do it again.
You can leave out the first reset, but this will give you consistent results.
It sounds like the best solution for you is to have two different CSS classes targeting a single input, and toggle back and forth between the two. There are several ways to do this:
CSS:
input[type="text"].a {...}
input[type="text"].b {...}
Here we have two different classes, a and b. When defining the input initially, set class="a". We'll then swap that with b when the button is clicked. Again, there are several ways of doing this:
jQuery:
$('.a').click(function(){
$(this).removeClass('a').addClass('b');
});
Plain JS
var button = document.querySelector('.a');
button.addEventListener('click', function(){
button.classList.remove('a');
button.classList.add('b');
});
This is the generally preferred method for achieving this kind of behaviour. It adheres strictly to standards, in that it separates logic, markup, and presentation into their respective pieces.
Note: The plain JS method listed above uses some pretty modern native JS code. Take a look at You Might Not Need jQuery to find suggestions for making this functionality cross-browser.
Instead of adding or removing properties to elements, I think the better way to do it is to put these extra properties in a CSS class and then add or remove this extra class to the elements as needed. And if you need override, then use !important. Now it's just about add/removing classes.
Here's an example in jQuery
.MyControl{background: blue;}
.MyControlAccessibility{background: red !important;}
$(SomeControl).click(function () { $(this).addClass('MyControlAccessibility'); }
$(SomeControl).click(function () { $(this).removeClass('MyControlAccessibility'); }
Using a global class on the body is good as mentioned.
Another way could be to put your light and dark "theme"-specific styles into separate stylesheets from the common CSS and then disable the one you do not want. This will avoid conflicts and needing to use !important, and you can keep things clean without having to hack away at various bits of jquery.css().
For example
base.css
a { text-decoration:none; }
dark.css
body { background-color:#000; }
a {color:#fff; font-size:1.2em;}
light.css
a {font-size:1.5em;}
Note that light.css has no properties for background-color etc. so when they switch from dark to light, the defaults will be used again.
To do the switch, you can do something along these lines:
for (var i = 0; i < document.styleSheets.length; i++) {
var ss = document.styleSheets[i];
// some browsers store in url, others in href
if((ss.href || ss.url || '').indexOf('dark.css') > -1) {
ss.disabled = true;
}
}
By disabling instead of removing the current one, it should be easier to switch between the two.

Moving an element into a new div - doesn't take on its new css properties

So, I have an element that has some "pre-existing" behavior attached to it. So, I found that just moving it (as required by some new requirements) retains the existing behaviors = good. But the issue is, when I move the element I need to give it "new styles".
So, if I have something like this:
<div id="existingStructure">
<div id="legacyElement"></div>
</div>
Now, that has pre-existing styles attached to both. I can rearrange these styles etc.. but I can't change them. The styles are attached to the "id's" rather than a class definition. I believe I can change that if needed.
Now, I need to move "legacyElement" when certain things happen to a "new div".
I just
jQuery('#newStructure').append('#legacyElement');
<div id="newStructure">
<div id="legacyElement"></div>
</div>
Unfortunetly, the styles I have on newStructure don't seem to be applying to *legacyElement" when it gets moved here dynamically.
I was thinking of moving all the styles to a class rather than associated to the ids, and when I move it.. I just jQuery().addClass / jQuery().removeClass etc...
Is there a better/easier more robust way that I can just have the legacyElement loose its styles when it sits under existingStructure and then get new ones when moved to "newStructure" etc.. and vice versa. That element "legacyElement" will be pinging back and forth.. so, I need it to have the styles under each parent div as it goes there.
so when an action happens on page, I move it back:
jQuery('#existingStructure').append('#legacyElement');
If I am not succinct enough, please let me know.
The EXISTING styles are in an external CSS file and are like so..
#existingStructure {
// bunch of css
}
#existingStructure .item1 input[type="text"] {
// bunch of css
}
#legacyElement{
// bunch of css
}
and new styles are sorta the same except 'additional styles' might be applied.
#newStructure {
// bunch of css
}
#newStructure .item1 input[type="text"] {
// bunch of css
}
You can certainly target your div styles by their parents:
#existingStructure #legacyElement {some styles}
#newStructure #legacyElement {some other styles}
To explain futher, this arrangement should result in greater specificity, overriding styles that are simply applied to either #existingStructure or #legacyElement. I'm hoping no one did anything foolish like using !important on them.
Short answer: It should.
Here's an example I quickly made in jsFiddle: http://jsfiddle.net/CCm4J/1/
So then why isn't yours? Most likely you have css rules that are embedded that apply only when in the existingStructure id/class perhaps? Without see more of your css I'm not sure how specific I can get. I would just verify that your css rules are allowed to apply outside of existingStructure (and even that existingStructure might have rules for its parent too!)

javascript, change class but only when it meets certain criteria

I have a class that is used ~~120 times on a page. I would like to change the margin-bottom only if other things affecting that class meet a certain criteria. The problem with the below code is that it changes everything associated with that class...not just the ones that meet the criteria.
if (actualCharacters > charactersCanFitInContainer) {
$("#Full .t16").each(function () {
$(this).css('margin-bottom', '1.25em');
});
$("#Full .t8").each(function () {
$(this).css('margin-bottom', '4.4175em');
});
}
I want to help you with your problem, but the premise is wrong. You shouldn't be trying to do this in jQuery. Maybe there is something wrong with your CSS, and it would be better to try to resolve it using CSS.
Having said that let's go over some of the problems with your code:
(1) Don't use $( selector ).each()
When you do a call to jQuery
$( selector );
This little guy returns an array of all elements that match the css of the selector.
There may be 200 elements, or even a thousand, but never, ever, do an .each() call unless you tend to explicitly change every individual element in a unique way.
$( selector ).each() runs a for loop on the array of matched selectors, which will give you performance problems if your matched set is too large.
To change all matched elements you need only to do this:
$( selector ).css('margin-bottom', '1.25em');
Read more about .each()
(2) Do not use Javascript (or jQuery), to do CSS's job
From your question it seems like you're having a problem with spacing. CSS has a number of ways to resolve this using rules like overflow, white-space, text-overflow that you should explore before resorting to scripting.
(3) Avoid using .css()
You should avoid using the $( selector ).css() function since it also introduces performance problems, especially on large sets. Instead, you should create a class that you can apply to the set and use:
$( selector ).toggleClass( 'myclass' );
//or
$( selector ).addClass( 'myclass' ); //and
$( selector ).removeClass( 'myclass' );
since these functions are more performant.
To take it a step further, do not apply a class to every set of matching elements, rather, add the class to a parent element and let the children inherit their styles from the updated parent class.
Read more about toggleClass()
(5) Stick to conventions
While it's perfectly OK to use capital letters in naming your CSS rules, you should probably avoid that since it's not standard practice. The whole point of having a standard practice is so that everyone can look at your code and know exactly what is going on, which makes your code easier to debug and share.
(6) Don't try to compensate for bad design with over-engineered solutions
Also consider that, sometimes, the requirements need to be changed if your only solution is to script the styles.
I've run into situations where what a project manager wanted to accomplish was not technically feasible. If they want a large body of text to display in a limited area, they need to allow for things like scrollbars or consider keeping a standard size limitation on blocks of text.
Go back to the stakeholder for this project and tell them that what they want to do is not reasonable, and make decisions together to design this widget better.
As a developer, you should not allow for unreasonable requirements, some things simply need to be redesigned instead of making you come up with a messy way to resolve bad design.
Conclusion
In terms of your problem, play around with the CSS rules that specifically address the spacing problem you're having (overflow, text-overflow, etc). You may need to look deeper into the styles to find a better way to do this.
In case anyone stumbles across this question and answer, I wanted to post what I ended up doing - special thanks to Qodeninja for setting me straight.
Rather than change CSS via javascript, I went with simple #media queries to handle the various font and spacing changes below. There are a variety of differing methods to do responsive design. This worked best for my requirements
#media all and (max-width: 1500px)
{
body
{
font-size:1.1em;
}
#Full
{
width:1200px;
background-color:Blue;
}
.theTeams
{
width:8.925em;
padding-left:.238em;
padding-right:.238em;
}
.t8{margin-bottom:4.75em;}
.t4{margin-bottom:11.75em;}
.t2{margin-bottom:26.5em;}
.tend{margin-bottom:0em;}
.rtR{margin-left:19em;}
#media all and (max-width: 1200px)
{
body
{
font-size:0.9em;
}
#Full
{
width:900px;
background-color:Orange;
}
.theTeams
{
width:7.75em;
padding-left:.14em;
padding-right:.14em;
height:2.1em;
}
.t8{margin-bottom:4.95em;}
.t4{margin-bottom:12.25em;}
.t2{margin-bottom:27.75em;}
.tend{margin-bottom:0em;}
.rtL{margin-left:16em;}
}

Categories