JavaScript – CSS-Tricks https://css-tricks.com Tips, Tricks, and Techniques on using Cascading Style Sheets. Wed, 01 May 2024 17:06:08 +0000 en-US hourly 1 https://wordpress.org/?v=6.6.1 https://i0.wp.com/css-tricks.com/wp-content/uploads/2021/07/star.png?fit=32%2C32&ssl=1 JavaScript – CSS-Tricks https://css-tricks.com 32 32 45537868 Named Element IDs Can Be Referenced as JavaScript Globals https://css-tricks.com/named-element-ids-can-be-referenced-as-javascript-globals/ https://css-tricks.com/named-element-ids-can-be-referenced-as-javascript-globals/#comments Tue, 27 Sep 2022 12:58:21 +0000 https://css-tricks.com/?p=373209 Did you know that DOM elements with IDs are accessible in JavaScript as global variables? It’s one of those things that’s been around, like, forever but I’m really digging into it for the first time.

If this is the first …


Named Element IDs Can Be Referenced as JavaScript Globals originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
Did you know that DOM elements with IDs are accessible in JavaScript as global variables? It’s one of those things that’s been around, like, forever but I’m really digging into it for the first time.

If this is the first time you’re hearing about it, brace yourself! We can see it in action simply by adding an ID to an element in HTML:

<div id="cool"></div>

Normally, we’d define a new variable using querySelector("#cool") or getElementById("cool") to select that element:

var el = querySelector("#cool");

But we actually already have access to #cool without that rigmorale:

So, any id — or name attribute, for that matter — in the HTML can be accessed in JavaScript using window[ELEMENT_ID]. Again, this isn’t exactly “new” but it’s really uncommon to see.

As you may guess, accessing the global scope with named references isn’t the greatest idea. Some folks have come to call this the “global scope polluter.” We’ll get into why that is, but first…

Some context

This approach is outlined in the HTML specification, where it’s described as “named access on the Window object.”

Internet Explorer was the first to implement the feature. All other browsers added it as well. Gecko was the only browser at the time to not support it directly in standards mode, opting instead to make it an experimental feature. There was hesitation to implement it at all, but it moved ahead in the name of browser compatibility (Gecko even tried to convince WebKit to move it out of standards mode) and eventually made it to standards mode in Firefox 14.

One thing that might not be well known is that browsers had to put in place a few precautionary measures — with varying degrees of success — to ensure generated globals don’t break the webpage. One such measure is…

Variable shadowing

Probably the most interesting part of this feature is that named element references don’t shadow existing global variables. So, if a DOM element has an id that is already defined as a global, it won’t override the existing one. For example:

<head>
  <script>
    window.foo = "bar";
  </script>
</head>
<body>
  <div id="foo">I won't override window.foo</div>
  <script>
    console.log(window.foo); // Prints "bar"
  </script>
</body>

And the opposite is true as well:

<div id="foo">I will be overridden :(</div>
<script>
  window.foo = "bar";
  console.log(window.foo); // Prints "bar"
</script>

This behavior is essential because it nullifies dangerous overrides such as <div id="alert" />, which would otherwise create a conflict by invalidating the alert API. This safeguarding technique may very well be the why you — if you’re like me — are learning about this for the first time.

The case against named globals

Earlier, I said that using global named elements as references might not be the greatest idea. There are lots of reasons for that, which TJ VanToll has covered nicely over at his blog and I will summarize here:

  • If the DOM changes, then so does the reference. That makes for some really “brittle” (the spec’s term for it) code where the separation of concerns between HTML and JavaScript might be too much.
  • Accidental references are far too easy. A simple typo may very well wind up referencing a named global and give you unexpected results.
  • It is implemented differently in browsers. For example, we should be able to access an anchor with an id — e.g. <a id="cool"> — but some browsers (namely Safari and Firefox) return a ReferenceError in the console.
  • It might not return what you think. According to the spec, when there are multiple instances of the same named element in the DOM — say, two instances of <div class="cool"> — the browser should return an HTMLCollection with an array of the instances. Firefox, however, only returns the first instance. Then again, the spec says we ought to use one instance of an id in an element’s tree anyway. But doing so won’t stop a page from working or anything like that.
  • Maybe there’s a performance cost? I mean, the browser’s gotta make that list of references and maintain it. A couple of folks ran tests in this StackOverflow thread, where named globals were actually more performant in one test and less performant in a more recent test.

Additional considerations

Let’s say we chuck the criticisms against using named globals and use them anyway. It’s all good. But there are some things you might want to consider as you do.

Polyfills

As edge-case-y as it may sound, these types of global checks are a typical setup requirement for polyfills. Check out the following example where we set a cookie using the new CookieStore API, polyfilling it on browsers that don’t support it yet:

<body>
  <img id="cookieStore"></img>
  <script>
    // Polyfill the CookieStore API if not yet implemented.
    // https://developer.mozilla.org/en-US/docs/Web/API/CookieStore
    if (!window.cookieStore) {
      window.cookieStore = myCookieStorePolyfill;
    }
    cookieStore.set("foo", "bar");
  </script>
</body>

This code works perfectly fine in Chrome, but throws the following error in Safari.:

TypeError: cookieStore.set is not a function

Safari lacks support for the CookieStore API as of this writing. As a result, the polyfill is not applied because the img element ID creates a global variable that clashes with the cookieStore global.

JavaScript API updates

We can flip the situation and find yet another issue where updates to the browser’s JavaScript engine can break a named element’s global references.

For example:

<body>
  <input id="BarcodeDetector"></input>
  <script>
    window.BarcodeDetector.focus();
  </script>
</body>

That script grabs a reference to the input element and invokes focus() on it. It works correctly. Still, we don’t know how long it will continue to work.

You see, the global variable we’re using to reference the input element will stop working as soon as browsers start supporting the BarcodeDetector API. At that point, the window.BarcodeDetector global will no longer be a reference to the input element and .focus() will throw a “window.BarcodeDetector.focus is not a function” error.

Conclusion

Let’s sum up how we got here:

  • All major browsers automatically create global references to each DOM element with an id (or, in some cases, a name attribute).
  • Accessing these elements through their global references is unreliable and potentially dangerous. Use querySelector or getElementById instead.
  • Since global references are generated automatically, they may have some side effects on your code. That’s a good reason to avoid using the id attribute unless you really need it.

At the end of the day, it’s probably a good idea to avoid using named globals in JavaScript. I quoted the spec earlier about how it leads to “brittle” code, but here’s the full text to drive the point home:

As a general rule, relying on this will lead to brittle code. Which IDs end up mapping to this API can vary over time, as new features are added to the web platform, for example. Instead of this, use document.getElementById() or document.querySelector().

I think the fact that the HTML spec itself recommends to staying away from this feature speaks for itself.


Named Element IDs Can Be Referenced as JavaScript Globals originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/named-element-ids-can-be-referenced-as-javascript-globals/feed/ 17 373209
Comparing Node JavaScript to JavaScript in the Browser https://css-tricks.com/node-javascript-compared-to-javascript/ https://css-tricks.com/node-javascript-compared-to-javascript/#comments Fri, 18 Feb 2022 19:01:08 +0000 https://css-tricks.com/?p=363467 Being able to understand Node continues to be an important skill if you’re a front-end developer. Deno has arrived as another way to run JavaScript outside the browser, but the huge ecosystem of tools and software built with Node mean …


Comparing Node JavaScript to JavaScript in the Browser originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
Being able to understand Node continues to be an important skill if you’re a front-end developer. Deno has arrived as another way to run JavaScript outside the browser, but the huge ecosystem of tools and software built with Node mean it’s not going anywhere anytime soon.

If you’ve mainly written JavaScript that runs in the browser and you’re looking to get more of an understanding of the server side, many articles will tell you that Node JavaScript is a great way to write server-side code and capitalize on your JavaScript experience.

I agree, but there are a lot of challenges jumping into Node.js, even if you’re experienced at authoring client-side JavaScript. This article assumes you’ve got Node installed, and you’ve used it to build front-end apps, but want to write your own APIs and tools using Node.

For a beginners explanation of Node and npm you can check out Jamie Corkhill’s “Getting Started With Node” on Smashing Magazine.

Asynchronous JavaScript

We don’t need to write a whole lot of asynchronous code on the browser. The most common usage of asynchronous code on the browser is fetching data from an API using fetch (or XMLHttpRequest if you’re old-school). Other uses of async code might include using setInterval, setTimeout, or responding to user input events, but we can get pretty far writing JavaScript UI without being asynchronous JavaScript geniuses.

If you’re using Node, you will nearly always be writing asynchronous code. From the beginning, Node has been built to leverage a single-threaded event loop using asynchronous callbacks. The Node team blogged in 2011 about how “Node.js promotes an asynchronous coding style from the ground up.” In Ryan Dahl’s talk announcing Node.js in 2009, he talks about the performance benefits of doubling down on asynchronous JavaScript.

The asynchronous-first style is part of the reason Node gained popularity over other attempts at server-side JavaScript implementations such as Netscape’s application servers or Narwhal. However, being forced to write asynchronous JavaScript might cause friction if you aren’t ready for it.

Setting up an example

Let’s say we’re writing a quiz app. We’re going to allow users to build quizes out of multichoice questions to test their friends’ knowledge. You can find a more complete version of what we’ll build at this GitHub repo. You could also clone the entire front-end and back-end to see how it all fits together, or you can take a look at this CodeSandbox (run npm run start to fire it up) and get an idea of what we’re making from there.

Screenshot of a quiz editor written in Node JavaScript that contains four inputs two checkboxes and four buttons.

The quizzes in our app will consist of a bunch of questions, and each of these questions will have a number of answers to choose from, with only one answer being correct.

We can hold this data in an SQLite database. Our database will contain:

  • A table for quizzes with two columns:
    • an integer ID
    • a text title
  • A table for questions with three columns:
    • an integer ID
    • body text
    • An integer reference matching the ID of the quiz each question belongs to
  • A table for answers with four columns:
    • an integer ID
    • body text
    • whether the answer is correct or not
    • an integer reference matching the ID of the question each answer belongs to

SQLite doesn’t have a boolean data type, so we can hold whether an answer is correct in an integer where 0 is false and 1 is true.

First, we’ll need to initialize npm and install the sqlite3 npm package from the command line:

npm init -y
npm install sqlite3

This will create a package.json file. Let’s edit it and add:

"type":"module"

To the top-level JSON object. This will allow us to use modern ES6 module syntax. Now we can create a node script to set up our tables. Let’s call our script migrate.js.

// migrate.js

import sqlite3 from "sqlite3"; 

let db = new sqlite3.Database("quiz.db");
    db.serialize(function () {
      // Setting up our tables:
      db.run("CREATE TABLE quiz (quizid INTEGER PRIMARY KEY, title TEXT)");
      db.run("CREATE TABLE question (questionid INTEGER PRIMARY KEY, body TEXT, questionquiz INTEGER, FOREIGN KEY(questionquiz) REFERENCES quiz(quizid))");
      db.run("CREATE TABLE answer (answerid INTEGER PRIMARY KEY, body TEXT, iscorrect INTEGER, answerquestion INTEGER, FOREIGN KEY(answerquestion) REFERENCES question(questionid))");
      // Create a quiz with an id of 0 and a title "my quiz" 
      db.run("INSERT INTO quiz VALUES(0,\"my quiz\")");
      // Create a question with an id of 0, a question body
      // and a link to the quiz using the id 0
      db.run("INSERT INTO question VALUES(0,\"What is the capital of France?\", 0)");
      // Create four answers with unique ids, answer bodies, an integer for whether
      // they're correct or not, and a link to the first question using the id 0
      db.run("INSERT INTO answer VALUES(0,\"Madrid\",0, 0)");
      db.run("INSERT INTO answer VALUES(1,\"Paris\",1, 0)");
      db.run("INSERT INTO answer VALUES(2,\"London\",0, 0)");
      db.run("INSERT INTO answer VALUES(3,\"Amsterdam\",0, 0)");
  });
db.close();

I’m not going to explain this code in detail, but it creates the tables we need to hold our data. It will also create a quiz, a question, and four answers, and store all of this in a file called quiz.db. After saving this file, we can run our script from the command line using this command:

node migrate.js

If you like, you can open the database file using a tool like DB Browser for SQLite to double check that the data has been created.

Changing the way you write JavaScript

Let’s write some code to query the data we’ve created.

Create a new file and call it index.js .To access our database, we can import sqlite3, create a new sqlite3.Database, and pass the database file path as an argument. On this database object, we can call the get function, passing in an SQL string to select our quiz and a callback that will log the result:

// index.js
import sqlite3 from "sqlite3";

let db = new sqlite3.Database("quiz.db");

db.get(`SELECT * FROM quiz WHERE quizid  = 0`, (err, row) => {
  if (err) {
    console.error(err.message);
  }
  console.log(row);
  db.close();
});

Running this should print { quizid: 0, title: 'my quiz' } in the console.

How not to use callbacks

Now let’s wrap this code in a function where we can pass the ID in as an argument; we want to access any quiz by its ID. This function will return the database row object we get from db.

Here’s where we start running into trouble. We can’t simply return the object inside of the callback we pass to db and walk away. This won’t change what our outer function returns. Instead, you might think we can create a variable (let’s call it result) in the outer function and reassign this variable in the callback. Here is how we might attempt this:

// index.js
// Be warned! This code contains BUGS
import sqlite3 from "sqlite3";

function getQuiz(id) {
  let db = new sqlite3.Database("quiz.db");
  let result;
  db.get(`SELECT * FROM quiz WHERE quizid  = ?`, [id], (err, row) => {
    if (err) {
      return console.error(err.message);
    }
    db.close();
    result = row;
  });
  return result;
}
console.log(getQuiz(0));

If you run this code, the console log will print out undefined! What happened?

We’ve run into a disconnect between how we expect JavaScript to run (top to bottom), and how asynchronous callbacks run. The getQuiz function in the above example runs like this:

  1. We declare the result variable with let result;. We haven’t assigned anything to this variable so its value is undefined.
  2. We call the db.get() function. We pass it an SQL string, the ID, and a callback. But our callback won’t run yet! Instead, the SQLite package starts a task in the background to read from the quiz.db file. Reading from the file system takes a relatively long time, so this API lets our user code move to the next line while Node.js reads from the disk in the background.
  3. Our function returns result. As our callback hasn’t run yet, result still holds a value of undefined.
  4. SQLite finishes reading from the file system and runs the callback we passed, closing the database and assigning the row to the result variable. Assigning this variable makes no difference as the function has already returned its result.

Passing in callbacks

How do we fix this? Before 2015, the way to fix this would be to use callbacks. Instead of only passing the quiz ID to our function, we pass the quiz ID and a callback which will receive the row object as an argument.

Here’s how this looks:

// index.js
import sqlite3 from "sqlite3";
function getQuiz(id, callback) {
  let db = new sqlite3.Database("quiz.db");
  db.get(`SELECT * FROM quiz WHERE quizid  = ?`, [id], (err, row) => {
    if (err) {
       console.error(err.message);
    }
    else {
       callback(row);
    }
    db.close();
  });
}
getQuiz(0,(quiz)=>{
  console.log(quiz);
});

That does it. It’s a subtle difference, and one that forces you to change the way your user code looks, but it means now our console.log runs after the query is complete.

Callback hell

But what if we need to do multiple consecutive asynchronous calls? For instance, what if we were trying to find out which quiz an answer belonged to, and we only had the ID of the answer.

First, I’m going to refactor getQuiz to a more general get function, so we can pass in the table and column to query, as well as the ID:

Unfortunately, we are unable to use the (more secure) SQL parameters for parameterizing the table name, so we’re going to switch to using a template string instead. In production code you would need to scrub this string to prevent SQL injection.

function get(params, callback) {
  // In production these strings should be scrubbed to prevent SQL injection
  const { table, column, value } = params;
  let db = new sqlite3.Database("quiz.db");
  db.get(`SELECT * FROM ${table} WHERE ${column} = ${value}`, (err, row) => {
    callback(err, row);
    db.close();
  });
}

Another issue is that there might be an error reading from the database. Our user code will need to know whether each database query has had an error; otherwise it shouldn’t continue querying the data. We’ll use the Node.js convention of passing an error object as the first argument of our callback. Then we can check if there’s an error before moving forward.

Let’s take our answer with an id of 2 and check which quiz it belongs to. Here’s how we can do this with callbacks:

// index.js
import sqlite3 from "sqlite3";

function get(params, callback) {
  // In production these strings should be scrubbed to prevent SQL injection
  const { table, column, value } = params;
  let db = new sqlite3.Database("quiz.db");
  db.get(`SELECT * FROM ${table} WHERE ${column} = ${value}`, (err, row) => {
    callback(err, row);
    db.close();
  });
}

get({ table: "answer", column: "answerid", value: 2 }, (err, answer) => {
  if (err) {
    console.log(err);
  } else {
    get(
      { table: "question", column: "questionid", value: answer.answerquestion },
      (err, question) => {
        if (err) {
          console.log(err);
        } else {
          get(
            { table: "quiz", column: "quizid", value: question.questionquiz },
            (err, quiz) => {
              if (err) {
                console.log(err);
              } else {
                // This is the quiz our answer belongs to
                console.log(quiz);
              }
            }
          );
        }
      }
    );
  }
});

Woah, that’s a lot of nesting! Every time we get an answer back from the database, we have to add two layers of nesting — one to check for an error, and one for the next callback. As we chain more and more asynchronous calls our code gets deeper and deeper.

We could partially prevent this by using named functions instead of anonymous functions, which would keep the nesting lower, but make our code our code less concise. We’d also have to think of names for all of these intermediate functions. Thankfully, promises arrived in Node back in 2015 to help with chained asynchronous calls like this.

Promises

Wrapping asynchronous tasks with promises allows you to prevent a lot of the nesting in the previous example. Rather than having deeper and deeper nested callbacks, we can pass a callback to a Promise’s then function.

First, let’s change our get function so it wraps the database query with a Promise:

// index.js
import sqlite3 from "sqlite3";
function get(params) {
  // In production these strings should be scrubbed to prevent SQL injection
  const { table, column, value } = params;
  let db = new sqlite3.Database("quiz.db");

  return new Promise(function (resolve, reject) {
    db.get(`SELECT * FROM ${table} WHERE ${column} = ${value}`, (err, row) => {
      if (err) {
        return reject(err);
      }
      db.close();
      resolve(row);
    });
  });
}

Now our code to search for which quiz an answer is a part of can look like this:

get({ table: "answer", column: "answerid", value: 2 })
  .then((answer) => {
    return get({
      table: "question",
      column: "questionid",
      value: answer.answerquestion,
    });
  })
  .then((question) => {
    return get({
      table: "quiz",
      column: "quizid",
      value: question.questionquiz,
    });
  })
  .then((quiz) => {
    console.log(quiz);
  })
  .catch((error) => {
    console.log(error);
  }
);

That’s a much nicer way to handle our asynchronous code. And we no longer have to individually handle errors for each call, but can use the catch function to handle any errors that happen in our chain of functions.

We still need to write a lot of callbacks to get this working. Thankfully, there’s a newer API to help! When Node 7.6.0 was released, it updated its JavaScript engine to V8 5.5 which includes the ability to write ES2017 async/await functions.

Async/Await

With async/await we can write our asynchronouse code almost the same way we write synchronous code. Sarah Drasner has a great post explaining async/await.

When you have a function that returns a Promise, you can use the await keyword before calling it, and it will prevent your code from moving to the next line until the Promise is resolved. As we’ve already refactored the get() function to return a promise, we only need to change our user-code:

async function printQuizFromAnswer() {
  const answer = await get({ table: "answer", column: "answerid", value: 2 });
  const question = await get({
    table: "question",
    column: "questionid",
    value: answer.answerquestion,
  });
  const quiz = await get({
    table: "quiz",
    column: "quizid",
    value: question.questionquiz,
  });
  console.log(quiz);
}

printQuizFromAnswer();

This looks much more familiar to code that we’re used to reading. Just this year, Node released top-level await. This means we can make this example even more concise by removing the printQuizFromAnswer() function wrapping our get() function calls.

Now we have concise code that will sequentially perform each of these asynchronous tasks. We would also be able to simultaneously fire off other asynchronous functions (like reading from files, or responding to HTTP requests) while we’re waiting for this code to run. This is the benefit of all the asynchronous style.

As there are so many asynchronous tasks in Node, such as reading from the network or accessing a database or filesystem. It’s especially important to understand these concepts. It also has a bit of a learning curve.

Using SQL to its full potential

There’s an even better way! Instead of having to worry about these asynchronous calls to get each piece of data, we could use SQL to grab all the data we need in one big query. We can do this with an SQL JOIN query:

// index.js
import sqlite3 from "sqlite3";

function quizFromAnswer(answerid, callback) {
  let db = new sqlite3.Database("quiz.db");
  db.get(
    `SELECT *,a.body AS answerbody, ques.body AS questionbody FROM answer a 
    INNER JOIN question ques ON a.answerquestion=ques.questionid 
    INNER JOIN quiz quiz ON ques.questionquiz = quiz.quizid 
    WHERE a.answerid = ?;`,
    [answerid],
    (err, row) => {
      if (err) {
        console.log(err);
      }
      callback(err, row);
      db.close();
    }
  );
}
quizFromAnswer(2, (e, r) => {
  console.log(r);
});

This will return us all the data we need about our answer, question, and quiz in one big object. We’ve also renamed each body column for answers and questions to answerbody and questionbody to differentiate them. As you can see, dropping more logic into the database layer can simplify your JavaScript (as well as possibly improve performance).

If you’re using a relational database like SQLite, then you have a whole other language to learn, with a whole lot of different features that could save time and effort and increase performance. This adds more to the pile of things to learn for writing Node.

Node APIs and conventions

There are a lot of new node APIs to learn when switching from browser code to Node.js.

Any database connections and/or reads of the filesystem use APIs that we don’t have in the browser (yet). We also have new APIs to set up HTTP servers. We can make checks on the operating system using the OS module, and we can encrypt data with the Crypto module. Also, to make an HTTP request from node (something we do in the browser all the time), we don’t have a fetch or XMLHttpRequest function. Instead, we need to import the https module. However, a recent pull request in the node.js repository shows that fetch in node appears to be on the way! There are still many mismatches between browser and Node APIs. This is one of the problems that Deno has set out to solve.

We also need to know about Node conventions, including the package.json file. Most front-end developers will be pretty familiar with this if they’ve used build tools. If you’re looking to publish a library, the part you might not be used to is the main property in the package.json file. This property contains a path that will point to the entry-point of the library.

There are also conventions like error-first callbacks: where a Node API will take a callback which takes an error as the first argument and the result as the second argument. You could see this earlier in our database code and below using the readFile function.

import fs from 'fs';

fs.readFile('myfile.txt', 'utf8' , (err, data) => {
  if (err) {
    console.error(err)
    return
  }
  console.log(data)
})

Different types of modules

Earlier on, I casually instructed you to throw "type":"module" in your package.json file to get the code samples working. When Node was created in 2009, the creators needed a module system, but none existed in the JavaScript specification. They came up with Common.js modules to solve this problem. In 2015, a module spec was introduced to JavaScript, causing Node.js to have a module system that was different from native JavaScript modules. After a herculean effort from the Node team we are now able to use these native JavaScript modules in Node.

Unfortunately, this means a lot of blog posts and resources will be written using the older module system. It also means that many npm packages won’t use native JavaScript modules, and sometimes there will be libraries that use native JavaScript modules in incompatible ways!

Other concerns

There are a few other concerns we need to think about when writing Node. If you’re running a Node server and there is a fatal exception, the server will terminate and will stop responding to any requests. This means if you make a mistake that’s bad enough on a Node server, your app is broken for everyone. This is different from client-side JavaScript where an edge-case that causes a fatal bug is experienced by one user at a time, and that user has the option of refreshing the page.

Security is something we should already be worried about in the front end with cross-site scripting and cross-site request forgery. But a back-end server has a wider surface area for attacks with vulnerabilities including brute force attacks and SQL injection. If you’re storing and accessing people’s information with Node you’ve got a big responsibility to keep their data safe.

Conclusion

Node is a great way to use your JavaScript skills to build servers and command line tools. JavaScript is a user-friendly language we’re used to writing. And Node’s async-first nature means you can smash through concurrent tasks quickly. But there are a lot of new things to learn when getting started. Here are the resources I wish I saw before jumping in:

And if you are planning to hold data in an SQL database, read up on SQL Basics.


Comparing Node JavaScript to JavaScript in the Browser originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/node-javascript-compared-to-javascript/feed/ 2 363467
9 New React and JavaScript Links for February 2022 https://css-tricks.com/9-new-react-and-javascript-links-for-february-2022/ https://css-tricks.com/9-new-react-and-javascript-links-for-february-2022/#respond Fri, 11 Feb 2022 23:11:28 +0000 https://css-tricks.com/?p=363396 Every now and then, I find that I’ve accumulated a bunch of links about various things I find interesting. Like React and JavaScript! Here’s a list of nine links to other articles about them that I’ve been saving up and …


9 New React and JavaScript Links for February 2022 originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
Every now and then, I find that I’ve accumulated a bunch of links about various things I find interesting. Like React and JavaScript! Here’s a list of nine links to other articles about them that I’ve been saving up and think are worth sharing.

React and JavaScript code snippets with colorful hand-marked scribbles of notes.
Source: “Good advice on JSX conditionals” by Vladimir Klepov
  • Seed Funding for Remix
    Remix went open source after taking funding which seems like a solid move. It’s a for-now-React-only framework, so I think it’s fair that everyone asks how does it compare to Next.js. Which they answered. Probably worth noting again for us CSS folks, Kent mentioned: “Because Remix allows me to easily control which of my CSS files is on the page at any given time, I don’t have all the problems that triggered the JavaScript community to invent workarounds like CSS-in-JS.”
  • React Router v6
    Speaking of that gang, they released React Router v6, which looks like a positive move — all hooks based, 50% smaller than v5 — but is yet another major version with API changes. React Router has a history of API changes like this and they trigger plenty of grumbling in the community. There is plenty of that again.
  • React Aria
    “A library of React Hooks that provides accessible UI primitives for your design system” from… Adobe. Interesting. Looks like some pretty hard problems being solved here, like FocusScope (“When the contain prop is set, focus is contained within the scope.”) and interesting color inputs, like useColorField, useColorSlider, and useColorWheel. There are 59 hooks in all, ranging from interactions and forms to overlays and internationalization, with plenty of others in between.
  • Front End Tables: Sorting, Filtering, and Pagination
    Tania Rascia: “One thing I’ve had to do at every job I’ve had is implement a table on the front end of an application that has sorting, filtering, and pagination.” No shame in reaching for a big library with all these features, but sometimes it’s best to DIY.
  • Good advice on JSX conditionals
    Vladimir Klepov covers the (weirdly) many ways fairly simple conditionals can go wrong, like the number 0 leaking into your markup, and how to manage update versus remount in conditionals.
  • useProseMirror
    I’ve found ProseMirror to be a pretty nice rich text editor in the past. The library itself isn’t actually in React, so I think it’s a smart call here to make a modern React wrapper for it.
  • Spead up sluggish inputs with useDeferredValue
    You can introduce gnarly input delay the more work that an onChange function has to do on a text input. useDeferredValue gives us a way to separate high priority updates from low priority updates for cases like this.”
  • 🎥 A Cartoon Intro to WebAssembly
    If you don’t have a good understanding of what WebAssembly is, then Lin Clark will get you there in this video from JSConf EU 2017. So, no, not a new link or anything, but it’s new to me!
  • 🎥 Turborepo Demo and Walkthrough
    Vercel bought Turborepo. Turborepo is specifically focused on making monorepos better. As someone who’s main codebase is a monorepo with Lerna and Yarn Workspaces such that we can have multiple different sites all share things like a design system, this is right up our alley. This video is with the Turborepo creator Jared Palmer and Lee Robinson, head of developer relations at Vercel. In this video, you get to see it all work.


9 New React and JavaScript Links for February 2022 originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/9-new-react-and-javascript-links-for-february-2022/feed/ 0 https://www.youtube.com/embed/YX5yoApjI3M Turborepo Demo and Walkthrough (High-Performance Monorepos) nonadult 363396
How to Cycle Through Classes on an HTML Element https://css-tricks.com/cycle-through-classes-html-element/ https://css-tricks.com/cycle-through-classes-html-element/#comments Wed, 26 Jan 2022 19:48:49 +0000 https://css-tricks.com/?p=361200 Say you have three HTML classes, and a DOM element should only have one of them at a time:

<div class="state-1"</div<div class="state-2"</div<div class="state-3"</div

Now your job is to rotate them. That is, cycle through classes …


How to Cycle Through Classes on an HTML Element originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
Say you have three HTML classes, and a DOM element should only have one of them at a time:

<div class="state-1"></div>
<div class="state-2"></div>
<div class="state-3"></div>

Now your job is to rotate them. That is, cycle through classes on an HTML element. When some event occurs, if the element has state-1 on it, remove state-1 and add state-2. If it has state-2 on it, remove that and add state-3. On the last state, remove it, and cycle back to state-1.

Example of how to Cycle Through Classes on an HTML Element. Here a large <button> with an <svg> inside cycles through state-1, state-2, and state-3 classes, turning from red to yellow to green.

It’s notable that we’re talking about 3+ classes here. The DOM has a .classList.toggle() function, even one that takes a conditional as a second parameter, but that’s primarily useful in a two-class on/off situation, not cycling through classes.

Why? There is a number of reasons. Changing a class name gives you lots of power to re-style things in the DOM, and state management like that is a cornerstone of modern web development. But to be specific, in my case, I was wanting to do FLIP animations where I’d change a layout and trigger a tween animation between the different states.

Careful about existing classes! I saw some ideas that overwrote .className, which isn’t friendly toward other classes that might be on the DOM element. All these are “safe” choices for cycling through classes in that way.

Because this is programming, there are lots of ways to get this done. Let’s cover a bunch of them — for fun. I tweeted about this issue, so many of these solutions are from people who chimed into that discussion.

A verbose if/else statement to cycle through classes

This is what I did at first to cycle through classes. That’s how my brain works. Just write out very specific instructions for exactly what you want to happen:

if (el.classList.contains("state-1")) {
  el.classList.remove("state-1");
  el.classList.add("state-2");
} else if (el.classList.contains("state-2")) {
  el.classList.remove("state-2");
  el.classList.add("state-3");
} else {
  el.classList.remove("state-3");
  el.classList.add("state-1");
}

I don’t mind the verbosity here, because to me it’s super clear what’s going on and will be easy to return to this code and “reason about it,” as they say. You could consider the verbosity a problem — surely there is a way to cycle through classes with less code. But a bigger issue is that it isn’t very extensible. There is no semblance of configuration (e.g. change the names of the classes easily) or simple way to add classes to the party, or remove them.

We could use constants, at least:

const STATE_1 = "state-1";
const STATE_2 = "state-2";
const STATE_3 = "state-3";

if (el.classList.contains(STATE_1)) {
  el.classList.remove(STATE_1);
  el.classList.add(STATE_2);
} else if (el.classList.contains(STATE_2)) {
  el.classList.remove(STATE_2);
  el.classList.add(STATE_3);
} else {
  el.classList.remove(STATE_3);
  el.classList.add(STATE_1);
}

But that’s not wildly different or better.

RegEx off the old class, increment state, then re-add

This one comes from Tab Atkins. Since we know the format of the class, state-N, we can look for that, pluck off the number, use a little ternary to increment it (but not higher than the highest state), then add/remove the classes as a way of cycling through them:

const oldN = +/\bstate-(\d+)\b/.exec(el.getAttribute('class'))[1];
const newN = oldN >= 3 ? 1 : oldN+1;
el.classList.remove(`state-${oldN}`);
el.classList.add(`state-${newN}`);

Find the index of the class, then remove/add

A bunch of techniques to cycle through classes center around setting up an array of classes up front. This acts as configuration for cycling through classes, which I think is a smart way to do it. Once you have that, you can find the relevant classes for adding and removing them. This one is from Christopher Kirk-Nielsen:

const classes = ["state-1", "state-2", "state-3"];
const activeIndex = classes.findIndex((c) => el.classList.contains(c));
const nextIndex = (activeIndex + 1) % classes.length;

el.classList.remove(classes[activeIndex]);
el.classList.add(classes[nextIndex]);

Christopher had a nice idea for making the add/remove technique shorter as well. Turns out it’s the same…

el.classList.remove(classes[activeIndex]);
el.classList.add(classes[nextIndex]);

// Does the same thing.
el.classList.replace(classes[activeIndex], classes[nextIndex]);

Mayank had a similar idea for cycling through classes by finding the class in an array, only rather than using classList.contains(), you check the classes currently on the DOM element with what is in the array.

const states = ["state-1", "state-2", "state-3"];
const current = [...el.classList].find(cls => states.includes(cls));
const next = states[(states.indexOf(current) + 1) % states.length];
el.classList.remove(current);
el.classList.add(next);

Variations of this were the most common idea. Here’s Jhey’s and here’s Mike Wagz which sets up functions for moving forward and backward.

Cascading replace statements

Speaking of that replace API, Chris Calo had a clever idea where you chain them with the or operator and rely on the fact that it returns true/false if it works or doesn’t. So you do all three and one of them will work!

 el.classList.replace("state-1", "state-2") ||
 el.classList.replace("state-2", "state-3") ||
 el.classList.replace("state-3", "state-1");

Nicolò Ribaudo came to the same conclusion.

Just cycle through class numbers

If you pre-configured a 1 upfront, you could cycle through classes 1-3 and add/remove them based on that. This is from Timothy Leverett who lists another similar option in the same tweet.

// Assumes a `let s = 1` upfront
el.classList.remove(`state-${s + 1}`);
s = (s + 1) % 3;
el.classList.add(`state-${s + 1}`);

Use data-* attributes instead

Data attributes have the same specificity power, so I have no issue with this. They might actually be more clear in terms of state handling, but even better, they have a special API that makes them nice to manipulate. Munawwar Firoz had an idea that gets this down to a one-liner:

el.dataset.state = (+el.dataset.state % 3) + 1

A data attribute state machine

You can count on David Khourshid to be ready with a state machine:

const simpleMachine = {
  "1": "2",
  "2": "3",
  "3": "1"
};
el.dataset.state = simpleMachine[el.dataset.state];

You’ll almost surely want a function

Give yourself a little abstraction, right? Many of the ideas wrote code this way, but so far I’ve move it out to focus on the idea itself. Here, I’ll leave the function in. This one is from Andrea Giammarchi in which a unique function for cycling through classes is set up ahead of time, then you call it as needed:

const rotator = (classes) => ({ classList }) => {
  const current = classes.findIndex((cls) => classList.contains(cls));
  classList.remove(...classes);
  classList.add(classes[(current + 1) % classes.length]);
};

const rotate = rotator(["state-1", "state-2", "state-3"]);
rotate(el);

I heard from Kyle Simpson who had this same idea, almost character for character.

Others?

There were more ideas in the replies to my original tweet, but are, best I can tell, variations on what I’ve already shared above. Apologies if I missed yours! Feel free to share your idea again in the comments here. I see nobody used a switch statements — that could be a possibility!

David Desandro went as far as recording a video, which is wonderful as it slowly abstracts the concepts further and further until it’s succinct but still readable and much more flexible:

And here’s a demo Pen with all the code for each example in there. They are numbered, so to test out another one, comment out the one that is uncommented, and uncomment another example:


How to Cycle Through Classes on an HTML Element originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/cycle-through-classes-html-element/feed/ 6 https://www.youtube.com/embed/hXrHZ_LSzkk State variable and cycling through an array nonadult 361200
The Invisible JavaScript Backdoor https://css-tricks.com/the-invisible-javascript-backdoor/ https://css-tricks.com/the-invisible-javascript-backdoor/#comments Wed, 08 Dec 2021 16:00:01 +0000 https://css-tricks.com/?p=358496 An interesting (scary) trick of an nearly undetectable exploit. Wolfgang Ettlinger:

What if a backdoor literally cannot be seen and thus evades detection even from thorough code reviews?

I’ll post the screenshot of the exploit from the post with the …


The Invisible JavaScript Backdoor originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
An interesting (scary) trick of an nearly undetectable exploit. Wolfgang Ettlinger:

What if a backdoor literally cannot be seen and thus evades detection even from thorough code reviews?

I’ll post the screenshot of the exploit from the post with the actual exploit circled:

If you were really looking super closely you’d probably see that, but I can see how it would be easy to miss as it would avoid any linting problems and doesn’t mess up syntax highlighting at all. Then the way this code is written, the commands are executed:

Each element in the array, the hardcoded commands as well as the user-supplied parameter, is then passed to the exec function. This function executes OS commands.

They consider it worthy of change:

The Cambridge team proposes restricting Bidi Unicode characters. As we have shown, homoglyph attacks and invisible characters can pose a threat as well.


The Invisible JavaScript Backdoor originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/the-invisible-javascript-backdoor/feed/ 10 358496
Responsible JavaScript https://css-tricks.com/responsible-javascript-2/ https://css-tricks.com/responsible-javascript-2/#comments Tue, 02 Nov 2021 14:17:55 +0000 https://css-tricks.com/?p=355395 High five to Jeremy on the big release of Responsible JavaScript on A Book Apart. There is a lot of talk about how the proliferation of JavaScript has had a negative impact on the web, but now we have …


Responsible JavaScript originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
High five to Jeremy on the big release of Responsible JavaScript on A Book Apart. There is a lot of talk about how the proliferation of JavaScript has had a negative impact on the web, but now we have the canonical reference tome.

The book is just chock-full of Jeremy framing some of the biggest arguments discussions about modern web development, dissecting them, and helping us learn from them. I say “modern web development” there on purpose, because JavaScript has gotten to be such a massive part of building websites these days that the two terms are almost synonymous, for better or worse. While the book title is Responsible JavaScript, it might as well be “Responsible Web Development” to make it go hand and hand with Scott’s book (and Mat’s book, if you need a more gentle introduction to JavaScript).

I like how Jeremy blends the old and new together. Readers are introduced and shown how techniques as old as the web (like progressive enhancement) are still useful today and perhaps even more useful than they have ever been. But this isn’t a history novel. New technology (like service workers) is handled with equal grace, and modern techniques for performance gains are given the credit they are due (like build tools and code splitting).


As an aside here — have you ever had an inkling to write a tech book? I have both heard and given this advice: Write a blog post first, or maybe a whole bunch of blog posts. That’ll prove you clearly have words to say about this. Plus it will get the energy of your idea into the world. You might get feedback and constructive insights on the idea as it is shared. Then, hopefully, you can turn that blog post into a talk. That’ll really get you thinking deeply about your idea while getting even more comfortable with the idea of sharing it clearly. And, if all goes well, turn all of that into a book!

Let’s see what happened here. Jeremy wrote a couple of blog posts (hey, nice title). They became a talk (hey, nice title). And that turned into a book (hey, nice title).


Responsible JavaScript originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/responsible-javascript-2/feed/ 2 355395
Links on React and JavaScript II https://css-tricks.com/links-on-react-and-javascript-ii/ https://css-tricks.com/links-on-react-and-javascript-ii/#comments Fri, 01 Oct 2021 21:09:25 +0000 https://css-tricks.com/?p=352717
  • How To Use The Vite Build Tool with ReactVite is hot, in part, because it’s based on esbuild and wickedly fast. It’s from Evan You of Vue fame, but it’s not a Vue-specific tool. Here, NARUHODO covers how

  • Links on React and JavaScript II originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

    ]]>
  • How To Use The Vite Build Tool with ReactVite is hot, in part, because it’s based on esbuild and wickedly fast. It’s from Evan You of Vue fame, but it’s not a Vue-specific tool. Here, NARUHODO covers how to configure it to work with React.
  • React Architecture: How to Structure and Organize a React Application — Tania Rascia with “an opinionated guide” on project structure. Looks pretty nice to me. I like the @ import aliases. Looks like it would support a monorepo-type environment pretty well. I also like the distinction between global vs. resuable components (called just “components” here) and views vs. pages. I’d probably separate into three: Library Components (no global state, no queries/mutations, more design-y and intentionally reusable), Project Components (business logic, global state, not very reuable), and Pages (routing concerned).
  • What’s NOT new in React 18 — Benny Powers is a little salty about React’s lack of <web-components /> support. I agree it’s unfortunate, as web components do some things really well and React does some things really well and it would be nice to see them make buddies.
  • How React got Traction — A bit of irony when considering the above link… Shawn Wang and Pete Hunt talk on this podcast about the history of React and how it came to be so popular: “How React overcame its haters: by listening.”
  • Compound Components In React — Ichoku Chinonso covers this super useful pattern. Some components are built from a bucket of other little components (think Tabs, TabBar, Tab, TabPanels, TabPanel) and, with the Compound Component model, you get more flexibility, logical importing, and usage of the whole lot. I’m curious about the origins of this pattern. I know Ryan Florence was talking about it in 2017, and I first saw them via this Kent Dodds post. Googlin’ around, there are loads of random articles about it. Maybe it comes from deeper computer science concepts?
  • The Perils of Rehydration — Josh Comeau covers a bug that I’ve had to fight against multiple times in the last few weeks: React looking like it’s completely pooping the bed on constructing the DOM. Like elements that are clearly nested properly in the JSX appearing in parent elements, or like you’ve forgotten to close half your dang HTML elements and the browser is majorly confused. The problem comes from trying to do server side rendering (SSR) and client side rendering (CSR), which confuses the rehydration. The DOM from the SSR doesn’t match when CSR takes over. Fortunately, there is some fairly straightforward trickery to fix it.

  • Links on React and JavaScript II originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

    ]]>
    https://css-tricks.com/links-on-react-and-javascript-ii/feed/ 1 352717
    Comparing Methods for Appending and Inserting With JavaScript https://css-tricks.com/comparing-methods-for-appending-and-inserting-with-javascript/ https://css-tricks.com/comparing-methods-for-appending-and-inserting-with-javascript/#comments Fri, 24 Sep 2021 14:34:59 +0000 https://css-tricks.com/?p=352342 Let’s say we want to add something to a webpage after the initial load. JavaScript gives us a variety of tools. Perhaps you’ve used some of them, like append, appendChild, insertAdjacentHTML, or innerHTML.

    The difficult thing …


    Comparing Methods for Appending and Inserting With JavaScript originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

    ]]>
    Let’s say we want to add something to a webpage after the initial load. JavaScript gives us a variety of tools. Perhaps you’ve used some of them, like append, appendChild, insertAdjacentHTML, or innerHTML.

    The difficult thing about appending and inserting things with JavaScript isn’t so much about the tools it offers, but which one to use, when to use them, and understanding how each one works.

    Let’s try to clear things up.

    Super quick context

    It might be helpful to discuss a little background before jumping in. At the simplest level, a website is an HTML file downloaded from a server to a browser.

    Your browser converts the HTML tags inside your HTML file into a bunch of objects that can be manipulated with JavaScript. These objects construct a Document Object Model (DOM) tree. This tree is a series of objects that are structured as parent-child relationships.

    In DOM parlance, these objects are called nodes, or more specifically, HTML elements.

    <!-- I'm the parent element -->
    <div>
      <!-- I'm a child element -->
      <span>Hello</span>
    </div>

    In this example, the HTML span element is the child of the div element, which is the parent.

    And I know that some of these terms are weird and possibly confusing. We say “node”, but other times we may say “element” or “object” instead. And, in some cases, they refer to the same thing, just depending on how specific we want to be .

    For example, an “element” is a specific type of “node”, just like an apple is a specific type of fruit.

    We can organize these terms from most general, to most specific: ObjectNodeElementHTML Element

    Understanding these DOM items is important, as we’ll interact with them to add and append things with JavaScript after an initial page load. In fact, let’s start working on that.

    Setup

    These append and insert methods mostly follow this pattern:

    Element.append_method_choice(stuff_to_append)

    Again, an element is merely an object in the DOM Tree that represents some HTML. Earlier, we had mentioned that the purpose of the DOM tree is to give us a convenient way to interact with HTML using JavaScript.

    So, how do we use JavaScript to grab an HTML element?

    Querying the DOM

    Let’s say we have the following tiny bit of HTML:

    <div id="example" class="group">
      Hello World
    </div>

    There are a few common ways to query the DOM:

    // Query a specific selector (could be class, ID, element type, or attribute):
    const my_element1 = document.querySelector('#example')
    
    // Query an element by its ID:
    const my_element2 = document.getElementbyId('example')
    
    // Query an element by its class:
    const my_element3 = document.getElementsbyClassName('group')[0] 

    In this example, all three lines query the same thing, but look for it in different ways. One looks at any of the item’s CSS selectors; one looks at the item’s ID; and one looks at the item’s class.

    Note that the getElementbyClass method returns an array. That’s because it’s capable of matching multiple elements in the DOM and storing those matches in an array makes sure all of them are accounted for.

    What we can append and insert

    // Append Something
    const my_element1 = document.querySelector('#example')
    my_element1.append(something)

    In this example, something is a parameter that represents stuff we want to tack on to the end of (i.e. append to) the matched element.

    We can’t just append any old thing to any old object. The append method only allows us to append either a node or plain text to an element in the DOM. But some other methods can append HTML to DOM elements as well.

    1. Nodes are either created with document.createElement() in JavaScript, or they are selected with one of the query methods we looked at in the last section.
    2. Plain text is, well, text. It’s plain text in that it does not carry any HTML tags or formatting with it. (e.g. Hello).
    3. HTML is also text but, unlike plain text, it does indeed get parsed as markup when it’s added to the DOM (e.g. <div>Hello</div>).

    It might help to map out exactly which parameters are supported by which methods:

    MethodNodeHTML TextText
    appendYesNoYes
    appendChildYesNoNo
    insertAdjacentHTMLNoYesYes1
    innerHTML2NoYesYes
    1 This works, but insertAdjacentText is recommended.
    2 Instead of taking traditional parameters, innerHTML is used like: element.innerHTML = 'HTML String'

    How to choose which method to use

    Well, it really depends on what you’re looking to append, not to mention certain browser quirks to work around.

    • If you have existing HTML that gets sent to your JavaScript, it’s probably easiest to work with methods that support HTML.
    • If you’re building some new HTML in JavasScript, creating a node with heavy markup can be cumbersome, whereas HTML is less verbose.
    • If you want to attach event listeners right away, you’ll want to work with nodes because we call addEventListener on nodes, not HTML.
    • If all you need is text, any method supporting plain text parameters is fine.
    • If your HTML is potentially untrustworthy (i.e. it comes from user input, say a comment on a blog post), then you’ll want to be careful when using HTML, unless it has been sanitized (i.e. the harmful code has been removed).
    • If you need to support Internet Explorer, then using append is out of the question.

    Example

    Let’s say we have a chat application, and we want to append a user, Dale, to a buddy list when they log in.

    <!-- HTML Buddy List -->
    <ul id="buddies">
      <li><a>Alex</a></li>
      <li><a>Barry</a></li>
      <li><a>Clive</a></li>
      <!-- Append next user here -->
    </ul>

    Here’s how we’d accomplish this using each of the methods above.

    append

    We need to create a node object that translates to <li><a>Dale</a></li>.

    const new_buddy = document.createElement('li')
    const new_link = document.createElement('a')
    
    const buddy_name = "Dale"
    
    new_link.append(buddy_name) // Text param
    new_buddy.append(new_link) // Node param
    
    const list = document.querySelector('#buddies')
    list.append(new_buddy) // Node param

    Our final append places the new user at the end of the buddy list, just before the closing </ul> tag. If we’d prefer to place the user at the front of the list, we could use the prepend method instead.

    You may have noticed that we were also able to use append to fill our <a> tag with text like this:

    const buddy_name = "Dale"
    new_link.append(buddy_name) // Text param

    This highlights the versatility of append.

    And just to call it out once more, append is unsupported in Internet Explorer.

    appendChild

    appendChild is another JavaScript method we have for appending stuff to DOM elements. It’s a little limited in that it only works with node objects, so we we’ll need some help from textContent (or innerText) for our plain text needs.

    Note that appendChild, unlike append, is supported in Internet Explorer.

    const new_buddy = document.createElement('li')
    const new_link = document.createElement('a')
    
    const buddy_name = "Dale"
    
    new_link.textContent = buddy_name
    new_buddy.appendChild(new_link) // Node param
    
    const list = document.querySelector('#buddies')
    list.appendChild(new_buddy) // Node param

    Before moving on, let’s consider a similar example, but with heavier markup.

    Let’s say the HTML we wanted to append didn’t look like <li><a>Dale</a></li>, but rather:

    <li class="abc" data-tooltip="Click for Dale">
      <a id="user_123" class="def" data-user="dale">
        <img src="images/dale.jpg" alt="Profile Picture"/>
        <span>Dale</span>
      </a>
    </li>

    Our JavaScript would look something like:

    const buddy_name = "Dale"
    
    const new_buddy = document.createElement('li')
    new_buddy.className = 'abc'
    new_buddy.setAttribute('data-tooltip', `Click for ${buddy_name}`)
    
    const new_link = document.createElement('a')
    new_link.id = 'user_123'
    new_link.className = 'def'
    new_link.setAttribute('data-user', buddy_name)
    
    const new_profile_img = document.createElement('img')
    new_profile_img.src = 'images/dale.jpg'
    new_profile_img.alt = 'Profile Picture'
    
    const new_buddy_span = document.createElement('span')
    new_buddy_span.textContent = buddy_name
    
    new_link.appendChild(new_profile_img) // Node param
    new_link.appendChild(new_buddy_span) // Node param
    new_buddy.appendChild(new_link) // Node param
    
    const list = document.querySelector('#buddies')
    list.appendChild(new_buddy) // Node param

    There’s no need to follow all of above JavaScript – the point is that creating large amounts of HTML in JavaScript can become quite cumbersome. And there’s no getting around this if we use append or appendChild.

    In this heavy markup scenario, it might be nice to just write our HTML as a string, rather than using a bunch of JavaScript methods…

    insertAdjacentHTML

    insertAdjacentHTML is is like append in that it’s also capable of adding stuff to DOM elements. One difference, though, is that insertAdjacentHTML inserts that stuff at a specific position relative to the matched element.

    And it just so happens to work with HTML. That means we can insert actual HTML to a DOM element, and pinpoint exactly where we want it with four different positions:

    <!-- beforebegin -->
    <div id="example" class="group">
      <!-- afterbegin -->
      Hello World
      <!-- beforeend -->
    </div>
    <!-- afterend -->

    So, we can sorta replicate the same idea of “appending” our HTML by inserting it at the beforeend position of the #buddies selector:

    const buddy_name = "Dale"
    
    const new_buddy = `<li><a>${buddy_name}</a></li>`
    const list = document.querySelector('#buddies')
    list.insertAdjacentHTML('beforeend', new_buddy)

    Remember the security concerns we mentioned earlier. We never want to insert HTML that’s been submitted by an end user, as we’d open ourselves up to cross-site scripting vulnerabilities.

    innerHTML

    innerHTML is another method for inserting stuff. That said, it’s not recommended for inserting, as we’ll see.

    Here’s our query and the HTML we want to insert:

    const buddy_name = "Dale"
    const new_buddy = `<li><a>${buddy_name}</a></li>`
    const list = document.querySelector('#buddies')  
    list.innerHTML += new_buddy

    Initially, this seems to work. Our updated buddy list looks like this in the DOM:

    <ul id="buddies">
      <li><a>Alex</a></li>
      <li><a>Barry</a></li>
      <li><a>Clive</a></li>
      <li><a>Dale</a></li>
    </ul>

    That’s what we want! But there’s a constraint with using innerHTML that prevents us from using event listeners on any elements inside of #buddies because of the nature of += in list.innerHTML += new_buddy.

    You see, A += B behaves the same as A = A + B. In this case, A is our existing HTML and B is what we’re inserting to it. The problem is that this results in a copy of the existing HTML with the additional inserted HTML. And event listeners are unable to listen to copies. That means if we want to listen for a click event on any of the <a> tags in the buddy list, we’re going to lose that ability with innerHTML.

    So, just a word of caution there.

    Demo

    Here’s a demo that pulls together all of the methods we’ve covered. Clicking the button of each method inserts “Dale” as an item in the buddies list.

    Go ahead and open up DevTools while you’re at it and see how the new list item is added to the DOM.

    Recap

    Here’s a general overview of where we stand when we’re appending and inserting stuff into the DOM. Consider it a cheatsheet for when you need help figuring out which method to use.

    MethodNode
    HTML TextText
    Internet Explorer?Event ListenersSecure?
    HTML Templating
    appendYesNoYesNoPreservesYesMedium
    appendChildYesNoNoYesPreservesYesMedium
    insertAdjacentHTMLNoYesYes1YesPreservesCarefulEasy
    innerHTML2NoYesYesYesLosesCarefulEasy
    1 This works, but insertAdjacentText is recommended.
    2 Instead of taking traditional parameters, innerHTML is used like: element.innerHTML = 'HTML String'

    If I had to condense all of that into a few recommendations:

    • Using innerHTML for appending is not recommended as it removes event listeners.
    • append works well if you like the flexibility of working with node elements or plain text, and don’t need to support Internet Explorer.
    • appendChild works well if you like (or need) to work with node elements, and want full browser coverage.
    • insertAdjacentHTML is nice if you need to generate HTML, and want to more specific control over where it is placed in the DOM.

    Dig deeper

    The methods discussed above are commonly used, and should cover the majority of your use cases.

    That said, there are some additional append/insert methods, if you’re curious:

    Last thought and a quick plug :)

    This post was inspired by real issues I recently ran into when building a chat application. As you’d imagine, a chat application relies on a lot of appending/inserting — people coming online, new messages, notifications, etc.

    That chat application is called Bounce. It’s a peer-to-peer learning chat. Assuming you’re a JavaScript developer (among other things), you probably have something to teach! And you can earn some extra cash.

    If you’re curious, here’s a link to the homepage, or my profile on Bounce. Cheers!


    Comparing Methods for Appending and Inserting With JavaScript originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

    ]]>
    https://css-tricks.com/comparing-methods-for-appending-and-inserting-with-javascript/feed/ 10 352342
    Implementing a single GraphQL across multiple data sources https://css-tricks.com/implementing-a-single-graphql-across-multiple-data-sources/ https://css-tricks.com/implementing-a-single-graphql-across-multiple-data-sources/#respond Thu, 19 Aug 2021 14:30:41 +0000 https://css-tricks.com/?p=350035 In this article, we will discuss how we can apply schema stitching across multiple Fauna instances. We will also discuss how to combine other GraphQL services and data sources with Fauna in one graph.

    Get the code

    What is Schema…


    Implementing a single GraphQL across multiple data sources originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

    ]]>
    In this article, we will discuss how we can apply schema stitching across multiple Fauna instances. We will also discuss how to combine other GraphQL services and data sources with Fauna in one graph.

    What is Schema Stitching?

    Schema stitching is the process of creating a single GraphQL API from multiple underlying GraphQL APIs.

    Where is it useful?

    While building large-scale applications, we often break down various functionalities and business logic into micro-services. It ensures the separation of concerns. However, there will be a time when our client applications need to query data from multiple sources. The best practice is to expose one unified graph to all your client applications. However, this could be challenging as we do not want to end up with a tightly coupled, monolithic GraphQL server. If you are using Fauna, each database has its own native GraphQL. Ideally, we would want to leverage Fauna’s native GraphQL as much as possible and avoid writing application layer code. However, if we are using multiple databases our front-end application will have to connect to multiple GraphQL instances. Such arrangement creates tight coupling. We want to avoid this in favor of one unified GraphQL server.

    To remedy these problems, we can use schema stitching. Schema stitching will allow us to combine multiple GraphQL services into one unified schema. In this article, we will discuss

    1. Combining multiple Fauna instances into one GraphQL service
    2. Combining Fauna with other GraphQL APIs and data sources
    3. How to build a serverless GraphQL gateway with AWS Lambda?

    Combining multiple Fauna instances into one GraphQL service

    First, let’s take a look at how we can combine multiple Fauna instances into one GraphQL service. Imagine we have three Fauna database instances ProductInventory, and Review. Each is independent of the other. Each has its graph (we will refer to them as subgraphs). We want to create a unified graph interface and expose it to the client applications. Clients will be able to query any combination of the downstream data sources.

    We will call the unified graph to interface our gateway service. Let’s go ahead and write this service.

    We’ll start with a fresh node project. We will create a new folder. Then navigate inside it and initiate a new node app with the following commands.

    mkdir my-gateway 
    cd my-gateway
    npm init --yes

    Next, we will create a simple express GraphQL server. So let’s go ahead and install the express and express-graphqlpackage with the following command.

    npm i express express-graphql graphql --save

    Creating the gateway server

    We will create a file called gateway.js . This is our main entry point to the application. We will start by creating a very simple GraphQL server.

    const express = require('express');
    const { graphqlHTTP } = require('express-graphql');
    const { buildSchema }  = require('graphql');
    
    // Construct a schema, using GraphQL schema language
    const schema = buildSchema(`
      type Query {
        hello: String
      }
    `);
    
    // The root provides a resolver function for each API endpoint
    const rootValue = {
        hello: () => 'Hello world!',
    };
    
    const app = express();
    
    app.use(
      '/graphql',
      graphqlHTTP((req) => ({
        schema,
        rootValue,
        graphiql: true,
      })),
    );
    
    app.listen(4000);
    console.log('Running a GraphQL API server at <http://localhost:4000/graphql>');

    In the code above we created a bare-bone express-graphql server with a sample query and a resolver. Let’s test our app by running the following command.

    node gateway.js

    Navigate to [<http://localhost:4000/graphql>](<http://localhost:4000/graphql>) and you will be able to interact with the GraphQL playground.

    Creating Fauna instances

    Next, we will create three Fauna databases. Each of them will act as a GraphQL service. Let’s head over to fauna.com and create our databases. I will name them ProductInventory and Review

    Once the databases are created we will generate admin keys for them. These keys are required to connect to our GraphQL APIs.

    Let’s create three distinct GraphQL schemas and upload them to the respective databases. Here’s how our schemas will look.

    # Schema for Inventory database
    type Inventory {
      name: String
      description: String
      sku: Float
      availableLocation: [String]
    }
    # Schema for Product database
    type Product {
      name: String
      description: String
      price: Float
    }
    # Schema for Review database
    type Review {
      email: String
      comment: String
      rating: Float
    }

    Head over to the relative databases, select GraphQL from the sidebar and import the schemas for each database.

    Now we have three GraphQL services running on Fauna. We can go ahead and interact with these services through the GraphQL playground inside Fauna. Feel free to enter some dummy data if you are following along. It will come in handy later while querying multiple data sources.

    Setting up the gateway service

    Next, we will combine these into one graph with schema stitching. To do so we need a gateway server. Let’s create a new file gateway.js. We will be using a couple of libraries from graphql tools to stitch the graphs.

    Let’s go ahead and install these dependencies on our gateway server.

    npm i @graphql-tools/schema @graphql-tools/stitch @graphql-tools/wrap cross-fetch --save
    

    In our gateway, we are going to create a new generic function called makeRemoteExecutor. This function is a factory function that returns another function. The returned asynchronous function will make the GraphQL query API call.

    // gateway.js
    
    const express = require('express');
    const { graphqlHTTP } = require('express-graphql');
    const { buildSchema }  = require('graphql');
    
     function makeRemoteExecutor(url, token) {
        return async ({ document, variables }) => {
          const query = print(document);
          const fetchResult = await fetch(url, {
            method: 'POST',
            headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + token },
            body: JSON.stringify({ query, variables }),
          });
          return fetchResult.json();
        }
     }
    
    // Construct a schema, using GraphQL schema language
    const schema = buildSchema(`
      type Query {
        hello: String
      }
    `);
    
    // The root provides a resolver function for each API endpoint
    const rootValue = {
        hello: () => 'Hello world!',
    };
    
    const app = express();
    
    app.use(
      '/graphql',
      graphqlHTTP(async (req) => {
        return {
          schema,
          rootValue,
          graphiql: true,
        }
      }),
    );
    
    app.listen(4000);
    console.log('Running a GraphQL API server at http://localhost:4000/graphql');

    As you can see above the makeRemoteExecutor has two parsed arguments. The url argument specifies the remote GraphQL url and the token argument specifies the authorization token.

    We will create another function called makeGatewaySchema. In this function, we will make the proxy calls to the remote GraphQL APIs using the previously created makeRemoteExecutor function.

    // gateway.js
    
    const express = require('express');
    const { graphqlHTTP } = require('express-graphql');
    const { introspectSchema } = require('@graphql-tools/wrap');
    const { stitchSchemas } = require('@graphql-tools/stitch');
    const { fetch } = require('cross-fetch');
    const { print } = require('graphql');
    
    function makeRemoteExecutor(url, token) {
      return async ({ document, variables }) => {
        const query = print(document);
        const fetchResult = await fetch(url, {
          method: 'POST',
          headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + token },
          body: JSON.stringify({ query, variables }),
        });
        return fetchResult.json();
      }
    }
    
    async function makeGatewaySchema() {
    
        const reviewExecutor = await makeRemoteExecutor('https://graphql.fauna.com/graphql', 'fnAEQZPUejACQ2xuvfi50APAJ397hlGrTjhdXVta');
        const productExecutor = await makeRemoteExecutor('https://graphql.fauna.com/graphql', 'fnAEQbI02HACQwTaUF9iOBbGC3fatQtclCOxZNfp');
        const inventoryExecutor = await makeRemoteExecutor('https://graphql.fauna.com/graphql', 'fnAEQbI02HACQwTaUF9iOBbGC3fatQtclCOxZNfp');
    
        return stitchSchemas({
            subschemas: [
              {
                schema: await introspectSchema(reviewExecutor),
                executor: reviewExecutor,
              },
              {
                schema: await introspectSchema(productExecutor),
                executor: productExecutor
              },
              {
                schema: await introspectSchema(inventoryExecutor),
                executor: inventoryExecutor
              }
            ],
            
            typeDefs: 'type Query { heartbeat: String! }',
            resolvers: {
              Query: {
                heartbeat: () => 'OK'
              }
            }
        });
    }
    
    // ...

    We are using the makeRemoteExecutor function to make our remote GraphQL executors. We have three remote executors here one pointing to Product , Inventory , and Review services. As this is a demo application I have hardcoded the admin API key from Fauna directly in the code. Avoid doing this in a real application. These secrets should not be exposed in code at any time. Please use environment variables or secret managers to pull these values on runtime.

    As you can see from the highlighted code above we are returning the output of the switchSchemas function from @graphql-tools. The function has an argument property called subschemas. In this property, we can pass in an array of all the subgraphs we want to fetch and combine. We are also using a function called introspectSchema from graphql-tools. This function is responsible for transforming the request from the gateway and making the proxy API request to the downstream services.

    You can learn more about these functions on the graphql-tools documentation site.

    Finally, we need to call the makeGatewaySchema. We can remove the previously hardcoded schema from our code and replace it with the stitched schema.

    // gateway.js
    
    // ...
    
    const app = express();
    
    app.use(
      '/graphql',
      graphqlHTTP(async (req) => {
        const schema = await makeGatewaySchema();
        return {
          schema,
          context: { authHeader: req.headers.authorization },
          graphiql: true,
        }
      }),
    );
    
    // ...

    When we restart our server and go back to localhost we will see that queries and mutations from all Fauna instances are available in our GraphQL playground.

    Let’s write a simple query that will fetch data from all Fauna instances simultaneously.

    Stitch third party GraphQL APIs

    We can stitch third-party GraphQL APIs into our gateway as well. For this demo, we are going to stitch the SpaceX open GraphQL API with our services.

    The process is the same as above. We create a new executor and add it to our sub-graph array.

    // ...
    
    async function makeGatewaySchema() {
    
      const reviewExecutor = await makeRemoteExecutor('https://graphql.fauna.com/graphql', 'fnAEQdRZVpACRMEEM1GKKYQxH2Qa4TzLKusTW2gN');
      const productExecutor = await makeRemoteExecutor('https://graphql.fauna.com/graphql', 'fnAEQdSdXiACRGmgJgAEgmF_ZfO7iobiXGVP2NzT');
      const inventoryExecutor = await makeRemoteExecutor('https://graphql.fauna.com/graphql', 'fnAEQdR0kYACRWKJJUUwWIYoZuD6cJDTvXI0_Y70');
    
      const spacexExecutor = await makeRemoteExecutor('https://api.spacex.land/graphql/')
    
      return stitchSchemas({
        subschemas: [
          {
            schema: await introspectSchema(reviewExecutor),
            executor: reviewExecutor,
          },
          {
            schema: await introspectSchema(productExecutor),
            executor: productExecutor
          },
          {
            schema: await introspectSchema(inventoryExecutor),
            executor: inventoryExecutor
          },
          {
            schema: await introspectSchema(spacexExecutor),
            executor: spacexExecutor
          }
        ],
            
        typeDefs: 'type Query { heartbeat: String! }',
        resolvers: {
          Query: {
            heartbeat: () => 'OK'
          }
        }
      });
    }
    
    // ...

    Deploying the gateway

    To make this a true serverless solution we should deploy our gateway to a serverless function. For this demo, I am going to deploy the gateway into an AWS lambda function. Netlify and Vercel are the two other alternatives to AWS Lambda.

    I am going to use the serverless framework to deploy the code to AWS. Let’s install the dependencies for it.

    npm i -g serverless # if you don't have the serverless framework installed already
    npm i serverless-http body-parser --save 
    

    Next, we need to make a configuration file called serverless.yaml

    # serverless.yaml
    
    service: my-graphql-gateway
    
    provider:
      name: aws
      runtime: nodejs14.x
      stage: dev
      region: us-east-1
    
    functions:
      app:
        handler: gateway.handler
        events:
          - http: ANY /
          - http: 'ANY {proxy+}'

    Inside the serverless.yaml we define information such as cloud provider, runtime, and the path to our lambda function. Feel free to take look at the official documentation for the serverless framework for more in-depth information.

    We will need to make some minor changes to our code before we can deploy it to AWS.

    npm i -g serverless # if you don't have the serverless framework installed already
    npm i serverless-http body-parser --save 

    Notice the highlighted code above. We added the body-parser library to parse JSON body. We have also added the serverless-http library. Wrapping the express app instance with the serverless function will take care of all the underlying lambda configuration.

    We can run the following command to deploy this to AWS Lambda.

    serverless deploy

    This will take a minute or two to deploy. Once the deployment is complete we will see the API URL in our terminal.

    Make sure you put /graphql at the end of the generated URL. (i.e. https://gy06ffhe00.execute-api.us-east-1.amazonaws.com/dev/graphql).

    There you have it. We have achieved complete serverless nirvana 😉. We are now running three Fauna instances independent of each other stitched together with a GraphQL gateway.

    Feel free to check out the code for this article here.

    Conclusion

    Schema stitching is one of the most popular solutions to break down monoliths and achieve separation of concerns between data sources. However, there are other solutions such as Apollo Federation which pretty much works the same way. If you would like to see an article like this with Apollo Federation please let us know in the comment section. That’s it for today, see you next time.


    Implementing a single GraphQL across multiple data sources originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

    ]]>
    https://css-tricks.com/implementing-a-single-graphql-across-multiple-data-sources/feed/ 0 350035
    Choice Words about the Upcoming Deprecation of JavaScript Dialogs https://css-tricks.com/choice-words-about-the-upcoming-deprecation-of-javascript-dialogs/ https://css-tricks.com/choice-words-about-the-upcoming-deprecation-of-javascript-dialogs/#comments Mon, 09 Aug 2021 21:23:44 +0000 https://css-tricks.com/?p=346368 It might be the very first thing a lot of people learn in JavaScript:

    alert("Hello, World");

    One day at CodePen, we woke up to a ton of customer support tickets about their Pens being broken, which ultimately boiled down to …


    Choice Words about the Upcoming Deprecation of JavaScript Dialogs originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

    ]]>
    It might be the very first thing a lot of people learn in JavaScript:

    alert("Hello, World");

    One day at CodePen, we woke up to a ton of customer support tickets about their Pens being broken, which ultimately boiled down to a version of Chrome that shipped where they ripped out alert() from functioning in cross-origin iframes. And all other native “JavaScript Dialogs” like confirm(), prompt() and I-don’t-know-what-else (onbeforeunload?, .htpasswd protected assets?).

    Cross-origin iframes are essentially the heart of how CodePen works. You write code, and we execute it for you in an iframe that doesn’t share the same domain as CodePen itself, as the very first line of security defense. We didn’t hear any heads up or anything, but I’m sure the plans were on display.

    I tweeted out of dismay. I get that there are potential security concerns here. JavaScript dialogs look the same whether they are triggered by an iframe or not, so apparently it’s confusing-at-best when they’re triggered by an iframe, particularly a cross-origin iframe where the parent page likely has little control. Well, outside of, ya know, a website like CodePen. Chrome cite performance concerns as well, as the nature of these JavaScript dialogs is that they block the main thread when open, which essentially halts everything.

    There are all sorts of security and UX-annoyance issues that can come from iframes though. That’s why sandboxing is a thing. I can do this:

    <iframe sandbox></iframe>

    And that sucker is locked down. If some form tried to submit something in there: nope, won’t work. What if it tries to trigger a download? Nope. Ask for device access? No way. It can’t even load any JavaScript at all. That is unless I let it:

    <iframe sandbox="allow-scripts allow-downloads ...etc"></iframe>

    So why not an attribute for JavaScript dialogs? Ironically, there already is one: allow-modals. I’m not entirely sure why that isn’t good enough, but as I understand it, nuking JavaScript dialogs in cross-origin iframes is just a stepping stone on the ultimate goal: removing them from the web platform entirely.

    Daaaaaang. Entirely? That’s the word. Imagine the number of programming tutorials that will just be outright broken.

    For now, even the cross-origin removal is delayed until January 2022, but as far as we know this is going to proceed, and then subsequent steps will happen to remove them entirely. This is spearheaded by Chrome, but the status reports that both Firefox and Safari are on board with the change. Plus, this is a specced change, so I guess we can waggle our fingers literally everywhere here, if you, like me, feel like this wasn’t particularly well-handled.

    What we’ve been told so far, the solution is to use postMessage if you really absolutely need to keep this functionality for cross-origin iframes. That sends the string the user uses in window.alert up to the parent page and triggers the alert from there. I’m not the biggest fan here, because:

    1. postMessage is not blocking like JavaScript dialogs are. This changes application flow.
    2. I have to inject code into users code for this. This is new technical debt and it can harm the expectations of expected user output (e.g. an extra <script> in their HTML has weird implications, like changing what :nth-child and friends select).
    3. I’m generally concerned about passing anything user-generated to a parent to execute. I’m sure there are theoretical ways to do it safely, but XSS attack vectors are always surprising in their ingenouity.

    Even lower-key suggestions, like window.alert = console.log, have essentially the same issues.

    Allow me to hand the mic over to others for their opinions.

    Couldn’t the alert be contained to the iframe instead of showing up in the parent window?

    Jaden Baptista, Twitter

    Yes, please! Doesn’t that solve a big part of this? While making the UX of these dialogs more useful? Put the dang dialogs inside the <iframe>.

    “Don’t break the web.” to “Don’t break 90% of the web.” and now “Don’t break the web whose content we agree with.”

    Matthew Phillips, Twitter

    I respect the desire to get rid of inelegant parts [of the HTML spec] that can be seen as historical mistakes and that cause implementation complexity, but I can’t shake the feeling that the existing use cases are treated with very little respect or curiosity.

    Dan Abramov, Twitter

    It’s weird to me this is part of the HTML spec, not the JavaScript spec. Right?!

    I always thought there was a sort of “prime directive” not to break the web? I’ve literally seen web-based games that used alert as a “pause”, leveraging the blocking nature as a feature. Like: <button onclick="alert('paused')">Pause</button>[.] Funny, but true.

    Ben Lesh, Twitter

    A metric was cited that only 0.006% of all page views contain a cross-origin iframe that uses these functions, yet:

    Seems like a misleading metric for something like confirm(). E.g. if account deletion flow is using confirm() and breaks because of a change to it, this doesn’t mean account deletion flow wasn’t important. It just means people don’t hit it on every session.

    Dan Abramov, Twitter

    That’s what’s extra concerning to me: alert() is one thing, but confirm() literally returns true or false, meaning it is a logical control structure in a program. Removing that breaks websites, no question. Chris Ferdinandi showed me this little obscure website that uses it:

    Speaking of Chris:

    The condescending “did you actually read it, it’s so clear” refrain is patronizing AF. It’s the equivalent of “just” or “simply” in developer documentation.

    I read it. I didn’t understand it. That’s why I asked someone whose literal job is communicating with developers about changes Chrome makes to the platform.

    This is not isolated to one developer at Chrome. The entire message thread where this change was surfaced is filled with folks begging Chrome not to move forward with this proposal because it will break all-the-things.

    Chris Ferdinandi, “Google vs. the web”

    And here’s Jeremy:

    […] breaking changes don’t happen often on the web. They are—and should be—rare. If that were to change, the web would suffer massively in terms of predictability.

    Secondly, the onus is not on web developers to keep track of older features in danger of being deprecated. That’s on the browser makers. I sincerely hope we’re not expected to consult a site called canistilluse.com.

    Jeremy Keith, “Foundations”

    I’ve painted a pretty bleak picture here. To be fair, there were some tweets with the Yes!! Finally!! vibe, but they didn’t feel like critical assessments to me as much as random Google cheerleading.

    Believe it or not, I generally am a fan of Google and think they do a good job of pushing the web forward. I also think it’s appropriate to waggle fingers when I see problems and request they do better. “Better” here means way more developer and user outreach to spell out the situation, way more conversation about the potential implications and transition ideas, and way more openness to bending the course ahead.


    Choice Words about the Upcoming Deprecation of JavaScript Dialogs originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

    ]]>
    https://css-tricks.com/choice-words-about-the-upcoming-deprecation-of-javascript-dialogs/feed/ 26 346368
    The State Of Web Workers In 2021 https://css-tricks.com/the-state-of-web-workers-in-2021/ https://css-tricks.com/the-state-of-web-workers-in-2021/#comments Wed, 04 Aug 2021 23:44:06 +0000 https://css-tricks.com/?p=345866 You gotta appreciate the tenacity of Surma. He’s been advocating for Web Workers as a path forward to better-feeling websites for a lot of years now. He’s at it again making sure we all understand the landscape:

    […] regardless


    The State Of Web Workers In 2021 originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

    ]]>
    You gotta appreciate the tenacity of Surma. He’s been advocating for Web Workers as a path forward to better-feeling websites for a lot of years now. He’s at it again making sure we all understand the landscape:

    […] regardless of where you look, multithreading is used everywhere. iOS empowers developers to easily parallelize code using Grand Central Dispatch, Android does this via their new, unified task scheduler WorkManager and game engines like Unity have job systems. The reason for any of these platforms to not only support multithreading, but making it as easy as possible is always the same: Ensure your app feels great.

    Surma, “The State Of Web Workers In 2021”

    So pretty much every platform has its own version of multi-threading, including the web. It’s just that on the web we have to sort of “fight” against the single-threaded nature of JavaScript by using Web Workers (which are “universally supported” if you’re wondering about that). The question is: use them how and for what? For the latter, Surma shows off an example of a game where “the entire app state and game logic is running in a worker.” For the former, the helper library comlink looks like a big reduction in toil.

    Personally, I wish popular tooling would just kinda… do it. I don’t know what that really looks like, but it kinda feels like developer outreach isn’t really moving the needle on this. What if popular tooling like Apollo — which is in charge of a lot of “app state” — were to magically handle all of that off the main thread. Does that make sense? Is it possible?

    To Shared LinkPermalink on CSS-Tricks


    The State Of Web Workers In 2021 originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

    ]]>
    https://css-tricks.com/the-state-of-web-workers-in-2021/feed/ 3 345866
    ES2021 Features https://css-tricks.com/es2021-features/ https://css-tricks.com/es2021-features/#comments Tue, 27 Jul 2021 23:18:12 +0000 https://css-tricks.com/?p=345400 Hemanth HM very succinctly shows off ES2021 features. Gosh it doesn’t feel like that long ago that all we could talk about is ES2015, and now that’s over a half-decade behind us.

    There are new things like “arbitrarily chuck underscores …


    ES2021 Features originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

    ]]>
    Hemanth HM very succinctly shows off ES2021 features. Gosh it doesn’t feel like that long ago that all we could talk about is ES2015, and now that’s over a half-decade behind us.

    There are new things like “arbitrarily chuck underscores in numbers.” I kinda dig that. Like 1_000_000_000 is the same as 1000000000 but more readable. To be honest, I barely even understand the other features.

    It’s interesting to observe the JavaScript truck moving forward with new features, while also being around people that write Go a lot and how starkly different, philosophically, it seems to me. Like there is only one kind of loop in Go, a for loop, and that’s it, while JavaScript has a bunch of them—four just for Arrays! Go doesn’t add syntactic sugar on purpose, while JavaScript feels addicted to it. Sugar is a helluva drug.

    To Shared LinkPermalink on CSS-Tricks


    ES2021 Features originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

    ]]>
    https://css-tricks.com/es2021-features/feed/ 2 345400
    ECMAScript proposal: JSON modules https://css-tricks.com/ecmascript-proposal-json-modules/ https://css-tricks.com/ecmascript-proposal-json-modules/#comments Wed, 21 Jul 2021 21:08:08 +0000 https://css-tricks.com/?p=344948 Dr. Axel Rauschmayer looks at JSON modules, which is already live in Chrome 91 (but nothing else). It looks just like an ES Modules-style import, only you asset the type at the end.

    import configData from './config-data.json' assert {type: 


    ECMAScript proposal: JSON modules originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

    ]]>
    Dr. Axel Rauschmayer looks at JSON modules, which is already live in Chrome 91 (but nothing else). It looks just like an ES Modules-style import, only you asset the type at the end.

    import configData from './config-data.json' assert {type: 'json'};

    How nice is that? Once this makes its way across browsers, we’ve gone on a journey from “you’ll almost definitely want to use an Ajax library” because of the cross-browser complexity and weirdness of XMLHttpRequest to the much nicer (but you still gotta write some code) fetch API, to a one-liner (if what you need is JSON data).

    Snagging some JSON data seems like it should be as easy as a one-liner to me, and now it is. I like how the URL can be dynamic now too.

    To Shared LinkPermalink on CSS-Tricks


    ECMAScript proposal: JSON modules originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

    ]]>
    https://css-tricks.com/ecmascript-proposal-json-modules/feed/ 6 344948
    Typewriter Animation That Handles Anything You Throw at It https://css-tricks.com/typewriter-animation-that-handles-anything-you-throw-at-it/ https://css-tricks.com/typewriter-animation-that-handles-anything-you-throw-at-it/#comments Tue, 20 Jul 2021 15:03:40 +0000 https://css-tricks.com/?p=344497 I watched Kevin Powell’s video where he was able to recreate a nice typewriter-like animation using CSS. It’s neat and you should definitely check it out because there are bonafide CSS tricks in there. I’m sure you’ve seen other CSS …


    Typewriter Animation That Handles Anything You Throw at It originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

    ]]>
    I watched Kevin Powell’s video where he was able to recreate a nice typewriter-like animation using CSS. It’s neat and you should definitely check it out because there are bonafide CSS tricks in there. I’m sure you’ve seen other CSS attempts at this, including this site’s very own snippet.

    Like Kevin, I decided to recreate the animation, but open it up to JavaScript. That way, we have a few extra tools that can make the typing feel a little more natural and even more dynamic. Many of the CSS solutions rely on magic numbers based on the length of the text, but with JavaScript, we can make something that’s capable of taking any text we throw at it.

    So, let’s do that. In this tutorial, I’m going to show that we can animate multiple words just by changing the actual text. No need to modify the code every time you add a new word because JavaScript will do that for you!

    Starting with the text

    Let’s start with text. We are using a monospace font to achieve the effect. Why? Because each character or letter occupies an equal amount of horizontal space in a monospaced font, which will come handy when we’ll use the concept of steps() while animating the text. Things are much more predictable when we already know the exact width of a character and all characters share the same width.

    We have three elements placed inside a container: one element for the actual text, one for hiding the text, and one for animating the cursor.

    <div class="container">
      <div class="text_hide"></div>
      <div class="text">Typing Animation</div>
      <div class="text_cursor"></div>
    </div>

    We could use ::before and ::after pseudo-elements here, but they aren’t great for JavaScript. Pseudo-elements are not part of the DOM, but instead are used as extra hooks for styling an element in CSS. It’d be better to work with real elements.

    We’re completely hiding the text behind the .text_hide element. That’s key. It’s an empty div that stretches the width of the text and blocks it out until the animation starts—that’s when we start to see the text move out from behind the element.

    A light orange rectangle is on top of the words Hidden Text with an orange arrow blow it indicating that it moves from left to right to reveal the text.

    In order to cover the entire text element, position the .text_hide element on top of the text element having the same height and width as that of the text element. Remember to set the background-color of the .text_hide element exactly same as that of the background surrounding the text so everything blends in together.

    .container {
      position: relative;
    }
    .text {
      font-family: 'Roboto Mono', monospace;
      font-size: 2rem;
    }
    .text_hide {
      position: absolute;
      top: 0;
      bottom: 0;
      left: 0;
      right: 0;
      background-color: white;
    }

    The cursor

    Next, let’s make that little cursor thing that blinks as the text is being typed. We’ll hold off on the blinking part for just a moment and focus just on the cursor itself.

    Let’s make another element with class .text_cursor. The properties are going to be similar to the .text_hide element with a minor difference: instead of setting a background-color, we will keep the background-color transparent (since its technically unnecessary, then add a border to the left edge of the new .text_cursor element.

    .text_cursor{
      position: absolute;
      top: 0;
      bottom: 0;
      left: 0;
      right: 0;
      background-color: transparent;
      border-left: 3px solid black;
    }

    Now we get something that looks like a cursor that’s ready to move as the text moves:

    The words hidden text behind a light orange rectangle that representing the element hiding the text. A cursor is on the left side of the hidden text.

    JavaScript animation

    Now comes the super fun part—let’s animate this stuff with JavaScript! We’ll start by wrapping everything inside a function called typing_animation().

    function typing_animation(){
      // code here
    }
    typing_animation();

    Next task is to store each and every character of text in a single array using the split() method. This divides the string into a substring that has only one character and an array containing all the substrings is returned.

    function typing_animation(){
      let text_element = document.querySelector(".text");
      let text_array = text_element.innerHTML.split("");
    }

    For example, if we take “Typing Animation” as a string, then the output is:

    (16) ["T", "y", "p", "i", "n", "g", " ", "A", "n", "i", "m", "a", "t", "i", "o", "n"]

    We can also determine the total number of characters in the string. In order to get just the words in the string, we replace split("") with split(" "). Note that there is a difference between the two. Here, " " acts as a separator. Whenever we encounter a single space, it will terminate the substring and store it as an array element. Then the process goes on for the entire string.

    function typing_animation(){
      let text_element = document.querySelector(".text");
      let text_array = text_element.innerHTML.split("");
      let all_words = text_element.innerHTML.split(" ");
    }

    For example, for a string ‘Typing Animation’, the output will be,

    (2") ["Typing", "Animation"]

    Now, let’s calculate the length of the entire string as well as the length of each and every individual word.

    function typing_animation() {
      let text_element = document.querySelector(".text");
      let text_array = text_element.innerHTML.split("");
      let all_words = text_element.innerHTML.split(" ");
      let text_len = text_array.length;
    
      const word_len = all_words.map((word) => {
        return word.length;
      });
    }

    To get the length of the entire string, we have to access the length of the array containing all the characters as individual elements. If we’re talking about the length of a single word, then we can use the map() method, which accesses one word at a time from the all_words array and then stores the length of the word into a new array called word_len. Both the arrays have the same number of elements, but one contains the actual word as an element, and the other has the length of the word as an element.

    Now we can animate! We’re using the Web Animation API because we’re going with pure JavaScript here—no CSS animations for us in this example.

    First, let’s animate the cursor. It needs to blink on and off infinitely. We need keyframes and animation properties, both of which will be stored in their own JavaScript object. Here are the keyframes:

    document.querySelector(".text_cursor").animate([
      {
        opacity: 0
      },
      {
        opacity: 0, offset: 0.7
      },
      {
        opacity: 1
      }
    ], cursor_timings);

    We have defined three keyframes as objects which are stored in an array. The term offset: 0.7 simply means that after 70% completion of the animation, the opacity will transition from 0 to 1.

    Now, we have to define the animation properties. For that, let’s create a JavaScript object that holds them together:

    let cursor_timings = {
      duration: 700, // milliseconds (0.7 seconds)
      iterations: Infinity, // number of times the animation will work
      easing: 'cubic-bezier(0,.26,.44,.93)' // timing-function
    }

    We can give the animation a name, just like this:

    let animation = document.querySelector(".text_cursor").animate([
      // keyframes
    ], //properties);

    Here’s a demo of what we have done so far:

    Great! Now, let’s animate the .text_hide element that, true to its name, hides the text. We define animation properties for this element:

    let timings = {
      easing: `steps(${Number(word_len[0])}, end)`,
      delay: 2000, // milliseconds
      duration: 2000, // milliseconds
      fill: 'forwards'
    }

    The easing property defines how the rate of animation will change over time. Here, we have used the steps() timing function. This animates the element in discrete segments rather than a smooth continuous animation—you know, for a more natural typing movement. For example, the duration of the animation is two seconds, so the steps() function animates the element in 9 steps (one step for each character in “Animation”) for two seconds, where each step has a duration of 2/9 = 0.22 seconds.

    The end argument makes the element stay in its initial state until the duration of first step is complete. This argument is optional and its default value is set to end. If you want an in-depth insight on steps(), then you can refer this awesome article by Joni Trythall.

    The fill property is the same as animation-fill-mode property in CSS. By setting its value to forwards, the element will stay at the same position as defined by the last keyframe after the animation gets completed.

    Next, we will define the keyframes.

    let reveal_animation_1 = document.querySelector(".text_hide").animate([
      { left: '0%' },
      { left: `${(100 / text_len) * (word_len[0])}%` }
    ], timings);

    Right now we are animating just one word. Later, we will see how to animate multiple words.

    The last keyframe is crucial. Let’s say we want to animate the word “Animation.” Its length is 9 (as there are nine characters) and we know that it’s getting stored as a variable thanks to our typing_animation() function. The declaration 100/text_len results to 100/9, or 11.11%, which is the width of each and every character in the word “Animation.” That means the width of each and every character is 11.11% the width of the entire word. If we multiply this value by the length of the first word (which in our case is 9), then we get 100%. Yes, we could have directly written 100% instead of doing all this stuff. But this logic will help us when we are animating multiple words.

    The result of all of this is that the .text_hide element animates from left: 0% to left: 100%. In other words, the width of this element decreases from 100% to 0% as it moves along.

    We have to add the same animation to the .text_cursor element as well because we want it to transition from left to right along with the .text_hide element.

    Yayy! We animated a single word. What if we want to animate multiple words? Let’s do that next.

    Animating multiple words

    Let’s say we have two words we want typed out, perhaps “Typing Animation.” We animate the first word by following the same procedure we did last time. This time, however, we are changing the easing function value in the animation properties.

    let timings = {
      easing: `steps(${Number(word_len[0] + 1)}, end)`,
      delay: 2000,
      duration: 2000,
      fill: 'forwards'
    }

    We have increased the number by one step. Why? Well, what about a single space after a word? We must take that into consideration. But, what if there is only one word in a sentence? For that, we will write an if condition where, if the number of words is equal to 1, then steps(${Number(word_len[0])}, end). If the number of words is not equal to 1, then steps(${Number(word_len[0] + 1)}, end).

    function typing_animation() {
      let text_element = document.querySelector(".text");
      let text_array = text_element.innerHTML.split("");
      let all_words = text_element.innerHTML.split(" ");
      let text_len = text_array.length;
      const word_len = all_words.map((word) => {
        return word.length;
      })
      let timings = {
        easing: `steps(${Number(word_len[0])}, end)`,
        delay: 2000,
        duration: 2000,
        fill: 'forwards'
      }
      let cursor_timings = {
        duration: 700,
        iterations: Infinity,
        easing: 'cubic-bezier(0,.26,.44,.93)'
      }
      document.querySelector(".text_cursor").animate([
        {
          opacity: 0
        },
        {
          opacity: 0, offset: 0.7
        },
        {
          opacity: 1
        }
      ], cursor_timings);
      if (all_words.length == 1) {
        timings.easing = `steps(${Number(word_len[0])}, end)`;
        let reveal_animation_1 = document.querySelector(".text_hide").animate([
          { left: '0%' },
          { left: `${(100 / text_len) * (word_len[0])}%` }
        ], timings);
        document.querySelector(".text_cursor").animate([
          { left: '0%' },
          { left: `${(100 / text_len) * (word_len[0])}%` }
        ], timings);
      } else {
        document.querySelector(".text_hide").animate([
          { left: '0%' },
          { left: `${(100 / text_len) * (word_len[0] + 1)}%` }
        ], timings);
        document.querySelector(".text_cursor").animate([
          { left: '0%' },
          { left: `${(100 / text_len) * (word_len[0] + 1)}%` }
      ], timings);
      }
    }
    typing_animation();

    For more than one word, we use a for loop to iterate and animate every word that follows the first word.

    for(let i = 1; i < all_words.length; i++){
      // code
    }

    Why did we take i = 1? Because by the time this for loop is executed, the first word has already been animated.

    Next, we will access the length of the respective word:

    for(let i = 1; i < all_words.length; i++){
      const single_word_len = word_len[i];
    }

    Let’s also define the animation properties for all words that come after the first one.

    // the following code goes inside the for loop
    let timings_2 = {
      easing: `steps(${Number(single_word_len + 1)}, end)`,
      delay: (2 * (i + 1) + (2 * i)) * (1000),
      duration: 2000,
      fill: 'forwards'
    }

    The most important thing here is the delay property. As you know, for the first word, we simply had the delay property set to two seconds; but now we have to increase the delay for the words following the first word in a dynamic way.

    The first word has a delay of two seconds. The duration of its animation is also two seconds which, together, makes four total seconds. But there should be some interval between animating the first and the second word to make the animation more realistic. What we can do is add a two-second delay between each word instead of one. That makes the second word’s overall delay 2 + 2 + 2, or six seconds. Similarly, the total delay to animate the third word is 10 seconds, and so on.

    The function for this pattern goes something like this:

    (2 * (i + 1) + (2 * i)) * (1000)

    …where we’re multiplying by 1000 to convert seconds to milliseconds.

    Length of the wordDuration taken by one character to animate
    62/6 = 0.33 seconds
    82/8 = 0.25 seconds
    92/9 = 0.22 seconds
    122/12 = 0.17 seconds
    * Total duration is 2 seconds

    The longer the word, the faster it is revealed. Why? Because the duration remains the same no matter how lengthy the word is. Play around with the duration and delay properties to get things just right.

    Remember when we changed the steps() value by taking into consideration a single space after a word? In the same way, the last word in the sentence doesn’t have a space after it, and thus, we should take that into consideration in another if statement.

    // the following code goes inside the for loop
    if (i == (all_words.length - 1)) {
      timings_2.easing = `steps(${Number(single_word_len)}, end)`;
      let reveal_animation_2 = document.querySelector(".text_hide").animate([
        { left: `${left_instance}%` },
        { left: `${left_instance + ((100 / text_len) * (word_len[i]))}%` }
      ], timings_2);
      document.querySelector(".text_cursor").animate([
        { left: `${left_instance}%` },
        { left: `${left_instance + ((100 / text_len) * (word_len[i]))}%` }
      ], timings_2);
    } else {
      document.querySelector(".text_hide").animate([
        { left: `${left_instance}%` },
        { left: `${left_instance + ((100 / text_len) * (word_len[i] + 1))}%` }
      ], timings_2);
      document.querySelector(".text_cursor").animate([
        { left: `${left_instance}%` },
        { left: `${left_instance + ((100 / text_len) * (word_len[i] + 1))}%` }
      ], timings_2);
    }

    What’s that left_instance variable? We haven’t discussed it, yet it is the most crucial part of what we’re doing. Let me explain it.

    0% is the initial value of the first word’s left property. But, the second word’s initial value should equal the first word’s final left property value.

    if (i == 1) {
      var left_instance = (100 / text_len) * (word_len[i - 1] + 1);
    }

    word_len[i - 1] + 1 refers to the length of the previous word (including a white space).

    We have two words, “Typing Animation.” That makes text_len equal 16 meaning that each character is 6.25% of the full width (100/text_len = 100/16) which is multiplied by the length of the first word, 7. All that math gives us 43.75 which is, in fact, the width of the first word. In other words, the width of the first word is 43.75% the width of the entire string. This means that the second word starts animating from where the first word left off.

    Last, let’s update the left_instance variable at the end of the for loop:

    left_instance = left_instance + ((100 / text_len) * (word_len[i] + 1));

    You can now enter as many words as you want in HTML and the animation just works!

    Bonus

    Have you noticed that the animation only runs once? What if we want to loop it infinitely? It’s possible:


    There we go: a more robust JavaScript version of a typewriting animation. It’s super cool that CSS also has an approach (or even multiple approaches) to do the same sort of thing. CSS might even be the better approach in a given situation. But when we need enhancements that push beyond what CSS can handle, sprinkling in some JavaScript does the trick quite nicely. In this case, we added support for all words, regardless of how many characters they contain, and the ability to animate multiple words. And, with a small extra delay between words, we get a super natural-looking animation.

    That’s it, hope you found this interesting! Signing off.


    Typewriter Animation That Handles Anything You Throw at It originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

    ]]>
    https://css-tricks.com/typewriter-animation-that-handles-anything-you-throw-at-it/feed/ 10 344497
    Beginner JavaScript Notes https://css-tricks.com/beginner-javascript-notes/ https://css-tricks.com/beginner-javascript-notes/#comments Thu, 08 Jul 2021 22:25:22 +0000 https://css-tricks.com/?p=344024 Wes has a heck of a set of “notes” for learning JavaScript. It’s organized like a curriculum, meaning if you teach JavaScript, you could do a lot worse. It’s actually more like 85 really fleshed-out blog posts organized into sections …


    Beginner JavaScript Notes originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

    ]]>
    Wes has a heck of a set of “notes” for learning JavaScript. It’s organized like a curriculum, meaning if you teach JavaScript, you could do a lot worse. It’s actually more like 85 really fleshed-out blog posts organized into sections and easily navigable. If you want to be walked through it via video, then buy the course. Smart.

    If you’re looking for other curriculum for JavaScript, your best bets are:

    Like any other learning experience in life, the best way to learn is multiple angles. If HTML & CSS are more your target, we have a bunch of suggestions there.

    To Shared LinkPermalink on CSS-Tricks


    Beginner JavaScript Notes originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

    ]]>
    https://css-tricks.com/beginner-javascript-notes/feed/ 3 344024