Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Efficiently apply style with JavaScript (best practice?)

Tags:

javascript

css

I am writing a userscript for Greasemonkey/Tampermonkey (and learning JS in the process). It is for a discussion forum (/bulletin board), where each post is assigned one of six possible styles based on some criteria. The post contains a div with the username, a div with a timestamp, a div with avatar and user info, and a div for the content itself (which may or may not include a quote div).

For simplicity's sake, let's just call the styles red, blue, green, yellow, black and white. (Note: it is not just color – each style has its own distinct value of "everything", even margins, paddings and other layout.)

Then, each post is styled with a call to a style-changing function, for example makeRed(post) or makeGreen(post).

Then, each of those functions look something like this:

const makeRed = post => {
    let author = post.querySelector(".author");
    author.style.fontSize = "...";
    author.style.fontFamily = "...";
    author.style.backgroundColor = "...";
    author.style.padding = "...";
    author.style.borderRadius = "...";
    author.style.margin = "...";
    author.style.flex = "...";
    // ...

    let date = post.querySelector(".date");
    date.style.fontFamily = "...";
    date.style.fontSize = "...";
    date.style.color = "...";
    date.style.margin = "...";
    date.style.flex = "...";
    // ...

    let avatarInfo = post.querySelector(".infoBox");
    avatarInfo.style.backgroundColor = "...";
    avatarInfo.style.fontSize = "...";
    // ...

    let content = post.querySelector(".content");
    content.style.borderColor = "...";
    content.style.backgroundImage = "...";
    // ...

    let quote = content.querySelector(".quote");
    if (quote) {
        // Lots of quote styling here
    } else {
        // Lots of quote-less styling here
    }
}

Each of these functions contain significantly more lines of code in a similar fashion, I just cut them out of this question to save some space.

So, to the question:
Is there any way to write this more concisely/elegantly?

I guess it's hard to avoid having a line for each style property, but at least in CSS it looks a bit nicer, IMO. Would it be a better practice to create a CSS-file and somehow import it with JavaScript (how?), or is this long list of element.style.property = "..." actually the way to go? Or is there a better way? How would you do it?

Also, I'm brand new to JS, so if you have any other advice (related to my code or not), please let me know!
Thank you very much! :-)

Edit:
I was asked to include the HTML structure. A typical post is simply something like this:

<div class="post">
  <div class="author">Author McAuthorson</div>
  <div class="date">2021.01.10 01:23</div>
  <div class="infoBox">
    <div class="avatar"><img src="..." /></div>
    <div class="userinfo">User info here</div>
  </div>
  <div class="content">
    <div class="quote">Some quote by some guy here</div>
    Some original text by McAuthorson here.
  </div>   
</div>
like image 695
genbatro Avatar asked Jan 10 '21 10:01

genbatro


People also ask

What is a best practice JavaScript?

Modularize — one function per task This is a general programming best practice — making sure that you create functions that fulfill one job at a time makes it easy for other developers to debug and change your code without having to scan through all the code to work out what code block performs what function.

When dealing with strings in JavaScript it is considered best practice to?

When dealing with strings in JavaScript, it is considered best practice to... use 'single' quotes.

Can JavaScript be used for styling?

Styling DOM Elements in JavaScript You can also apply style on HTML elements to change the visual presentation of HTML documents dynamically using JavaScript.

What are the best practices for using variables in JavaScript?

JavaScript Best Practices Avoid Global Variables Always Declare Local Variables Declarations on Top Initialize Variables Never Declare Number, String, or Boolean Objects

Is it a good practice to initialize variables in JavaScript?

It is a good coding practice to put all declarations at the top of each script or function. It is a good coding practice to initialize variables when you declare them. Initializing variables provides an idea of the intended use (and intended data type). JavaScript is loosely typed.

Why should you use strict mode in JavaScript?

If you wish to get notified whenever you make a JavaScript violation while writing code, then you must use strict. It helps you maintain programming decorum by enforcing rules like declared variables, preventing the use of reserved words and with the statement in your code. The JavaScript ES6 modules already work in strict mode.

Why should you learn JavaScript for web development?

And almost, all modern-day browsers support it. It is the main technology behind driving the modern UX/server-side frameworks such as AngularJS and Node.JS. Hence, if you learn to write clean and robust JavaScript code, then it will increase your chances of getting hired.


4 Answers

One approach would be to use CSS injection and add/remove classes to the elements you wish to alter.

Here is an example

const makeRed = post => post.classList.add('red')

const css = `
 .red > .author {
    background: red;
    font-weight: bold;
 }
 .red > .date {
    font-weight: bold;
 }
`

// inject the style first
document.head.insertAdjacentHTML("beforeend", `<style>${css}</style>`)

// call the func. to add the class then
setTimeout(() => makeRed(document.querySelector("#test")), 1000)
<div class="post" id="test">
  <div class="author">Author McAuthorson</div>
  <div class="date">2021.01.10 01:23</div>
  <div class="infoBox">
    <div class="avatar"><img src="..." /></div>
    <div class="userinfo">User info here</div>
  </div>
  <div class="content">
    <div class="quote">Some quote by some guy here</div>
    Some original text by McAuthorson here.
  </div>
</div>
like image 195
Tibebes. M Avatar answered Nov 14 '22 21:11

Tibebes. M


You can create CSS dynamically and inject it as follows:

function addStyle(styleString) {
    const style = document.createElement('style');
    style.textContent = styleString;
    document.head.append(style);
}

addStyle(`
    .red .author {
        font-size: 12px;
        font-family: 'Times New Roman', Times, serif;
        background-color: red;
        padding: 10px;
        border-radius: 5px;
        margin: 10px;
        flex: 1;
    }

    .red .date{
        font-family: "...";
        font-size: "...";
        color: "...";
        margin: "...";
        flex: "...";
    }

    .red ...{
        ...
    }
    /* similarly for other colors */

    .blue {
        color: blue;
    }
`);

function removeStyles(post) {
    //remove all the colors
    post.classList.remove('red', 'green', 'blue', ...);
}

const makeRed = post => {
    removeStyles(post);
    post.classList.add('red');
}

const makeGreen = post => {
    removeStyles(post);
    post.classList.add('green');
}
<div class="post">
        <div class="author">Author McAuthorson</div>
        <div class="date">2021.01.10 01:23</div>
        <div class="infoBox">
            <div class="avatar"><img src="..." /></div>
            <div class="userinfo">User info here</div>
        </div>
        <div class="content">
            <div class="quote">Some quote by some guy here</div>
            Some original text by McAuthorson here.
        </div>
    </div>
like image 30
ubaid shaikh Avatar answered Nov 14 '22 22:11

ubaid shaikh


Seems your quite new to JS. Basically, manipulating CSS values using JS isn't recommended unless the entire solution relies on the CSS values being dynamic alongside the JS (and even then, there's much more elegant, efficient solutions).

When working with JS, you ALWAYS need to consider how long things will actually take to run & what conflicts it could cause down the line. With your method, you're looking at some heavy future development work if you run into issues down the line (like you are now) and have to change something OR if something else you action conflicts with this.

As others have said, just utilize CSS (or SCSS) for this. The most simplistic way would be to define 6 unique CSS classes and nth number of shared classes between the elements if required. Then when a post is submitted on your bulletin board, the BACKEND code assigns the post with a class name (or ID, or something else that says this post means 'post-style__variant-1') that is relative to your class name (and can be relative to other things down the line).

Extra tip is to consider VARIANTS. Basically your bulletin post is static but that bulletin board post can have 6 different styling, thus at this point your bulletin board posts can have 6 variants based on the 6 different class names. If you build your posts submission system with variants in mind, you'll be able to extend it in the future. For the CSS/SCSS structure, you can just do the below

CSS

.post__variant-1 {
   color: red;
}

.post__variant-2 {
   color: green;
}

.post__variant-3 {
   color: blue;
}

^ Not recommended by the way, horrible to write and horrible to maintain. Judging by what you're learning, I'd recommend getting into SCSS as soon as possible. It'll make your life a lot easier. For example

.post__variant {

    display: block;

    &-1 {
        @extend .post__variant;

        color: red;
    }

    &-2 {
        @extend .post__variant;

        color: green;
    }


    &-3 {
        @extend .post__variant;

        color: blue;
    }
}

SCSS is a preprocessor by the way. You code the SCSS locally, then run a command which takes the SCSS files, runs them through the preprocessor which then spits out the SCSS as CSS. So everything gets rewritten for you into the language the browser understands. So the SCSS above would preprocess into

.post__variant, .post__variant-3, .post__variant-2, .post__variant-1 {
  display: block;
}
.post__variant-1 {
  color: red;
}
.post__variant-2 {
  color: green;
}
.post__variant-3 {
  color: blue;
}

Hopefully that all makes sense. I used to delve into bulletin board development when I started out, it's a good & pretty standard entry point. Additionally, ignore anyone calling you out (or negatively criticizing you) for going with a solution you thought up. Your solution might not be the best but developers are ALWAYS learning. As soon as you think you're the best and there's nothing else to learn. Congratulations, you've become a basement developer that'll be used to work on shitty legacy projects and ignored by whatever company you decide to work with. Only kept around to maintain the shite legacy stuff.

like image 28
alexjkni.dev Avatar answered Nov 14 '22 22:11

alexjkni.dev


You can use a helper function

changeStyle(document.querySelector(".sample"), {
  fontSize: "12px",
  fontFamily: "verdana, arial",
  lineHeight: "15px",
  fontWeight: "bold",
  textAlign: "center",
  backgroundColor: "#fffff0",
  color: "green",
  height: "1.2rem"
});

function changeStyle(forElement, props) {
  Object.entries(props).forEach( ([key, value]) => forElement.style[key] = value);
}
<div class="sample">Sample</div>

Or create/modify css within an injected custom css stylesheet

const wait = 5;
// initial styling 
changeRuleset("body", {
  font: "12px/15px 'verdana', 'arial'", 
  margin: "2rem" });
changeRuleset("pre", { 
  whiteSpace: "pre-line", 
  maxWidth: "800px", 
  marginTop: "2rem"});
changeRuleset(".sample", { 
  fontSize: "1.5rem", 
  fontFamily: "comic sans MS", color: "green" });
changeRuleset("[data-cntr]:before", {content: `"[" attr(data-cntr) "] "`});
//                        ^ pseudo class allowed

displaySheet(getOrCreateCustomStyleSheet(), "Initial");

document.querySelector(".sample").dataset.cntr = wait;
timer();

setTimeout(() => {
  // changed styling
  changeRuleset(".sample", {
    fontSize: "1.5rem", // camelCase or ...
    "background-color": "yellow", // hyphen ... both allowed
    "box-shadow": "2px 2px 8px #999",
    fontWeight: "bold",
    display: "inline-block",
    padding: "20px",
    color: "red"
  });
  changeRuleset(".sample:after", { content: "'!'" });
  displaySheet(getOrCreateCustomStyleSheet(), "Changed"); }, wait * 1000);

// change a ruleset in the custom style sheet
function changeRuleset(someRule, styleProps) {
  const customSheet = getOrCreateCustomStyleSheet();
  const formatKey = key => key.replace(/[A-Z]/g, a => `-${a.toLowerCase()}`);
  //                       ^ remove camelCase, apply hyphen
  const compareKeys = (key1, key2) =>
    key1.replace(/:/g, "") === formatKey(key2).replace(/:/g, "");
  //              ^ comparison for pseudoClasses
  let rulesStr = styleProps ?
    Object.entries(styleProps).reduce((acc, [key, value]) =>
      [...acc, `${formatKey(key)}: ${value};`]
      , []).join("\n") : "";
  const existingRuleIndex = customSheet.rules.length &&
    [...customSheet.rules]
      .findIndex(r =>
        r.selectorText && compareKeys(r.selectorText, someRule));

  if (existingRuleIndex > -1) {
    // replace existing rule
    customSheet.rules.length && customSheet.deleteRule(existingRuleIndex);
    customSheet.insertRule(`${someRule} { ${rulesStr} }`, existingRuleIndex);
  } else {
    // insert new rule
    customSheet.insertRule(`${someRule} { ${rulesStr} }`);
  }
}

// get or create and get the custom style sheet
function getOrCreateCustomStyleSheet() {
  let styleTrial = document.querySelector("#customCss");

  // create style block if it does not exists
  if (!styleTrial) {
    document.querySelector("head").appendChild(
      Object.assign(
        document.createElement("style"), {
          type: "text/css",
          id: "customCss",
          title: "Custom styles"
      })
    );
    styleTrial = document.querySelector("#customCss");
  }

  return styleTrial.sheet;
}

// for demo
function displaySheet(sheet, term) {
  document.querySelector("pre").textContent += `
  --------------------------------------
  **${term} rules in style#customCss**
  --------------------------------------
  ${
    [...sheet.rules].reduce( (acc, val) => 
      [ ...acc, `${val.cssText}\n`  ], []).join("\n")}`;
}

function timer() {
  setTimeout(() => {
    const cntrEl = document.querySelector("[data-cntr]");
    const current = cntrEl.dataset.cntr - 1;
    cntrEl.dataset.cntr = current > 0 ? current : "";
    return current > 0 
      ? timer() 
      : changeRuleset("[data-cntr]:before", {content: ""});
  }, 1000);
}
<div class="sample">Hello World</div>
<pre></pre>
like image 24
KooiInc Avatar answered Nov 14 '22 22:11

KooiInc