Apply multiple styles with .style() method in D3.js - javascript

As I'm making my first steps into d3.js I couldn't help myself noticing its approach is very similar to jQuery.
My question is:
When I need to modify multiple CSS properties of a style attribute
of matching element is there a shorthand approach, like jQuery or
ReactJS provide, like
.style({width:100, height:100, backgroundColor:'lightgreen'})`
if I need to apply width:100px, height:100px and
background-color:lightgreen to a <div>.
Sure, I may chain those, but changing multiple properties this way may become tedious:
d3
.select('#test')
.style('width','100px')
.style('height','100px')
.style('background-color','lightgreen')
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script><div id="test"></div>
Or I may combine multiple desired properties within a class and assign that class with a .classed(), which may also overcomplicate CSS stylesheet when dynamic properties are required:
d3
.select('#test')
.classed('testclass', true)
.testclass {
height: 100px;
width: 100px;
background-color: lightgreen;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script><div id="test"></div>
But those are not techniques I'm interested in.

The accepted answer is not correct ("there's no such syntax documented in API reference"), you can set multiple styles using d3-selection-multi. Pay attention to the fact that you have to use the method styles(), not style(). So, in your case, it would be:
.styles({width:100, height:100, 'background-color':'lightgreen'})
Here is your snippet with that change:
d3.select('#test')
.styles({
'width': '100px',
'height': '100px',
'background-color': 'lightgreen'
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<div id="test"></div>
<script src="https://d3js.org/d3-selection-multi.v1.min.js"></script>
As d3-selection-multiis not part of the default bundle, you'll have to reference it separately.

Edit (2021):
Note: I claimed (in initial version of this answer) that there's no embedded method to solve the OP's problem. And, as of D3 v6.7.0 you still cannot pass your styles as an object directly to .style() method
Two options you got by the time of this writing:
loop through your styles object and apply the styles incrementally
const style = {"width":"100px","height":"100px","background-color":"lightgreen"}
Object.entries(style).forEach(([prop,val]) => d3.select("#test").style(prop,val))
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script><div id="test"></div>
use d3-selection-multi (solution massively upvoted down this thread)
Why I would discourage you from doing the latter:
the referenced repo is long time archived (since 2017), so it may raise all sorts of interoperability issues, which may possibly be the reason for its descending popularity
under the hood that library does exactly the same thing (looping and applying styles incrementally)
So, whether you apply 1-line solution or add up to 13kB of legacy code to your application bundle for that sole purpose - is totally up to you.

Alternatively, you can use the attr() method and add the styles as part of the attribute of that particular tag being targeted:
d3.select("#test")
.attr("style", "width: 100px; height: 100px; background-color: lightgreen;")

Unfortunately, there is not a better shorthand method for applying multiple styles to an element using Vanilla JavaScript. The ideal solution, in my opinion, is to apply a class to the element you wish to style that contains the multiple styles you wish to apply.
You could, of course, write a helper method that duplicates the JQuery method.
function applyStyle(id, styles){
Object.keys(styles).forEach(key => document.getElementById(id).setAttribute("style",key +": "+styles[key]+";"));
}

Since you can access d3 node by calling .node() on needed selection, you can do smth like that:
item.node().style.cssText = `property: value; property2: value2`;
Also, if you use styled-components (react), you can simply insert your css like this:
item.node().style.cssText = PreviouslyDefinedCSS.toString();
where css is smth like this:
import { css } from "styled-components";
const PreviouslyDefinedCSS = css`
your scss here
`;

Related

How to modify a CSS variable in a shared-styles Polymer Element

Good evening everyone,
I'd like to know it is possible to modify the value of a CSS variable and if this change would take effect immediately across every polymer element that includes it.
Here is the déclaration of the CSS variables that i'd like to dinamically change :
<link rel="import" href="../bower_components/polymer/polymer-element.html">
<!-- shared styles for all views -->
<dom-module id="shared-styles">
<template>
<style>
:root {
--apc: #099b34;
--asc: #4CAF50; /*App Secondary Color*/
}
</style>
</template>
</dom-module>
Have a nice evening, thanks
NeitoFR
Edit 1 : I tried the first method given by #d.mares but it doesn't work.
As you can see on the picture it appears on the element.style part but do not override the host definitions and do not spread across other elements.
Edit 2 :
by doing some research, I managed to get an error that answer partially my question :
So.. It seems that it is not dinamically changeable. I need to find another way to do that.
Yes, you can, you would have to call this.updateStyles and pass the new values:
this.updateStyles({
'--apc': 'blue',
'--as': 'red'
});
For global effect you would need to modify the element that holds the original declaration of the variables.
CSS Variables are subject to the cascade and inherit their value from
their parent.
From the docs

Can I use javascript to change individual page css entries (as opposed to swapping stylesheets)?

I know it's possible to change css attributes on elements on the current page:
$('.changeMyStyle').css("color", "#FF0000");
But this won't affect new elements added after the change is made.
I know it's possible to remove, add, or swap out css stylesheets to re-style a page after it's been loaded:
$('link.swappableStylesheet').attr('href', 'path/to/new/style.css');
But this is a poor solution for changing one or two attributes, especially to programmatically-determined values (such as changing color from a colorpicker).
I could probably grab a stylesheet's raw data, search it, and modify it:
var sheet= document.styleSheets[0];
var rules= 'cssRules' in sheet? sheet.cssRules : sheet.rules; // IE compatibility
rules[0].style.padding= '0.32em 2em';
// assumes the first entry in the first stylesheet is the one you want to modify.
// if it's not, you have to search to find the exact selector you're looking for
// and pray it's not in a slightly different order
But that's also a poor solution and requires IE-compatibility hacks.
This linked answer also suggests appending another <style> element and adding css there. That could work for narrow cases, but it's still not ideal (and the answer is 5 years old, so new tools may be available now).
Is there a way to alter the page's css at a selector & attribute level instead of stylesheet level or DOM element level? jQuery and vanilla javascript solutions both welcome, as well as libraries designed to do this specifically. Ideally I'd like something that's as easy and versatile as
$(document).stylesheet('.arbitraryCssSelector.Here').put('color', '#FF0000');
...where .stylesheet('.Here.arbitraryCssSelector') would modify the exact same style entry.
Even Chrome's dev tools just modifies the stylesheet it's using when you make modifications or add new rules. There's not currently a way around it, but you can keep a dedicated stylesheet at the bottom of the page that you update with the newest rules. If it's empty or contains invalid rules it will just fall back to the current stylesheet. If any library exists out there this is how it would do it, and it's very little code.
I think the key to keeping it uncluttered is to simply keep overwriting one stylesheet instead of adding new stylesheets to the DOM.
document.getElementById("dynamic-color").addEventListener("input", function () {
document.getElementById("dynamic-styles").innerHTML = "label { color: " + this.value + " }";
});
label {
color: blue;
}
<label for="#dynamic-color">Change the label's color!</label>
<input id="dynamic-color" />
<style id="dynamic-styles"></style>

Polymer change custom element

I have a question regarding custom elements in polymer.
I have acustom element that has a style nested inside it's template, for a div inside the template like so:
<template>
<style>
#navWrapper{
height: auto;;
color:red;
position:fixed;
background-color:black;
width: 100%;
}
</style>
<div id="navWrapper"><content></content></div>
</template>
Now, I'd like to change the color of navWrapper when I scroll down the page.
I want to use jquery for that.
Therefore I have a script inside of my custom element like so:
<script>
Polymer('responsive-nav',{ready: function() {
$(window).scroll (function () {
var sT = $(this).scrollTop();
if (sT >= 100) {
$(this.$.navWrapper).css({backgroundColor : 'rgba(0,0,0,0.0)' })
}else {
...
}
})
} });
</script>
Now sadly that does nothing. I have put an alert(test) before the $(this.$.navWrapper) to find out if I even get to that point and I do. Also when I want to change the background color or something else from an element that lives in my normal HTML file it works. For example $("#testDiv").css({...}) changes. So my question is: How do I access the css of my element properly?
Looks like your JQuery CSS call is wrong.
I think it should be:
.css("background-color", "rgba(0,0,0,0.0)")
rather than
.css({backgroundColor : 'rgba(0,0,0,0.0)' })
Cross referencing what you've done with the JQuery docs, you've definately missed the '-' out of 'backgroundColor', but also I don't see anything in the docs that states using a JSON object to make the change.
You can however use an array of property names and values (Which is what I suspect you may have been trying to do)
Update (Approx 1 hour later)
Since Iv'e been wrestling with a not too dissimilar problem today (but involving bootstrap rather than jquery) I was already investigating things around similar concepts.
As a result, I took the OP's original code and started playing with it.
What I present below is a partial JQuery solution (we still use JQ's scroll detection) where I also figured out an alternate way of changing the style using polymers conditional binding syntax.
http://www.polymer-project.org/docs/polymer/expressions.html
Essentially what i did was to pass a user data object into the scroll event at set-up time.
This user object contained a property that reflects the current polymer object (This is needed so that the JQ handler when it fires can update polymer properties)
When the window scroll event occurs, the handler extracts this property, then uses it to get at a local variable inside the polymer element, and thus updating it with the current scroll top value.
Since that locally scoped property is part of the actual polymer object, ANY polymer data-binding can read it, at this point it's simply just a matter of creating a couple of styles, then using the expression binding to pick the correct style.
Remember, styles cascade, so you can easily just make one master style for the whole thing, then 2 styles that simply just change the background color as appropriate.
Expression bindings work by using the text on the left side of the : only if the expression on the right evaluates to true eg:
{{ {frogs: a == 1} }}
would replace the expression binding with '' if property 'a' was NOT equal to 1 and set it to 'frogs' if property 'a' was equal to 1.
Expression bindings however are singular in nature, so in order to have more than 1 expression binding you need to pass the entire thing through one of polymers filters, specifically the 'tokenList' filter.
once you do this, you can build a whole object of different checks up, so expanding on our previous example:
{{ {frogs: a == 1, tadpoles: a == 2} | tokenList }}
will now make the result be equal to '' if property 'a' was NOT equal to 1 and NOT equal to 2 , while setting the result to 'frogs' if property 'a' was equal to 1 and setting the result to 'tadpoles' if property 'a' was equal to 2.
you can expand this to as many checks as you like, but the more you add in (I'm guessing anyway - iv'e not tested it) the slower performance is likely to be.
For now, the code below will do exactly what you need, well once you make a few alterations so that it targets your own elements and set's your own styles up anyway :-)
<link rel="import" href="polymer.html">
<polymer-element name="x-mypolymercomponent">
<template>
<style>
.under100
{
background-color: green;
}
.over100
{
background-color: red;
}
</style>
<h1 class="{{ {under100: scroll_top <= 100, over100: scroll_top > 100} | tokenList }}">This header has a dynamic class</h1>
</template>
<script>
Polymer('x-mypolymercomponent', {
ready: function ()
{
$(window).scroll({scope: this}, function(event) {
var sT = $(this).scrollTop();
event.data.scope.scroll_top = sT;
})
},
scroll_top: 0,
});
</script>
</polymer-element>
Iv'e just tested this in the .NET project I'm currently working on, and it works fine in chrome 36, there are still problems in Firefox though, but this I think is possibly due to a bug I discovered last night so I wouldn't worry too much about it.
I have JQuery loaded at page level too, so it would appear that the component can pick up the page events just fine from it.
Give it a try see where you go from here, I'm about to adapt it for my specific issue, then turn it into a typescript module :-)

How to include CSS for a specific part of the page

In my website, the users have to enter markdown in a textarea, for which I am using a markdown editor. The problem is: it uses icomoon font, and my websites too. Both uses the same class to define the fonts, but not both uses the same icons. The question is simple: is there a way to define the editor.css for a special div?
Like that:
<div css="editor.css"></div>
Give the DIV a Class and then add a CSS file like this:
.markdown
{
color: red;
}
If you import a new css dynamic, the old styles will be overwritten.
Some help, for dynamic css loading: How to apply inline and/or external CSS loaded dynamically with jQuery
Namespace your editor styles
You can add a selector that namespaces your editor and allows you to style it:
<div class="editor-style">
<div id="cool-custom-editor">...</div>
</div>
In your css:
.editor-style .icon-thumbs-up { color: green; }
Using Scoped Styles (needs polyfill)
As mentioned in #adeneo's comment below your question there is the option of using scoped style tags.
Supposing your editor looks like this:
<div id="cool-custom-editor">...</div>
You can apply a specific style using the scoped attribute like so:
<div>
<style scoped>#import url(editor.css);</style>
<div id="cool-custom-editor">...</div>
<div>
Caveats
According to can I use the scoped attribute is only natively supported by Firefox 26+.
If you want to use it you will have to polyfill:
Plain JavaScript polyfill
jQuery scoped plugin
Further Reading
HTML5 Doctor - The scoped attribute
CSSTricks - Saving the day with scoped styles
HTML5Rocks - A new experimental feature - Scoped Styles
You dont need multiple files. You can give the div an id or class like so
<div class="div1">
<span></span
...
</div>
and now in you css you do this
.div1 span{
font:(whatever font);
}
I don't think so, no. At least not without using any js workarounds. The best solution would be to use kind of namespace for user-specific css classes.
No you can't do that.. I think you should solve the conflit by changing the icomoon class names in this file.
OK solved: renaming the classes in editor for icomoon was a lot easier than I dared tough.
not a good way but it can help:
$("[css]").each(function(i){
var elm = $(this);
$.when($.get(elm.attr("css"))).done(function(response) {
var newresponse=response.replace(/(.+{)/gi,".customCss"+i+" $1");
elm.addClass("customCss"+i);
elm.before('<style>'+newresponse+'</style>');
});
});

css3pie messes up DOM, results in jQuery selector errors

In order to get CSS3 effects (border-radius, box-shadow...) on IE 6/7/8, I'm using css3pie.
However, css3pie generates some css3-container (v1) / css3pie (v2) tags in DOM, which disorders the expected architecture. Here is an example:
CSS
pre {
border: 1px solid #aaa;
border-radius: 5px;
behavior: url(pie.htc);
}
HTML
<div class="foo">bar</div>
<p class="getme">paragraph</p>
<pre>preformatted</pre>
jQuery
// undefined expected: getme
alert($("pre").prev().attr("class"));
// css3-container expected: p
alert($("pre").prev()[0].tagName);
// getme expected: foo
alert($("pre").prev().prev().attr("class"));
// 4 expected: 3
alert($("body").children().size());
// will not set expected: Impact
$("p+pre").css({fontFamily: "Impact"});
// it almost affects all such jQuery selctors
The actual generated source is like this:
<DIV class="foo">bar</DIV>
<P class="paragraph">paragraph</P>
<css3-container...>
<border...>
<shape...><stroke></stroke><stroke></stroke></shape>
</border>
</css3-container>
<PRE>preformatted</PRE>
Has anyone encountered this kind of problems? Any workaround? Is there an alternative to css3pie to get CSS3 working on IE 6/7/8?
I tried using CSS3PIE too, and faced similar problems (mostly with jquery and mediaqueries). I found no easy/practical solution for all the problems it causes, indeed.
My advice would be to use Modernizr's load to progressively enhance older IE's user experience. It requires an harder/longer process, as you've to setup a single polyfill for each and every CSS3 feature. As mario.tco already told you, there's a list of cross-browser polyfills on Modernizr's repo. And here's a list of feature detection snippets.
Also have a look at html5please and caniuse.
In regard to IE6 and 7, unless your site statistics indicate something different, usage rates are below 1% on average (with some exceptions, check ie6countdown), so you can almost ignore them. However, with conditional comments you can target each IE<10 version with specific fallbacks.
Keep in mind that you don't really need to have box-shadows and other visual decorations (unless they are needed for usability) on IE<9. Indeed, any fallback will probably cause a huge performance problem (think about what hardware an IE7 user could have). Websites don't need to look exactly the same in any browser.
CSS3PIE is a very useful and powerful way to simulate CSS3 rounded corners - and in my company it is the one that we chose, but there are many other ways to do it.
The way CSS3PIE creates the rounded corners it will create the <css3-container> tag as the previous sibling to the element that has the behavior attribute, so it will change DOM structure and break your prev() calls. The css-container is important because it is a VML drawing of the rounded corner background behind your <pre> tag.
One way you could do this would be to wrap your <pre> tag in something else like a <div> and then use that <div> to navigate the DOM using the prev() function.
Another way you could do this would be to create a jQuery plugin like this
/* This adds a plugin prevPie and nextPie - it is the same as the
existing prev and next, but it will ignore css3-containers. */
(function($){
function addPlugin(name) {
$.fn[name + 'Pie'] = function() {
var result = [];
this[name]().each(function(i,el){
if (el.tagName == 'css3-container') {
var val = $(el)[name]()[0];
val && result.push(val);
} else {
result.push(el);
}
});
return $(result);
}
}
addPlugin('prev');
addPlugin('next');
})(jQuery);
Now the following should work like you wanted it to in all browsers.
// undefined expected: getme
alert($("pre").prevPie().attr("class"));
// css3-container expected: p
alert($("pre").prevPie()[0].tagName);
// getme expected: foo
alert($("pre").prevPie().prevPie().attr("class"));
// P expected: div
alert($("pre").prevPie().prevPie()[0].tagName));
Have you tried this:
http://blog.reybango.com/2010/10/11/how-polyfills-fill-in-the-gaps-to-make-html5-and-css3-usable-today/
Here is a list of polyfills you can use for other features:
https://github.com/Modernizr/Modernizr/wiki/HTML5-Cross-browser-Polyfills
This is probably not the answer you're looking for, but instead of trying to get jQuery to ignore PIE's injected elements, I recommend (re)writing your jQuery to use classes / IDs more and be less dependent on page structure. This has the benefit of making your code more resilient against other page structure changes, as well as making your code a bit more portable and reusable.
When you must traverse the DOM, most (if not all) of jQuery's traversal methods include a filter selector argument that you can use to exclude PIE's elements, for example:
$("pre").prevUntil('*', 'not(css3-container)')
$("body").children('not(css3-container)')
Instead of just using the raw prev() add a CSS selector to it to narrow down the search
$("pre").prevUntil('p').attr("class");
// OR
$("pre").prevUntil('.paragraph').attr("class");
If you are going to use a CSS3 "hack" to make IE 6/7/8 behave correctly don't try and rely on expected DOM structure when walking the DOM try to be more specific.
EDIT
changed the prev() function call to prevUntil()

Categories

Resources