Archiwum kategorii: CSS

Intro to Firebase and React

Post pobrano z: Intro to Firebase and React

Let’s take a look at building something using Firebase and React. We’ll be building something called Fun Food Friends, a web application for planning your next potluck, which hopefully feels like something rather „real world”, in that you can imagine using these technologies in your own production projects. The big idea in this app is that you and your friends will be able to log in and be able to see and post information about what you’re planning to bring to the potlock.

When we’re finished, it will look like this:

Our example app: Fun Food Friends

This article assumes you already have some basic knowledge of how React works and maybe built a few small apps with React. If you haven’t, I would recommend checking out a series like Wes Bos’ React for Beginners first before continuing on.

What is Firebase?

Google’s Firebase is a cloud-based database hosting service that will set up a database for you and host it, as well as offer you the tools to interact with it. You can use it to store and retrieve data in real time. That’s not all Firebase does, it can do more things like handle user authentication and store files, but we’ll be mainly focusing on data storage.

The data storage ability of Firebase make it a perfect fit for React. A persistent, real-time backend for your application to plug in to!

How does Firebase store data?

Firebase stores data as a giant object with key-value pairs. Unlike JSON or JavaScript objects, there are no arrays in Firebase.

A Firebase database might look something like this:

{
      "groceries": {
        "-KjQTqG3R2dPT8s2jylW": "tomato",
        "-KjQTrds1feHT3GH_29o": "pasta",
        "-KjQTsmfBR8zN1SwPPT8": "milk",
        "-KjQTtnzt_jJZPoCHWUM": "sugar"
      },
      "users": {
        "name": {
          "-KjQTyIfKFEVMYJRZ09X": "simon",
          "-KjQU-Xuy5s7I-On9rYP": "ryan",
          "-KjQU0MYVeKRsLuIQCYX": "sylvia"
        }
      }
}

For more information on the nuances of structuring data in Firebase, you can read the amazing Firebase documentation.

Ready to start? Let’s dig in!

Getting Started: Setting up Our App

We’ll start by using the incredibly handy `create-react-app` package in order to quickly set up a new React project without having to worry about any build configuration. Open up your command line, and type the following:

npm install -g create-react-app
    
create-react-app fun-food-friends
cd fun-food-friends
yarn add firebase --dev
yarn start

This will boot up your app in the browser, and start a watch task in your terminal so that we can begin hacking away at the project. We’re also installing the `firebase` package here as we’ll need it for the next step.

Creating our Firebase Database

Now that our app is set up, we’ll need to create an account and database on Firebase so that we can link up our application to it.

Head on over to Firebase’s website, and click Get Started.

This will take you to a page where you’ll be asked to authenticate with your Google account. Select the account that you’d like this project to be affiliated with, and press OK.

This should take you to the Firebase console, which looks something like this:

Now let’s create our project’s database. Click Add Project. Let’s call it „fun-food-friends” and press OK.

This will take you to your app’s dashboard, which looks like this:

Since we’ll be building a web app, select Add Firebase to your web app. This will trigger a popup with some code that looks like this:

<script src="https://www.gstatic.com/firebasejs/3.9.0/firebase.js"></script>
<script>
  // Initialize Firebase
  var config = {
     apiKey: "AIzaSyDblTESEB1SbAVkpy2q39DI2OHphL2-Jxw",
    authDomain: "fun-food-friends-eeec7.firebaseapp.com",
    databaseURL: "https://fun-food-friends-eeec7.firebaseio.com",
    projectId: "fun-food-friends-eeec7",
    storageBucket: "fun-food-friends-eeec7.appspot.com",
    messagingSenderId: "144750278413"
  };
  firebase.initializeApp(config);
</script>

Since we’ll be importing Firebase into our project using ES6 modules, we won’t need those script tags. That config object is important though: it’s how we authenticate our React application with our Firebase database.

Hooking up our App to Firebase

Copy that whole config object, and head back over to your React project. Find your `src` folder, and create a file called `firebase.js`. Inside of it, let’s import firebase, our config, and initialize our app:

// src/firebase.js
import firebase from 'firebase'
const config = {
    apiKey: "AIzaSyDblTESEB1SbAVkpy2q39DI2OHphL2-Jxw",
    authDomain: "fun-food-friends-eeec7.firebaseapp.com",
    databaseURL: "https://fun-food-friends-eeec7.firebaseio.com",
    projectId: "fun-food-friends-eeec7",
    storageBucket: "fun-food-friends-eeec7.appspot.com",
    messagingSenderId: "144750278413"
};
firebase.initializeApp(config);
export default firebase;

One last thing we’ll need to do before we can dive into roughing out our App. We need to temporarily disable authentication requirements on our app so that we can add and remove items without needing to have any kind of user authentication flow.

From the Firebase Dashboard, on the left-hand side of the screen, you’ll notice that there is a Database tab. Click on it. Then, on the right-hand side, under the subheading Realtime Database, you’ll see a Rules tab. This will cause an object to appear that looks something like this:

{
    "rules": {
      ".read": "auth != null",
      ".write": "auth != null"
    }
}

We need to set .read and .write to both be equal to true, otherwise later, when we try to add data to our database from our application, Firebase won’t let us. When you’re finished, it should look something like this:

Make sure to click the Publish button.

And that’s all there is to hooking up our database! Anytime we need a component of our application to connect with our Firebase database, we simply need to import our firebase module and we’ll have direct reference to it.

Building out our App’s Rough Skeleton

Let’s build out a rough HTML skeleton for our application. We’ll build a simple form with two inputs:

  1. A field where the user can submit their name
  2. A field where the user can enter what food they’re bringing to the potluck.

Since our app is quite simple, we’ll keep everything inside of one main component, `App.js`. Open up `src/App.js`, and remove the `App` component, replacing it with this basic skeleton:

import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';

class App extends Component {
  render() {
    return (
      <div className='app'>
        <header>
            <div className='wrapper'>
              <h1>Fun Food Friends</h1>
              
            </div>
        </header>
        <div className='container'>
          <section className='add-item'>
              <form>
                <input type="text" name="username" placeholder="What's your name?" />
                <input type="text" name="currentItem" placeholder="What are you bringing?" />
                <button>Add Item</button>
              </form>
          </section>
          <section className='display-item'>
            <div className='wrapper'>
              <ul>
              </ul>
            </div>
          </section>
        </div>
      </div>
    );
  }
}
export default App;

Get the CSS

I’ve prepared a little bit of CSS for you to paste into the `App.css` file, just so that our app doesn’t look totally bland. If you want to grab it, just go here and copy and paste the raw contents you find there into your `src/App.css` file!

We’ll also need to embed a link to Google Fonts and Font Awesome, so go ahead and open up `public/index.html` and add the following lines below the favicon:

<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">

<!-- add the lines below -->

<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Open+Sans">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css">  

At this point, your app should look like this:

Connecting our Form to Component State

Before we can start adding data into our Firebase database, we need to connect our inputs to our component’s state, so that React can keep track of them.

First, let’s carve out some space in our component’s state – a space to keep track of the user using our app (username) and the item they intend to bring (currentItem). We’ll do this by creating a constructor() hook for our app and setting a default value for our input’s state there:

class App extends Component {
  constructor() {
    super();
    this.state = {
      currentItem: '',
      username: ''
    }
  }
  // ....

We’ll add a onChange event handlers to our inputs, as well as providing them with a value derived from our state (this is called a „controlled input”), like this:

<section className="add-item">
  <form>
    <input type="text" name="username" placeholder="What's your name?" onChange={this.handleChange} value={this.state.username} />
    <input type="text" name="currentItem" placeholder="What are you bringing?" onChange={this.handleChange} value={this.state.currentItem} />
    <button>Add Item</button>
  </form>
</section>

And finally, we’ll create a catch-all handleChange method that receives the event from our inputs, and updates that input’s corresponding piece of state:

handleChange(e) {
  this.setState({
    [e.target.name]: e.target.value
  });
}

If you aren’t familiar with using brackets to dynamically determine key name in an object literal, check out the MDN docs on computed properties.

Since we’re using ES6 classes and need access to this in our handleChange method, we’ll also need to bind it back in our constructor() component like this:

constructor() {
  super();
  this.state = {
    username: '',
    currentItem: ''
  }
  this.handleChange = this.handleChange.bind(this);
}

If you now use the React DevTools to inspect your App component’s state, you’ll see that both of your inputs are now successfully hooked up and being tracked in your component’s state:

Adding a new Potluck Item to your Database

Now that we’re tracking our inputs, let’s make it so that we can add a new item to our database so that Firebase can keep track of it.

First we’ll need to connect to Firebase in order to do this, we’ll start by importing our firebase module that we created earlier. We’ll also delete the logo.svg import, since it’s just an unneeded part of the create-react-app boiler plate and will cause warnings if we don’t:

import React, { Component } from 'react';
import logo from './logo.svg'; // <--- remove this line
import './App.css';
import firebase from './firebase.js'; // <--- add this line

Once that’s done, we’ll need to make our 'Add Item’ button let Firebase know what we’d like to add to our database and where we’d like to put it.

First we’ll attach a submit event listener for our form, and have it call a handleSubmit method we’ll write in a minute:

<form onSubmit={this.handleSubmit}>
  <input type="text" name="username" placeholder="What's your name?" onChange={this.handleChange} value={this.state.username} />
  <input type="text" name="currentItem" placeholder="What are you bringing ?" onChange={this.handleChange} value={this.state.currentItem} />
  <button>Add Item</button>
</form>

Don’t forget to bind it in the constructor!

constructor() {
  super();
  this.state = {
    currentItem: '',
    username: ''
  }
  this.handleChange = this.handleChange.bind(this);
  this.handleSubmit = this.handleSubmit.bind(this); // <-- add this line
}

And now add the handleSubmit method to your component:

handleSubmit(e) {
  e.preventDefault();
  const itemsRef = firebase.database().ref('items');
  const item = {
    title: this.state.currentItem,
    user: this.state.username
  }
  itemsRef.push(item);
  this.setState({
    currentItem: '',
    username: ''
  });
}

Let’s break down what’s going here:

  • e.preventDefault() – we need to prevent the default behavior of the form, which if we don’t will cause the page to refresh when you hit the submit button.
  • const itemsRef = firebase.database().ref('items'); – we need to carve out a space in our Firebase database where we’d like to store all of the items that people are bringing to the potluck. We do this by calling the ref method and passing in the destination we’d like them to be stored (items).
  • const item = { /* .. */ } here we grab the item the user typed in (as well as their username) from the state, and package it into an object so we ship it off to our Firebase database.
  • itemsRef.push(item) similar to the Array.push method, this sends a copy of our object so that it can be stored in Firebase.
  • Finally this.setState({ currentItem: '', username: '' }); is just so that we can clear out the inputs so that an additional item can be added.

Now try adding a new item, and hitting submit! If you don’t have any errors in your console, you should be able to head on over to the Firebase dashboard, where you’ll see something like this inside your Database tab:

If you click the little + next to items you’ll be able to look inside, like this:

That strange looking -Kk8lHSMqC5oP6Qai0Vx key you see is a programmatically generated key created by Firebase when we called the push method, but inside you’ll find whatever item you added to the Potluck.

You’ll notice that all of our records are stored as objects with properties that have the generated names you see above – just another quick reminder that there are no arrays in Firebase!

Try adding more items and see what happens.

Way to go! We’re almost there, but we still have one more step: getting our potluck items to appear on the page.

Retrieving our Potluck Items from the database

Just like in a traditional React app, we need to find some way to keep track of all of the potluck dishes so that we can display what people are planning to bring on to the page.

Without a database, this poses an issue, since every time we refresh the page any new dishes that were added to the potluck would get lost. But with Firebase, this is a snap to fix!

First, let’s create a variable called items inside of default state. This will eventually hold all of the potluck items that are currently being tracked inside of our Firebase database.

constructor() {
  super();
  this.state = {
    currentItem: '',
    username: '',
    items: []
  }
  this.handleChange = this.handleChange.bind(this);
  this.handleSubmit = this.handleSubmit.bind(this);
}

Next, we need to actually grab those items from our Firebase database so that we can store them into our state.

The Firebase API offers us an incredibly easy way to not only grab this kind information from our database, but also to update us when new values get added to our database. It accomplishes this using the value custom event listener.

It looks like this:

itemsRef.on('value', (snapshot) => {
  console.log(snapshot.val());
});

The callback here, which we’ve called snapshot, provides you with a bird’s eye overview of the items ref inside of your database. From here, you can easily grab a list of all of the properties inside of that items ref, using the .val() method which you can call on the snapshot.

This value automatically fires on two occassions:

  1. Any time a new item is added or removed from our items reference inside of our database
  2. The first time the event listener is attached

This makes it especially useful for initially grabbing a list of all of the items inside of our database, and then subsequently tracking when new items get added and removed.

We’ll attach this event listener inside of our componentDidMount, so that we start tracking our Potluck items as soon as our component loads on to the page:

componentDidMount() {
  const itemsRef = firebase.database().ref('items');
  itemsRef.on('value', (snapshot) => {
    let items = snapshot.val();
    let newState = [];
    for (let item in items) {
      newState.push({
        id: item,
        title: items[item].title,
        user: items[item].user
      });
    }
    this.setState({
      items: newState
    });
  });
}

Here, we instantiate a new array and populate it with the results that come back from our value listener. We for…in over each key, and push the result into an object inside our newState array. Finally, once all the keys are iterated over (and therefore all items are grabbed from our database), we update the state with this list of items from our database.

Inspect your App using the React Dev Tools – you’ll notice that you now have an items property inside of your state with all of the items people have submitted for your potluck!

Displaying Potluck Items on the Page

Now let’s get these potluck items to actually display on the page. This is relatively easy, now that we have a list of all of our items being grabbed from Firebase and stored inside of our state. We just map over it and print the results on to the page, like so:

<section className='display-item'>
  <div className="wrapper">
    <ul>
      {this.state.items.map((item) => {
        return (
          <li key={item.id}>
            <h3>{item.title}</h3>
            <p>brought by: {item.user}</p>
          </li>
        )
      })}
    </ul>
  </div>
</section>

Try adding a new item through your form. You’ll notice that it automatically causes a new list item to appear on the page!

It’s not magic, Firebase’s value event is firing when you push the new item into your database, and sending back a new snapshot with a list of all of the items currently in your database, which ultimate updates your component through a setState which triggers a re-render and displays the new item on the page.

But we digress. There’s still one more step! We need to make it so that we can remove an item from the page.

Removing Items from the Page

We’ll need to create a new method on our component for this: removeItem. This method will need to be passed that unique key which serves as the identifier for each one of the items inside of our Firebase database.

It’s very simple, and looks like this:

removeItem(itemId) {
  const itemRef = firebase.database().ref(`/items/${itemId}`);
  itemRef.remove();
}

Here, instead of grabbing all of the items as we did before when adding a new item, we instead look up a specific item by its key (that strange –Kk8lHSMqC5oP6Qai0Vx key from before). We can then call firebase.database()’s remove method, which strips it from the page.

Finally, we’ll need to add a button to our UI with an onClick that calls our removeItem method and passes it the item’s key, like follows:

{this.state.items.map((item) => {
    return (
      <li key={item.id}>
        <h3>{item.title}</h3>
        <p>brought by: {item.user}</p>
        <button onClick={() => this.removeItem(item.id)}>Remove Item</button>
      </li>
    )
  })
}

And that’s all there is to it! Just like our addItem method, our UI and component state automatically update when an item is removed from the database.

Here’s what our completed `App.js` should look like:

import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
import firebase from './firebase.js';

class App extends Component {
  constructor() {
    super();
    this.state = {
      currentItem: '',
      username: '',
      items: []
    }
    this.handleChange = this.handleChange.bind(this);
    this.handleSubmit = this.handleSubmit.bind(this);
  }
  handleChange(e) {
    this.setState({
      [e.target.name]: e.target.value
    });
  }
  handleSubmit(e) {
    e.preventDefault();
    const itemsRef = firebase.database().ref('items');
    const item = {
      title: this.state.currentItem,
      user: this.state.username
    }
    itemsRef.push(item);
    this.setState({
      currentItem: '',
      username: ''
    });
  }
  componentDidMount() {
    const itemsRef = firebase.database().ref('items');
    itemsRef.on('value', (snapshot) => {
      let items = snapshot.val();
      let newState = [];
      for (let item in items) {
        newState.push({
          id: item,
          title: items[item].title,
          user: items[item].user
        });
      }
      this.setState({
        items: newState
      });
    });
  }
  removeItem(itemId) {
    const itemRef = firebase.database().ref(`/items/${itemId}`);
    itemRef.remove();
  }
  render() {
    return (
      <div className='app'>
        <header>
            <div className="wrapper">
              <h1>Fun Food Friends</h1>
                             
            </div>
        </header>
        <div className='container'>
          <section className='add-item'>
                <form onSubmit={this.handleSubmit}>
                  <input type="text" name="username" placeholder="What's your name?" onChange={this.handleChange} value={this.state.username} />
                  <input type="text" name="currentItem" placeholder="What are you bringing?" onChange={this.handleChange} value={this.state.currentItem} />
                  <button>Add Item</button>
                </form>
          </section>
          <section className='display-item'>
              <div className="wrapper">
                <ul>
                  {this.state.items.map((item) => {
                    return (
                      <li key={item.id}>
                        <h3>{item.title}</h3>
                        <p>brought by: {item.user}
                          <button onClick={() => this.removeItem(item.id)}>Remove Item</button>
                        </p>
                      </li>
                    )
                  })}
                </ul>
              </div>
          </section>
        </div>
      </div>
    );
  }
}
export default App;

Conclusion

Now you can truly see how Firebase and React play beautifully together. Firebase’s ability to persist data on the fly, coupled with React’s component lifecycle, makes for an incredibly simple and powerful way to quickly build up simple applications.

This article just scratches the surface of what the Firebase API can provide us. For example, with just a few more steps (and perhaps we will go over this in a future article), it would be incredibly easy to expand this application so that users could log in and out, be able to have a display photo next to the item that they are bringing, and only be able to remove their own items.

Happy Firebasing!


Intro to Firebase and React is a post from CSS-Tricks

Componentizing a Framework

Post pobrano z: Componentizing a Framework

I’m sure most of you understand how you work with a framework like Bootstrap, Foundation, or Materialize. You use their CSS and JavaScript. You also use their chunks of HTML, piecing together and applying classes as needed to do what you need to do.

You’re on your own piecing the HTML together. That’s good, because it’s flexible. People use frameworks like this in all kinds of CMS’s and backend systems. But what if you want to apply some structure to this, making actual components out of the components given to you in the framework?

That’s exactly what Morgan Feeney did in Component-Led Design Patterns with Nunjucks & Grunt last year.

For example, Bootstrap gives you some HTML for alert messages, that are like this:”

<div class="alert alert-success" role="alert">...</div>
<div class="alert alert-info" role="alert">...</div>
<div class="alert alert-warning" role="alert">...</div>
<div class="alert alert-danger" role="alert">...</div>

We could abstract that into a reusable component by:

  1. Pass in the type of alert (second half of the second class)
  2. Pass in the content inside the alert

I’m sure you could imagine doing that in the backend or templating language of your choice. A single PHP file in which you set variables representing those things before you include it. A Rails partial in which you pass locals to it. A literal React component in JSX where you pass the stuff as props. This kind of thing makes these patterns a lot easier to reuse.

Morgan did this with Nunjucks:

{% macro alert(class="success", text="<strong>Well done!</strong> You successfully read this important alert message.") %}
  <div class="alert alert-{{ class }}" role="alert">
    {{ text | safe }}
  </div>
{% endmacro %}

I think this is super compelling and the kind of thing we’ll be doing more and more as design systems are becoming more of a standard practice.

I also think Nunjucks is pretty darn cool.

I ported Morgan’s idea (which is already a repo) over to a CodePen Project if you’d like to have a play there.


Componentizing a Framework is a post from CSS-Tricks

HelloSign: The Industry’s Fastest eSignature API Integration

Post pobrano z: HelloSign: The Industry’s Fastest eSignature API Integration

My favorite kind of software products are the ones that very clearly make life simpler. Being able to legally sign a document by clicking a button in an email and squiggling my mouse to make my signature is definitely one of those things.

You can provide that to your users with HelloSign! You can set up your documents there (it supports all the formats you’d need, like PDF, Microsoft Word, Powerpoint, etc) and start collecting the signatures you need very easily. Set up templates of your commonly used documents. Make sure your branding is present during the signing process. Get notifications when documents are reviewed and signed.

There are a bunch more killer features you should be aware of. For example, like I mentioned, you can sign documents without ever leaving your email with their Chrome browser extension for Gmail. Same with Google Docs and Salesforce!

Perhaps most importantly, you can use HelloSign right from your own interface through their API. That’s great for all us developers interested in building seamless useful experiences right in our own products. You can embed documents directly on your website with just a few lines of code!

Direct Link to ArticlePermalink


HelloSign: The Industry’s Fastest eSignature API Integration is a post from CSS-Tricks

Reactive UI’s with VanillaJS – Part 1: Pure Functional Style

Post pobrano z: Reactive UI’s with VanillaJS – Part 1: Pure Functional Style

Last month Chris Coyier wrote a post investigating the question, „When Does a Project Need React?” In other words, when do the benefits of using React (acting as a stand-in for data-driven web frameworks in general), rather than server-side templates and jQuery, outweigh the added complexity of setting up the requisite tooling, build process, dependencies, etc.? A week later, Sacha Greif wrote a counterpoint post arguing why you should always use such a framework for every type of web project. His points included future-proofing, simplified workflow from project to project (a single architecture; no need to keep up with multiple types of project structures), and improved user experience because of client-side re-rendering, even when the content doesn’t change very often.

In this pair of posts, I delve into a middle ground: writing reactive-style UI’s in plain old JavaScript – no frameworks, no preprocessors.

Article Series:

  1. Pure Functional Style (You are here!)
  2. Class Based Components (Coming soon!)

There are two very different ways to write React components.

  1. You can write them as classes. Stateful objects with lifecycle hooks and internal data.
  2. Or, you can write them as functions. Just a piece of HTML that gets constructed and updated based on parameters that are passed in.

The former is often more useful for large, complex applications with lots of moving parts, while the latter is a more elegant way to display information if you don’t have a lot of dynamic state. If you’ve ever used a templating engine like Handlebars or Swig, their syntax looks pretty similar to function-style React code.

In this pair of posts, our target use case is websites that might otherwise be static, but would benefit from JavaScript-based rendering were it not for the overhead of setting up a framework like React. Blogs, forums, etc. Therefore, this first post will focus on the functional approach to writing a component-based UI, because it’ll be more practical for that type of scenario. The second post will be more of an experiment; I’ll really push the limit on how far we can take things without a framework, trying to replicate React’s class-based component pattern as closely as possible with only Vanilla JavaScript, probably at the expense of some practicality.

About functional programming

Functional programming has soared in popularity over the last couple years, driven primarily by Clojure, Python, and React. A full explanation of functional programming is outside the scope of this post, but the part that’s relevant to us right now is the concept of values that are functions of other values.

Let’s say your code needs to represent the concept of a rectangle. A rectangle has width and height, but it also has area, perimeter, and other attributes. At first, one might think to represent a rectangle with the following object:

var rectangle = {
  width: 2,
  height: 3,
  area: 6,
  perimeter: 10
};

But, it would quickly become apparent that there’s a problem. What happens if the width gets changed? Now we have to also change the area and perimeter, or they’d be wrong. It’s possible to have conflicting values, where you can’t just change one without the possibility of having to update something else. This is called having multiple sources of truth.

In the rectangle example, the functional programming-style solution is to make area and perimeter into functions of a rectangle:

var rectangle = {
  width: 2,
  height: 3
};

function area(rect) {
  return rect.width * rect.height;
}

function perimeter(rect) {
  return rect.width * 2 + rect.height * 2;
}

area(rectangle); // = 6
perimeter(rectangle); // = 10

This way, if width or height changes, we don’t have to manually modify anything else to reflect that fact. The area and perimeter just are correct. This is called having a single source of truth.

This idea is powerful when you substitute the rectangle with whatever data your application may have, and the area and perimeter with HTML. If you can make your HTML a function of your data, then you only have to worry about modifying data – not DOM – and the way it gets rendered on the page will be implicit.

UI components as functions

We want to make our HTML a function of our data. Let’s use the example of a blog post:

var blogPost = {
  author: 'Brandon Smith',
  title: 'A CSS Trick',
  body: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.'
};

function PostPage(postData) {
  return  '<div class="page">' +
            '<div class="header">' + 
              'Home' +
              'About' +
              'Contact' +
            '</div>' + 
            '<div class="post">' + 
              '<h1>' + postData.title + '</h1>' + 
              '<h3>By ' + postData.author + '</h3>' +
              '<p>' + postData.body + '</p>' +
            '</div>' +
          '</div>';
}

document.querySelector('body').innerHTML = PostPage(blogPost);

Okay. We’ve made a function of a post object, which returns an HTML string that renders our blog post. It’s not really „componentized” though. It’s all one big thing. What if we also wanted to render all our blog posts in a sequence on the home page? What if we wanted to reuse that header across different pages? Luckily, it’s really easy to build functions out of other functions. This is called composing functions:

var blogPost = {
  author: 'Brandon Smith',
  title: 'A CSS Trick',
  body: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.'
};

function Header() {
  return '<div class="header">' + 
            'Home' +
            'About' +
            'Contact' +
          '</div>';
}

function BlogPost(postData) {
  return '<div class="post">' + 
            '<h1>' + postData.title + '</h1>' + 
            '<h3>By ' + postData.author + '</h3>' +
            '<p>' + postData.body + '</p>' +
          '</div>';
}

function PostPage(postData) {
  return  '<div class="page">' +
            Header() +
            BlogPost(postData) +
          '</div>';
}

function HomePage() {
  return '<div class="page">' +
            Header() +
            '<h1>Welcome to my blog!</h1>' +
            '<p>It\'s about lorem ipsum dolor sit amet, consectetur ad...</p>' +
          '</div>';
}

document.querySelector('body').innerHTML = PostPage(blogPost);

That’s so much nicer. We didn’t have to duplicate the header for the home page; we have a single source of truth for that HTML code. If we wanted to display a post in a different context, we could do so easily.

Prettier syntax with template literals

Okay, but all those plus signs are horrible. They’re a pain to type, and they make it harder to read what’s going on. There has to be a better way, right? Well, the folks at W3C are way ahead of you. They created template literals – which, while still relatively new, have pretty good browser support at this point. Simply wrap your string in backticks instead of quotes, and it will get a couple of extra superpowers.

The first superpower is the ability to span multiple lines. So our BlogPost component up above can become:

// ...

function BlogPost(postData) {
  return `<div class="post">
            <h1>` + postData.title + `</h1>
            <h3>By ` + postData.author + `</h3>
            <p>` + postData.body + `</p>
          </div>`;
}

// ...

That’s nice. But the other power is even nicer: variable substitution. Variables (or any JavaScript expression, including function calls!) can be inserted directly into the string if they’re wrapped in ${ }:

// ...

function BlogPost(postData) {
  return `<div class="post">
            <h1>${postData.title}</h1>
            <h3>By ${postData.author}</h3>
            <p>${postData.body}</p>
          </div>`;
}

// ...

Much better. It almost looks like JSX now. Let’s see our full example again, with template literals:

var blogPost = {
  author: 'Brandon Smith',
  title: 'A CSS Trick',
  body: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.'
};

function Header() {
  return `<div class="header">
            Home
            About
            Contact
          </div>`;
}

function BlogPost(postData) {
  return `<div class="post">
            <h1>${postData.title}</h1>
            <h3>By ${postData.author}</h3>
            <p>${postData.body}</p>
          </div>`;
}

function PostPage(postData) {
  return  `<div class="page">
            ${Header()}
            ${BlogPost(postData)}
          </div>`;
}

function HomePage() {
  return `<div class="page">
            ${Header()}
            <h1>Welcome to my blog!</h1>
            <p>It's about lorem ipsum dolor sit amet, consectetur ad...</p>
          </div>`;
}

document.querySelector('body').innerHTML = PostPage(blogPost);

More than just filling in blanks

So we can fill in variables, and even other components through functions, but sometimes more complex rendering logic is necessary. Sometimes we need to loop over data, or respond to a condition. Let’s go over some JavaScript language features that make it easier to do more complex rendering in a functional style.

The ternary operator

We’ll start with the simplest logic: if-else. Of course, since our UI components are just functions, we could use an actual if-else if we wanted to. Let’s see what that would look like:

var blogPost = {
  isSponsored: true,
  author: 'Brandon Smith',
  title: 'A CSS Trick',
  body: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.'
};

function BlogPost(postData) {
  var badgeElement;
  if(postData.isSponsored) {
    badgeElement = `<img src="badge.png">`;
  } else {
    badgeElement = '';
  }

  return `<div class="post">
            <h1>${postData.title} ${badgeElement}</h1>
            <h3>By ${postData.author}</h3>
            <p>${postData.body}</p>
          </div>`;
}

That’s… not ideal. It adds a whole lot of lines for something that isn’t that complicated, and it separates part of our rendering code from its place within the rest of the HTML. This is because a classical if-else statement decides which lines of code to run, rather than which value to evaluate to. This is an important distinction to understand. You can only stick an expression into a template literal, not a series of statements.

The ternary operator is like an if-else, but for an expression instead of a set of statements:

var wantsToGo = true;
var response = wantsToGo ? 'Yes' : 'No'; // response = 'Yes'

wantsToGo = false;
response = wantsToGo ? 'Yes' : 'No'; // response = 'No'

It takes the form [conditional] ? [valueIfTrue] : [valueIfFalse]. So, the blog post example above becomes:

var blogPost = {
  isSponsored: true,
  author: 'Brandon Smith',
  title: 'A CSS Trick',
  body: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.'
};

function BlogPost(postData) {
  return `<div class="post">
            <h1>
              ${postData.title} ${postData.isSponsored ? '<img src="badge.png">' : ''}
            </h1>
            <h3>By ${postData.author}</h3>
            <p>${postData.body}</p>
          </div>`;
}

Much better.

Array.map()

On to loops. Anytime we have an array of data that we want to render, we’re going to need to loop over those values to generate the corresponding HTML. But if we used a for-loop, we’d run into the exact same issue we had with the if-else statement above. A for loop doesn’t evaluate to a value, it executes a series of statements in a certain way. Luckily, ES6 added some very helpful methods to the Array type which serve this specific need.

Array.map() is an Array method that takes a single argument, which is a callback function. It loops over the array it’s called on (similar to Array.forEach()), and calls the supplied callback once for each item, passing the array element to it as an argument. The thing that makes it different from Array.forEach() is that the callback is supposed to return a value – presumably one that’s based on the corresponding item in the array – and the full expression returns the new array of all the items returned from the callback. For example:

var myArray = [ 'zero', 'one', 'two', 'three' ];

// evaluates to [ 'ZERO', 'ONE', 'TWO', 'THREE' ]
var capitalizedArray = myArray.map(function(item) {
  return item.toUpperCase();
});

You might be able to guess why this is so useful for what we’re doing. Earlier we established the concept of a value being a function of another value. Array.map() allows us to get an entire array, for which each item is a function of the corresponding item in another array. Let’s say we have an array of blog posts that we want to display:

function BlogPost(postData) {
  return `<div class="post">
            <h1>${postData.title}</h1>
            <h3>By ${postData.author}</h3>
            <p>${postData.body}</p>
          </div>`;
}

function BlogPostList(posts) {
  return `<div class="blog-post-list">
            ${posts.map(BlogPost).join('')}
          </div>`
}

var allPosts = [
  {
    author: 'Brandon Smith',
    title: 'A CSS Trick',
    body: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.'
  },
  {
    author: 'Chris Coyier',
    title: 'Another CSS Trick',
    body: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.'
  },
  {
    author: 'Bob Saget',
    title: 'A Home Video',
    body: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.'
  }
]

document.querySelector('body').innerHTML = BlogPostList(allPosts);

Each object containing the info for a single blog post is passed, one by one, to the BlogPost function, and the returned HTML strings are placed into a new array. We then just call join() on that new array to combine the array of strings into a single string (separated by an empty string), and we’re done. No for-loops, just a list of objects converted to a list of HTML elements.

Re-rendering

We can now implicitly generate HTML for given data, in a way that’s reusable and composable, all within the browser. But, how do we update when the data changes? How do we even know when to trigger an update? This subject is one of the most complex and hotly debated in the JavaScript framework community today. Making large numbers of DOM updates efficiently is an amazingly difficult problem, one which engineers at Facebook and Google have spent years working on.

Luckily, our proverbial website is just a blog. The content pretty much only changes when we look at a different blog post. There aren’t a ton of interactions to detect, we don’t have to optimize our DOM operations. When we load a new blog post, we can just scrap the DOM and rebuild it.

document.querySelector('body').innerHTML = PostPage(postData);

We could make this a little nicer by wrapping it in a function:

function update() {
  document.querySelector('body').innerHTML = PostPage(postData);
}

Now whenever we load a new blog post, we can just call update() and it will appear. If our application were complicated enough that it needed to re-render frequently – maybe a couple times per second in certain situations – it would get choppy really fast. You could write complex logic to figure out which sections of the page truly need to update given a particular change in data and only update those, but that’s the point where you should just use a framework.

Not just for content

At this point pretty much all our rendering code has been used to determine the actual HTML and text content inside the elements, but we don’t have to stop there. Since we’re just creating an HTML string, anything inside there is fair game. CSS classes?

function BlogPost(postData) {
  return `<div class="post ${postData.isSponsored ? 'sponsored-post' : ''}">
            <h1>
              ${postData.title} ${postData.isSponsored ? '<img src="badge.png">' : ''}
            </h1>
            <h3>By ${postData.author}</h3>
            <p>${postData.body}</p>
          </div>`;
}

Check. HTML attributes?

function BlogPost(postData) {
  return `<div class="post ${postData.isSponsored ? 'sponsored-post' : ''}">
            <input type="checkbox" ${postData.isSponsored ? 'checked' : ''}>
            <h1>
              ${postData.title} ${postData.isSponsored ? '<img src="badge.png">' : ''}
            </h1>
            <h3>By ${postData.author}</h3>
            <p>${postData.body}</p>
          </div>`;
}

Check. Feel free to get really creative with this. Think about your data, and think about how all the different aspects of it should be represented in markup, and write expressions that turn one into the other.

Summary

Hopefully, this post gives you a good set of tools for writing simple reactive, data-driven web interfaces without the overhead of any tools or frameworks. This type of code is far easier to write and maintain than jQuery spaghetti, and there’s no hurdle at all to using it right now. Everything we’ve talked about here comes free with all reasonably modern browsers, without so much as a library.

Part 2 will focus on class-based, stateful components, which will get near the territory of too-complicated-to-reasonably-do-in-VanillaJS. But by golly, we’re going to try anyway, and it’s going to be interesting.

Article Series:

  1. Pure Functional Style (You are here!)
  2. Class Based Components (Coming soon!)

Reactive UI’s with VanillaJS – Part 1: Pure Functional Style is a post from CSS-Tricks

Browserslist is a Good Idea

Post pobrano z: Browserslist is a Good Idea

I think, as front-end developers, we’re well aware that different browsers (and versions) support different things. We make choices based on web features we’d like to use balanced with what statistics tell us about our users and what browsers they use. Only 0.01% of users left on IE 9, we might see from our Google Analytics, OK, let’s start using Flexbox and .classList.

Enter Autoprefixer. Autoprefixer, over time, became a nearly ubiquitously part of CSS build processes because it helped us with cross browser support almost effortlessly. Even though IE 10 only supported an older Flexbox syntax, we didn’t have to worry about that because Autoprefixer did it’s best to port the modern syntax to the older one, and it did a great job at that.

Autoprefixer allows you to configure what browsers you wanted to target with the prefixing. This means you don’t have to generate prefixes for every browser ever (resulting in potentially more code output than you want) but instead only generate prefixes for the browsers you’ve decided to support. There are lots of ways to use Autoprefixer, but let’s say it’s a part of your Grunt build:

grunt.initConfig({
  autoprefixer: {
    options: {
      options: {
        browsers: ['last 2 versions', 'ie 8', 'ie 9']
      }
    },
    your_target: {
      // Target-specific file lists and/or options go here.
    },
  },
});

As you might guess, that Autoprefixer configuration will process based on the last 2 versions of all major browsers as well as specifically do what is needed for IE 8 and IE 9.

That’s great, but Autoprefixer isn’t the only tool out there making choices about browser versions.

Indeed.

I bet many of you have worked with Babel or at least heard of it. You write as modern of JavaScript as you like, and it processes it into JavaScript that will run in older browsers. There is a project called babel-preset-env which allows you to configure what browsers Babel will compile down to. For example:

"babel": {
  "presets": [
    [
      "env",
      {
        "targets": {
          "browsers": ["Edge 15"]
        }
      }
    ]
  ]
},

There we are specifically targeting Edge 15. Just as one little example, Babel won’t even bother converting anything in const a = `string`; because Edge 15 supports const and `backticks`. But if we told it to also target IE 10, we’d get var a = "string";.

Browserslist is about a single configuration for all tools that need to know what browsers you support.

We just looked at two major tools that can be configured based on which browsers to support: Autoprefixer and Babel. Doesn’t it make sense to be targeting the same list of browsers? (Yes.)

Enter Browserslist.

browserl.ist helps you see exactly what browsers you’re supported based on your configuration string.

With Autoprefixer, just by having a Browserslist configuration, it will automatically use it.

{
  "browserslist": [
    "> 1%",
    "last 2 versions",
    "IE 10"
  ]
}

That’s an example of having the configuration stored in your `package.json` file. There are other ways to make sure a Browserslist configuration is available though, like having a BROWSERSLIST environment variable or a `.browserslistrc` config dotfile.

Babel still requires babel-preset-env.

There are other interesting tools using Browserslist.

For example, your linting setup can be configured to warn you if you use code that isn’t supported outside of your Browserslist setup. This is done with the eslint-plugin-compat plugin for ESLint.

On the CSS side, the same can be done with stylelint and the stylelint-no-unsupported-browser-features plugin.

Those things feel like a natural extension of Browserslist, and it’s really cool they exist already. Perhaps slightly more surprising is PostCSS Normalize, which actually builds a CSS „reset” (Normalize isn’t really a reset, but you know what I mean, it handles cross browser differences in CSS) based on the browsers your target.

If you’d like to read a bit more, check out the article Autoprefixer 7.0 and Browserslist 2.0 from the developers behind these projects.

Even better, check out an example repo of all of these things combined together in a minimal example.


Browserslist is a Good Idea is a post from CSS-Tricks

Managing State in CSS with Reusable JavaScript Functions – Part 2

Post pobrano z: Managing State in CSS with Reusable JavaScript Functions – Part 2

In my previous article, which shall now retroactively be known as Managing State in CSS with Reusable JavaScript Functions – Part 1, we created a powerful reusable function which allows us to quickly add, remove and toggle stateful classes via click.

One of the reasons I wanted to share this approach was to see what kind of response it would generate. Since then I’ve received some interesting feedback from other developers, with some raising valid shortcomings about this approach that would have never otherwise occurred to me.

In this article, I’ll be providing some solutions to these shortcomings, as well as baking in more features and general improvements to make our reusable function even more powerful.

Article Series:

  1. Original article
  2. Managing State in CSS with Reusable JavaScript Functions (You are here!)

For reference, here’s the JavaScript from Part 1 for our reusable function as it stands:

// Grab all elements with required attributes
var elems = document.querySelectorAll("[data-class][data-class-element]");

// closestParent helper function
closestParent = function(child, match) {
  if (!child || child == document) {
    return null;
  }
  if (child.classList.contains(match) || child.nodeName.toLowerCase() == match) {
    return child;
  }
  else {
    return closestParent(child.parentNode, match);
  }
}

// Loop through if any are found
for(var i = 0; i < elems.length; i++){
  // Add event listeners to each one
  elems[i].addEventListener("click", function(e){

    // Prevent default action of element
    e.preventDefault();

    // Grab classes list and convert to array
    var dataClass = this.getAttribute('data-class');
    dataClass = dataClass.split(", ");

    // Grab linked elements list and convert to array
    var dataClassElement = this.getAttribute('data-class-element');
    dataClassElement = dataClassElement.split(", ");

    // Grab data-class-behaviour list if present and convert to array
    if(this.getAttribute("data-class-behaviour")) {
      var dataClassBehaviour = this.getAttribute("data-class-behaviour");
      dataClassBehaviour = dataClassBehaviour.split(", ");
    }

    // Grab data-scope list if present and convert to array
    if(this.getAttribute("data-class-scope")) {
      var dataClassScope = this.getAttribute("data-class-scope");
      dataClassScope = dataClassScope.split(", ");
    }

    // Loop through all our dataClassElement items
    for(var b = 0; b < dataClassElement.length; b++) {
      // Grab elem references, apply scope if found
      if(dataClassScope && dataClassScope[b] !== "false") {
        // Grab parent
        var elemParent = closestParent(this, dataClassScope[b]),

        // Grab all matching child elements of parent
        elemRef = elemParent.querySelectorAll("." + dataClassElement[b]);

        // Convert to array
        elemRef = Array.prototype.slice.call(elemRef);

        // Add parent if it matches the data-class-element and fits within scope
        if(dataClassScope[b] === dataClassElement[b] && elemParent.classList.contains(dataClassElement[b])) {
          elemRef.unshift(elemParent);
        }
      }
      else {
        var elemRef = document.querySelectorAll("." + dataClassElement[b]);
      }
      // Grab class we will add
      var elemClass = dataClass[b];
      // Grab behaviour if any exists
      if(dataClassBehaviour) {
        var elemBehaviour = dataClassBehaviour[b];
      }
      // Do
      for(var c = 0; c < elemRef.length; c++) {
        if(elemBehaviour === "add") {
          if(!elemRef[c].classList.contains(elemClass)) {
            elemRef[c].classList.add(elemClass);
          }
        }
        else if(elemBehaviour === "remove") {
          if(elemRef[c].classList.contains(elemClass)) {
            elemRef[c].classList.remove(elemClass);
          }
        }
        else {
          elemRef[c].classList.toggle(elemClass);
        }
      }
    }

  });    
}

Going forward, this is going to serve as the base for our improvements.

Let’s get started!

Accessibility

The most common piece of feedback I’ve received from other developers in response to Part 1 was the approach’s lack of consideration for accessibility. More specifically, it’s lack of support for ARIA attributes (or ARIA states if you prefer) and its failure to provide keyboard events for triggering our reusable function.

Let’s see how we can integrate both.

ARIA attributes

ARIA attributes form part of the WAI-ARIA specification. In the words of the specification they…

…are used to support platform accessibility APIs on various operating system platforms. Assistive technologies may access this information through an exposed user agent DOM or through a mapping to the platform accessibility API. When combined with roles, the user agent can supply the assistive technologies with user interface information to convey to the user at any time. Changes in states or properties will result in a notification to assistive technologies, which could alert the user that a change has occurred.

Revisiting the accordion example from part 1, an aria-expanded attribute set to true when the component is expanded, and vice versa when at its default state, would allow assistive technologies such as screen readers to better assess the component.

In addition to providing these benefits, as Ben Frain explores in his article, we can drop stateful classes and instead rely on ARIA attributes as our CSS hooks for styling some component state:

Adopting this approach results in what is (cringingly) referred to as a „Win Win” situation. We get to improve the accessibility of our web application, while also gaining a clearly defined, well-considered lexicon for communicating the states we need in our application logic.

For example, instead of:

.c-accordion.is-active .c-accordion__content {
  [...]
}

We would have:

.c-accordion[aria-expanded="true"] .c-accordion__content {
  [...]
}

Coming back to our reusable function, we’ll build in support so the data-class attribute can also accept an ARIA attribute reference. Since we’re now manipulating attributes rather than just classes, it would make sense semantically to rename data-class and all of its associated attributes to data-state:

<div class="c-mycomponent" data-state="aria-expanded" data-state-element="c-mycomponent" aria-expanded="false" tabindex="0">

In the above example, clicking c-mycomponent should toggle aria-expanded on itself. Whilst in the below example, in addition to the previous behaviour my-class would be removed from c-myothercomponent.

<div class="c-mycomponent" data-state="aria-expanded, my-class" data-state-element="c-mycomponent, c-myothercomponent" data-state-behaviour="toggle, remove" aria-expanded="false" tabindex="0">

In addition to aria-expanded, other examples of how ARIA attributes could be used instead of stateful classes are:

  • aria-disabled="true" instead of is-disabled
  • aria-checked="true" instead of is-checked
  • aria-pressed="true" or aria-selected="true" instead of is-active

Here’s a handy ARIA cheatsheet which came in handy whilst researching this article.

Implementation

Our reusable function currently assumes that everything passed to it via our newly renamed data-state attribute is a class. It then acts accordingly based on either what’s defined in data-state-behaviour or its default toggle behaviour:

// Cycle through target elements
for(var c = 0; c < elemRef.length; c++) {
  if(elemBehaviour === "add") {
    if(!elemRef[c].classList.contains(elemClass)) {
      elemRef[c].classList.add(elemClass);
    }
  }
  else if(elemBehaviour === "remove") {
    if(elemRef[c].classList.contains(elemClass)) {
      elemRef[c].classList.remove(elemClass);
    }
  }
  else {
    elemRef[c].classList.toggle(elemClass);
  }
}

Let’s tweak this slightly:

// Cycle through target elements
for(var c = 0; c < elemRef.length; c++) {
    // Find out if we're manipulating aria-attributes or classes
    var toggleAttr;
    if(elemRef[c].getAttribute(elemState)) {
        toggleAttr = true;
    }
    else {
        toggleAttr = false;
    }
    if(elemBehaviour === "add") {
        if(toggleAttr) {
            elemRef[c].setAttribute(elemState, true);
        }
        else {
            elemRef[c].classList.add(elemState);
        }
    }
    else if(elemBehaviour === "remove") {
        if(toggleAttr) {
            elemRef[c].setAttribute(elemState, false);
        }
        else {
            elemRef[c].classList.remove(elemState);
        }
    }
    else {
        if(toggleAttr) {
            if(elemRef[c].getAttribute(elemState) === "true") {
                elemRef[c].setAttribute(elemState, false);
            }
            else {
                elemRef[c].setAttribute(elemState, true);
            }
        }
        else {
            elemRef[c].classList.toggle(elemState);
        }
    }
}

To support ARIA attributes, we’ve simply added a check to first see if the given ARIA attribute exists on the element, and if not, assume it’s a class and process it as before. This way we can support both ARIA attributes and classes to cover all eventualities. Also removed are the classList.contains() checks, as in current spec classList.add() and classList.remove() are smart enough to account for this.

Keyboard Events

For a website to be considered accessible, it’s important that it can be easily navigated and interacted with through just the use of a keyboard. As far as the developer is concerned, this often involves the usage of the tabindex attribute and leveraging keyboard events.

In most browsers, elements such as the anchor already have these properties by default. You can tab to them and when in focus they can be activated on the press of the enter key. However for many components built with a combination of semantic elements and divs, this is not the case.

Let’s make our reusable function pick up this slack by writing logic to automatically add a keyboard event to the trigger element so it can be activated – like an anchor – with a press of the enter key.

Implementation

At the moment, as the function logic is triggered by clicking an element with data-state and data-state-element attributes, everything is wrapped in a click event listener:

elems[i].addEventListener("click", function(e){
  // Function logic
});

As a press of the enter key is going to need to trigger the same function logic as a click, it makes sense to seperate out this logic into it’s own function so it can be triggered from either. We’ll call it processChange():

// Assign click event
elem.addEventListener("click", function(e){
    // Prevent default action of element
    e.preventDefault(); 
    // Run state function
    processChange(this);
});
// Add keyboard event for enter key to mimic anchor functionality
elem.addEventListener("keypress", function(e){
    // e.which refers to the key pressed, 13 being the enter key.
    if(e.which === 13) {
        // Prevent default action of element
        e.preventDefault();
        // Run state function
        processChange(this);
    }
});

In addition to the existing click event listener, we’ve added an extra listener to react when the enter key is pressed. When a matching keypress event occurs on a focused trigger element, it’s just a matter of running our new processChange() function and passing the element.

You will also notice there’s no logic to automatically add a tabIndex attribute. This is because it may conflict with any tabIndex hierarchy already defined on the page and interfere with developer intent.

Example

Here’s a modified version of the accordion example from part 1, but fully updated to leverage ARIA attributes and keyboard events to make it a more accessible component. You can see the full reusable function as it now stands in the JavaScript panel.

See the Pen #7) Accessibility example by Luke Harrison (@lukedidit) on CodePen.

Accounting for elements added to DOM later

In Part 1, an issue was raised in the comments section:

I think this would have some issues for elements added to the DOM at a later point. In that case you would need to repeat assigning the click event. Am I correct?

That is correct! Any elements with data-state and data-state-element attributes added after the initial render of the DOM won’t have any event listeners assigned to them. So when they are clicked or swiped, nothing will happen.

Why? This is because in our Javascript, once the initial round of assigning event listeners to elements with data-state and data-state-element is complete, there’s no functionality in place to say „Hey! Watch out for any new elements withdata-state and data-state-element attributes and make them work.”

Implementation

To fix this, we’ll leverage something called MutationObservers. Whilst they can be explained much better in David Walsh’s great overview of the API, MutationObservers essentially allow us to track any nodes added or removed from the DOM (also known as „DOM mutations”).

We can setup one up like so:

// Setup mutation observer to track changes for matching elements added after initial DOM render
var observer = new MutationObserver(function(mutations) {
    mutations.forEach(function(mutation) {
        for(var d = 0; d < mutation.addedNodes.length; d++) {
            // Check if we're dealing with an element node
            if(typeof mutation.addedNodes[d].getAttribute === 'function') {
                if(mutation.addedNodes[d].getAttribute("data-state") && mutation.addedNodes[d].getAttribute("data-state-element")) {
                     // Create click and keyboard event listeners etc
                }
            }
        }
    });  
});

// Define type of change our observer will watch out for
observer.observe(document.body, {
  childList: true,
  subtree: true
});

This is what our MutationObserver is doing:

  1. Recording any DOM mutation to the body element which are immediate children childList: true or its decendents subtree: true
  2. Checking if that DOM mutation is a new element node, rather than a text node
  3. If true, then check if the new element node has data-state and data-state-element attributes

The next step, assuming these 3 checks pass, would be to setup our click and keypress event listeners. Like with the implementation of keyboard events, let’s seperate out this setup logic into its own function so we can reuse it both on page load and when an element with data-state and data-state-element attributes is detected by our MutationObserver.

We’ll call this new function initDataState().

// Init function
initDataState = function(elem){
  // Add event listeners to each one
  elems.addEventListener("click", function(e){
    // Prevent default action of element
    e.preventDefault();
    // Run state function
    processChange(this);
  });    
  // Add keyboard event for enter key to mimic anchor functionality
  elems.addEventListener("keypress", function(e){
      if(e.which === 13) {
          // Prevent default action of element
          e.preventDefault();
          // Run state function
          processChange(this);
      }
  });
}

Then it’s just a matter of hooking everything up correctly:

// Run when DOM has finished loading
document.addEventListener("DOMContentLoaded", function() {

  // Grab all elements with required attributes
  var elems = document.querySelectorAll("[data-state][data-state-element]");

  // Loop through if any are found
  for(var a = 0; a < elems.length; b++){
    initDataState(elems[a]);
  }

  // Setup mutation observer to track changes for matching elements added after initial DOM render
  var observer = new MutationObserver(function(mutations) {
      mutations.forEach(function(mutation) {
          for(var d = 0; d < mutation.addedNodes.length; d++) {
              // Check if we're dealing with an element node
              if(typeof mutation.addedNodes[d].getAttribute === 'function') {
                  if(mutation.addedNodes[d].getAttribute("data-state")) {
                       initDataState(mutation.addedNodes[d]);
                  }
              }
          }
      });  
  });

  // Define type of change our observer will watch out for
  observer.observe(document.body, {
      childList: true,
      subtree: true
  });
});

Example

Click the „Add” button to insert more elements into the page (example Pen below):

See the Pen #8) Correctly set up new data-class elements when they are added to the DOM by Luke Harrison (@lukedidit) on CodePen.

Swipe support

At the moment, our reusable function is using click and keyboard events to trigger state logic. This is fine at desktop level, but on touch devices for some UI components (Such as closing a sliding navigation menu for instance) it’s often more useful to have this logic trigger on detection of a swipe.

Let’s build in optional swipe support for our reusable function. This will require adding a new data attribute to compliment our existing set:

data-state-swipe

The purpose of this new attribute is to allow us to define the swipe direction which should trigger our state logic. These directions should be:

  • up
  • right
  • down
  • left

Let’s also build in the option to specify if or not the swipe event should replace the click event, or if both should coexist. We can add a comma separated boolean to data-state-swipe to trigger this behaviour:

  • true – Swipe event listener replaces click event listener
  • false – Swipe event listener and click event listener are both added (Default)

So for example, when the div below detects a left swipe, the aria-expanded attribute on js-elem would be changed to true. The swipe event listener would also in this instance replace the click event listener, as we’re passing true in data-state-swipe:

<div data-state="aria-expanded" data-state-element="js-elem" data-state-swipe="left, true" data-state-behaviour="add">

Now let’s make the changes.

Implementation

Swipe is managed in just the same way as you would manage clicks and keyboard input – via event listeners. To keep the article focused on our reusable function, I’ll be using a helper function called swipeDetect() which will handle all the calculations required for accurate swipe detection. However feel free to use your own preferred method of detecting swipe direction in place of it.

We’re building swipe into our reusable function as another way of triggering function logic, so it makes sense that it should sit with the click and keyboard event listeners in initDateState() and then trigger processChange() once our requirements for a desired swipe direction are met.

Though we also have to account for the behaviour flag passed in data-state-swipe that determines if swipe should replace click. Let’s refactor initDataState() to add some scaffolding to properly support all of this:

// Init function
initDataState = function(elem){
    // Detect data-swipe attribute before we do anything, as its optional
    // If not present, assign click event like before
    if(elem.getAttribute("data-state-swipe")){
        // Grab swipe specific data from data-state-swipe   
        var elemSwipe = elem.getAttribute("data-state-swipe"),
              elemSwipe = elemSwipe.split(", "),
              swipeDirection = elemSwipe[0],
              elemSwipeBool = elemSwipe[1],
              currentElem = elem;

        // If the behaviour flag is set to "false", or not set at all, then assign our click event
        if(elemSwipeBool === "false" || !elemSwipeBool) {
            // Assign click event
            elem.addEventListener("click", function(e){
                // Prevent default action of element
                e.preventDefault(); 
                // Run state function
                processChange(this);
            });
        }
        // Use our swipeDetect helper function to determine if the swipe direction matches our desired direction
        swipeDetect(elem, function(swipedir){
            if(swipedir === swipeDirection) {
                // Run state function
                processChange(currentElem);
            }
        })
    }
    else {
        // Assign click event
        elem.addEventListener("click", function(e){
            // Prevent default action of element
            e.preventDefault(); 
            // Run state function
            processChange(this);
        });
    }
    // Add keyboard event for enter key to mimic anchor functionality
    elem.addEventListener("keypress", function(e){
        if(e.which === 13) {
            // Prevent default action of element
            e.preventDefault();
            // Run state function
            processChange(this);
        }
    });
};

These amends to initDataState now give it 3 different outcomes:

  1. If there’s a data-state-swipe attribute on the trigger element, and its behaviour boolean is set to true, then only swipe and keyboard events are assigned.
  2. If there’s a data-state-swipe attribute on the trigger element, but its behaviour boolean is set to false, then swipe, click and keyboard events are all assigned.
  3. If there’s no data-state-swipe attribute on the trigger element altogether, only click and keyboard event listeners are assigned.

Example

Here’s a very barebones example of the new swipe functionality in practice. Click the button to toggle the menu and then swipe right on the menu whilst on a touch device (or your preferred browser inspector) to close it. Simple.

See the Pen #9) Adding swipe support to our reusable function by Luke Harrison (@lukedidit) on CodePen.

Function Refinements

Finally, we’ll be looking into ways we can refine our reusable function to make it more efficient and easier to use.

Targeting the trigger element

Say for example I have an element named c-btn which on click would need to toggle aria-pressed on itself. With our reusable function as it stands, the HTML would look something like this:

<button class="c-btn" data-state="aria-pressed" data-state-element="c-btn" aria-pressed="false">

The problem here is that on click, aria-pressed would be toggled on all instances of c-btn everywhere, which isn’t the behaviour we’re looking for.

This was the problem which data-state-scope was created to resolve. By scoping our data-state instance to the nearest c-btn (which in this case would be itself) then we are creating the desired toggle behaviour.

<button class="c-btn" data-state="aria-pressed" data-state-element="c-btn" data-class-scope="c-btn" aria-pressed="false">

Whilst the above snippet works fine, it’s a bit jarring having all these attributes all referencing the same c-btn element. Ideally if data-state-element and data-state-scope aren’t defined, then the function should default to the element triggering it. This would allow easy targeting of our trigger element. Like so:

<button class="c-btn" data-state="aria-pressed" aria-pressed="false">

Implementation

data-scope-element is currently a required attribute. If it isn’t present, the function will not be able to assign any event listeners. This is because in our reusable function as it stands, the initial scan of the document is looking for elements with both the data-scope and data-scope-element attributes:

// Grab all elements with required attributes
var elems = document.querySelectorAll("[data-state][data-state-element]");

We need to tweak this so we’re only looking for elements with data-state, as data-state-element will shortly be relegated to an optional attribute.

// Grab all elements with required attributes
var elems = document.querySelectorAll("[data-state]");

In addition, we need to add an if-statement to processChange() which wraps around the retrieval of the data-state-element value, as if it’s not present, the function will throw an error when trying to call getAttribute() on something which doesn’t exist.

// Grab data-state-element list and convert to array
if(elem.getAttribute("data-state-element")) {
  var dataStateElement = elem.getAttribute("data-state-element");
  dataStateElement = dataStateElement.split(", ");
}

Next, let’s implement the logic which makes data-state-element and data-state-scope default to the trigger element if they are not explicitly defined. We can build on our previous amends to processChange() and add an else block to our data-state-element check to manually declare our target element and scope.

// Grab data-state-element list and convert to array
// If data-state-element isn't found, pass self, set scope to self if none is present, essentially replicating "this"
if(elem.getAttribute("data-state-element")) {
  var dataStateElement = elem.getAttribute("data-state-element");
  dataStateElement = dataStateElement.split(", ");
}
else {
  var dataStateElement = [];
  dataStateElement.push(elem.classList[0]);
  if(!dataStateScope) {
    var dataStateScope = dataStateElement;
  }
}

Another consequence of making data-state-element no longer required is that in processChange(), it’s length is used in the for loop to make sure all the elements defined in data-state-element receive their changes of state. This is the loop as it stands:

// Loop through all our dataStateElement items
for(var b = 0; b < dataStateElement.length; b++) {
  [...]
}

Thankfully, all we need to do here is swap out our now optional data-state-element attribute for our still required data-state element as the base for this loop.

// Loop through all our dataStateElement items
for(var b = 0; b < dataState.length; b++) {
  [...]
}

This is because in instances where multiple values have been passed to each data-state attribute (For example: <div data-state="is-active, is-disabled" data-state-element="my-elem, my-elem-2" data-state-behaviour="add, remove">) the length of the array which is derived from each of these is always going to match, so we’re always going to get the same amount of loops in the for block.

Simplifying repeated values

Another improvement which we could make relates to assigning similar types of logic in a single data-state use. Consider the below example:

<a data-state="my-state, my-state-2, my-state-3" data-state-element="c-btn, c-btn, c-btn" data-state-behaviour="remove, remove, remove" data-state-scope="o-mycomponent, o-mycomponent, o-mycomponent">

Whilst this a legitimate use of our reusable function, one thing you will notice is that we have a lot of repeated values in many of the data-state attributes. Ideally, if we want to assign similar types of logic many times over, we should be able to just write a value once and have the function interpret that as repeat values.

For example, the below HTML snippet should perform the same action as the one above.

<a data-state="my-state, my-state-2, my-state-3" data-state-element="c-btn" data-state-behaviour="remove" data-state-scope="o-mycomponent">

Here is another example of what should be considered a valid data-state use:

<a data-state="aria-expanded" data-state-element="c-menu, c-other-menu, c-final-menu" data-state-behaviour="remove, add">

Implementation

The first thing we need to consider is the for loop in processChange() which we last amended in the previous section. As it uses the length of data-state as a base for it’s loop amount, implementing these changes would expose a bug in scenarios where we have one class being applied to many elements.

Consider the following:

<a data-state="my-state" data-state-element="c-btn, c-btn-2" data-state-behaviour="remove" data-state-scope="o-mycomponent">

What would happen here is because data-state only has a single value, the for loop in processChange() would only loop a single time, meaning our intended logic for c-btn-2 would never be assigned.

To fix this, we need to compare data-state and data-state-element. Whichever has the most values then becomes the base for our loop. Like so:

// Find out which has the biggest length between states and elements and use that length as loop number
// This is to make sure situations where we have one data-state-element value and many data-state values are correctly setup
var dataLength = Math.max(dataStateElement.length, dataState.length);

// Loop
for(var b = 0; b < dataLength; b++) {
  [...]
}

As for the rest of the implementation, it’s now a matter of adding logic in the for loop for each attribute which says „If a matching value can’t be found, use the last valid one”.

Let’s use data-state value as an example. Currently, the code in the for loop which grabs the state value looks like:

// Grab state we will add
var elemState = dataState[b];

The problem now is if we have 3 data-state-element values, but only 1 data-state value, on loops 2 and 3 elemState would be undefined.

What we need to do is only redefine elemState if we have a value to give it. Like so:

// Grab state we will add
// If one isn't found, keep last valid one
if(dataState[b] !== undefined) {
  var elemState = dataState[b];
}

This would ensure elemState would always have a value, including inheriting any previous values if one can’t initially be found.

Example

Here’s a final example showing all of our function refinements:

See the Pen #10) Allow easier targeting of self & general improvements by Luke Harrison (@lukedidit) on CodePen.

Closing

In this article, we’ve covered how to build upon the reusable function created in Part 1 to make it more accessible and easier to use.

In addition, we’ve also added swipe support for trigger elements and have made sure any data-state elements added after the initial render of the DOM are no longer ignored.

As before, any comments or constructive feedback are welcome. I’ll leave you will the full reusable function which we’ve developed over the last 2 articles:

(function(){

  // SWIPE DETECT HELPER
  //----------------------------------------------

  var swipeDetect = function(el, callback){ 
    var touchsurface = el,
    swipedir,
    startX,
    startY,
    dist,
    distX,
    distY,
    threshold = 100, //required min distance traveled to be considered swipe
    restraint = 100, // maximum distance allowed at the same time in perpendicular direction
    allowedTime = 300, // maximum time allowed to travel that distance
    elapsedTime,
    startTime,
    eventObj,
    handleswipe = callback || function(swipedir, eventObj){}

    touchsurface.addEventListener('touchstart', function(e){
      var touchobj = e.changedTouches[0]
      swipedir = 'none'
      dist = 0
      startX = touchobj.pageX
      startY = touchobj.pageY
      startTime = new Date().getTime() // record time when finger first makes contact with surface
      eventObj = e;
    }, false)

    touchsurface.addEventListener('touchend', function(e){
      var touchobj = e.changedTouches[0]
      distX = touchobj.pageX - startX // get horizontal dist traveled by finger while in contact with surface
      distY = touchobj.pageY - startY // get vertical dist traveled by finger while in contact with surface
      elapsedTime = new Date().getTime() - startTime // get time elapsed
      if (elapsedTime <= allowedTime){ // first condition for awipe met
        if (Math.abs(distX) >= threshold && Math.abs(distY) <= restraint){ // 2nd condition for horizontal swipe met
          swipedir = (distX < 0)? 'left' : 'right' // if dist traveled is negative, it indicates left swipe
        }
        else if (Math.abs(distY) >= threshold && Math.abs(distX) <= restraint){ // 2nd condition for vertical swipe met
          swipedir = (distY < 0)? 'up' : 'down' // if dist traveled is negative, it indicates up swipe
        }
      }
      handleswipe(swipedir, eventObj)
    }, false)
  }


  // CLOSEST PARENT HELPER FUNCTION
  //----------------------------------------------

  closestParent = function(child, match) {
    if (!child || child == document) {
      return null;
    }
    if (child.classList.contains(match) || child.nodeName.toLowerCase() == match) {
      return child;
    }
    else {
      return closestParent(child.parentNode, match);
    }
  }


  // REUSABLE FUNCTION
  //----------------------------------------------

  // Change function
  processChange = function(elem){

    // Grab data-state list and convert to array
    var dataState = elem.getAttribute("data-state");
    dataState = dataState.split(", ");

    // Grab data-state-behaviour list if present and convert to array
    if(elem.getAttribute("data-state-behaviour")) {
      var dataStateBehaviour = elem.getAttribute("data-state-behaviour");
      dataStateBehaviour = dataStateBehaviour.split(", ");
    }

    // Grab data-scope list if present and convert to array
    if(elem.getAttribute("data-state-scope")) {
      var dataStateScope = elem.getAttribute("data-state-scope");
      dataStateScope = dataStateScope.split(", ");
    }

    // Grab data-state-element list and convert to array
    // If data-state-element isn't found, pass self, set scope to self if none is present, essentially replicating "this"
    if(elem.getAttribute("data-state-element")) {
      var dataStateElement = elem.getAttribute("data-state-element");
      dataStateElement = dataStateElement.split(", ");
    }
    else {
      var dataStateElement = [];
      dataStateElement.push(elem.classList[0]);
      if(!dataStateScope) {
        var dataStateScope = dataStateElement;
      }
    }

    // Find out which has the biggest length between states and elements and use that length as loop number
    // This is to make sure situations where we have one data-state-element value and many data-state values are correctly setup
    var dataLength = Math.max(dataStateElement.length, dataState.length);

    // Loop
    for(var b = 0; b < dataLength; b++) {

      // If a data-state-element value isn't found, use last valid one
      if(dataStateElement[b] !== undefined) {
        var dataStateElementValue = dataStateElement[b];
      } 

      // If scope isn't found, use last valid one
      if(dataStateScope && dataStateScope[b] !== undefined) {
        var cachedScope = dataStateScope[b];
      }
      else if(cachedScope) {
        dataStateScope[b] = cachedScope;
      }

      // Grab elem references, apply scope if found
      if(dataStateScope && dataStateScope[b] !== "false") {

        // Grab parent
        var elemParent = closestParent(elem, dataStateScope[b]);

        // Grab all matching child elements of parent
        var elemRef = elemParent.querySelectorAll("." + dataStateElementValue);

        // Convert to array
        elemRef = Array.prototype.slice.call(elemRef);

        // Add parent if it matches the data-state-element and fits within scope
        if(elemParent.classList.contains(dataStateElementValue)) {
          elemRef.unshift(elemParent);
        }
      }
      else {
        var elemRef = document.querySelectorAll("." + dataStateElementValue);
      }
      // Grab state we will add
      // If one isn't found, keep last valid one
      if(dataState[b] !== undefined) {
        var elemState = dataState[b];
      }   
      // Grab behaviour if any exists
      // If one isn't found, keep last valid one
      if(dataStateBehaviour) {
        if(dataStateBehaviour[b] !== undefined) {
          var elemBehaviour = dataStateBehaviour[b];
        }
      }
      // Do
      for(var c = 0; c < elemRef.length; c++) {
        // Find out if we're manipulating aria-attributes or classes
        var toggleAttr;
        if(elemRef[c].getAttribute(elemState)) {
          toggleAttr = true;
        }
        else {
          toggleAttr = false;
        }
        if(elemBehaviour === "add") {
          if(toggleAttr) {
            elemRef[c].setAttribute(elemState, true);
          }
          else {
            elemRef[c].classList.add(elemState);
          }
        }
        else if(elemBehaviour === "remove") {
          if(toggleAttr) {
            elemRef[c].setAttribute(elemState, false);
          }
          else {
            elemRef[c].classList.remove(elemState);
          }
        }
        else {
          if(toggleAttr) {
            if(elemRef[c].getAttribute(elemState) === "true") {
              elemRef[c].setAttribute(elemState, false);
            }
            else {
              elemRef[c].setAttribute(elemState, true);
            }
          }
          else {
            elemRef[c].classList.toggle(elemState);
          }
        }
      }

    }

  },
    // Init function
    initDataState = function(elem){
    // Detect data-swipe attribute before we do anything, as its optional
    // If not present, assign click event like before
    if(elem.getAttribute("data-state-swipe")){
      // Grab swipe specific data from data-state-swipe
      var elemSwipe = elem.getAttribute("data-state-swipe"),
          elemSwipe = elemSwipe.split(", "),
          direction = elemSwipe[0],
          elemSwipeBool = elemSwipe[1],
          currentElem = elem;

      // If the behaviour flag is set to "false", or not set at all, then assign our click event
      if(elemSwipeBool === "false" || !elemSwipeBool) {
        // Assign click event
        elem.addEventListener("click", function(e){
          // Prevent default action of element
          e.preventDefault(); 
          // Run state function
          processChange(this);
        });
      }
      // Use our swipeDetect helper function to determine if the swipe direction matches our desired direction
      swipeDetect(elem, function(swipedir){
        if(swipedir === direction) {
          // Run state function
          processChange(currentElem);
        }
      })
    }
    else {
      // Assign click event
      elem.addEventListener("click", function(e){
        // Prevent default action of element
        e.preventDefault(); 
        // Run state function
        processChange(this);
      });
    }
    // Add keyboard event for enter key to mimic anchor functionality
    elem.addEventListener("keypress", function(e){
      if(e.which === 13) {
        // Prevent default action of element
        e.preventDefault();
        // Run state function
        processChange(this);
      }
    });
  };

  // Run when DOM has finished loading
  document.addEventListener("DOMContentLoaded", function() {

    // Grab all elements with required attributes
    var elems = document.querySelectorAll("[data-state]");

    // Loop through our matches and add click events
    for(var a = 0; a < elems.length; a++){
      initDataState(elems[a]);
    }

    // Setup mutation observer to track changes for matching elements added after initial DOM render
    var observer = new MutationObserver(function(mutations) {
      mutations.forEach(function(mutation) {
        for(var d = 0; d < mutation.addedNodes.length; d++) {
          // Check if we're dealing with an element node
          if(typeof mutation.addedNodes[d].getAttribute === 'function') {
            if(mutation.addedNodes[d].getAttribute("data-state")) {
              initDataState(mutation.addedNodes[d]);
            }
          }
        }
      });    
    });

    // Define type of change our observer will watch out for
    observer.observe(document.body, {
      childList: true,
      subtree: true
    });
  });
}());

Article Series:

  1. Original article
  2. Managing State in CSS with Reusable JavaScript Functions (You are here!)

Managing State in CSS with Reusable JavaScript Functions – Part 2 is a post from CSS-Tricks

Production Progressive Web Apps with JavaScript Frameworks

Post pobrano z: Production Progressive Web Apps with JavaScript Frameworks

This last week at Google I/O, Addy Osmani announced some amazing developer resources for creating Progressive Web Applications (PWAs) that prioritize performance with JavaScript Frameworks.

This was truly a team effort- a lot of people worked on these projects to get them going, and it’s a really valuable contribution to the community. A lot of people want better performance for their framework of choice but can’t get buy-in for time and resources to devote to this kind of endeavor. The ability to start with a baseline of high performance and good lighthouse scores is incredibly valuable, allowing developers to enjoy both the productivity and ergonomics of exciting frameworks, without sacrificing speed and user experience.

Here are some of the highlights!

Addy created a site to explore some of the templates that they built out with the different PWA solutions, as a successor to the very popular TodoMVC, called HN PWA. You can explore all of the demos and the GitHub repo here. He then went through some major company implementations of each of the frameworks rebuilt as PWAs. Throughout a lot of the case studies that Addy features, the heavy-hitters in building for better web experiences lied in link rel=preload, requestIdleCallback(), and HTTP2 Server Push. There were many mentions the PRPL pattern, in essence prioritizing what you’re going to use first, by Pushing critical resources for the initial URL route, Rendering the initial route, Pre-caching remaining routes, and Lazy-loading and create remaining routes on demand. A lot of the performance wins were framed within the ability to be interactive on mobile within 5 seconds and trying to lower the overhead of the framework itself so that you had more time in that 5 seconds for your own application code.

React

React announced that Create React App will now be a PWA by default! This is a big win here. They now employ service workers with an offline-first caching strategy, code-splitting with dynamic import(), helpful overlays for errors, and it gives you 1.5s of headroom for your application code. More information on the release here.

Preact

Now has a CLI! Announced at the event, this is a pretty amazing development worth playing around with. You can find the project here. Among other really nice features you can read through in the readme, it has a 100/100 Lighthouse score out of the box, as well as a whopping 3s of headroom to work on your own application code.

If you’re not familiar with Preact, it’s an extremely fast 3kb React alternative with the same API, including the use of components & virtual DOM. It’s similar to React, but the small filesize is central to the software design. The only caveat mentioned is that due to its emphasis on slim builds, there may be offerings in the React ecosystem that still need work for seamless integration. That said, Preact was the clear winner in performance here, so I wouldn’t be surprised to see the community rally around this solution.

Vue

Vue announced a PWA template, offered directly from Vue-cli, which you can access easily with vue init pwa.

vue init pwa from within vue-cli

Among a lot of great offerings, it gives you two 2s of headroom for application code on mobile, code-splitting with dynamic import(), service worker for offline caching, and JS chunks are preloaded or prefetched.

If you’re not familiar with Vue, I’ve written up a guide here. I think Vue is an amazing piece of software, and the ability to strike all of the lighthouse credentials out of the gate is pretty incredible. This workflow makes it so easy to create beautiful and complex apps.

There are many more details that I didn’t get to in this post, and Addy is a great speaker. He even created a video game for his talk. It’s a worthwhile watch the whole way through, you can view it here:


Production Progressive Web Apps with JavaScript Frameworks is a post from CSS-Tricks

A Unified Styling Language

Post pobrano z: A Unified Styling Language

This article by Mark Dalgleish will go down as one of the most important front-end development articles of 2017.

It’s about the hot topic that is „CSS in JavaScript”. Mark walks us through how that’s actually not a simple and singular idea, but a continuum of concepts and implementations. There are lots of projects that all approach it in different ways. It’s likely that the best ways are the projects that actually generate real CSS. These things are a far cry from „inline styles”.

Regular ol’ CSS isn’t going anywhere, but these CSS in JS ideas aren’t either. There is some serious benefits to them for a lot of projects. Scoped styles preventing styling mistakes. Performance gains through critical styles. Only shipping the minimum amount of styles needed. Stylesheets that nobody is afraid of and don’t become the proverbial „append only” stylesheets. Not to mention that if you are going to be styling in a JavaScript environment, you get the possibility of dynamic styles and porting those styles to other platforms and such.

Direct Link to ArticlePermalink


A Unified Styling Language is a post from CSS-Tricks

Snap Animation States

Post pobrano z: Snap Animation States

There are many ways to make icons for a website. Inline SVG is scalable, easy to modify with CSS, and can even be animated. If you’re interested in learning more about the merits of using inline SVG, I recommend reading Inline SVG vs Icon Fonts. With ever increasing browser support, there’s never been a better time to start working with SVGs. Snap Animation States is a JavaScript plugin built around Snap.svg to help create and extend icon libraries with scaleable, editable SVG icons. Snap Animation States makes it easy to load and animate those SVGs with a simple schema.

Getting Started

Lets start with a basic SVG hamburger menu. This one was made using Affinity Designer, but there are many other free (Inkscape) and paid for (Adobe Illustrator) options available for making vector images.

<svg width="100%" height="100%" viewBox="0 0 65 60" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:square;stroke-miterlimit:1.5;"  fill="none" stroke="#000" stroke-width="10">
   <g>
      <path class="hamburger-top" d="m 5,10 55,0" />
      <path class="hamburger-middle" d="m 5,30 55,0" />
   </g>
   <path class="hamburger-bottom" d="m 5,50 55,0" />
</svg>

Although this is a pretty basic SVG, it still takes up multiple lines in an HTML document. This can be a pain to write if you want to use the SVG in multiple locations across several different web pages. And what if you have to modify your SVG? Then you’re scrambling to remember all the places you used the SVG so you can update it. It’s not very clean or reusable. This is what Snap Animation States is all about solving.

Let’s continue to use the same SVG, but this time we’ll use the plugin to load it to the DOM. The schema for the plugin requires at least two properties: selector: ".some-css-selector", and svg: "svg string". Check out the following demo:

See the Pen Lydgoo by Briant Diehl (@bkdiehl) on CodePen.

You’ll notice in the Pen above that I call icon-hamburger just like I would call a font icon. Just remember that the selector property needs a CSS selector as its value.

There’s more we can do since this plugin is an extension of Snap.svg, a JavaScript library used to create and animate SVGs. So let’s see what’s needed to give this hamburger icon some basic animation.

When I created my SVG, I added classes to the elements I knew I would be animating.

<g>
   <path class="hamburger-top" d="m 5,10 55,0" />
   <path class="hamburger-middle" d="m 5,30 55,0" />
</g>
<path class="hamburger-bottom" d="m 5,50 55,0" />

In my schema, I can start to include the properties needed for animation, and I start by giving it transitionTime: 250. The transition time is applied to each step in the transform chain and can be overridden later by an individual transform.

Now it’s time to include my animation states. I start by setting the property states:{}. The property names for this object should correlate to the state the animation will lead to. In this case I’m going to name my properties open and closed. The property values for this object are arrays of transform objects. So far, the additions to the schema should look like this:

transitionTime: 250,
states: {
  open:[],
  closed: []
}

Next, we need to include the transform objects that define how the SVG elements are to be transformed.

open:[
  { id: "top-lower", element: ".hamburger-top", y:20 },
  { id: "bottom-raise", element: ".hamburger-bottom", y:-20 },
  { waitFor: "top-lower", element: "g", r:45 },
  { waitFor: "bottom-raise", element: ".hamburger-bottom", r:-45},
]

Each transform object has either an id, a waitFor, or a combination of the two. Each id needs to be unique. Objects with an id represent a link in a chain of animations. waitFor always needs to reference a link id that precedes it. In this case there’s an object with id:"top-lower" and an object with waitFor:"top-lower". When the animation starts, id:top-lower will be the first link in the chain, and it will run for 250ms. When it has finished, waitFor:"top-lower" will run for 250ms.

Every transform object must reference an element. The element value can be either a css selector or a direct element reference. For example, one element property has the value of "g" referencing the <g> element in the SVG, while another has the value of ".hamburger-bottom", referencing the class I added to the <path> element.

Now that we know the order of animation and the elements that need to be transformed, we just need to define the transform objects. For those of you unfamiliar with how SVG transforms work, you could start with Transforms on SVG Elements. Otherwise, simply put, imagine that the SVG element you are manipulating starts at the points [0, 0] on an x/y axis. Something else to remember is that x goes left to right, while y goes from top to bottom. In the example above, we see:

{ id: "top-lower", element: ".hamburger-top", y:20 },

This transform object is referring to the top line of the hamburger menu. y: 20 is telling the plugin that, starting at the element’s point of origin [0, 0], I want to move the top line down 20px. The reverse is true for:

{ id: "bottom-raise", element: ".hamburger-bottom", y:-20 },

Here I’m telling the plugin to move my element up by 20px. The same principle applies to the rotations:

{ waitFor: "top-lower", element: "g", r:45 },
{ waitFor: "bottom-raise", element: ".hamburger-bottom", r:-45}

The element that’s being rotated starts at a rotation of 0 degrees. r: 45 is telling the plugin to rotate from 0 degrees to 45 degrees and vice versa for r: -45.

Our second state in the states object looks like this:

closed: [
  { id: "top-angle", element: "g", r: 0 },
  { id: "bottom-angle", element: ".hamburger-bottom", r: 0 },                   
  { waitFor: "top-angle", element: ".hamburger-top", y: 0 },
  { waitFor: "bottom-angle", element: ".hamburger-bottom", y: 0 }
]

You’ll notice that for all of the elements being transformed, their y and r values are set to 0. This is because this state’s purpose is to return the SVG elements to their original state. Since 0 was the point of origin, we’re just performing a transform on each of the elements that will return them to their origin.

We’re almost done. Now that the animation states are defined, I have to decide what’s going to initiate these animations. This requires one more property on the schema: events. events takes an array of objects, since you may want to initiate your animation with more than one event. For the hamburger icon, it’s going to look like this:

events: [
  { event: "click", state: ["open", "closed"] }
]

The object in the array may include the following properties:

  1. event: designed to listen for javascript events. The hamburger icon listens for a 'click’ event on <i class="icon-hamburger"</i> since that’s what the selector in the schema references.
  2. state: takes either a string or an array. If the state here is "open", then when <i class="icon-hamburger"></i> is clicked, only the „open” animation would run on a click event. Since the value of state is an array, the click event will actually toggle between the „open” and „closed” animations. The array is only designed to take two values and enable toggling.
  3. The last property is optional: selector. By default this value is your schema selector + „animate”. In this case it would be icon-hamburger-animate. You can change the selector if you want by declaring it here. It’s purpose is to enable the javascript events to be tied to either a parent or sibling element of your SVG. For example, if I had an SVG that I wanted to animate inside of a button when the button was clicked, I would need to do this:
<button class="icon-hamburger-animate">
  <i class="icon-hamburger"></i>
</button>

Whew, we made it. Now it’s time to see the final product.

See the Pen bWwQJZ by Briant Diehl (@bkdiehl) on CodePen.

Was it worth it? You may be thinking, that’s a lot of work to just have a single icon. And I would agree with you. Which is why I created a Gulp plugin to help with the heavy lifting.

Gulp Animation States

So far, we have a single icon that we can use wherever I’ve included the schema. Ideally, the schema for icon-hamburger would be saved to a js file that gets bundled up and included site-wide, which means that I could call icon-hamburger wherever I want. What if this js file was autogenerated and contained the schema and plugin call for as many SVG icons as you had access to? You could have easy access library of SVG icons! That’s the purpose of Gulp Animation States. Make sure to check out the documentation here.

Let’s begin with the file structure. Say I went to IcoMoon and generated all the SVG files I needed for my new project. I would want to drop all these newly generated files into a folder in my project. Let’s call that folder `svg`. My file structure would look something like this:

svg
|-- icon-folder.svg
|-- icon-hamburger.svg
|-- icon-mic.svg
|-- icon-wall.svg
|-- icon-wrench.svg

Using Gulp Animation States I can combine all the SVG files in my `svg` folder into a single js file with the selector for each icon set according to the file name of the SVG. The file contents would look something like this:

var iconFolder = {"selector": ".icon-folder","svg": "<svg>Content</svg>"};
SnapStates(iconFolder);
var iconHamburger= {"selector": ".icon-hamburger","svg": "<svg>Content</svg>"};
SnapStates(iconHamburger);
var iconMic= {"selector": ".icon-mic","svg": "<svg>Content</svg>"};
SnapStates(iconMic);
var iconWall= {"selector": ".icon-wall","svg": "<svg>Content</svg>"};
SnapStates(iconWall);
var iconWrench= {"selector": ".icon-wrench","svg": "<svg>Content</svg>"};
SnapStates(iconWrench);

This file could be bundled up with the rest of a website’s key JavaScript, enabling SVG icon usage wherever it’s wanted. But what about the animations? How do they get included in this JavaScript file?

We already have the animation for the hamburger icon, so we’ll use that. In the `svg` folder, we need to create a new file called `icon-hamburger.js`. Note that it has the same name as it’s corresponding SVG file. Here is the new file structure:

svg
|-- icon-folder.svg
|-- icon-hamburger.svg
|-- icon-hamburger.js
|-- icon-mic.svg
|-- icon-wall.svg
|-- icon-wrench.svg

And the contents of `icon-hamburger.js` would be:

{
  transitionTime: 250,
  states: {
    open:[
      { id: "top-lower", element: ".hamburger-top", y:20 },
      { id: "bottom-raise", element: ".hamburger-bottom", y:-20 },
      { waitFor: "top-lower", element: "g", r:45 },
      { waitFor: "top-lower", element: ".hamburger-bottom", r:-45},
    ],
    closed: [
      { id: "top-angle", element: "g", r: 0 },
      { id: "bottom-angle", element: ".hamburger-bottom", r: 0 },                   
      { waitFor: "top-angle", element: ".hamburger-top", y: 0 },
      { waitFor: "bottom-angle", element: ".hamburger-bottom", y: 0 },
    ]
  },
  events: [
    { event: "click", state: ["open", "closed"] }
  ]
}

The Gulp plugin will look for js files with the same name as the SVG file it’s creating a schema for. Demonstrating the output again with the animation states:

var iconFolder = {"selector": ".icon-folder","svg": "<svg>Content</svg>"};
SnapStates(iconFolder);
var iconHamburger= {"selector": ".icon-hamburger","svg": "<svg>Content</svg>", "transitionTime":250,"states":{"open":[{"id":"top-lower","element":".hamburger-top","y":20},{"id":"bottom-raise","element":".hamburger-bottom","y":-20},{"waitFor":"top-lower","element":"g","r":45},{"waitFor":"top-lower","element":".hamburger-bottom","r":-45}],"closed":[{"id":"top-angle","element":"g","r":0},{"id":"bottom-angle","element":".hamburger-bottom","r":0},{"waitFor":"top-angle","element":".hamburger-top","y":0},{"waitFor":"bottom-angle","element":".hamburger-bottom","y":0}]},"events":[{"event":"click","state":["open","closed"]}};
SnapStates(iconHamburger);
var iconMic= {"selector": ".icon-mic","svg": "<svg>Content</svg>"};
SnapStates(iconMic);
var iconWall= {"selector": ".icon-wall","svg": "<svg>Content</svg>"};
SnapStates(iconWall);
var iconWrench= {"selector": ".icon-wrench","svg": "<svg>Content</svg>"};
SnapStates(iconWrench);

Using Gulp Animation States, you manage to retain smaller, bite-sized files that you can easily edit when you need to change something. Those bite-sized pieces compile nicely into a single file that can be bundled with other key components of your site, allowing quick and easy calls to include an SVG in your HTML document.

Further Examples

The hamburger icon was fairly simple, so let’s look at a few more complex icons. We’ll start with a speaker icon.

See the Pen WjoOoy by Briant Diehl (@bkdiehl) on CodePen.

You’ll notice that, overall, the schema is mostly the same. You’ll notice the property easing is new. easing has a default value of easeinout. Besides that, the only changes worth noticing are in my transform objects.

{ id: "waveline1", element: ".wave-line-1", x:-10, s:0.1, attr:{ opacity:.8 }, transitionTime: 250 },
{ id: "waveline2", element: ".wave-line-2", x:-16, s:0.1, attr:{ opacity:.8 }, transitionTime: 300 },
{ id: "waveline3", element: ".wave-line-3", x:-22, s:0.1, attr:{ opacity:.8 }, transitionTime: 350 }

s is for scale, and just like in css, an object’s scale always starts at 1. The attr property allows you to modify any attribute on an SVG element, in this case, the opacity. Finally, remember in the beginning of the article how I mentioned that transitionTime can be overridden by an individual transform? Well, here is how it’s done. I didn’t even declare transitionTime in the main schema. That’s because I wanted each transform to have a unique transition time.

Next, let’s look at a line drawing animation.

See the Pen OmbxVV by Briant Diehl (@bkdiehl) on CodePen.

The first major difference I want you to see is that I’m not declaring svg in the schema. The SVG is inside the <i class="icon-new-document"></i>. This is mostly for demo purposes, so as not to bloat the schema that I want you to be viewing. However, the plugin does allow for this functionality. The use case is for those users who only have a few SVG icons that they need in their document and don’t want to use the gulp plugin.

The transform objects are what I really want to focus on here. There’s a lot of new stuff going on here.

{ id: 'line1-init', element: ".new-document-line1", drawPath: { min: 25, max: 75 }, transitionTime: { min: 500, max: 1000 }, repeat: {times:1} },        
{ id: 'line2-init', element: ".new-document-line2", drawPath: { min: 25, max: 75 }, transitionTime: { min: 500, max: 1000 }, repeat: {times:1} },
{ id: 'line3-init', element: ".new-document-line3", drawPath: { min: 25, max: 75 }, transitionTime: { min: 500, max: 1000 }, repeat: {times:1} },
{ id: 'line4-init', element: ".new-document-line4", drawPath: { min: 25, max: 75 }, transitionTime: { min: 500, max: 1000 }, repeat: {times:1} },
{ id: 'line5-init', element: ".new-document-line5", drawPath: { min: 25, max: 75 }, transitionTime: { min: 500, max: 1000 }, repeat: {times:1} },
{ waitFor: 'line1-init', element: ".new-document-line1", drawPath: 100, transitionTime: { min: 500, max: 1000 } },
{ waitFor: 'line2-init', element: ".new-document-line2", drawPath: 100, transitionTime: { min: 500, max: 1000 } },
{ waitFor: 'line3-init', element: ".new-document-line3", drawPath: 100, transitionTime: { min: 500, max: 1000 } },
{ waitFor: 'line4-init', element: ".new-document-line4", drawPath: 100, transitionTime: { min: 500, max: 1000 } },
{ waitFor: 'line5-init', element: ".new-document-line5", drawPath: 100, transitionTime: { min: 500, max: 1000 } },

If you’ve looked at the Pen, you’ll have noticed that hovering over the new document icon causes the lines to shrink and grow. Each of those lines is a path, and a path can be drawn. The first transform object above includes drawPath. drawpath takes either a number or an object with properties min and max. The number represents a percentage. Let’s say that the transform object had drawPath: 0. That would mean that I want the current path to be drawn to 0% of its length. The transform object really has drawPath: { min: 25, max: 75 }. When the value of drawpath is an object, I’m telling my plugin that I want the path to be drawn to a random percentage between the min and the max. In this case it would be a random number between 25 and 75. If you hover over the icon again, you can see that the line length changes each time the animation occurs. The same principle of setting a random number with min and max applies to transitionTime.

The last newcomer to this animation schema is repeat. repeat takes an object with four valid properties.

  1. loop: takes a Boolean value. If set to true, the animation and all of the transforms further down the chain will repeat until told otherwise. In order to break out of a loop you must either set a loopDuration or change to another animation state.
  2. loopDuration: takes an integer. If I set loop to true and loopDuration to 5000, then the animation chain will repeat itself for 5000ms. If the duration of the animation loop isn’t exactly 5000ms, then the loop will continue its final animation past the set time.
  3. times: takes an integer. If I set times to 2, then my animation will run a total of 3 times. Once because the animation always runs at least once and then 2 more times.
  4. delay: takes an integer. Represents the amount of time you want between the end of the animation and the beginning of the repeat loop.

Next, I want to illustrate a longer animation chain.

See the Pen KmNXdW by Briant Diehl (@bkdiehl) on CodePen.

Take a look at the shake state. The first and last transform objects have either an id or a waitFor property. Every other transform object has both an id and a waitFor property.

shake: [
  { id: "shake-right", element: '.wrench', r: 10 },
  { id: "shake-left", waitFor: 'shake-right', element: '.wrench', r: -10 },
  { id: "back-to-right", waitFor: 'shake-left', element: '.wrench', r: 10 },
  { id: "back-to-left", waitFor: 'back-to-right', element: '.wrench', r: -10 },
  { waitFor: 'back-to-left', element: '.wrench', r: 0 }
]

Each of the three middle transform objects is referencing the id of the preceding transform object with their waitFor. The first animation starts a chain that leads to the reset value r:0 at the very end.

Finally, I want to demonstrate how we would draw lines by setting stroke-dashoffset and stroke-dasharray.

See the Pen rmWzyW by Briant Diehl (@bkdiehl) on CodePen.

First, I want you to notice that on many of my path elements that I include stroke-dashoffset:1000; stroke-dasharray:1000, 1000;

<path class="right-upper-branch" d="M45.998,21.196C43.207,23.292 44.195,27.857 47.629,28.59C48.006,28.671 48.399,28.699 48.784,28.672C49.659,28.611 50.276,28.34 50.994,27.849C51.413,27.563 51.839,27.05 52.092,26.616C53.906,23.507 50.981,19.611 47.489,20.486C46.946,20.622 46.446,20.86 45.998,21.196L41.015,14.571" style="fill:none;stroke:#fff;stroke-width:1.7px;stroke-dashoffset:1000; stroke-dasharray:1000, 1000;"/>

For a more detailed explanation of stroke-dasharray, view stroke-dasharray. For my purposes, let’s say that stroke-dasharray is basically setting the length of the path that I don’t want to show. Now, my path certainly isn’t 1000px long. It’s a bit of an exaggeration, but it’s an exaggeration that makes sure that no portion of my path is shown prematurely. The path is drawn to completion in the following transform.

{ id:["right-upper-branch", 600], element: ".right-upper-branch", drawPath:100  },

When I set drawPath back to 0 it will adjust the stroke-dasharray and stroke-dashoffset accordingly. The last thing I want to point out about this line is the id. It’s an array instead of a string. The first value in the array is the name of the id. The second value will always be an integer representing a timeout. If I only used this transform object in the animation I would only see a path being drawn 600ms after the mouseover event.

For more examples and further documentation you can check out my demo page.

Conclusion

There may be many of you that are still on the fence about whether switching your icon system to something new is a good thing. There are pros and cons to the different icon systems currently available. I’ve tried to create a simple way for you to make the move to SVG icons. I hope you find it useful.


Snap Animation States is a post from CSS-Tricks

Full Page Background Video Styles

Post pobrano z: Full Page Background Video Styles

Making a full page background video is slightly trickier than a full page background image. Over on the Media Temple blog, I take a look at how that’s done, but then also what the design patterns are once you’ve done it. You likely need text over top the video, so do you center it? Do you let the page scroll and cover the video? Do you get fancy and fade out the header as you scroll?

Direct Link to ArticlePermalink


Full Page Background Video Styles is a post from CSS-Tricks