When it comes to rendering on somebody else's DOM, you can't naively write HTML and CSS like you might for your own self-contained web application. You've got to think carefully about how preexisting CSS and JavaScript code might affect your application.
Before you begin to write any HTML or CSS, you'll have to make an important decision regarding the look and feel of your application. Do you want your application to look the same everywhere? Or do you want the application to inherit the native look and feel of the page on which it's hosted? Your answer will have a profound effect on your strategy for rendering your app.
One thing is constant: at some level, you'll be practicing what we call defensive rendering. By defensive, we mean taking steps to output HTML and CSS that minimize the impact of the parent page on your application. The less you want your widget impacted by the parent page, the more steps you'll have to take. These steps can be as small as namespacing your HTML and CSS to reduce name conflicts, or overspecifying your CSS rules so they take priority over rules from the parent page. For widgets that want complete immunity from the parent page, it could also mean serving your widget on a completely separate DOM, embedded in an iframe.
We'll focus on rendering HTML and CSS that live on the same DOM as the publisher's page. For widgets that aim to offer some level of customization, this can be the most flexible solution for publishers, since the publisher can easily target your elements and style them to their preferences.
Unfortunately, this is also the downside. The publisher could unknowingly have CSS rules and/or JavaScript code that inadvertently target your widget and wreak havoc.
We'll look at a number of ways to shield your application's HTML and CSS from the publisher's code. First, you'll learn about HTML and CSS namespaces. Then, you'll learn about CSS specificity, and how the parent page's styles can override your own.
Finally, you'll learn techniques for overruling the page's parent styles, by overspecifying your CSS and abusing the !important keyword. First up, namespaces.
Namespaces
All DOM IDs, classes, data-* attributes, and matching CSS selectors have been prefixed with stork-. The purpose? To reduce the likelihood of those attributes conflicting with the parent page.
Consider the following situation. Your widget has a top-level <div> element that acts as a container. It does this by setting an explicit width and height, effectively bounding the area taken up by your widget. You've given this <div> a straightforward class name, container, which matches a style rule in your accompanying CSS:
<div>
...
</div>
<style>
.container { width: 200px; height: 200px; }
</style>
This might be perfectly appropriate for a regular stay-at-home application, but for a third-party app, it's a complete no-no. The reason? Such a generic class name has a good chance of already being used by the parent page. If you introduce this style rule, you might override an existing style rule put in place by the publisher and ruin their site layout. Or, on the flip side, their rule might override yours and resize your widget inadvertently.
The solution? Prefixing all of your class names (and other attributes) with an identifier unique to your application—a namespace. In the case of the Stork widget, the previous markup should be amended to look like this:
<div>
...
</div>
<style>
.stork-container { width: 200px; height: 200px }
</style>
The idea is that you namespace your JavaScript code so that you don't declare global objects that conflict with code running on the parent page. It extends to every piece of HTML you insert into the page: IDs, classes, data-* attributes, form names, and so on.
Namespacing HTML and CSS is a must for any third-party application that renders directly to the publisher's page. This isn't just necessary for preventing conflicting CSS rules; it's also conceivable that the parent page has JavaScript that's querying the DOM for elements whose identifying properties might match your own. Be rigorous in namespacing everything you put on the DOM.
CSS specificity
It's important to note that, though helpful, namespacing your HTML and CSS only prevents cases where the publisher is using styles or queries that reference attributes with the same name as yours. Unfortunately, your widget can still conflict with styles defined by the parent page, even if their CSS uses IDs, class names, and attributes that don't directly reference your elements. This is because some CSS rules are weighed more heavily by the browser, and can take precedence over seemingly unrelated rules you might define. This phenomenon is referred to as CSS specificity, and you'll need to understand it before you can safely render elements on the publisher's page.
Let's go back to the container example from the previous section on namespaces. Suppose the publisher's HTML has a top-level DIV that wraps all their content, with an ID of page:
<div id="page">
... <!-- Publisher content -->
<div>
... <!-- Stork content -->
</div>
</div>
Additionally, let's say the page has the following CSS, where the first rule is defined by the publisher, and the second rule, targeting stork-container, is added by your third-party script:
/* Publisher */
#page div {
background-color: green;
}
/* Camera Stork */
.stork-container {
background-color: blue;
}
Now, what color will .stork-container have? The answer might shock and appall you: green. In this simple example, the publisher rule (#page div) takes priority over your third-party application's class rule (.stork-container). This happens because the browser weighs rules containing IDs higher than those that target classes or attributes.
CSS rule priorities
The W3C CSS specification outlines how browsers are meant to prioritize different rule types. Here's a list of these rule types, ordered from highest priority to lowest:
- Inline styles (style="…")
- IDs
- Classes, attributes, and pseudo-classes (:focus, :hover)
- Elements (div, span, and so on) and pseudo-elements (:before, :after)
According to this chart, inline styles are weighed above all subsequent rule types: IDs, classes, and elements. This continues logically down the list, with IDs prioritized higher than classes and elements, and so on. There's one exception to this list: properties tagged with the !important keyword take highest priority. But note that the !important keyword affects a single property within a rule, not the entire rule.
What happens when you have multiple CSS rules of the same weight, each of which could conceivably affect the same element? Let's take a look at an example:
<div>
<span class="stork-msg">Eat your vegetables!</span>
</div>
<style>
.stork-container { background-color: blue; }
.stork-container span { background-color: red; }
.stork-container .stork-msg { background-color: yellow; }
</style>
What do you suppose the color of the span is? The answer again might be surprising: yellow. Even though these rules are all primarily class-based, the second rule (.storkcontainer span) is considered more specific than the first rule, and the third rule (.stork-container .stork-msg) more specific than the second. How does this work?
Inline styles are king
In terms of CSS specificity, that is. If you recall from earlier in this chapter, we mentioned that inline styles have the benefit of rarely conflicting with the parent page. Now it's clear why: they're prioritized over every other type of regular CSS rule (excluding those with the !important keyword). If you're writing a particularly simple widget, it might not be a bad idea to use inline styles; you'll avoid most CSS specificity conflicts.
The browser uses a simple scoring system to determine which rule takes priority. For a given rule, each selector composing that rule is worth a certain value. Those values are summed to create a specificity score. When multiple rules affect the same element, the browser compares each rule's specificity score, and the rule with the highest score takes priority. In the case of a tie, the rule that was defined last wins. Inline style attributes: 1000; IDs: 100; classes, pseudo-classes and attributes: 10, elements and pseudo-elements: 1.
So, looking back at our previous example, those CSS rules would have been assigned the following scores, with the highest-scoring rule being prioritized by the browser:You'll quickly notice these aren't ordinary numbers. A specificity score is actually a tuple of the form (a , b , c , d ), with a being more valuable than b , b being more valuable than c , and so on. That means that a style caused by a single inline style attribute (1, 0, 0, 0) has higher specificity than a rule with one hundred ID selectors (0, 100, 0, 0).
- .stork-container (0,0,1,0—one class selector)
- .stork-container span (0,0,1,1—one class selector, one element selector)
- .stork-container .stork-msg (0,0,2,0—two class selectors)
At this point, you should have a good handle on how CSS specificity works, and why the browser prioritizes some rules over others. You'll next put this knowledge to use, as we explore some approaches for writing CSS that stands tall in the face of conflicting publisher styles.
Overspecifying CSS
The first and simplest approach to writing CSS that doesn't conflict with the publisher's page is to overspecify your rules. This means declaring additional selectors to boost the specificity of your rules, such that when the browser compares your rules against those from the parent page, they're likely to score higher and be prioritized.
Let's look at this in practice. Consider this revised example of the Stork widget container, now sporting two container elements, each with a unique ID:
<div id="stork-main">
<div id="stork-container">
<h3>Mikon E90 Digital SLR</h3>
<img src="http://camerastork.com/img/products/1337-small.png"/>
<p>$599</p>
<p>4.3/5.0 • 176 Reviews</p>
</div>
</div>
The accompanying CSS for this HTML could then look like this:
#stork-main #stork-container { ... }
#stork-main #stork-container .stork-product { ... }
#stork-main #stork-container .stork-price { ... }
By redundantly specifying both container IDs as parent selectors of all your CSS rules, you're effectively giving each of your CSS rules a minimum specificity score of (0,2,0,0). Afterward, the publisher's generic #page rule from earlier will no longer conflict with your widget, because it only uses a single ID . Neither will any purely class- or element-based rules conflict, because those are an entire CSS weight class below ID s. Even though, for selection purposes, it's completely unnecessary to specify a second ID for your rules, here it works as an effective device for boosting specificity.
Preserve your sanity with a CSS preprocessor
Writing overspecified CSS can be a real drag: you have to constantly rewrite the same IDs over and over again for each one of your CSS rules. You can remedy this by using a CSS preprocessor, which extends the CSS language with additional features like the ability to declare nested hierarchies of rules. For example, using the LESS CSS preprocessor, you could write the previous example like this:
#stork-main {
#stork-container {
.stork-product { ... }
.stork-price { ... }
}
}
A number of popular CSS preprocessors are available today, all of which have varying feature sets. Among the most popular are LESS, Sass, and Stylus.
On the flip side, this example requires that your widget use top-level containers with IDs, which won't be practical for widgets that can be rendered multiple times on the same page. Additionally, it's still not bulletproof: a publisher could follow your lead and overspecify their own CSS rules, resulting in the same problem you had before.
But this is an unlikely scenario, especially since you've redundantly specified two IDs in each of the rules. You could alternatively use one, but this will of course be more vulnerable. The reality is that most publishers use sane CSS rules, and overspecifying your rules like this will be compatible with most of them.
Overspecifying CSS doesn't mix with code quality tools
If you take to overspecifying your CSS like this, you might find an unlikely enemy: tools that evaluate the quality of your CSS code, such as CSS Lint, Google Page Speed, and Yahoo's YSlow. These tools will indicate that you're making redundant CSS selectors, and they'll advise you to remove such selectors to reduce file size and improve browsers' CSS performance. Unfortunately, these tools aren't programmed with third-party scripts in mind, and don't fairly evaluate the usefulness of overspecifying CSS. The benefits of overspecification for third-party applications will outweigh the extra file size and minuscule performance hit.
Abusing !important
If you feel that overspecifying your CSS with extra ID or class selectors doesn't go far enough, you can break out the nuclear option: the !important keyword. Properties within a CSS rule that sport the !important keyword are prioritized highest of all, even above inline styles. This is because the !important keyword was designed to give browser users a surefire way to override "author" (publisher) styles, in the case of browser plugins or site-specific styles. You can abuse !important by using it on all of your CSS properties, effectively prioritizing them over all other rules.
Here's how you might use the !important keyword on a single CSS rule:
.stork-price {
font-size: 11px !important;
color: #888 !important;
text-decoration: none !important;
display: block !important;
}
Since it's per property, the !important keyword needs to be repeated like this, which can become a drag over a long and complex stylesheet. But, in exchange, you get a rock-solid set of stylesheets that are very unlikely to be reset by the publisher's page.
It's still conceivable that the publisher could in turn use !important to target your elements and set their own styles, at which point they're likely purposely targeting your elements for customization. On one hand, that can be frustrating if you're trying to maintain a consistent look and feel. But, if you've decided to allow publishers to customize your widget, this is probably desired behavior.
One thing should be clear: sharing the DOM with the publisher can make it particularly difficult to render a consistently styled widget. Although you can take steps to overspecify your CSS rules to reduce the likelihood of conflicts, it's always possible for the publisher to target your elements with their rules, either accidentally or purposely.
But if sharing the DOM with the publisher is what's causing so much grief, is it possible to render your widget off of the DOM? Why, yes—yes you can.
Summary
For a third-party JavaScript application, injecting HTML and CSS into the publisher's page requires more care than if you were adding markup to a "safe" environment. You must make sure that when outputting HTML to the page, you aren't slowing down the page with a blocking operation. You also have to consider that your script might get included multiple times on the same page, and it should render multiple instances gracefully. Additionally, you should choose an optimal method for injecting CSS into the publisher's page—either by inlining all your styles, appending link elements, or embedding CSS rules in your JavaScript.
But just getting HTML and CSS on the page isn't enough. You have to recognize that elements you introduce to the DOM can conflict with the parent page. You also must consider how your styles might conflict with existing styles defined by the publisher. You can use a number of techniques for reducing the impact of parent styles on your widget: by overspecifying your CSS rules or presenting your content behind an iframe, whether it's a src-less iframe or one that contains an external HTML document.
What techniques do you use when producing CSS and HTML for third parties? Do you ever fall back on !important? Let us know in the comments.
Featured image/thumbnail, defence image via Shutterstock.