Web Standards

How to Build Vue Components in a WordPress Theme

Css Tricks - Tue, 02/18/2020 - 5:27am

Intrigued by the title and just wanna see some code? Skip ahead.

A few months ago, I was building a WordPress website that required a form with a bunch of fancy conditional fields. Different options and info were required for different choices you could make on the form, and our client needed complete control over all fields 1. In addition, the form needed to appear in multiple places in each page, with slightly different configs.

And the header instance of the form needed to be mutually exclusive with the hamburger menu, so that opening one closes the other.

And the form had text content that was relevant to SEO.

And we wanted the server response to present some cute animated feedback.

(Phew.)

The whole thing felt complex enough that I didn't want to handle all that state manually. I remembered reading Sarah Drasner’s article "Replacing jQuery With Vue.js: No Build Step Necessary” which shows how to replace classic jQuery patterns with simple Vue micro-apps. That seemed like a good place to start, but I quickly realized that things would get messy on the PHP side of WordPress.

What I really needed were reusable components

PHP ? JavaScript

I love the static-first approach of Jamstack tools, like Nuxt, and was looking to do something similar here — send the full content from the server, and progressively enhance on the client side.

But PHP doesn’t have a built-in way to work with components. It does, however, support require-ing files inside other files 2. WordPress has an abstraction of require called get_template_part, that runs relative to the theme folder and is easier to work with. Dividing code into template parts is about the closest thing to components that WordPress provides 3.

Vue, on the other hand, is all about components — but it can only do its thing after the page has loaded and JavaScript is running.

The secret to this marriage of paradigms turns out to be the lesser-known Vue directive inline-template. Its great and wonderful powers allow us to define a Vue component using the markup we already have. It’s the perfect middle ground between getting static HTML from the server, and mounting dynamic DOM elements in the client.

First, the browser gets the HTML, then Vue makes it do stuff. Since the markup is built by WordPress, rather than by Vue in the browser, components can easily use any information that site administrators can edit. And, as opposed to .vue files (which are great for building more app-y things), we can keep the same separation of concerns we use for the whole site — structure and content in PHP, style in CSS, and functionality in JavaScript.

To show how this all fits together, we’re going to build a few features for a recipe blog. First, we’ll add a way for users to rate recipes. Then we’ll build a feedback form based on that rating. Finally, we’ll allow users to filter recipes, based on tags and rating.

We’ll build a few components that share state and live on the same page. To get them to play nicely together — and to make it easy to add additional components in the future — we’ll make the whole page our Vue app, and register components inside it.

Each component will live in its own PHP file and be included in the theme using get_template_part.

Laying the groundwork

There are a few special considerations to take into account when applying Vue to existing pages. The first is that Vue doesn't want you loading scripts inside it — it will send ominous errors to the console if you do. The easiest way to avoid this is to add a wrapper element around the content for every page, then load scripts outside of it (which is already a common pattern for all kinds of reasons). Something like this:

<?php /* header.php */ ?> <body <?php body_class(); ?>> <div id="site-wrapper"> <?php /* footer.php */ ?> </div> <!-- #site-wrapper --> <?php wp_footer(); ?>

The second consideration is that Vue has to be called at the end of body element so that it will load after the rest of the DOM is available to parse. We’ll pass true as the fifth argument  (in_footer) for the wp_enqueue_script  function. Also, to make sure Vue is loaded first, we’ll register it as a dependency of the main script.

<?php // functions.php add_action( 'wp_enqueue_scripts', function() { wp_enqueue_script('vue', get_template_directory_uri() . '/assets/js/lib/vue.js', null, null, true); // change to vue.min.js for production wp_enqueue_script('main', get_template_directory_uri() . '/assets/js/main.js', 'vue', null, true);

Finally, in the main script, we’ll initialize Vue on the site-wrapper element.

// main.js new Vue({ el: document.getElementById('site-wrapper') }) The star rating component

Our single post template currently looks like this:

<?php /* single-post.php */ ?> <article class="recipe"> <?php /* ... post content */ ?> <!-- star rating component goes here --> </article>

We’ll register the star rating component and add some logic to manage it:

// main.js Vue.component('star-rating', { data () { return { rating: 0 } }, methods: { rate (i) { this.rating = i } }, watch: { rating (val) { // prevent rating from going out of bounds by checking it to on every change if (val < 0) this.rating = 0 else if (val > 5) this.rating = 5 // ... some logic to save to localStorage or somewhere else } } }) // make sure to initialize Vue after registering all components new Vue({ el: document.getElementById('site-wrapper') })

We’ll write the component template in a separate PHP file. The component will comprise six buttons (one for unrated, 5 with stars). Each button will contain an SVG with either a black or transparent fill.

<?php /* components/star-rating.php */ ?> <star-rating inline-template> <div class="star-rating"> <p>Rate recipe:</p> <button @click="rate(0)"> <svg><path d="..." :fill="rating === 0 ? 'black' : 'transparent'"></svg> </button> <button v-for="(i in 5) @click="rate(i)"> <svg><path d="..." :fill="rating >= i ? 'black' : 'transparent'"></svg> </button> </div> </star-rating>

As a rule of thumb, I like to give a component’s top element a class name that is identical to that of the component itself. This makes it easy to reason between markup and CSS (e.g. <star-rating> can be thought of as .star-rating).

And now we’ll include it in our page template.

<?php /* single-post.php */ ?> <article class="recipe"> <?php /* post content */ ?> <?php get_template_part('components/star-rating'); ?> </article>

All the HTML inside the template is valid and understood by the browser, except for <star-rating>. We can go the extra mile to fix that by using Vue’s is directive:

<div is="star-rating" inline-template>...</div>

Now let’s say that the maximum rating isn’t necessarily 5, but is controllable by the website’s editor using Advanced Custom Fields, a popular WordPress plugin that adds custom fields for pages, posts and other WordPress content. All we need to do is inject that value as a prop of the component that we’ll call maxRating:

<?php // components/star-rating.php // max_rating is the name of the ACF field $max_rating = get_field('max_rating'); ?> <div is="star-rating" inline-template :max-rating="<?= $max_rating ?>"> <div class="star-rating"> <p>Rate recipe:</p> <button @click="rate(0)"> <svg><path d="..." :fill="rating === 0 ? 'black' : 'transparent'"></svg> </button> <button v-for="(i in maxRating) @click="rate(i)"> <svg><path d="..." :fill="rating >= i ? 'black' : 'transparent'"></svg> </button> </div> </div>

And in our script, let’s register the prop and replace the magic number 5:

// main.js Vue.component('star-rating', { props: { maxRating: { type: Number, default: 5 // highlight } }, data () { return { rating: 0 } }, methods: { rate (i) { this.rating = i } }, watch: { rating (val) { // prevent rating from going out of bounds by checking it to on every change if (val < 0) this.rating = 0 else if (val > maxRating) this.rating = maxRating // ... some logic to save to localStorage or somewhere else } } })

In order to save the rating of the specific recipe, we’ll need to pass in the ID of the post. Again, same idea:

<?php // components/star-rating.php $max_rating = get_field('max_rating'); $recipe_id = get_the_ID(); ?> <div is="star-rating" inline-template :max-rating="<?= $max_rating ?>" recipe-id="<?= $recipe_id ?>"> <div class="star-rating"> <p>Rate recipe:</p> <button @click="rate(0)"> <svg><path d="..." :fill="rating === 0 ? 'black' : 'transparent'"></svg> </button> <button v-for="(i in maxRating) @click="rate(i)"> <svg><path d="..." :fill="rating >= i ? 'black' : 'transparent'"></svg> </button> </div> </div> // main.js Vue.component('star-rating', { props: { maxRating: { // Same as before }, recipeId: { type: String, required: true } }, // ... watch: { rating (val) { // Same as before // on every change, save to some storage // e.g. localStorage or posting to a WP comments endpoint someKindOfStorageDefinedElsewhere.save(this.recipeId, this.rating) } }, mounted () { this.rating = someKindOfStorageDefinedElsewhere.load(this.recipeId) } })

Now we can include the same component file in the archive page (a loop of posts), without any additional setup:

<?php // archive.php if (have_posts()): while ( have_posts()): the_post(); ?> <article class="recipe"> <?php // Excerpt, featured image, etc. then: get_template_part('components/star-rating'); ?> </article> <?php endwhile; endif; ?> The feedback form

The moment a user rates a recipe is a great opportunity to ask for more feedback, so let’s add a little form that appears right after the rating is set.

// main.js Vue.component('feedback-form', { props: { recipeId: { type: String, required: true }, show: { type: Boolean, default: false } }, data () { return { name: '', subject: '' // ... other form fields } } }) <?php // components/feedback-form.php $recipe_id = get_the_ID(); ?> <div is="feedback-form" inline-template recipe-id="<?= $recipe_id ?>" v-if="showForm(recipe-id)"> <form class="recipe-feedback-form" id="feedback-form-<?= $recipe_id ?>"> <input type="text" :id="first-name-<?= $recipe_id ?>" v-model="name"> <label for="first-name-<?= $recipe_id ?>">Your name</label> <?php /* ... */ ?> </form> </div>

Notice that we’re appending a unique string (in this case, recipe-id) to each form element’s ID. This is to make sure they all have unique IDs, even if there are multiple copies of the form on the page.

So, where do we want this form to live? It needs to know the recipe’s rating so it knows it needs to open. We’re just building good ol’ components, so let’s use composition to place the form inside the <star-rating>:

<?php // components/star-rating.php $max_rating = get_field('max_rating'); $recipe_id = get_the_ID(); ?> <div is="star-rating" inline-template :max-rating="<?= $max_rating ?>" recipe-id="<?= $recipe_id ?>"> <div class="star-rating"> <p>Rate recipe:</p> <button @click="rate(0)"> <svg><path d="..." :fill="rating === 0 ? 'black' : 'transparent'"></svg> </button> <button v-for="(i in maxRating) @click="rate(i)"> <svg><path d="..." :fill="rating >= i ? 'black' : 'transparent'"></svg> </button> <?php get_template_part('components/feedback-form'); ?> </div> </div>

If at this point you’re thinking, “We really should be composing both components into a single parent component that handles the rating state,” then please give yourself 10 points and wait patiently.

A small progressive enhancement we can add to make the form usable without JavaScript, is to give it the traditional PHP action and then override it in Vue. We’ll use @submit.prevent to prevent the original action, then run a submit method to send the form data in JavaScript.

<?php // components/feedback-form.php $recipe_id = get_the_ID(); ?> <div is="feedback-form" inline-template recipe-id="<?= $recipe_id ?>"> <form action="path/to/feedback-form-handler.php" @submit.prevent="submit" class="recipe-feedback-form" id="feedback-form-<?= $recipe_id ?>"> <input type="text" :id="first-name-<?= $recipe_id ?>" v-model="name"> <label for="first-name-<?= $recipe_id ?>">Your name</label> <!-- ... --> </form> </div>

Then, assuming we want to use fetch, our submit method can be something like this:

// main.js Vue.component('feedback-form', { // Same as before methods: { submit () { const form = this.$el.querySelector('form') const URL = form.action const formData = new FormData(form) fetch(URL, {method: 'POST', body: formData}) .then(result => { ... }) .catch(error => { ... }) } } })

OK, so what do we want to do in .then and .catch? Let’s add a component that will show real-time feedback for the form’s submit status. First let’s add the state to track sending, success, and failure, and a computed property telling us if we’re pending results.

// main.js Vue.component('feedback-form', { // Same as before data () { return { name: '', subject: '' // ... other form fields sent: false, success: false, ?? error: null } }, methods: { submit () { const form = this.$el.querySelector('form') const URL = form.action const formData = new FormData(form) fetch(URL, {method: 'POST', body: formData}) .then(result => { this.success = true }) .catch(error => { this.error = error }) this.sent = true } } })

To add the markup for each message type (success, failure, pending), we could make another component like the others we’ve built so far. But since these messages are meaningless when the server renders the page, we’re better off rendering them only when necessary. To do this we’re going to place our markup in a native HTML <template> tag, which doesn't render anything in the browser. Then we’ll reference it by id as our component’s template.

<?php /* components/form-status.php */ ?> <template id="form-status-component" v-if="false"> <div class="form-message-wrapper"> <div class="pending-message" v-if="pending"> <img src="<?= get_template_directory_uri() ?>/spinner.gif"> <p>Patience, young one.</p> </div> <div class="success-message" v-else-if="success"> <img src="<?= get_template_directory_uri() ?>/beer.gif"> <p>Huzzah!</p> </div> <div class="success-message" v-else-if="error"> <img src="<?= get_template_directory_uri() ?>/broken.gif"> <p>Ooh, boy. It would appear that: {{ error.text }}</p> </div> </div </template>

Why add v-if="false" at the top, you ask? It’s a tricky little thing. Once Vue picks up the HTML <template>, it will immediately think of it as a Vue <template> and render it. Unless, you guessed it, we tell Vue not to render it. A bit of a hack, but there you have it.

Since we only need this markup once on the page, we’ll include the PHP component in the footer.

<?php /* footer.php */ ?> </div> <!-- #site-wrapper --> <?php get_template_part('components/form-status'); ?> <?php wp_footer(); ?>

Now we’ll register the component with Vue…

// main.js Vue.component('form-status', { template: '#form-status-component' props: { pending: { type: Boolean, required: true }, success: { type: Boolean, required: true }, error: { type: [Object, null], required: true }, } })

…and call it inside our form component:

<?php // components/feedback-form.php $recipe_id = get_the_ID(); ?> <div is="feedback-form" inline-template recipe-id="<?= $recipe_id ?>"> <form action="path/to/feedback-form-handler.php" @submit.prevent="submit" class="recipe-feedback-form" id="feedback-form-<?= $recipe_id ?>"> <input type="text" :id="first-name-<?= $recipe_id ?>" v-model="name"> <label for="first-name-<?= $recipe_id ?>">Your name</label> <?php // ... ?> </form> <form-status v-if="sent" :pending="pending" :success="success" :error="error" /> </div>

Since we registered <form-status> using Vue.component, it's available globally, without specifically including it in the parent’s components: { }.

Filtering recipes

Now that users can personalize some bits of their experience on our blog, we can add all kinds of useful functionality. Specifically, let's allow users to set a minimum rating they want to see, using an input at the top of the page.
The first thing we need is some global state to track the minimum rating set by the user. Since we started off by initializing a Vue app on the whole page, global state will just be data on the Vue instance:

// main.js // Same as before new Vue({ el: document.getElementById('site-wrapper'), data: { minimumRating: 0 } })

And where can we put the controls to change this? Since the whole page is the app, the answer is almost anywhere. For instance, at the top of the archive page:

<?php /* archive.php */ ?> <label for="minimum-rating-input">Only show me recipes I've rated at or above:</label> <input type="number" id="minimum-rating-input" v-model="minimumRating"> <?php if (have_posts()): while ( have_posts()): the_post(); ?> <article class="recipe"> <?php /* Post excerpt, featured image, etc. */ ?> <?php get_template_part('components/star-rating'); ?> </article> <?php endwhile; endif; ?>

As long as it’s inside our site-wrapper and not inside another component, it’ll just work. If we want, we could also build a filtering component that would change the global state. And if we wanted to get all fancy, we could even add Vuex to the mix (since Vuex can’t persist state between pages by default, we could add something like vuex-persist to use localStorage).

So, now we need to hide or show a recipe based on the filter. To do this, we’ll need to wrap the recipe content in its own component, with a v-show directive. It’s probably best to use the same component for both the single page and the archive page. Unfortunately, neither require nor get_template_part can pass parameters into the called file — but we can use global variables:

<?php /* archive.php */ ?> <label for="minimum-rating-input">Only show me recipes I've rated at or above:</label> <input type="number" id="minimum-rating-input" v-model="minimumRating"> <?php $is_archive_item = true; if (have_posts()): while ( have_posts()): the_post(); get_template_part('components/recipe-content'); endwhile; endif; ?>

We can then use $is_archive_item as a global variable inside the PHP component file to check if it is set and true. Since we won’t need to hide the content on the single post page, we’ll conditionally add the v-show directive.

<?php // components/recipe-content.php global $is_archive_item; ?> <div is="recipe-content"> <article class="recipe" <?php if ($is_archive_item): ?> v-show="show" <?php endif; ?> > <?php if ($is_archive_item): the_excerpt(); else the_content(); endif; get_template_part('components/star-rating'); ?> </article> </div>

In this specific example, we could have also tested with  is_archive() inside the component, but in most cases we’ll need to set explicit props.

We’ll need to move the rating state and logic up into the <recipe-content> component so it can know if it needs to hide itself. Inside <star-rating>, we’ll make a custom v-model by replacing rating with value, and this.rating = i with  $emit('input', i) as well . So our component registration will now look like this:

// main.js Vue.component('recipe-content', { data () { rating: 0 }, watch: { rating (val) { // ... } }, mounted () { this.rating = someKindOfStorageDefinedElsewhere.load(this.recipeId) } }) Vue.component('star-rating', { props: { maxRating: { /* ... */ }, recipeId: { /* ... */ }, value: { type: Number, required: true } }, methods: { rate (i) { this.$emit('input', i) } }, })

We’ll add v-model in star-rating.php and change rating to value. In addition, we can now move the <feedback-form> up into <recipe-content>:

<?php // components/star-rating.php $max_rating = get_field('max_rating'); $recipe_id = get_the_ID(); ?> <div is="star-rating" inline-template :max-rating="<?= $ max_rating ?>" recipe-id="<?= $recipe_id ?>" v-model="value" > <div class="star-rating"> <p>Rate recipe:</p> <button @click="rate(0)"> <svg><path d="..." :fill="value === 0 ? 'black' : 'transparent'"></svg> </button> <button v-for="(i in maxRating) @click="rate(i)"> <svg><path d="..." :fill="value >= i ? 'black' : 'transparent'"></svg> </button> </div> </div> <?php // components/recipe-content.php global $is_archive_item; ?> <div is="recipe-content"> <article class="recipe" <?php if ($is_archive_item): ?> v-show="show" <?php endif; ?> > <?php if ($is_archive_item): the_excerpt(); else the_content(); endif; get_template_part('components/star-rating'); get_template_part('components/feedback-form'); ?> </article> </div>

Now everything is set up so the initial render shows all recipes, and then the user can filter them based on their rating. Moving forward, we could add all kinds of parameters to filter content. And it doesn’t have to be based on user input — we can allow filtering based on the content itself (e.g. number of ingredients or cooking time) by passing the data from PHP to Vue.

Conclusion

Well, that was a bit of a long ride, but look at what we’ve built: independent, composable, maintainable, interactive, progressively enhanced components in our WordPress theme. We brought together the best of all worlds!

I’ve been using this approach in production for a while now, and I love the way it allows me to reason about the different parts of my themes. I hope I’ve inspired you to try it out too.

  1. Of course, two days before launch, the client’s legal department decided they don't want to collect all that info. Currently the live form is but a shadow of its development self.
  2. Fun fact: Rasmus Lerdorf said that his original intent was for PHP to be templating only, with all business logic handled in C. Let that sink in for a moment. Then clear an hour from your schedule and watch the whole talk.
  3. There are third-party WordPress templating engines that can compile down to optimized PHP. Twig, for example, comes to mind. We’re trying to go the reverse route and send vanilla PHP to be handled by JavaScript.

The post How to Build Vue Components in a WordPress Theme appeared first on CSS-Tricks.

Web Component for a Code Block

Css Tricks - Tue, 02/18/2020 - 5:27am

We'll get to that, but first, a long-winded introduction.

I'm still not in a confident place knowing a good time to use native web components. The templating isn't particularly robust, so that doesn't draw me in. There is no state management, and I like having standard ways of handling that. If I'm using another library for components anyway, seems like I would just stick with that. So, at the moment, my checklist is something like:

  • Not using any other JavaScript framework that has components
  • Templating needs aren't particularly complex
  • Don't need particularly performant re-rendering
  • Don't need state management

I'm sure there is tooling that helps with these things and more (the devMode episode with some folks from Stencil was good), but if I'm going to get into tooling-land, I'd be extra tempted to go with a framework, and probably not framework plus another thing with a lot of overlap.

The reasons I am tempted to go with native web components are:

  • They are native. No downloads of frameworks.
  • The Shadow DOM is a true encapsulation in a way a framework can't really do.
  • I get to build my own HTML element that I use in HTML, with my own API design.

It sorta seems like the sweet spot for native web components is design system components. You build out your own little API for the components in your system, and people can use them in a way that is a lot safer than just copy and paste this chunk of HTML. And I suppose if consumers of the system wanted to BYO framework, they could.

So you can use like <our-tabs active-tab="3"> rather than <div class="tabs"> ... <a href="#3" class="tab-is-active">. Refactoring the components certainly gets a lot easier as changes percolate everywhere.

I've used them here on CSS-Tricks for our <circle-text> component. It takes the radius as a parameter and the content via, uh, content, and outputs an <svg> that does the trick. It gave us a nice API for authoring that abstracted away the complexity.

So!

It occurred to me a "code block" might be a nice use-case for a web component.

  • The API would be nice for it, as you could have attributes control useful things, and the code itself as the content (which is a great fallback).
  • It doesn't really need state.
  • Syntax highlighting is a big gnarly block of CSS, so it would be kinda cool to isolate that away in the Shadow DOM.
  • It could have useful functionality like a "click to copy" button that people might enjoy having.

Altogether, it might feel like a yeah, I could use this kinda component.

This probably isn't really production ready (for one thing, it's not on npm or anything yet), but here's where I am so far:

CodePen Embed Fallback

Here's a thought dump!

  • What do you do when a component depends on a third-party lib? The syntax highlighting here is done with Prism.js. To make it more isolated, I suppose you could copy and paste the whole lib in there somewhere, but that seems silly. Maybe you just document it?
  • Styling web components doesn't feel like it has a great story yet, despite the fact that Shadow DOM is cool and useful.
  • Yanking in pre-formatted text to use in a template is super weird. I'm sure it's possible to do without needing a <pre> tag inside the custom element, but it's clearly much easier if you grab the content from the <pre>. Makes the API here just a smidge less friendly (because I'd prefer to use the <code-block> alone).
  • I wonder what a good practice is for passing along attributes that another library needs. Like is data-lang="CSS" OK to use (feels nicer), and then convert it to class="language-css" in the template because that's what Prism wants? Or is it better practice to just pass along attributes as they are? (I went with the latter.)
  • People complain that there aren't really "lifecycle methods" in native web components, but at least you have one: when the thing renders: connectedCallback. So, I suppose you should do all the manipulation of HTML and such before you do that final shadowRoot.appendChild(node);. I'm not doing that here, and instead am running Prism over the whole shadowRoot after it's been appended. Just seemed to work that way. I imagine it's probably better, and possible, to do it ahead of time rather than allow all the repainting caused by injecting spans and such.
  • The whole point of this is a nice API. Seems to me thing would be nicer if it was possible to drop un-escaped HTML in there to highlight and it could escape it for you. But that makes the fallback actually render that HTML which could be bad (or even theoretically insecure). What's a good story for that? Maybe put the HTML in HTML comments and test if <!-- is the start of the content and handle that as a special situation?

Anyway, if you wanna fork it or do anything fancier with it, lemme know. Maybe we can eventually put it on npm or whatever. We'll have to see how useful people think it could be.

The post Web Component for a Code Block appeared first on CSS-Tricks.

A Complete Guide to Data Attributes

Css Tricks - Mon, 02/17/2020 - 2:07pm
Table of Contents
  1. Introduction
  2. Syntax
  3. Styling with data attributes
  4. Accessing data attributes in JavaScript
Introduction

HTML elements can have attributes on them that are used for anything from accessibility information to stylistic control.

<!-- We can use the `class` for styling in CSS, and we've also make this into a landmark region --> <div class="names" role="region" aria-label="Names"></div>

What is discouraged is making up your own attributes, or repurposing existing attributes for unrelated functionality.

<!-- `highlight` is not an HTML attribute --> <div highlight="true"></div> <!-- `large` is not a valid value of `width` --> <div width="large">

There are a variety of reasons this is bad. Your HTML becomes invalid, which may not have any actual negative consequences, but robs you of that warm fuzzy valid HTML feeling. The most compelling reason is that HTML is a living language and just because attributes and values that don't do anything today doesn't mean they never will.

Good news though: you can make up your own attributes. You just need to prefix them with data-* and then you're free to do what you please!

Syntax

It can be awfully handy to be able to make up your own HTML attributes and put your own information inside them. Fortunately, you can! That's exactly what data attributes are. They are like this:

<!-- They don't need a value --> <div data-foo></div> <!-- ...but they can have a value --> <div data-size="large"></div> <!-- You're in HTML here, so careful to escape code if you need to do something like put more HTML inside --> <li data-prefix="Careful with HTML in here."><li> <!-- You can keep dashing if you like --> <aside data-some-long-attribute-name><aside>

Data attributes are often referred to as data-* attributes, as they are always formatted like that. The word data, then a dash -, then other text you can make up.

Can you use the data attribute alone? <div data=""></div>

It's probably not going to hurt anything, but you won't get the JavaScript API we'll cover later in this guide. You're essentially making up an attribute for yourself, which as I mentioned in the intro, is discouraged.

What not to do with data attributes

Store content that should be accessible. If the content should be seen or read on a page, don't only put them in data attributes, but make sure that content is in the HTML content somewhere.

<!-- This isn't accessible content --> <div data-name="Chris Coyier"></div> <!-- If you need programmatic access to it but shouldn't be seen, there are other ways... --> <div> <span class="visually-hidden">Chris Coyier</span> </div>

Here's more about hiding things.

Styling with data attributes

CSS can select HTML elements based on attributes and their values.

/* Select any element with this data attribute and value */ [data-size="large"] { padding: 2rem; font-size: 125%; } /* You can scope it to an element or class or anything else */ button[data-type="download"] { } .card[data-pad="extra"] { }

This can be compelling. The predominant styling hooks in HTML/CSS are classes, and while classes are great (they have medium specificity and nice JavaScript methods via classList) an element either has it or it doesn't (essentially on or off). With data-* attributes, you get that on/off ability plus the ability to select based on the value it has at the same specificity level.

/* Selects if the attribute is present at all */ [data-size] { } /* Selects if the attribute has a particular value */ [data-state="open"], [aria-expanded="true"] { } /* "Starts with" selector, meaning this would match "3" or anything starting with 3, like "3.14" */ [data-version^="3"] { } /* "Contains" meaning if the value has the string anywhere inside it */ [data-company*="google"] { } The specificity of attribute selectors

It's the exact same as a class. We often think of specificity as a four-part value:

inline style, IDs, classes/attributes, tags

So a single attribute selector alone is 0, 0, 1, 0. A selector like this:

div.card[data-foo="bar"] { }

...would be 0, 0, 2, 1. The 2 is because there is one class (.card) and one attribute ([data-foo="bar"]), and the 1 is because there is one tag (div).

Attribute selectors have less specificity than an ID, more than an element/tag, and the same as a class.

Case-insensitive attribute values

In case you're needing to correct for possible capitalization inconsistencies in your data attributes, the attribute selector has a case-insensitive variant for that.

/* Will match <div data-state="open"></div> <div data-state="Open"></div> <div data-state="OPEN"></div> <div data-state="oPeN"></div> */ [data-state="open" i] { }

It's the little i within the bracketed selector.

Using data attributes visually

CSS allows you to yank out the data attribute value and display it if you need to.

/* <div data-emoji="✅"> */ [data-emoji]::before { content: attr(data-emoji); /* Returns '✅' */ margin-right: 5px; } Example styling use-case

You could use data attributes to specify how many columns you want a grid container to have.

<div data-columns="2"></div> <div data-columns="3"></div> <div data-columns="4"></div> CodePen Embed Fallback Accessing data attributes in JavaScript

Like any other attribute, you can access the value with the generic method getAttribute.

let value = el.getAttribute("data-state"); // You can set the value as well. // Returns data-state="collapsed" el.setAttribute("data-state", "collapsed");

But data attributes have their own special API as well. Say you have an element with multiple data attributes (which is totally fine):

<span data-info="123" data-index="2" data-prefix="Dr. " data-emoji-icon="&#x1f3cc;️‍♀️" ></span>

If you have a reference to that element, you can set and get the attributes like:

// Get span.dataset.info; // 123 span.dataset.index; // 2 // Set span.dataset.prefix = "Mr. "; span.dataset.emojiIcon = "&#x1f3aa;";

Note the camelCase usage on the last line there. It automatically converts kebab-style attributes in HTML, like data-this-little-piggy, to camelCase style in JavaScript, like dataThisLittlePiggy.

This API is arguably not quite as nice as classList with the clear add, remove, toggle, and replace methods, but it's better than nothing.

You have access to inline datasets as well:

<img src="spaceship.png" data-ship-id="324" data-shields="72%" onclick="pewpew(this.dataset.shipId)"> </img> JSON data inside data attributes <ul> <li data-person=' { "name": "Chris Coyier", "job": "Web Person" } '></li> </ul>

Hey, why not? It's just a string and it's possible to format it as valid JSON (mind the quotes and such). You can yank that data and parse it as needed.

const el = document.querySelector("li"); let json = el.dataset.person; let data = JSON.parse(json); console.log(data.name); // Chris Coyier console.log(data.job); // Web Person JavaScript use-cases

The concept is that you can use data attributes to put information in HTML that JavaScript may need access to do certain things.

A common one would have to do with database functionality. Say you have a "Like" button:

<button data-id="435432343">?</button>

That button could have a click handler on it which performs an Ajax request to the server to increment the number of likes in a database on click. It knows which record to update because it gets it from the data attribute.

Specifications Browser support

This browser support data is from Caniuse, which has more detail. A number indicates that browser supports the feature at that version and up.

DesktopChromeFirefoxIEEdgeSafari7611125.1Mobile / TabletAndroid ChromeAndroid FirefoxAndroidiOS Safari796835.0-5.1

The post A Complete Guide to Data Attributes appeared first on CSS-Tricks.

Moving from Vanilla JavaScript to a Reusable Vue Component

Css Tricks - Mon, 02/17/2020 - 5:42am

I recently wrote an article explaining how you can create a countdown timer using HTML, CSS and JavaScript. Now, let’s look at how we can make that a reusable component by porting it into Vue using basic features that the framework provides.

Why do this at all? Well there are few reasons, but two stand out in particular:

  • Keeping UI in sync with the timer state: If you look at the code from the first post,  it all lives in the timerInterval function, most noticeably the state management. Each time it runs (every second) we need to manually find the proper element on our document — whether it’s the time label or the remaining time path or whatever — and change either its value or an attribute. Vue comes with an HTML-based template syntax that allows you to declaratively bind the rendered DOM to the underlying Vue instance’s data. That takes all the burden of finding and updating proper UI elements so we can rely purely on the component instance’s properties.
  • Having a highly reusable component: The original example works fine when only one timer is present on our document, but imagine that you want to add another one. Oops! We rely the element’s ID to perform our actions and using the same ID on multiple instances would prevent them from working independently. That means we would have to assign different IDs for each timer. If we create a Vue component, all it’s logic is encapsulated and connected to that specific instance of the component. We can easily create 10, 20, 1,000 timers on a single document without changing a single line in the component itself!

Here’s the same timer we created together in the last post, but in Vue.

Template and styles

From the Vue docs:

Vue uses an HTML-based template syntax that allows you to declaratively bind the rendered DOM to the underlying Vue instance’s data. All Vue.js templates are valid HTML that can be parsed by spec-compliant browsers and HTML parsers.

Let’s create our component by opening a new file called BaseTimer.vue. Here’s the basic structure we need for that:

// Our template markup will go here <template> // ... </template> // Our functional scripts will go here <script> // ... </script> // Our styling will go here <style> // ... </style>

In this step, we will concentrate on the <template> and <style> sections. Let’s move our timer template to the <template> section and all our CSS to <style> section. The markup mostly consists of SVG and we can use the exact same code we used from the first article.

<template> // The wrapper for the timer <div class="base-timer"> // This all comes from the first article <svg class="base-timer__svg" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"> <g class="base-timer__circle"> <circle class="base-timer__path-elapsed" cx="50" cy="50" r="45"></circle> <path id="base-timer-path-remaining" stroke-dasharray="283" class="base-timer__path-remaining ${remainingPathColor}" d=" M 50, 50 m -45, 0 a 45,45 0 1,0 90,0 a 45,45 0 1,0 -90,0 " ></path> </g> </svg> // The label showing the remaining time <span id="base-timer-label" class="base-timer__label" > ${formatTime(timeLeft)} </span> </div> </template> // "scoped" means these styles will not leak out to other elements on the page <style scoped> .base-timer { position: relative; width: 100px; height: 100px; } </style>

Let’s have a look at the template we just copied to identify where we can use our framework. There are few parts that are responsible for making our timer count down the time and show the remaining time.

  • stroke-dasharray: A value passed to the SVG <path> element that is responsible for holding the remaining time.
  • remainingPathColor: A CSS class responsible for changing the color of the timer’s circular ring, giving is a way to visually indicate that time is running out.
  • formatTime(timeLeft): A value responsible for showing how much time is left inside the timer

We can control our timer by manipulating those values.

Constants and variables

OK, let’s go down to our <script> section and see what Vue gives us out of the box to make our life easier. One thing it lets us do is define our constants up front, which keeps them scoped to the component.

In the last post, we spent a little time tweaking the stroke-dasharray  value to make sure the animation of the timer’s top layer (the ring that animates and changes color as time progresses) is perfectly in line with its bottom layer (the gray ring that indicates past time). We also defined “thresholds” for when the top layer should change colors (orange at 10 remaining seconds and red at five seconds). We also created constants for those colors.

We can move all of those directly into the <script> section:

<script> // A value we had to play with a bit to get right const FULL_DASH_ARRAY = 283; // When the timer should change from green to orange const WARNING_THRESHOLD = 10; // When the timer should change from orange to red const ALERT_THRESHOLD = 5; // The actual colors to use at the info, warning and alert threshholds const COLOR_CODES = { info: { color: "green" }, warning: { color: "orange", threshold: WARNING_THRESHOLD }, alert: { color: "red", threshold: ALERT_THRESHOLD } }; // The timer's starting point const TIME_LIMIT = 20; </script>

Now, let’s have a look at our variables:

let timePassed = 0; let timeLeft = TIME_LIMIT; let timerInterval = null; let remainingPathColor = COLOR_CODES.info.color;

We can identify two different types of variables here:

  1. Variables in which the values are directly re-assigned in our methods:
    • timerInterval: Changes when we start or stop the timer
    • timePassed: Changes each second when the timer is running
  2. Variables in which the values change when other variables change:
    • timeLeft: Changes when the value of timePassed changes
    • remainingPathColor: Changes when the value of timeLeft breaches the specified threshold

It is essential to identify that difference between those two types as it allows us to use different features of the framework. Let’s go through each of the type separately.

Variables in which values are directly re-assigned

Let’s think what we want to happen when we change the timePassed value. We want to calculate how much time is left, check if we should change the top ring’s color, and trigger re-render on a part of our view with new values. 

Vue comes with its own reactivity system that updates the view to match the new values of specific properties. To add a property to Vue’s reactivity system we need to declare that property on a data object in our component. By doing that,Vue will create a getter and a setter for each property that will track changes in that property and respond accordingly.

<script> // Same as before export default { data() { return { timePassed: 0, timerInterval: null }; } </script>

There are two important things we need to remember.

  1. We need to declare all reactive variables in our data object up front. That means if we know that a variable will exist but we don’t know what the value will be, we still need to declare it with some value. If we forgot to declare it in data it will not be reactive, even if it is added later.
  2. When declaring our data option object, we always need to return a new object instance (using return). This is vital because, if we don’t follow this rule, the declared properties will be shared between all instances of the component.

You can see that second issue in action:

Variables in which values change when other variable change

These variables rely on the value of another variable. For example, timeLeft relies purely on timePassed. In our original example that uses vanilla JavaScript, we were calculating that value in the interval that was responsible for changing the value of timePassed. With Vue, we can extract that value to a computed property.

A computed property is a function that returns a value. These values are bound to the dependency values and only update when required. Even more importantly, computed properties are cached, meaning they remember the values that the computed property depends on and calculate the new value only if that dependent property value changed. If the value does not change, the previously cached value is returned.

<script> // Same as before computed: { timeLeft() { return TIME_LIMIT - this.timePassed; } } } </script>

The function passed to the computed property must be a pure function. It can’t cause any side effects and must return a value. Also, the output value must only be dependent on the values passed into the function.

Now, we can move more logic to computed properties:

  • circleDasharray: This returns a value previously that is calculated in the setCircleDasharray method.
  • formattedTimeLeft: This returns a value from the formatTime method.
  • timeFraction: This is an abstraction of the calculateTimeFraction method.
  • remainingPathColor: This is an abstraction of the setRemainingPathColor method.
<script> // Same as before computed: { circleDasharray() { return `${(this.timeFraction * FULL_DASH_ARRAY).toFixed(0)} 283`; }, formattedTimeLeft() { const timeLeft = this.timeLeft; const minutes = Math.floor(timeLeft / 60); let seconds = timeLeft % 60; if (seconds < 10) { seconds = `0${seconds}`; } return `${minutes}:${seconds}`; }, timeLeft() { return TIME_LIMIT - this.timePassed; }, timeFraction() { const rawTimeFraction = this.timeLeft / TIME_LIMIT; return rawTimeFraction - (1 / TIME_LIMIT) * (1 - rawTimeFraction); }, remainingPathColor() { const { alert, warning, info } = COLOR_CODES; if (this.timeLeft <= alert.threshold) { return alert.color; } else if (this.timeLeft <= warning.threshold) { return warning.color; } else { return info.color; } } } </script>

We now have all the values we need! But now we need to put them to use in our template.

Using data and computed properties in the template

Here’s where we left off with our template:

<template> <div class="base-timer"> <svg class="base-timer__svg" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"> <g class="base-timer__circle"> <circle class="base-timer__path-elapsed" cx="50" cy="50" r="45"></circle> <path id="base-timer-path-remaining" stroke-dasharray="283" class="base-timer__path-remaining ${remainingPathColor}" d=" M 50, 50 m -45, 0 a 45,45 0 1,0 90,0 a 45,45 0 1,0 -90,0 " ></path> </g> </svg> <span id="base-timer-label" class="base-timer__label" > ${formatTime(timeLeft)} </span> </div> </template>

Let’s start with formatTime(timeLeft). How we can dynamically bind the rendered value to our formattedTimeLeftcomputed property?

Vue uses HTML-based template syntax that allowsus to declaratively bind the rendered DOM to the underlying data of the Vue instance. That means all properties are available in the template section. To render any of them, we use text interpolation using the “Mustache” syntax (double curly braces, or {{ }}).

<span id="base-timer-label" class="base-timer__label" > {{ formattedTimeLeft }} </span>

Next will be stroke-dasharray. We can see we don’t want to render that value. Instead, we want to change the value of the <path> attribute. Mustache cannot be used inside HTML attributes, but fear not! Vue comes with another way: the v-bind directive. We can bind a value to an attribute like this:

<path v-bind:stroke-dasharray="circleDasharray"></path>

To facilitate the usage of that directive, we can also use a shorthand.

<path :stroke-dasharray="circleDasharray"></path>

The last one is remainingPathColor, which adds a proper class to an element. We can do that using the same v-bind directive as above, but assign the value to the class attribute of an element.

<path :class="remainingPathColor"></path>

Let’s have a look at our template after changes.

<template> <div class="base-timer"> <svg class="base-timer__svg" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"> <g class="base-timer__circle"> <circle class="base-timer__path-elapsed" cx="50" cy="50" r="45"></circle> <path :stroke-dasharray="circleDasharray" class="base-timer__path-remaining" :class="remainingPathColor" d=" M 50, 50 m -45, 0 a 45,45 0 1,0 90,0 a 45,45 0 1,0 -90,0 " ></path> </g> </svg> <span class="base-timer__label">{{ formattedTimeLeft }}</span> </div> </template>

We have our template ready, we moved all variables to data or computed, and we got rid off most of the methods by creating corresponding computed properties. We are still missing one vital part, though: we need to start our timer.

Methods and component lifecycle hooks

If we look at our startTimer method, we can see that all the calculations, changes in attributes, etc. happen in the interval.

function startTimer() { timerInterval = setInterval(() => { timePassed = timePassed += 1; timeLeft = TIME_LIMIT - timePassed; document.getElementById("base-timer-label").innerHTML = formatTime( timeLeft ); setCircleDasharray(); setRemainingPathColor(timeLeft); if (timeLeft === 0) { onTimesUp(); } }, 1000); }

Since we’ve already moved all that logic into the computed property, all we need to do in our timerInterval is change the value of timePassed — the rest will happen magically in the computed properties

<script> // Same as before methods: { startTimer() { this.timerInterval = setInterval(() => (this.timePassed += 1), 1000); } } </script>

We have the method ready, but we still don’t call it anywhere. Each Vue component comes with a series of hooks that allows us to run a specific logic within a specific period of the component’s lifecycle. These are called lifecycle hooks. In our case, as we want to call our method immediately when the component gets loaded. That makes mounted the lifecycle hook what we want.

<script> // Same as before mounted() { this.startTimer(); }, // Same methods as before </script>

That’s it, we just turned our timer into a consistent and reusable component using Vue!

Let's say we now want to use this component in another component. That requires a few things:

  1. First, we import the component.
  2. Next, we register the component.
  3. Finally, we instantiate the component in the template.
// App.vue import BaseTimer from "./components/BaseTimer" export default { components: { BaseTimer } }; That’s a wrap!

This example shows how we can move a component from vanilla JavaScript to a component-based front-end framework, like Vue. 

We can now treat the timer as a standalone component where all the markup, logic and styling is contained in a way that won’t leak out to or conflict with other elements. Components are often children of a larger parent component that assembles multiple components together — like a form or perhaps a card — where the parent’s properties can be accessed and shared. Here’s an example of the timer component where it’s taking orders from a parent component

I hope I got you interested in Vue and the power of components! I’d encourage you to go to Vue docs to get more detailed description of the features we used in our example. There’s so much Vue can do!

The post Moving from Vanilla JavaScript to a Reusable Vue Component appeared first on CSS-Tricks.

Blame the implementation, not the technique

Css Tricks - Mon, 02/17/2020 - 5:42am

I'm not sure we've gotten much better at this since Tim Kadlec wrote this in 2012:

Stop me if you’ve heard this one before.

“Responsive design is bad for performance.”
“User agent detection is bad. Don’t segment the web.”
“Hybrid apps don’t work as well as native apps.”
“CSS preprocessors shouldn’t be used because they create bloated CSS.”

... Find out for yourself if the tool is really where the blame should be placed.

I'm sure there is some psychological concept that explains why we transfer blame from the offending thing to what we perceive to be the cause.

Sometimes we're good at this. Remember the AMP letter:

The AMP format is not in itself, a problem, but two aspects of its implementation...

Or the fact that accessibility issues aren't React's fault. Pointing at the tools makes it harder to talk about the real problems that need to be resolved.

Sometimes I'm not so good at this. I'm linking to Tim here in an effort to help me remember this.

Direct Link to ArticlePermalink

The post Blame the implementation, not the technique appeared first on CSS-Tricks.

Listen to your web pages

Css Tricks - Sun, 02/16/2020 - 10:50am

A clever idea from Tom Hicks combining MutationObserver (which can "observe" changes to elements like when their attributes, text, or children change) and the Web Audio API for creating sounds. Plop this code into the console on a page where you'd like to listen to essentially any DOM change to hear it doing stuff.

I played with it on my serverless site because it's an SPA so there is plenty of DOM activity as you navigate around.

const audioCtx = new (window.AudioContext || window.webkitAudioContext)() const observer = new MutationObserver(function(mutationsList) { const oscillator = audioCtx.createOscillator() oscillator.connect(audioCtx.destination) oscillator.type = "sine" oscillator.frequency.setValueAtTime( Math.log(mutationsList.length + 5) * 880, audioCtx.currentTime, ) oscillator.start() oscillator.stop(audioCtx.currentTime + 0.01) }) observer.observe(document, { attributes: true, childList: true, subtree: true, characterData: true, })

Looks like Tom is experimenting with other audio... what should we call them? Auralizations? Like this sweep-swoop one. There is already a browser extension for it, which includes sounds for network activity happening.

Direct Link to ArticlePermalink

The post Listen to your web pages appeared first on CSS-Tricks.

“CSS4” Update

Css Tricks - Sat, 02/15/2020 - 1:16pm

Since I first chimed in on the CSS4¹ thing, there's been tons of more discussion on it. I'm going to round up my favorite thoughts from others here. There is an overwhelming amount of talk about this, so I'm going to distill it here down as far as I can, hopefully making it easier to follow.

  • Jen Simmons kicked off a discussion on GitHub under the CSS Working Group draft repo. The best we have for developers trying to stay current on CSS right now is "just keep up" and that's not working. Better to "draw a line" around certain things and say "Here, this. This part is ready. This part is done."
  • Michelle Barker says it's hard to prioritize what to learn without guidance.
  • Nicole Sullivan thinks if developers can "tick the checkbox" of CSS4, it would "drive developers to upgrade their skills, businesses to upgrade their tech stacks, and browsers to focus on cross-browser compat."
  • Dave Rupert sees value in the Perceived Velocity through Version Numbers.
  • Natalya Shelburne says that, in "a time-scarce world", engineers she works with don't feel like new CSS is production-ready and don't prioritize learning it.
  • Rachel Andrew thinks that the browser support of features, particularly the complex and nuanced nature of subfeatures, is a barrier. Since we don't know anyone else's browser support requirements, it's irresponsible to suggest blanket features to learn/use.
  • Brian Kardell brought up that JavaScript is "versioned" yearly and it seems to work over there (Although Shawn Wang rightly mentioned that we should probably be asking them what has worked and what hasn't). Personally, I prefer the idea of CSS2020 over CSS4 (I just like the synchronicity with JavaScript), and Brain Kardell thinks just because we use a year in the name doesn't mean we have to do it every year. Chen Hui Jing mentioned that Babel throws a wrench in things a bit. Polyfilling new JavaScript is a different beast than polyfilling new CSS and that affects expectations.
  • Miriam thinks we need to settle on a list of features, but that it should be criteria-based. I suppose criteria would be status of spec, browser support ("2 major browsers" is the most common theme), and some kind of importance modifier. My first article took a stab at a feature list and here's another thought. PPK agrees that flexbox is too old, and favors grid and custom properties. Timothy Miller has a few ideas.
  • fantasai doesn't think this message should come from the CSSWG.
  1. The more we keep calling it CSS4, the harder it will be to call it anything else. If that's how it shakes out, fine, but my preference is to mimic JavaScript "versioning". I'm going to stop calling it CSS4 after this until a name settles in better.

The post “CSS4” Update appeared first on CSS-Tricks.

What is CSS4?

QuirksBlog - Tue, 02/11/2020 - 1:27am

If we want CSS4 to be a thing it is necessary to define it a little more. However, that does not mean it’s necessary to divide all existing CSS modules into CSS3 and CSS4 buckets. This article discusses these issues, and goes through some of the feedback to my original article.

Responses

My article led to a few responses. Louis Lazaris disagrees with me, and thinks we should stick to the individual modules, as we do today. I leave it to Dan Q to summarise my disagreement:

Nobody’s going to buy a book that promises to teach them “CSS3 Selectors Level 3, Fonts Level 3, Writing Modes Level 3, and Containment Level 1”: that title’s not even going to fit on the cover. But if we wrapped up a snapshot of what’s current and called it CSS4… now that’s going to sell.

In a comment, Ilya Streltsin points out that there are too many CSS modules, which makes them less suitable for high-level teaching and marketing. A list of twelve modules is inherently more boring than “CSS4.”

Still, Louis isn’t entirely wrong. We need to say something about what CSS4 is. Chris Coyier and Timothy Miller have some ideas that I’ll get back to below.

I think we should pick two or so modules that would become poster children for “CSS4,” mostly to raise awareness and enthusiasm among web developers who don’t follow CSS too closely.

CSS4 is undefined

Picking two or so modules is not the same as going through all of CSS and deciding which parts are CSS3 and which are CSS4. Therefore Johan Ronsse’s fears are unfounded:

As a teacher of sorts, I for one don’t want to explain the difference between CSS3 and CSS4 to junior web devs. There is simply no point. CSS is just CSS. We should be happy that it’s stable. We should be happy that we dropped the 3.

CSS modules are not CSS3 or CSS4; they’re just CSS. The term “CSS4” is meant to draw people to your teaching, but once you have their attenton you largely drop the term and just teach them what they need to know.

Instead of attempting to define it, we should airily refer to CSS4 but be rather vague about what it means exactly. That allows people to project their own feelings and ideas onto it. CSS4 is here, and it means whatever you want it to mean. Now come and learn. It’s cool!

Remember: this is a marketing exercise; not a technical description of CSS.

Setting minds at ease

Nonetheless, in order to prove that CSS4 is cool we need a few example modules. “Learn CSS4! It allows you to do X and Y!” Which modules should we pick?

When I was a history teacher, long ago, I learned one valuable trick: when writing tests for students, always start with a simple question that all of them know the answer to. The purpose is to put the students’ minds at ease and make them feel they know at least something about the topic.

For instance, if they have to learn the chapter on the French Revolution, start the test by asking “In which year did the French Revolution take place?” The chapter prominently states the answer, 1789, in the first two paragraphs, so you can be reasonably certain that almost all students have actually learned this by heart. So they’ll sigh a breath of relief, give the correct answer, and gain more confidence for the rest of the test.

I feel we should do something similar f0r CSS4. One of the modules we tout as “CSS4” should be one that even people who’re not all that good in CSS have used and know a little bit. That’ll make them feel that they already know something about the subject, so the rest shouldn’t be too hard. It will draw them in.

CSS4 — the known parts

Chris Coyier created a draft list, and Timothy Miller added a few ideas. The first module Chris mentions is Flexbox, but he notes it may be too old. Sadly, I must concur. Flexbox would have been a brilliant “set-your-mind-at-ease-CSS4-is-not-as-hard-as-you-think” module, because almost everyone has heard of it, and plenty of people who are otherwise not terribly into CSS have used it.

Unfortunately it’s really too old: if we’d use it as our poster child people might lose the suspension of disbelief that’s necessary for the CSS4 trick to work. “Huh? Flexbox? But that’s been around for ages!”

Grid is the obvious next option. It’s not too old, and some people have heard of it and use it. On the other hand, I learned doing research for the book that there are some web developers who feel they don’t have to learn Grid because they already know Flexbox. And both are for layout, right? So why learn two?

As an argument this does not make any sense, but the fact that this ideas is floating out there means using Grid as a poster child might backfire. (I’m not sure; I’m just guessing here. But my gut feeling says that Grid is the wrong module.) Update: I changed my mind: grid should definitely be part of CSS4.

For now I settled on CSS custom properties (or variables) as my choice. They are in use, but they’re not yet old news. More importantly, custom properties allow local scope in CSS, and that is quite important to JavaScripters and might draw them to the right articles and teachings.

I’m not quite sure yet if this is the best module — I’m open to arguments. But I have to say something, and this is where I stand right now.

CSS4 — the unknown parts

In addition to the well-known set-your-mind-at-ease module we should also have soemthing that’s really, really new. The idea would be to tout two modules as “CSS4.” People would lose their fear after recognizing custom properties, while the other module would intrigue them, and they’d be excited to learn about it.

But which module? I’m not sure. Chris mentions Houdini, CSS nesting, variable fonts, and offset paths as possibilities. Timothy adds media queries level 4 to the list. Chris also says:

Lemme just say I will personally spearhead this thing if container queries can get done and we make that a part of it.

Container queries would be suitable. Developers who predominantly use JavaScript would like to have them.

Unfortunately they’re not there yet. So although they’re an excellent choice for a future module (“CSS4 will eventually include container queries”), they won’t help us right now.

So ... does anyone have a useful suggestion for a new CSS module that is not well known yet, applies to more than just a subset of CSS, and that we can start teaching right now? (Lack of general applicability is my problem with variable fonts and offset paths.)

Houdini? I’m afraid its complexity will detract from learning simple CSS, so I’m not sold. Level 4 media queries? I feel there’s too little difference with level 3.

So I don’t know right now. I’ll continue to think about this, and meanwhile I’d appreciate hints and ideas.

New browser on the block: Flow

QuirksBlog - Tue, 01/21/2020 - 1:01am

2020 is only three weeks old, but there has been a lot of browser news that decreases rendering engine diversity. It’s time for some good news on that front: a new rendering engine, Flow. Below I conduct an interview with Piers Wombwell, Flow’s lead developer.

p.q { font-weight: 600; }

This year alone, on the negative side Mozilla announced it’s laying off 70 people, most of whom appear to come from the browser side of things, while it turns out that Opera’s main cash cow is now providing loans in Kenya, India, and Nigeria, and it is looking to use 'improved credit scoring' (from browsing data?) for its business practices.

On the positive side, the Chromium-based Edge is here, and it looks good. Still, rendering engine diversity took a hit, as we knew it would ever since the announcement.

So let’s up the diversity a notch by welcoming a new rendering engine to the desktop space. British company Ekioh is working on a the Flow browser, which sports a completely new multi-threaded rendering engine that does not have any relation to WebKit, Gecko, or Blink.

The last new rendering engine to come to the desktop was KHTML back in 2000 in the form of the Konqueror browser. Later Apple adapted KHTML into WebKit. And then Google forked WebKit to become Blink. And ... well, almost everyone browses with a KHTML descendant now. Let’s not forget how it all began.

It is far too early to tell if Flow will have a similar impact, but the news was reason enough for me to conduct an interview with lead developer Piers Wombwell.

PPK: Hi Piers, could you please introduce yourself?

PW: I’m Piers Wombwell, the co-founder of Ekioh, the company behind the Flow browser. I’m also the architect of the project and one of the software engineers on it.

Why did Ekioh decide to create a new browser?

In 2006 we started developing an SVG engine for user interfaces in the set-top box market. No existing browser was full-featured, or was fast enough on the low-powered set-top box chips available at the time. User interface developers wanted HTML, but couldn’t get the performance they needed, especially in animations. SVG seemed better suited to user interfaces as there was no time spent in complex box model layout.

A user interface running on our SVG engine was much faster than any of the HTML browsers at the time and was very popular in this niche market with millions of STBs running it across most continents.

Over the next six or so years, STB chips started to move to multi-core GPUs, at the same as TV resolutions were moving to 4K. HTML was becoming fast enough on set-top boxes. On the other hand, a 4K TV has four times as many pixels as an HD TV, and a multi-core GPU doesn't make each individual core any faster. Thus, a single threaded browser won’t really see any significant speed improvements. That's why we decided to make Flow multi-threaded.

Dabbling with HTML/CSS layout seemed equally fun technically as building an SVG browser, so that’s been the main focus since. It started off being an XHTML/CSS layout engine on top of SVG, but we got carried away and over time moved to full HTML.

But, really, I suppose we did it because it would be fun to do it.

How far along is Flow? Can people download it and use it right now?

Well, it can render and interact with Gmail quite well. It’s pretty much perfect on a few sites we’ve targeted as focuses during development, but it struggles with many others. We only started implementing HTML forms in the last few months in order to log into Gmail.

It’s not yet available for download as I think we need to address the usability of it first. It currently needs a configuration file tailored to your computer, and has no toolbar. You don't want a toolbar for TV interfaces, so we never implemented one.

For which platforms is Flow currently available?

For Mac, Linux, and Android. Plus, of course, for the set-top boxes that are our main market, most of which run Linux. As to Windows, none of us run Windows so its development is untested and lags behind a bit, but I’ve just compiled a version and it seems to work.

Is Flow open source?

It’s not. There’s no current plan for that as we don’t have a large corporation backing our development.

Which JavaScript engine do you use?

We chose Spidermonkey in 2006, and as far as I recall it was because of both licensing and a documented embedding API. It was around the time that TiVo were having arguments over the GPL. The paranoia over that also ruled out use of LGPL licensed libraries for a few years.

The core browser code is abstracted away from any Spidermonkey APIs, largely so we could handle upgrades over the years - we can still handle its legacy garbage collection model quite happily.

What are your long-term goals with Flow?

The primary goal is stability, followed by getting more websites rendering perfectly in Flow. They generally fail because of either layout bugs or missing JavaScript APIs in Flow, so we have to solve those. Even for the embedded market, getting as many websites working as possible improves our confidence that a new HTML user interface will function correctly, first time.

Our roadmap is very flexible, usually because of commercial needs, but also we prioritise what’s interesting to a developer at that given time.

You said Flow is multi-threaded. Which tasks exactly are divided among the multiple threads?

HTML and CSS parsing is single-threaded, as is JavaScript (if you ignore WebWorkers). It’s the layout, primarily word wrap of text, that is done in parallel. Several caveats apply, but in general, two paragraphs can be laid out in parallel since they don’t impact each other apart from their vertical position.

We wrote some technical papers on this process.

Is the word wrap of paragraphs the computationally most expensive part of laying out an HTML page?

Yes. Each letter is a separate rectangle, plus you have word wrap rules for groups of letters. It’s also probably the hardest to achieve, so it's a good place to start. Desktop browsers haven’t touched layout, and have instead concentrated on making whole components run in separate threads.

Is Flexbox one of the caveats you mentioned?

There are multiple passes across the tree, all in parallel. We first calculate, in parallel, essentially the min-content and max-content widths of each paragraph, flexbox or table cell. Once we have those constraints, a relatively quick pass (not in parallel for that one flex box) works out the final widths of each box.

But we can handle multiple flexboxes in parallel, or one flex box and a paragraph outside the flexbox, and so on.

How integral is multi-threading to Flow and its architecture? Could you remove it? Would other browsers be able to copy Flow's multi-threading?

Multithreading can be turned off with a config setting. I suspect it’s always going to be easier to rewrite the layout code with multithreading in mind than rework existing layout algorithms - Mozilla took that approach that with Servo, rather than rework Gecko. The new layout engine could then, in theory, be combined with the rest of an existing browser.

Can you give an example of tricky problems you encountered while creating this browser?

Many sites, Gmail being a good example, were very frustrating as the JavaScript can be so large and obfuscated. It’s almost impossible to tell what they are doing, and much of the debugging was educated guesses as to what it was trying to do. Thankfully, the web platform tests help us make sure we are compatible with other browsers once we figured out the blocking bug or missing feature.

We can’t realistically pass these tests 100% as they test such a huge set of APIs - it would take us years to catch up with other browsers so we can only focus on what is used by priority websites.

And something that was much easier to implement than you thought?

The HTML parser. I first wrote an HTML parser back in 2002, and back then there was no detailed specification of how to handle badly-nested elements. We spent so much time writing test cases to figure out what desktop browsers did in each situation, and trying to behave the same. Ten years later, the detail in the WHAT-WG specification was amazing, and it was perfectly possible to write an HTML parser that is completely compatible with all other browsers.

And a feature you decided not to implement for now?

HTML forms. A TV user interface doesn’t use most, if any, of the features of HTML forms so it was a very low priority. We started adding them because they are needed for general web browsing, but they are not complete.

We haven’t yet implemented WebGL or IndexedDB because they are not used on most of the websites we’ve tried. Obviously Google Maps uses WebGL and Google Docs uses IndexedDB but both have fallbacks. Implementing more features to allow a larger number of websites to work is a priority.

What is Flow's UA string?

For the Mac version, it's the following: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_0) EkiohFlow/5.7.4.30559 Flow/5.7.4 (like Gecko Firefox/53.0 rv:53.0)

The strings vary depending on the device, but the "EkiohFlow" and "Flow" strings should always occur.

Why do you emulate Firefox? I assumed it'd be Chrome.

We’ve spent ages on that UA string… I could probably write a blog post about it. Essentially, I copied Chrome. Things mostly worked. Then I hit the Instagram site, which decided to use ES6 features based on the UA string. I changed it to FireFox’s, using the version of SpiderMonkey that we were using (53 in the build you have), and the site worked. Then I added more afterwards (the rc:53) to get us to the more modern Google login box.

The UA string isn’t final at all but its choice is full of compromises.

Ekioh creates browsers for set-top boxes. What is Flow’s main purpose on set-top boxes?

It is used to render the UIs created by the box’s vendors, and not for actually surfing the web. But we don’t always get to see the UIs the vendors create, so being able to render all HTML flawlessly is the goal. That way, UI developers can do as they please.

Does the average set-top box have a browser meant for surfing the web?

Sort-of, but not really. I have a 2012 Sony TV with that functionality, but it was useless then and is useless now. IR has a significant lag, and that makes TV remotes far too painful to control a TV browser with. I don’t recall any modern TV/STBs that let you have open internet, but they probably exist. I can’t imagine anyone seriously using them.

Flow also runs on TVs and embedded devices. Could you give a few examples of embedded browsers? And TV browsers?

Back before we started our SVG engine, there were many HTML 4 browser engines for the TV market, such as ANT Fresco and Galio (which I also worked on), Access’s NetFront, Oregan, Espial and Opera. For the non-TV market, we have replaced Internet Explorer Mobile on a line of Windows CE devices. These days, almost all embedded browsers are based on Blink or WebKit.

What are your main competitors in the TV and embedded browser markets?

The main competitors to Flow are Blink and WebKit. Most STB providers often do their own port of one of these browsers. WebKit can be optimised for these low-powered devices, but Flow is usually able to out perform other browsers, and in the areas it’s not as fast, we can usually optimise it.

In a strange way, we also compete with ourselves - we offer our own embedded WebKit-based browser that is more feature-complete than Flow. The same developers work on maintaining and improving that.

Thanks for this interview!

You’re welcome.

CSS4 is here!

QuirksBlog - Wed, 01/15/2020 - 5:38am

I think that CSS would be greatly helped if we solemnly state that “CSS4 is here!” In this post I’ll try to convince you of my viewpoint.

I am proposing that we web developers, supported by the W3C CSS WG, start saying “CSS4 is here!” and excitedly chatter about how it will hit the market any moment now and transform the practice of CSS.

Of course “CSS4” has no technical meaning whatsoever. All current CSS specifications have their own specific versions ranging from 1 to 4, but CSS as a whole does not have a version, and it doesn’t need one, either.

Regardless of what we say or do, CSS 4 will not hit the market and will not transform anything. It also does not describe any technical reality.

Then why do it? For the marketing effect.

I think that announcing a new CSS version will bring desperately-needed attention to CSS, and will help the people evangelising CSS in the field make an impression on web developers who are otherwise not very interested in it.

Web developers are profoundly influenced by the cult of the new. The best way of getting their attention is announcing a new version of something, and that’s exactly what we would be doing here.

I have been given to understand that the CSS WG might be willing to support this narrative and confirm the existence of CSS4 — if we web developers can make a strong enough case that it will be beneficial to CSS. Today I am starting to build such a case.

Head, torso, long tail

Influenced by Ilya Grigorik’s excellent performance.now() presentation, I segment web developers into a head, a torso, and a long tail. CSS4 would help the latter two groups.

  • The head consists of clued-in web developers who closely follow what’s going on in our field
  • The torso consists of those that have some clue what they’re doing and read up on their craft from time to time
  • The long tail covers a wide range of web developers, from those who are fairly close to the torso to those that churn out interminable sites without understanding why using 7.8M of JavaScript per site is a really bad idea.

(These segments form by self-selection. Any web developer can become part of the head, but it takes more effort than being part of the long tail. Not everyone feels the need to put in that extra effort, and thus the three segments come into being.)

CSS is, and has always been, part of the head’s world. The head does not need CSS4 because it already follows CSS closely, goes to conferences, reads blogposts and articles about upcoming features, and knows what’s happening.

The torso does this to a much lesser extent, and the long tail doesn’t care one way or the other — they found all the answers when they selected their current toolchain. It’s them that we seek to influence by announcing CSS4.

In practice, all current outreach efforts such as conference presentations and blog posts or articles are aimed at the head. Not that the torso or long tail wouldn’t understand them, but they generally don’t seek them out. I would like to give them an incentive to do so.

In my opinion, “CSS4 is here!” would provide that incentive.

What do you think?

If you read this blog post you likely belong to the head. Based on your own experience you might not see the point of CSS4 because it doesn’t solve your problems. But CSS4 is not aimed at you; it’s aimed at the torso and the long tail.

When considering the pros and cons of CSS4, don’t reason from your own experience. Please put yourself in the shoes of someone whose time is limited, or who has never learned to pay a lot of attention to technical evangelisation. Will they be helped?

On the other hand, if your job, or your passion, includes evangelising CSS, you should definitely reason from your own experience. Will slapping on a “CSS4 compatible” tag help you?

So these are today’s questions. Will the announcement of CSS4 — new! shiny! cool! — spur on the torso and long tail to learn more about CSS? Will it help those who are already putting a lot of time and sweat into technical articles and presentations to reach a wider audience? Will it make a difference?

I think it will.

What do you think?

And if you agree with me, would you be willing to write something about it? That would show the CSS WG that there is developer support for this idea.

Wed, 12/31/1969 - 2:00pm
Syndicate content
©2003 - Present Akamai Design & Development.