Friday, 30 June 2017

Full Page Screenshots in Browsers

Five Huge CSS Milestones

CSS is over 20 years old now. I've only been using it for a little more than half that. In my experience, the biggest things to happen to CSS in that time were:

  1. Firebug
  2. Chrome
  3. CSS3
  4. Preprocessing
  5. Flexbox & Grid

And there is plenty more changes to come.

Direct Link to ArticlePermalink


Five Huge CSS Milestones is a post from CSS-Tricks



from CSS-Tricks http://ift.tt/2toY8rG
via IFTTT

Web Development Reading List #187: Webpack 3, Assisted Writing, And Automated Chrome Testing

How Content Can Succeed By Making Enemies - Whiteboard Friday

Posted by randfish

Getting readers on board with your ideas isn't the only way to achieve content success. Sometimes, stirring up a little controversy and earning a few rivals can work incredibly well — but there's certainly a right and a wrong way to do it. Rand details how to use the power of making enemies work to your advantage in today's Whiteboard Friday.

How content can succeed by making enemies

Click on the whiteboard image above to open a high-resolution version in a new tab!

Video Transcription

Howdy, Moz fans, and welcome to another edition of Whiteboard Friday. Today, we're going to chat about something a little interesting — how content can succeed by making enemies. I know you're thinking to yourself, "Wait a minute, I thought my job was to make friends with my content." Yes, and one of the best ways to make close friends is to make enemies too.

So, in my opinion, I think that companies and businesses, programs, organizations of all kinds, efforts of all kinds tend to do really well when they get people on their side. So if I'm trying to create a movement or I'm trying to get people to believe in what I'm doing, I need to have positions, data, stories, and content that can bring people to my site. One of the best ways to do that is actually to think about it in opposition to something else, basically try and figure out how you can earn some enemies.

A few examples of content that makes enemies & allies

I'll give you a few examples, because I think that will help add some context here. I did a little bit of research. My share data is from BuzzSumo, and my link data here is from Ahrefs. But for example, this piece called "There Are Now Twice as Many Solar Jobs as Coal Jobs in the US," this is essentially just data-driven content, but it clearly makes friends and enemies. It makes enemies with sort of this classic, old-school Americana belief set around how important coal jobs are, and it creates, through the enemy that it builds around that, simply by sharing data, it also creates allies, people who are on the side of this story, who want to share it and amplify it and have it reach its potential and reach more people.

Same is true here. So this is a story called "Yoga Is a Good Alternative to Physical Therapy." Clearly, it did extremely well, tens of thousands of shares and thousands of links, lots of ranking keywords for it. But it creates some enemies. Physical therapists are not going to be thrilled that this is the case. Despite the research behind it, this is frustrating for many of those folks. So you've created friends, allies, people who are yoga practitioners and yoga instructors. You've also created enemies, potentially those folks who don't believe that this might be the case despite what the research might show.

Third one, "The 50 Most Powerful Public Relations Firms in America," I think this was actually from The Observer. So they're writing in the UK, but they managed to rank for lots and lots of keywords around "best PR firms" and all those sorts of things. They have thousands of shares, thousands of links. I mean 11,000 links, that's darn impressive for a story of this nature. And they've created enemies. They've created enemies of all the people who are not in the 50 most powerful, who feel that they should be, and they've created allies of the people who are in there. They've also created some allies and enemies deeper inside the story, which you can check out.

"Replace Your Lawn with These Superior Alternatives," well, guess what? You have now created some enemies in the lawn care world and in the lawn supply world and in the passionate communities, very passionate communities, especially here in the United States, around people who sort of believe that homes should have lawns and nothing else, grass lawns in this case. This piece didn't do that well in terms of shares, but did phenomenally well in terms of links. This was on Lifehacker, and it ranks for all sorts of things, 11,000+ links.

Before you create, ask yourself: Who will help amplify this, and why?

So you can see that these might not be things that you naturally think of as earning enemies. But when you're creating content, if you can go through this exercise, I have this rule, that I've talked about many times over the years, for content success, especially content amplification success. That is before you ever create something, before you brainstorm the idea, come up with the title, come up with the content, before you do that, ask yourself: Who will help amplify this and why? Why will they help?

One of the great things about framing things in terms of who are my allies, the people on my side, and who are the enemies I'm going to create is that the "who" becomes much more clear. The people who support your ideas, your ethics, or your position, your logic, your data and want to help amplify that, those are people who are potential amplifiers. The people, the detractors, the enemies that you're going to build help you often to identify that group.

The "why" becomes much more clear too. The existence of that common enemy, the chance to show that you have support and beliefs in people, that's a powerful catalyst for that amplification, for the behavior you're attempting to drive in your community and your content consumers. I've found that thinking about it this way often gets content creators and SEOs in the right frame of mind to build stuff that can do really well.

Some dos and don'ts

Do... backup content with data

A few dos and don'ts if you're pursuing this path of content generation and ideation. Do back up as much as you can with facts and data, not just opinion. That should be relatively obvious, but it can be dangerous in this kind of world, as you go down this path, to not do that.

Do... convey a world view

I do suggest that you try and convey a world view, not necessarily if you're thinking on the political spectrum of like from all the way left to all the way right or those kinds of things. I think it's okay to convey a world view around it, but I would urge you to provide multiple angles of appeal.

So if you're saying, "Hey, you should replace your lawn with these superior alternatives," don't make it purely that it's about conservation and ecological health. You can also make it about financial responsibility. You can also make it about the ease with which you can care for these lawns versus other ones. So now it becomes something that appeals across a broader range of the spectrum.

Same thing with something like solar jobs versus coal jobs. If you can get it to be economically focused and you can give it a capitalist bent, you can potentially appeal to multiple ends of the ideological spectrum with that world view.

Do... collect input from notable parties

Third, I would urge you to get inputs from notable folks before you create and publish this content, especially if the issue that you're talking about is going to be culturally or socially or politically charged. Some of these fit into that. Yoga probably not so much, but potentially the solar jobs/coal jobs one, that might be something to run the actual content that you've created by some folks who are in the energy space so that they can help you along those lines, potentially the energy and the political space if you can.

Don't... be provocative just to be provocative

Some don'ts. I do not urge you and I'm not suggesting that you should create provocative content purely to be provocative. Instead, I'm urging you to think about the content that you create and how you angle it using this framing of mind rather than saying, "Okay, what could we say that would really piss people off?" That's not what I'm urging you to do. I'm urging you to say, "How can we take things that we already have, beliefs and positions, data, stories, whatever content and how do we angle them in such a way that we think about who are the enemies, who are the allies, how do we get that buy-in, how do we get that amplification?"

Don't... choose indefensible positions

Second, I would not choose enemies or positions that you can't defend against. So, for example, if you were considering a path that you think might get you into a world of litigious danger, you should probably stay away from that. Likewise, if your positions are relatively indefensible and you've talked to some folks in the field and done the dues and they're like, "I don't know about that," you might not want to pursue it.

Don't... give up on the first try

Third, do not give up if your first attempts in this sort of framing don't work. You should expect that you will have to, just like any other form of content, practice, iterate, and do this multiple times before you have success.

Don't... be unprofessional

Don't be unprofessional when you do this type of content. It can be a little bit tempting when you're framing things in terms of, "How do I make enemies out of this?" to get on the attack. That is not necessary. I think that actually content that builds enemies does so even better when it does it from a non-attack vector mode.

Don't... sweat the Haterade

Don't forget that if you're getting some Haterade for the content you create, a lot of people when they start drinking the Haterade online, they run. They think, "Okay, we've done something wrong." That's actually not the case. In my experience, that means you're doing something right. You're building something special. People don't tend to fight against and argue against ideas and people and organizations for no reason. They do so because they're a threat.

If you've created a threat to your enemies, you have also generally created something special for your allies and the people on your side. That means you're doing something right. In Moz's early days, I can tell you, back when we were called SEOmoz, for years and years and years we got all sorts of hate, and it was actually a pretty good sign that we were doing something right, that we were building something special.

So I look forward to your comments. I'd love to see any examples of stuff that you have as well, and we'll see you again next week for another edition of Whiteboard Friday. Take care.

Video transcription by Speechpad.com


Sign up for The Moz Top 10, a semimonthly mailer updating you on the top ten hottest pieces of SEO news, tips, and rad links uncovered by the Moz team. Think of it as your exclusive digest of stuff you don't have time to hunt down but want to read!



from The Moz Blog http://ift.tt/2stvf8K
via IFTTT

Wednesday, 28 June 2017

MozCon: Why You Should Attend & How to Get the Most Out of It

Posted by ronell-smith

MozCon 2013 (left to right): Greg Gifford, Nathan Bylof, Nathan Hammer, Susan Wenograd, and myself

I remember my first MozCon like it was yesterday.

It’s the place where I would hear the quote that would forever change the arc of my career.

“The world is freaking complicated, so let me start with everything I don’t know,” said Google’s Avinash Kaushik, during the Q&A, after speaking at MozCon 2013. “Nine hundred years from now, I will fix what’s broken today. …Get good at what you do.”

Though I didn’t know it at the the time, those were words I needed to hear, and that would lead me to make some career decisions I desperately needed to make. Decisions I never would have made if I hadn’t chosen to attend MozCon, the Super Bowl of marketing events (in my opinion).

Walking into the large (gigantic) room for the first time felt like being on the Space Mountain ride at Disneyland. I hurriedly raced to the front to find a seat so I could take in all of the action.

Once settled in, I sat back and enjoyed the music as lights danced along the walls.

Who wouldn’t want to be here? I thought.

Once the show started and Rand walked out, I was immediately sold: The decision to attend MozCon was the right one. By the end of the show, I would be saying it was one of the best career decisions I could have made.

But I almost missed it.

How and why MozCon?

I discovered MozCon like most of you: while reading the Moz blog, which I had been perusing since 2010, when I started building a website for an online, members-only newsletter.

One of my friends, an executive at a large company, had recently shared with me that online marketing was blistering hot.

“If you’re focusing your energy anywhere else, Ronell, you’re making a mistake,” he said. “We just hired a digital marketing manager, and we’re paying her more than $90,000.”

Those words served as an imprimatur for me to eagerly study and read SEO blogs and set up Twitter lists to follow prominent SEO authors.

Learning SEO was far less fun than applying it to the website I was in the process of helping to build.

In the years that followed, I continued reading the blog while making steps to meet members of the community, both locally and online.

One of the first people I met in the Moz SEO community was Greg Gifford, who agreed to meet me for lunch after I reached out to him via DM on Twitter.

He mentioned MozCon, which at the time wasn’t on my radar. (As a bonus, he said if I attended, he’d introduce me to Ruth Burr, who I’d been following on Twitter, and was a hyooge fan of.)

I started doing some investigating, wondering if it was an event I should invest in.

Also, during this same period, I was getting my content strategy sea legs and had reached out to Jon Colman, who was nice enough to mentor me. He also recommended that I attend MozCon, not the least because content strategy and UX superstar Karen McGrane was speaking.

I was officially sold.

That night, I put a plan into action:

  • Signed up for Moz Pro to get the MozCon discount
  • Bought a ticket to the show
  • Purchased airline and hotel tickets through Priceline

Then I used to following weeks to devise a plan to help me get everything I could out of the show.

The conference of all conferences

Honestly, I didn’t expect to be blow away by MozCon.

For seven of the 10 previous years, I edited a magazine that helped finance a trade show that hosted tens of thousands of people, from all over the world.

Nothing could top that, I thought. I was wrong.

The show, the lights, the people — and the single-track focus — blew me away. Right away.

I remember Richard Baxter was the first speaker up that first morning.

By the time he was done sharing strategies for effective outreach, I was thinking, “I’ve already recouped my expense. I don’t plan to ever miss this show again.”

And I haven’t.

So important did MozCon become to me after that first show, that I began to plan summer travel around it.

How could one event become that important?

Five key reasons:

  • Content
  • People & relationships
  • Personal & career development

I’ll explore each in detail since I think they each help make my point about the value of MozCon. (Also, if you haven’t read it already, check out Rand’s post, The Case For & Against Attending Marketing Conferences, which also touches on the value of these events.)

#1 - Content

You expect me to say the content you’ll be privy to at MozCon is the best you’ll hear anywhere.

Yeah, but…

The show hand-picks only the best speakers. But these same speakers present elsewhere, too, right?

What I mean by "content" is that the information you glean holistically from the show can help marketers from all areas of the business better do their work.

For example, when I came to my first MozCon, I had a handful of clients who’d reached out to me for PR, media relations, branding, and content work.

But I was starting to get calls and emails for this thing called “content marketing,” of which I was only vaguely familiar.

The information I learned from the speakers (and the informal conversations between speakers and after the show), made it possible for me to take on content marketing clients and, six months later, head content marketing for one of the most successful digital strategy agencies in Dallas/Fort Worth.

There really is something for everyone at MozCon.

#2 - People & relationships

Most of the folks I talk to on a daily, weekly, and monthly basis are folks I met at one of the last four MozCons.

For example, I met Susan E. Wenograd at MozCon 2013, where we shared a seat next to one another for the entire event. She’s been one of my closest friends ever since.

MozCon 2015: I'm chastising Damon Gochneaur for trying to sell me some links — I'm kidding, Google.

The folks seated beside you or roaming the halls during the event are some of the sharpest and most accomplished you’ll meet anywhere.

They are also some of the most helpful and genuine.

I felt this during my first event; I learned the truth of this sentiment in the weeks, months, and years that have followed.

Whether you’re as green as I was, or an advanced T-shaped marketer with a decade of experience behind you, the event will be fun, exciting, and full of new tips, tactics, and strategies you can immediately put to use.

#3 - Personal & career development

I know most people make decisions about attending events based on the cost and the known value — that is, based on previous similar events, how much they are likely to earn, either in a new job, new work, or additional responsibilities.

That’s the wrong way to look at MozCon, or any event.

Let’s keep it real for a moment: No matter who you are, where you work, what you do, or how much you enjoy your work, you’re are ALWAYS in the process of getting fired or (hopefully) changing jobs.

You should (must) be attending events to keep yourself relevant, visible, and on top of your game, whether that’s in paid media, content, social media, SEO, email marketing, etc.

That’s why the “Is it worth it?” argument is not beneficial at all.

I cannot tell you how many times, over the last four years, when I’ve been stuck on a content strategy, SEO or web design issue and been able to reach out to someone I would never have met were it not for MozCon.

For example, every time I share the benefits of Paid Social with a local business owner, I feel I should cut Kane Jamison (met at MozCon 2014) a check.

So, go to MozCon, not because you can see the tangible benefits (you cannot know those); go to MozCon because your career and your personal development will be nourished by it far beyond any financial reward.

Now you know how I feel and what I’ve gleaned from MozCon, you’re probably saying, “Yeah, but how can I be certain to get the most out of the event?”

I’m glad you asked.

How you can get the most out of MozCon

First, start following and interacting with Twitter and Facebook groups to find folks attending MozCon.

Dive in and ask questions, answer questions, or set up a get-together during the event.

Next, during the event, follow the #mozcon Twitter hashtag, making note of folks who are tweeting info from the event. Pay close attention to not simply the info, but also what they are gleaning and how they plan to use the event for their work.

If you find a few folks sharing info germane to your work or experiences, it wouldn’t hurt to retweet them and, maybe later during the show, send a group text asking to get together during the pub crawl or maybe join up for breakfast.

Then, once the show is over, continue to follow folks on social media, in addition to reading (and leaving comments on) their blogs, sending them “Great meeting you. Let’s stay in touch” emails, and looking for other opportunities to stay in their orbit, including meeting up at future events.

Many of the folks I initially met at MozCon have become friends I see throughout the year at other events.

But, wait!

I mentioned nothing about how to get the most out of the event itself.

Well, I have a different philosophy than most folks: Instead of writing copious notes and trying to capture every word from each speaker, I think of and jot down a theme for each talk while the speaker is still presenting. Along with that theme, I’ll include some notes that encapsulate the main nuggets of the talk and that will help me remember it later.

For example, Dr. Pete’s 2016 talk, You Can't Type a Concept: Why Keywords Still Matter, spurred me to redouble my focus (and my learning with regard to content and SEO) on search intent, on-page SEO, and knowing the audience’s needs as well as possible.

Then, once the show is over, I create a theme to encapsulate the entire event by asking myself three questions:

  1. What did I learn that I can apply right away?
  2. What can I create and share that’ll make me more valuable to teammates, clients or prospective clients?
  3. How does this information make me better at [X]?

For the 2013 show, my answers were…

  1. I don’t need to know everything about SEO to begin to take on SEO-related work, which I was initially reluctant to do.
  2. Content that highlights my in-depth knowledge of the types of content that resonates with audiences I’d researched/was familiar with.
  3. It makes me more aware of how how search, social, and content fit together.

After hearing Avinash’s quote, I had the theme in my head, for me and for the handful of brands I was consulting at the time: “You won’t win by running the competition’s race; make them chase you.”

MozCon 2013: Avinash Kaushik of Google

This meant I helped them think beyond content, social media, and SEO, and instead had them focus on creating the best content experience possible, which would help them more easily accomplish their goals.

I’ve repeated the process each year since, including in 2016, when I doubled-down on Featured Snippets after seeing Taking the Top Spot: How to Earn More Featured Snippets, by Rob Bucci.

You can do the same.

It all begins with attending the show and being willing to step outside your comfort zone.

What say you?

Are you MozCon bound?

Count me in!


Sign up for The Moz Top 10, a semimonthly mailer updating you on the top ten hottest pieces of SEO news, tips, and rad links uncovered by the Moz team. Think of it as your exclusive digest of stuff you don't have time to hunt down but want to read!



from The Moz Blog http://ift.tt/2siWzve
via IFTTT

Move Modal in on a Path

Form Validation Part 3: A Validity State API Polyfill

In the last article in this series, we build a lightweight script (6kb, 2.7kb minified) using the Validity State API to enhance the native form validation experience. It works in all modern browsers and provides support IE support back to IE10. But, there are some browser gotchas.

Not every browser supports every Validity State property. Internet Explorer is the main violator, though Edge does lack support for tooLong even though IE10+ support it. And Chrome, Firefox, and Safari got full support only recently.

Today, we'll write a lightweight polyfill that extends our browser support all the way back to IE9, and adds missing properties to partially supporting browsers, without modifying any of the core code in our script.

Article Series:

  1. Constraint Validation in HTML
  2. The Constraint Validation API in JavaScript
  3. A Validity State API Polyfill (You are here!)
  4. Validating the MailChimp Subscribe Form (Coming Soon!)

Let's get started.

Testing Support

The first thing we need to do is test the browser for Validity State support.

To do that, we'll use document.createElement('input') to create a form input, and then check to see if the validity property exists on that element.

// Make sure that ValidityState is supported
var supported = function () {
    var input = document.createElement('input');
    return ('validity' in input);
};

The supported() function will return true in supporting browsers, and false in unsupported ones.

It's not enough to just test for the validity property, though. We need to make sure the full range of Validity State properties exist as well.

Let's extend our supported() function to test for all of them.

// Make sure that ValidityState is supported in full (all features)
var supported = function () {
    var input = document.createElement('input');
    return ('validity' in input && 'badInput' in input.validity && 'patternMismatch' in input.validity && 'rangeOverflow' in input.validity && 'rangeUnderflow' in input.validity && 'stepMismatch' in input.validity && 'tooLong' in input.validity && 'tooShort' in input.validity && 'typeMismatch' in input.validity && 'valid' in input.validity && 'valueMissing' in input.validity);
};

Browsers like IE11 and Edge will fail this test, even though they support many Validity State properties.

Check input validity

Next, we'll write our own function to check the validity of a form field and return an object using the same structure as the Validity State API.

Setting up our checks

First, we'll set up our function and pass in the field as an argument.

// Generate the field validity object
var getValidityState = function (field) {
    // Run our validity checks...
};

Next, let's setup some variables for a few things we're going to need to use repeatedly in our validity tests.

// Generate the field validity object
var getValidityState = function (field) {

    // Variables
    var type = field.getAttribute('type') || input.nodeName.toLowerCase(); // The field type
    var isNum = type === 'number' || type === 'range'; // Is the field numeric
    var length = field.value.length; // The field value length

};

Testing Validity

Now, we'll create the object that will contain all of our validity tests.

// Generate the field validity object
var getValidityState = function (field) {

    // Variables
    var type = field.getAttribute('type') || input.nodeName.toLowerCase();
    var isNum = type === 'number' || type === 'range';
    var length = field.value.length;

    // Run validity checks
    var checkValidity = {
        badInput: false, // value does not conform to the pattern
        rangeOverflow: false, // value of a number field is higher than the max attribute
        rangeUnderflow: false, // value of a number field is lower than the min attribute
        stepMismatch: false, // value of a number field does not conform to the stepattribute
        tooLong: false, // the user has edited a too-long value in a field with maxlength
        tooShort: false, // the user has edited a too-short value in a field with minlength
        typeMismatch: false, // value of a email or URL field is not an email address or URL
        valueMissing: false // required field without a value
    };

};

You'll notice that the valid property is missing from the checkValidity object. We can only know what it is after we've run our other tests.

We'll loop through each one. If any of them are true, we'll set our valid state to false. Otherwise, we'll set it to true. Then, we'll return the entire checkValidity.

// Generate the field validity object
var getValidityState = function (field) {

    // Variables
    var type = field.getAttribute('type') || input.nodeName.toLowerCase();
    var isNum = type === 'number' || type === 'range';
    var length = field.value.length;

    // Run validity checks
    var checkValidity = {
        badInput: false, // value does not conform to the pattern
        rangeOverflow: false, // value of a number field is higher than the max attribute
        rangeUnderflow: false, // value of a number field is lower than the min attribute
        stepMismatch: false, // value of a number field does not conform to the stepattribute
        tooLong: false, // the user has edited a too-long value in a field with maxlength
        tooShort: false, // the user has edited a too-short value in a field with minlength
        typeMismatch: false, // value of a email or URL field is not an email address or URL
        valueMissing: false // required field without a value
    };

    // Check if any errors
    var valid = true;
    for (var key in checkValidity) {
        if (checkValidity.hasOwnProperty(key)) {
            // If there's an error, change valid value
            if (checkValidity[key]) {
                valid = false;
                break;
            }
        }
    }

    // Add valid property to validity object
    checkValidity.valid = valid;

    // Return object
    return checkValidity;

};

Writing the Tests

Now we need to write each of our tests. Most of these will involve using a regex pattern with the test() method against the field value.

badInput

For badInput, if the field is numeric, has at least one character, and at least one of the characters isn't a number, we'll return true.

badInput: (isNum && length > 0 && !/[-+]?[0-9]/.test(field.value))
patternMismatch

The patternMismatch property is one of the easier ones to test. This property is true if the field has a pattern attribute, has at least one character, and the field value doesn't match the included pattern regex.

patternMismatch: (field.hasAttribute('pattern') && length > 0 && new RegExp(field.getAttribute('pattern')).test(field.value) === false)
rangeOverflow

The rangeOverflow property should return true if the field has a max attribute, is a numeric, and has at least one character that's over the max value. We need to convert the string value of max to an integer using the parseInt() method.

rangeOverflow: (field.hasAttribute('max') && isNum && field.value > 1 && parseInt(field.value, 10) > parseInt(field.getAttribute('max'), 10))
rangeUnderflow

The rangeUnderflow property should return true if the field has a min attribute, is a numeric, and has at least one character that's under the min value. Like with rangeOverflow, we need to convert the string value of min to an integer using the parseInt() method.

rangeUnderflow: (field.hasAttribute('min') && isNum && field.value > 1 && parseInt(field.value, 10) < parseInt(field.getAttribute('min'), 10))
stepMismatch

For the stepMismatch property, if the field is numeric, has the step attribute, and the attribute's value isn't any, we'll use the remainder operator (%) to make sure that the field value divided by the step has no remainder. If there's a remainder, we'll return true.

stepMismatch: (field.hasAttribute('step') && field.getAttribute('step') !== 'any' && isNum && Number(field.value) % parseFloat(field.getAttribute('step')) !== 0)
tooLong

With tooLong, we'll return true if the field has a maxLength attribute greater than 0, and the field value length is greater than the attribute value.

<tooLong: (field.hasAttribute('maxLength') && field.getAttribute('maxLength') > 0 && length > parseInt(field.getAttribute('maxLength'), 10))
tooShort

Conversely, with tooShort, we'll return true if the field has a minLength attribute greater than 0, and the field value length is less than the attribute value.

tooShort: (field.hasAttribute('minLength') && field.getAttribute('minLength') > 0 && length > 0 && length < parseInt(field.getAttribute('minLength'), 10))
typeMismatch

The typeMismatch property is the most complicated to validate. We need to first make sure the field isn't empty. Then we need to run one regex text if the field type is email, and another if it's url. If it's one of those values and the field value does not match our regex pattern, we'll return true.

typeMismatch: (length > 0 && ((type === 'email' && !/^([^\x00-\x20\x22\x28\x29\x2c\x2e\x3a-\x3c\x3e\x40\x5b-\x5d\x7f-\xff]+|\x22([^\x0d\x22\x5c\x80-\xff]|\x5c[\x00-\x7f])*\x22)(\x2e([^\x00-\x20\x22\x28\x29\x2c\x2e\x3a-\x3c\x3e\x40\x5b-\x5d\x7f-\xff]+|\x22([^\x0d\x22\x5c\x80-\xff]|\x5c[\x00-\x7f])*\x22))*\x40([^\x00-\x20\x22\x28\x29\x2c\x2e\x3a-\x3c\x3e\x40\x5b-\x5d\x7f-\xff]+|\x5b([^\x0d\x5b-\x5d\x80-\xff]|\x5c[\x00-\x7f])*\x5d)(\x2e([^\x00-\x20\x22\x28\x29\x2c\x2e\x3a-\x3c\x3e\x40\x5b-\x5d\x7f-\xff]+|\x5b([^\x0d\x5b-\x5d\x80-\xff]|\x5c[\x00-\x7f])*\x5d))*$/.test(field.value)) || (type === 'url' && !/^(?:(?:https?|HTTPS?|ftp|FTP):\/\/)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-zA-Z\u00a1-\uffff0-9]-*)*[a-zA-Z\u00a1-\uffff0-9]+)(?:\.(?:[a-zA-Z\u00a1-\uffff0-9]-*)*[a-zA-Z\u00a1-\uffff0-9]+)*)(?::\d{2,5})?(?:[\/?#]\S*)?$/.test(field.value))))
valueMissing

The valueMissing property is also a little complicated. First, we want to check if the field has the required attribute. If it does we need to run one of three few different tests, depending on the field type.

If it's a checkbox or radio button, we want to make sure that it's checked. If it's a select menu, we need to make sure a value is selected. If it's another type of input, we need to make sure it has a value.

valueMissing: (field.hasAttribute('required') && (((type === 'checkbox' || type === 'radio') && !field.checked) || (type === 'select' && field.options[field.selectedIndex].value < 1) || (type !=='checkbox' && type !== 'radio' && type !=='select' && length < 1)))

The complete set of tests

Here's what the completed checkValidity object looks like with all of its tests.

// Run validity checks
var checkValidity = {
    badInput: (isNum && length > 0 && !/[-+]?[0-9]/.test(field.value)), // value of a number field is not a number
    patternMismatch: (field.hasAttribute('pattern') && length > 0 && new RegExp(field.getAttribute('pattern')).test(field.value) === false), // value does not conform to the pattern
    rangeOverflow: (field.hasAttribute('max') && isNum && field.value > 1 && parseInt(field.value, 10) > parseInt(field.getAttribute('max'), 10)), // value of a number field is higher than the max attribute
    rangeUnderflow: (field.hasAttribute('min') && isNum && field.value > 1 && parseInt(field.value, 10) < parseInt(field.getAttribute('min'), 10)), // value of a number field is lower than the min attribute
    stepMismatch: (field.hasAttribute('step') && field.getAttribute('step') !== 'any' && isNum && Number(field.value) % parseFloat(field.getAttribute('step')) !== 0), // value of a number field does not conform to the stepattribute
    tooLong: (field.hasAttribute('maxLength') && field.getAttribute('maxLength') > 0 && length > parseInt(field.getAttribute('maxLength'), 10)), // the user has edited a too-long value in a field with maxlength
    tooShort: (field.hasAttribute('minLength') && field.getAttribute('minLength') > 0 && length > 0 && length < parseInt(field.getAttribute('minLength'), 10)), // the user has edited a too-short value in a field with minlength
    typeMismatch: (length > 0 && ((type === 'email' && !/^([^\x00-\x20\x22\x28\x29\x2c\x2e\x3a-\x3c\x3e\x40\x5b-\x5d\x7f-\xff]+|\x22([^\x0d\x22\x5c\x80-\xff]|\x5c[\x00-\x7f])*\x22)(\x2e([^\x00-\x20\x22\x28\x29\x2c\x2e\x3a-\x3c\x3e\x40\x5b-\x5d\x7f-\xff]+|\x22([^\x0d\x22\x5c\x80-\xff]|\x5c[\x00-\x7f])*\x22))*\x40([^\x00-\x20\x22\x28\x29\x2c\x2e\x3a-\x3c\x3e\x40\x5b-\x5d\x7f-\xff]+|\x5b([^\x0d\x5b-\x5d\x80-\xff]|\x5c[\x00-\x7f])*\x5d)(\x2e([^\x00-\x20\x22\x28\x29\x2c\x2e\x3a-\x3c\x3e\x40\x5b-\x5d\x7f-\xff]+|\x5b([^\x0d\x5b-\x5d\x80-\xff]|\x5c[\x00-\x7f])*\x5d))*$/.test(field.value)) || (type === 'url' && !/^(?:(?:https?|HTTPS?|ftp|FTP):\/\/)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-zA-Z\u00a1-\uffff0-9]-*)*[a-zA-Z\u00a1-\uffff0-9]+)(?:\.(?:[a-zA-Z\u00a1-\uffff0-9]-*)*[a-zA-Z\u00a1-\uffff0-9]+)*)(?::\d{2,5})?(?:[\/?#]\S*)?$/.test(field.value)))), // value of a email or URL field is not an email address or URL
    valueMissing: (field.hasAttribute('required') && (((type === 'checkbox' || type === 'radio') && !field.checked) || (type === 'select' && field.options[field.selectedIndex].value < 1) || (type !=='checkbox' && type !== 'radio' && type !=='select' && length < 1))) // required field without a value
};

Special considerations for radio buttons

In supporting browsers, required will only fail on a radio button if no elements in the group have been checked. Our polyfill as it's currently written will throw return valueMissing as true on an unchecked radio button even if another button in the group is checked.

To fix this, we need to get every button in the group. If one of them is checked, we'll validate that radio button instead of the one that lost focus.

// Generate the field validity object
var getValidityState = function (field) {

        // Variables
        var type = field.getAttribute('type') || input.nodeName.toLowerCase(); // The field type
        var isNum = type === 'number' || type === 'range'; // Is the field numeric
        var length = field.value.length; // The field value length

        // If radio group, get selected field
        if (field.type === 'radio' && field.name) {
                var group = document.getElementsByName(field.name);
                if (group.length > 0) {
                        for (var i = 0; i < group.length; i++) {
                                if (group[i].form === field.form && field.checked) {
                                        field = group[i];
                                        break;
                                }
                        }
                }
        }

        ...

};

Adding the validity property to form fields

Finally, if the Validity State API isn't fully supported, we want to add or override the validity property. We'll do this using the Object.defineProperty() method.

// If the full set of ValidityState features aren't supported, polyfill
if (!supported()) {
    Object.defineProperty(HTMLInputElement.prototype, 'validity', {
        get: function ValidityState() {
            return getValidityState(this);
        },
        configurable: true,
    });
}

Putting it all together

Here's the polyfill in its entirety. To keep our functions out of the global scope, I've wrapped it in an IIFE (immediately invoked function expression).

;(function (window, document, undefined) {

    'use strict';

    // Make sure that ValidityState is supported in full (all features)
    var supported = function () {
        var input = document.createElement('input');
        return ('validity' in input && 'badInput' in input.validity && 'patternMismatch' in input.validity && 'rangeOverflow' in input.validity && 'rangeUnderflow' in input.validity && 'stepMismatch' in input.validity && 'tooLong' in input.validity && 'tooShort' in input.validity && 'typeMismatch' in input.validity && 'valid' in input.validity && 'valueMissing' in input.validity);
    };

    /**
     * Generate the field validity object
     * @param  {Node]} field The field to validate
     * @return {Object}      The validity object
     */
    var getValidityState = function (field) {

        // Variables
        var type = field.getAttribute('type') || input.nodeName.toLowerCase();
        var isNum = type === 'number' || type === 'range';
        var length = field.value.length;
        var valid = true;

        // Run validity checks
        var checkValidity = {
            badInput: (isNum && length > 0 && !/[-+]?[0-9]/.test(field.value)), // value of a number field is not a number
            patternMismatch: (field.hasAttribute('pattern') && length > 0 && new RegExp(field.getAttribute('pattern')).test(field.value) === false), // value does not conform to the pattern
            rangeOverflow: (field.hasAttribute('max') && isNum && field.value > 1 && parseInt(field.value, 10) > parseInt(field.getAttribute('max'), 10)), // value of a number field is higher than the max attribute
            rangeUnderflow: (field.hasAttribute('min') && isNum && field.value > 1 && parseInt(field.value, 10) < parseInt(field.getAttribute('min'), 10)), // value of a number field is lower than the min attribute
            stepMismatch: (field.hasAttribute('step') && field.getAttribute('step') !== 'any' && isNum && Number(field.value) % parseFloat(field.getAttribute('step')) !== 0), // value of a number field does not conform to the stepattribute
            tooLong: (field.hasAttribute('maxLength') && field.getAttribute('maxLength') > 0 && length > parseInt(field.getAttribute('maxLength'), 10)), // the user has edited a too-long value in a field with maxlength
            tooShort: (field.hasAttribute('minLength') && field.getAttribute('minLength') > 0 && length > 0 && length < parseInt(field.getAttribute('minLength'), 10)), // the user has edited a too-short value in a field with minlength
            typeMismatch: (length > 0 && ((type === 'email' && !/^([^\x00-\x20\x22\x28\x29\x2c\x2e\x3a-\x3c\x3e\x40\x5b-\x5d\x7f-\xff]+|\x22([^\x0d\x22\x5c\x80-\xff]|\x5c[\x00-\x7f])*\x22)(\x2e([^\x00-\x20\x22\x28\x29\x2c\x2e\x3a-\x3c\x3e\x40\x5b-\x5d\x7f-\xff]+|\x22([^\x0d\x22\x5c\x80-\xff]|\x5c[\x00-\x7f])*\x22))*\x40([^\x00-\x20\x22\x28\x29\x2c\x2e\x3a-\x3c\x3e\x40\x5b-\x5d\x7f-\xff]+|\x5b([^\x0d\x5b-\x5d\x80-\xff]|\x5c[\x00-\x7f])*\x5d)(\x2e([^\x00-\x20\x22\x28\x29\x2c\x2e\x3a-\x3c\x3e\x40\x5b-\x5d\x7f-\xff]+|\x5b([^\x0d\x5b-\x5d\x80-\xff]|\x5c[\x00-\x7f])*\x5d))*$/.test(field.value)) || (type === 'url' && !/^(?:(?:https?|HTTPS?|ftp|FTP):\/\/)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-zA-Z\u00a1-\uffff0-9]-*)*[a-zA-Z\u00a1-\uffff0-9]+)(?:\.(?:[a-zA-Z\u00a1-\uffff0-9]-*)*[a-zA-Z\u00a1-\uffff0-9]+)*)(?::\d{2,5})?(?:[\/?#]\S*)?$/.test(field.value)))), // value of a email or URL field is not an email address or URL
            valueMissing: (field.hasAttribute('required') && (((type === 'checkbox' || type === 'radio') && !field.checked) || (type === 'select' && field.options[field.selectedIndex].value < 1) || (type !=='checkbox' && type !== 'radio' && type !=='select' && length < 1))) // required field without a value
        };

        // Check if any errors
        for (var key in checkValidity) {
            if (checkValidity.hasOwnProperty(key)) {
                // If there's an error, change valid value
                if (checkValidity[key]) {
                    valid = false;
                    break;
                }
            }
        }

        // Add valid property to validity object
        checkValidity.valid = valid;

        // Return object
        return checkValidity;

    };

    // If the full set of ValidityState features aren't supported, polyfill
    if (!supported()) {
        Object.defineProperty(HTMLInputElement.prototype, 'validity', {
            get: function ValidityState() {
                return getValidityState(this);
            },
            configurable: true,
        });
    }

})(window, document);

Adding this to your site will extend the Validity State API back to IE9, and add missing properties to partially supporting browsers. (You can download the polyfill on GitHub, too.)

The form validation script we wrote in the last article also made use of the classList API, which is supported in all modern browsers and IE10 and above. To truly get IE9+ support, we should also include the classList.js polyfill from Eli Grey.

Article Series:

  1. Constraint Validation in HTML
  2. The Constraint Validation API in JavaScript
  3. A Validity State API Polyfill (You are here!)
  4. Validating the MailChimp Subscribe Form (Coming Soon!)

Form Validation Part 3: A Validity State API Polyfill is a post from CSS-Tricks



from CSS-Tricks http://ift.tt/2todxZi
via IFTTT

Tuesday, 27 June 2017

Form Validation Part 2: The Constraint Validation API (JavaScript)

In my last article, I showed you how to use native browser form validation through a combination of semantic input types (for example, <input type="email">) and validation attributes (such as required and pattern).

While incredibly easy and super lightweight, this approach does have a few shortcomings.

  1. You can style fields that have errors on them with the :invalid pseudo-selector, but you can't style the error messages themselves.
  2. Behavior is also inconsistent across browsers.

User studies from Christian Holst and Luke Wroblewski (separately) found that displaying an error when the user leaves a field, and keeping that error persistent until the issue is fixed, provided the best and fastest user experience.

Unfortunately, none of the browsers natively behave this way. However, there is a way to get this behavior without depending on a large JavaScript form validation library.

Article Series:

  1. Constraint Validation in HTML
  2. The Constraint Validation API in JavaScript (You are here!)
  3. A Validity State API Polyfill (Coming Soon!)
  4. Validating the MailChimp Subscribe Form (Coming Soon!)

The Constraint Validation API

In addition to HTML attributes, browser-native constraint validation also provides a JavaScript API we can use to customize our form validation behavior.

There are a few different methods the API exposes, but the most powerful, Validity State, allows us to use the browser's own field validation algorithms in our scripts instead of writing our own.

In this article, I'm going to show you how to use Validity State to customize the behavior, appearance, and content of your form validation error messages.

Validity State

The validity property provides a set of information about a form field, in the form of boolean (true/false) values.

var myField = document.querySelector('input[type="text"]');
var validityState = myField.validity;

The returned object contains the following properties:

  • valid - Is true when the field passes validation.
  • valueMissing - Is true when the field is empty but required.
  • typeMismatch - Is true when the field type is email or url but the entered value is not the correct type.
  • tooShort - Is true when the field contains a minLength attribute and the entered value is shorter than that length.
  • tooLong - Is true when the field contains a maxLength attribute and the entered value is longer than that length.
  • patternMismatch - Is true when the field contains a pattern attribute and the entered value does not match the pattern.
  • badInput - Is true when the input type is number and the entered value is not a number.
  • stepMismatch - Is true when the field has a step attribute and the entered value does not adhere to the step values.
  • rangeOverflow - Is true when the field has a max attribute and the entered number value is greater than the max.
  • rangeUnderflow - Is true when the field has a min attribute and the entered number value is lower than the min.

By using the validity property in conjunction with our input types and HTML validation attributes, we can build a robust form validation script that provides a great user experience with a relatively small amount of JavaScript.

Let's get to it!

Disable native form validation

Since we're writing our validation script, we want to disable the native browser validation by adding the novalidate attribute to our forms. We can still use the Constraint Validation API — we just want to prevent the native error messages from displaying.

As a best practice, we should add this attribute with JavaScript so that if our script has an error or fails to load, the native browser form validation will still work.

// Add the novalidate attribute when the JS loads
var forms = document.querySelectorAll('form');
for (var i = 0; i < forms.length; i++) {
    forms[i].setAttribute('novalidate', true);
}

There may be some forms that you don't want to validate (for example, a search form that shows up on every page). Rather than apply our validation script to all forms, let's apply it just to forms that have the .validate class.

// Add the novalidate attribute when the JS loads
var forms = document.querySelectorAll('.validate');
for (var i = 0; i < forms.length; i++) {
    forms[i].setAttribute('novalidate', true);
}

See the Pen Form Validation: Add `novalidate` programatically by Chris Ferdinandi (@cferdinandi) on CodePen.

Check validity when the user leaves the field

Whenever a user leaves a field, we want to check if it's valid. To do this, we'll setup an event listener.

Rather than add a listener to every form field, we'll use a technique called event bubbling (or event propagation) to listen for all blur events.

// Listen to all blur events
document.addEventListener('blur', function (event) {
    // Do something on blur...
}, true);

You'll note that the last argument in addEventListener is set to true. This argument is called useCapture, and it's normally set to false. The blur event doesn't bubble the way events like click do. Setting this argument to true allows us to capture all blur events rather than only those that happen directly on the element we're listening to.

Next, we want to make sure that the blurred element was a field in a form with the .validate class. We can get the blurred element using event.target, and get it's parent form by calling event.target.form. Then we'll use classList to check if the form has the validation class or not.

If it does, we can check the field validity.

// Listen to all blur events
document.addEventListener('blur', function (event) {

    // Only run if the field is in a form to be validated
    if (!event.target.form.classList.contains('validate')) return;

    // Validate the field
    var error = event.target.validity;
    console.log(error);

}, true);

If error.validity is true, the field is valid. Otherwise, there's an error.

See the Pen Form Validation: Validate On Blur by Chris Ferdinandi (@cferdinandi) on CodePen.

Getting the error

Once we know there's an error, it's helpful to know what the error actually is. We can use the other Validity State properties to get that information.

Since we need to check each property, the code for this can get a bit long. Let's setup a separate function for this and pass our field into it.

// Validate the field
var hasError = function (field) {
    // Get the error
};

// Listen to all blur events
document.addEventListner('blur', function (event) {

    // Only run if the field is in a form to be validated
    if (!event.target.form.classList.contains('validate')) return;

    // Validate the field
    var error = hasError(event.target);

}, true);

There are a few field types we want to ignore: fields that are disabled, file and reset inputs, and submit inputs and buttons. If a field isn't one of those, let's get it's validity.

// Validate the field
var hasError = function (field) {

    // Don't validate submits, buttons, file and reset inputs, and disabled fields
    if (field.disabled || field.type === 'file' || field.type === 'reset' || field.type === 'submit' || field.type === 'button') return;

    // Get validity
    var validity = field.validity;

};

If there's no error, we'll return null. Otherwise, we'll check each of the Validity State properties until we find the error.

When we find the match, we'll return a string with the error. If none of the properties are true but validity is false, we'll return a generic "catchall" error message (I can't imagine a scenario where this happens, but it's good to plan for the unexpected).

// Validate the field
var hasError = function (field) {

    // Don't validate submits, buttons, file and reset inputs, and disabled fields
    if (field.disabled || field.type === 'file' || field.type === 'reset' || field.type === 'submit' || field.type === 'button') return;

    // Get validity
    var validity = field.validity;

    // If valid, return null
    if (validity.valid) return;

    // If field is required and empty
    if (validity.valueMissing) return 'Please fill out this field.';

    // If not the right type
    if (validity.typeMismatch) return 'Please use the correct input type.';

    // If too short
    if (validity.tooShort) return 'Please lengthen this text.';

    // If too long
    if (validity.tooLong) return 'Please shorten this text.';

    // If number input isn't a number
    if (validity.badInput) return 'Please enter a number.';

    // If a number value doesn't match the step interval
    if (validity.stepMismatch) return 'Please select a valid value.';

    // If a number field is over the max
    if (validity.rangeOverflow) return 'Please select a smaller value.';

    // If a number field is below the min
    if (validity.rangeUnderflow) return 'Please select a larger value.';

    // If pattern doesn't match
    if (validity.patternMismatch) return 'Please match the requested format.';

    // If all else fails, return a generic catchall error
    return 'The value you entered for this field is invalid.';

};

This is a good start, but we can do some additional parsing to make a few of our errors more useful. For typeMismatch, we can check if it's supposed to be an email or url and customize the error accordingly.

// If not the right type
if (validity.typeMismatch) {

    // Email
    if (field.type === 'email') return 'Please enter an email address.';

    // URL
    if (field.type === 'url') return 'Please enter a URL.';

}

If the field value is too long or too short, we can find out both how long or short it's supposed to be and how long or short it actually is. We can then include that information in the error.

// If too short
if (validity.tooShort) return 'Please lengthen this text to ' + field.getAttribute('minLength') + ' characters or more. You are currently using ' + field.value.length + ' characters.';

// If too long
if (validity.tooLong) return 'Please short this text to no more than ' + field.getAttribute('maxLength') + ' characters. You are currently using ' + field.value.length + ' characters.';

If a number field is over or below the allowed range, we can include that minimum or maximum allowed value in our error.

// If a number field is over the max
if (validity.rangeOverflow) return 'Please select a value that is no more than ' + field.getAttribute('max') + '.';

// If a number field is below the min
if (validity.rangeUnderflow) return 'Please select a value that is no less than ' + field.getAttribute('min') + '.';

And if there is a pattern mismatch and the field has a title, we can use that as our error, just like the native browser behavior.

// If pattern doesn't match
if (validity.patternMismatch) {

    // If pattern info is included, return custom error
    if (field.hasAttribute('title')) return field.getAttribute('title');

    // Otherwise, generic error
    return 'Please match the requested format.';

}

Here's the complete code for our hasError() function.

// Validate the field
var hasError = function (field) {

    // Don't validate submits, buttons, file and reset inputs, and disabled fields
    if (field.disabled || field.type === 'file' || field.type === 'reset' || field.type === 'submit' || field.type === 'button') return;

    // Get validity
    var validity = field.validity;

    // If valid, return null
    if (validity.valid) return;

    // If field is required and empty
    if (validity.valueMissing) return 'Please fill out this field.';

    // If not the right type
    if (validity.typeMismatch) {

        // Email
        if (field.type === 'email') return 'Please enter an email address.';

        // URL
        if (field.type === 'url') return 'Please enter a URL.';

    }

    // If too short
    if (validity.tooShort) return 'Please lengthen this text to ' + field.getAttribute('minLength') + ' characters or more. You are currently using ' + field.value.length + ' characters.';

    // If too long
    if (validity.tooLong) return 'Please shorten this text to no more than ' + field.getAttribute('maxLength') + ' characters. You are currently using ' + field.value.length + ' characters.';

    // If number input isn't a number
    if (validity.badInput) return 'Please enter a number.';

    // If a number value doesn't match the step interval
    if (validity.stepMismatch) return 'Please select a valid value.';

    // If a number field is over the max
    if (validity.rangeOverflow) return 'Please select a value that is no more than ' + field.getAttribute('max') + '.';

    // If a number field is below the min
    if (validity.rangeUnderflow) return 'Please select a value that is no less than ' + field.getAttribute('min') + '.';

    // If pattern doesn't match
    if (validity.patternMismatch) {

        // If pattern info is included, return custom error
        if (field.hasAttribute('title')) return field.getAttribute('title');

        // Otherwise, generic error
        return 'Please match the requested format.';

    }

    // If all else fails, return a generic catchall error
    return 'The value you entered for this field is invalid.';

};

Try it yourself in the pen below.

See the Pen Form Validation: Get the Error by Chris Ferdinandi (@cferdinandi) on CodePen.

Show an error message

Once we get our error, we can display it below the field. We'll create a showError() function to handle this, and pass in our field and the error. Then, we'll call it in our event listener.

// Show the error message
var showError = function (field, error) {
    // Show the error message...
};

// Listen to all blur events
document.addEventListener('blur', function (event) {

    // Only run if the field is in a form to be validated
    if (!event.target.form.classList.contains('validate')) return;

    // Validate the field
    var error = hasError(event.target);

    // If there's an error, show it
    if (error) {
        showError(event.target, error);
    }

}, true);

In our showError function, we're going to do a few things:

  1. We'll add a class to the field with the error so that we can style it.
  2. If an error message already exists, we'll update it with new text.
  3. Otherwise, we'll create a message and inject it into the DOM immediately after the field.

We'll also use the field ID to create a unique ID for the message so we can find it again later (falling back to the field name in case there's no ID).

var showError = function (field, error) {

    // Add error class to field
    field.classList.add('error');

    // Get field id or name
    var id = field.id || field.name;
    if (!id) return;

    // Check if error message field already exists
    // If not, create one
    var message = field.form.querySelector('.error-message#error-for-' + id );
    if (!message) {
        message = document.createElement('div');
        message.className = 'error-message';
        message.id = 'error-for-' + id;
        field.parentNode.insertBefore( message, field.nextSibling );
    }

    // Update error message
    message.innerHTML = error;

    // Show error message
    message.style.display = 'block';
    message.style.visibility = 'visible';

};

To make sure that screen readers and other assistive technology know that our error message is associated with our field, we also need to add the aria-describedby role.

var showError = function (field, error) {

    // Add error class to field
    field.classList.add('error');

    // Get field id or name
    var id = field.id || field.name;
    if (!id) return;

    // Check if error message field already exists
    // If not, create one
    var message = field.form.querySelector('.error-message#error-for-' + id );
    if (!message) {
        message = document.createElement('div');
        message.className = 'error-message';
        message.id = 'error-for-' + id;
        field.parentNode.insertBefore( message, field.nextSibling );
    }

    // Add ARIA role to the field
    field.setAttribute('aria-describedby', 'error-for-' + id);

    // Update error message
    message.innerHTML = error;

    // Show error message
    message.style.display = 'block';
    message.style.visibility = 'visible';

};

Style the error message

We can use the .error and .error-message classes to style our form field and error message.

As a simple example, you may want to display a red border around fields with an error, and make the error message red and italicized.

.error {
  border-color: red;
}

.error-message {
  color: red;
  font-style: italic;
}

See the Pen Form Validation: Display the Error by Chris Ferdinandi (@cferdinandi) on CodePen.

Hide an error message

Once we show an error, your visitor will (hopefully) fix it. Once the field validates, we need to remove the error message. Let's create another function, removeError(), and pass in the field. We'll call this function from event listener as well.

// Remove the error message
var removeError = function (field) {
    // Remove the error message...
};

// Listen to all blur events
document.addEventListener('blur', function (event) {

    // Only run if the field is in a form to be validated
    if (!event.target.form.classList.contains('validate')) return;

    // Validate the field
    var error = event.target.validity;

    // If there's an error, show it
    if (error) {
        showError(event.target, error);
        return;
    }

    // Otherwise, remove any existing error message
    removeError(event.target);

}, true);

In removeError(), we want to:

  1. Remove the error class from our field.
  2. Remove the aria-describedby role from the field.
  3. Hide any visible error messages in the DOM.

Because we could have multiple forms on a page, and there's a chance those forms might have fields with the same name or ID (even though that's invalid, it happens), we're going to limit our search for the error message with querySelector the form our field is in rather than the entire document.

// Remove the error message
var removeError = function (field) {

    // Remove error class to field
    field.classList.remove('error');

    // Remove ARIA role from the field
    field.removeAttribute('aria-describedby');

    // Get field id or name
    var id = field.id || field.name;
    if (!id) return;

    // Check if an error message is in the DOM
    var message = field.form.querySelector('.error-message#error-for-' + id + '');
    if (!message) return;

    // If so, hide it
    message.innerHTML = '';
    message.style.display = 'none';
    message.style.visibility = 'hidden';

};

See the Pen Form Validation: Remove the Error After It's Fixed by Chris Ferdinandi (@cferdinandi) on CodePen.

If the field is a radio button or checkbox, we need to change how we add our error message to the DOM.

The field label often comes after the field, or wraps it entirely, for these types of inputs. Additionally, if the radio button is part of a group, we want the error to appear after the group rather than just the radio button.

See the Pen Form Validation: Issues with Radio Buttons & Checkboxes by Chris Ferdinandi (@cferdinandi) on CodePen.

First, we need to modify our showError() method. If the field type is radio and it has a name, we want get all radio buttons with that same name (ie. all other radio buttons in the group) and reset our field variable to the last one in the group.

// Show the error message
var showError = function (field, error) {

    // Add error class to field
    field.classList.add('error');

    // If the field is a radio button and part of a group, error all and get the last item in the group
    if (field.type === 'radio' && field.name) {
        var group = document.getElementsByName(field.name);
        if (group.length > 0) {
            for (var i = 0; i < group.length; i++) {
                // Only check fields in current form
                if (group[i].form !== field.form) continue;
                group[i].classList.add('error');
            }
            field = group[group.length - 1];
        }
    }

    ...

};

When we go to inject our message into the DOM, we first want to check if the field type is radio or checkbox. If so, we want to get the field label and inject our message after it instead of after the field itself.

// Show the error message
var showError = function (field, error) {

    ...

    // Check if error message field already exists
    // If not, create one
    var message = field.form.querySelector('.error-message#error-for-' + id );
    if (!message) {
        message = document.createElement('div');
        message.className = 'error-message';
        message.id = 'error-for-' + id;

        // If the field is a radio button or checkbox, insert error after the label
        var label;
        if (field.type === 'radio' || field.type ==='checkbox') {
            label = field.form.querySelector('label[for="' + id + '"]') || field.parentNode;
            if (label) {
                label.parentNode.insertBefore( message, label.nextSibling );
            }
        }

        // Otherwise, insert it after the field
        if (!label) {
            field.parentNode.insertBefore( message, field.nextSibling );
        }
    }

    ...

};

When we go to remove the error, we similarly need to check if the field is a radio button that's part of a group, and if so, use the last radio button in that group to get the ID of our error message.

// Remove the error message
var removeError = function (field) {

    // Remove error class to field
    field.classList.remove('error');

    // If the field is a radio button and part of a group, remove error from all and get the last item in the group
    if (field.type === 'radio' && field.name) {
        var group = document.getElementsByName(field.name);
        if (group.length > 0) {
            for (var i = 0; i < group.length; i++) {
                // Only check fields in current form
                if (group[i].form !== field.form) continue;
                group[i].classList.remove('error');
            }
            field = group[group.length - 1];
        }
    }

    ...

};

See the Pen Form Validation: Fixing Radio Buttons & Checkboxes by Chris Ferdinandi (@cferdinandi) on CodePen.

Checking all fields on submit

When a visitor submits our form, we should first validate every field in the form and display error messages on any invalid fields. We should also bring the first field with an error into focus so that the visitor can immediately take action to correct it.

We'll do this by adding a listener for the submit event.

// Check all fields on submit
document.addEventListener('submit', function (event) {
    // Validate all fields...
}, false);

If the form has the .validate class, we'll get every field, loop through each one, and check for errors. We'll store the first invalid field we find to a variable and bring it into focus when we're done. If no errors are found, the form can submit normally.

// Check all fields on submit
document.addEventListener('submit', function (event) {

    // Only run on forms flagged for validation
    if (!event.target.classList.contains('validate')) return;

    // Get all of the form elements
    var fields = event.target.elements;

    // Validate each field
    // Store the first field with an error to a variable so we can bring it into focus later
    var error, hasErrors;
    for (var i = 0; i < fields.length; i++) {
        error = hasError(fields[i]);
        if (error) {
            showError(fields[i], error);
            if (!hasErrors) {
                hasErrors = fields[i];
            }
        }
    }

    // If there are errrors, don't submit form and focus on first element with error
    if (hasErrors) {
        event.preventDefault();
        hasErrors.focus();
    }

    // Otherwise, let the form submit normally
    // You could also bolt in an Ajax form submit process here

}, false);

See the Pen Form Validation: Validate on Submit by Chris Ferdinandi (@cferdinandi) on CodePen.

Tying it all together

Our finished script weight just 6kb (2.7kb minified).

It works in all modern browsers and provides support IE support back to IE10. But, there are some browser gotchas…

  1. Because we can't have nice things, not every browser supports every Validity State property.
  2. Internet Explorer is, of course, the main violator, though Edge does lack support for tooLong even though IE10+ supports it. Go figure.

Here's the good news: with a lightweight polyfill (5kb, 2.7kb minified) we can extend our browser support all the way back to IE9, and add missing properties to partially supporting browsers, without having to touch any of our core code.

There is one exception to the IE9 support: radio buttons. IE9 doesn't support CSS3 selectors (like [name="' + field.name + '"]). We use that to make sure at least one radio button has been selected within a group. IE9 will always return an error.

I'll show you how to create this polyfill in the next article.

Article Series:

  1. Constraint Validation in HTML
  2. The Constraint Validation API in JavaScript (You are here!)
  3. A Validity State API Polyfill (Coming Soon!)
  4. Validating the MailChimp Subscribe Form (Coming Soon!)

Form Validation Part 2: The Constraint Validation API (JavaScript) is a post from CSS-Tricks



from CSS-Tricks http://ift.tt/2rXAE8A
via IFTTT

6 CRO Mistakes You Might Be Making (And How to Fix Them)

Posted by lkolowich

You just ran what you thought was a really promising conversion test. In an effort to raise the number of visitors that convert into demo requests on your product pages, you test an attractive new redesign on one of your pages using a good ol’ A/B test. Half of the people who visit that page see the original product page design, and half see the new, attractive design.

You run the test for an entire month, and as you expected, conversions are up — from 2% to 10%. Boy, do you feel great! You take these results to your boss and advise that, based on your findings, all product pages should be moved over to your redesign. She gives you the go-ahead.

But when you roll out the new design, you notice the number of demo requests goes down. You wonder if it’s seasonality, so you wait a few more months. That’s when you start to notice MRR is decreasing, too. What gives?

Turns out, you didn’t test that page long enough for results to be statistically significant. Because that product page only saw 50 views per day, you would’ve needed to wait until over 150,000 people viewed the page before you could achieve a 95% confidence level — which would take over eight years to accomplish. Because you failed to calculate those numbers correctly, your company is losing business.

A risky business

Miscalculating sample size is just one of the many CRO mistakes marketers make in the CRO space. It’s easy for marketers to trick themselves into thinking they’re improving their marketing, when in fact, they’re leading their business down a dangerous path by basing tests on incomplete research, small sample sizes, and so on.

But remember: The primary goal of CRO is to find the truth. Basing a critical decision on faulty assumptions and tests lacking statistical significance won’t get you there.

To help save you time and overcome that steep learning curve, here are some of the most common mistakes marketers make with conversion rate optimization. As you test and tweak and fine-tune your marketing, keep these mistakes in mind, and keep learning.


6 CRO mistakes you might be making

1) You think of CRO as mostly A/B testing.

Equating A/B testing with CRO is like calling a square a rectangle. While A/B testing is a type of CRO, it’s just one tool of many. A/B testing only covers testing a single variable against another to see which performs better, while CRO includes all manner of testing methodologies, all with the goal of leading your website visitors to take a desired action.

If you think you’re "doing CRO" just by A/B testing everything, you’re not being very smart about your testing. There are plenty of occasions where A/B testing isn’t helpful at all — for example, if your sample size isn’t large enough to collect the proper amount of data. Does the webpage you want to test get only a few hundred visits per month? Then it could take months to round up enough traffic to achieve statistical significance.

If you A/B test a page with low traffic and then decide six weeks down the line that you want to stop the test, then that’s your prerogative — but your test results won’t be based on anything scientific.

A/B testing is a great place to start with your CRO education, but it’s important to educate yourself on many different testing methodologies so you aren’t restricting yourself. For example, if you want to see a major lift in conversions on a webpage in only a few weeks, try making multiple, radical changes instead of testing one variable at a time. Take Weather.com, for example: They changed many different variables on one of their landing pages all at once, including the page design, headline, navigation, and more. The result? A whopping 225% increase in conversions.

2) You don’t provide context for your conversion rates.

When you read that line about the 225% lift in conversions on Weather.com, did you wonder what I meant by "conversions?"

If you did, then you’re thinking like a CRO.

Conversion rates can measure any number of things: purchases, leads, prospects, subscribers, users — it all depends on the goal of the page. Just saying “we saw a huge increase in conversions” doesn’t mean much if you don’t provide people with what the conversion means. In the case of Weather.com, I was referring specifically to trial subscriptions: Weather.com saw a 225% increase in trial subscriptions on that page. Now the meaning of that conversion rate increase is a lot more clear.

But even stating the metric isn’t telling the whole story. When exactly was that test run? Different days of the week and of the month can yield very different conversion rates.

conversion-rate-fluctuation.png

For that reason, even if your test achieves 98% significance after three days, you still need to run that test for the rest of the full week because of how different conversion rate can be on different days. Same goes for months: Don’t run a test during the holiday-heavy month of December and expect the results to be the same as if you’d run it for the month of March. Seasonality will affect your conversion rate.

Other things that can have a major impact on conversion rate? Device type is one. Visitors might be willing to fill out that longer form on desktop, but are mobile visitors converting at the same rate? Better investigate. Channel is another: Be wary of reporting “average” conversion rates. If some channels have much higher conversion rates than others, you should consider treating the channels differently.

Finally, remember that conversion rate isn’t the most important metric for your business. It’s important that your conversions are leading to revenue for the company. If you made your product free, I’ll bet your conversion rates would skyrocket — but you wouldn’t be making any money, would you? Conversion rate doesn’t always tell you whether your business is doing better than it was. Be careful that you aren’t thinking of conversions in a vacuum so you don’t steer off-course.

3) You don’t really understand the statistics.

One of the biggest mistakes I made when I first started learning CRO was thinking I could rely on what I remembered from my college statistics courses to run conversion tests. Just because you’re running experiments does not make you a scientist.

Statistics is the backbone of CRO, and if you don’t understand it inside and out, then you won’t be able to run proper tests and could seriously derail your marketing efforts.

What if you stop your test too early because you didn’t wait to achieve 98% statistical significance? After all, isn’t 90% good enough?

No, and here’s why: Think of statistical significance like placing a bet. Are you really willing to bet on 90% odds on your test results? Running a test to 90% significance and then declaring a winner is like saying, "I'm 90% sure this is the right design and I'm willing to bet everything on it.” It’s just not good enough.

If you’re in need of a statistics refresh, don’t panic. It’ll take discipline and practice, but it’ll make you into a much better marketer — and it’ll make your testing methodology much, much tighter. Start by reading this Moz post by Craig Bradford, which covers sample size, statistical significance, confidence intervals, and percentage change.

4) You don’t experiment on pages or campaigns that are already doing well.

Just because something is doing well doesn’t mean you should just leave it be. Often, it’s these marketing assets that have the highest potential to perform even better when optimized. Some of our biggest CRO wins here at HubSpot have come from assets that were already performing well.

I’ll give you two examples.

The first comes from a project run by Pam Vaughan on HubSpot’s web strategy team, called “historical optimization.” The project involved updating and republishing old blog posts to generate more traffic and leads.

But this didn’t mean updating just any old blog posts; it meant updating the blog posts that were already the most influential in generating traffic and leads. In her attribution analysis, Pam made two surprising discoveries:

  • 76% of our monthly blog views came from "old" posts (in other words, posts published prior to that month).
  • 92% of our monthly blog leads also came from "old" posts.

Why? Because these were the blog posts that had slowly built up search authority and were ranking on search engines like Google. They were generating a ton of organic traffic month after month after month.

The goal of the project, then, was to figure out: a) how to get more leads from our high-traffic but low-converting blog posts; and b) how to get more traffic to our high-converting posts. By optimizing these already high-performing posts for traffic and conversions, we more than doubled the number of monthly leads generated by the old posts we've optimized.

hubspot-conversion-increase-chart.jpg

Another example? In the last few weeks, Nick Barrasso from our marketing acquisition team did a leads audit of our blog. He discovered that some of our best-performing blog posts for traffic were actually leading readers to some of our worst-performing offers.

To give a lead conversion lift to 50 of these high-traffic, low-converting posts, Nick conducted a test in which he replaced each post’s primary call-to-action with a call-to-action leading visitors to an offer that was most tightly aligned with the post’s topic and had the highest submission rate. After one week, these posts generated 100% more leads than average.

The bottom line is this: Don’t focus solely on optimizing marketing assets that need the most work. Many times, you’ll find that the lowest-hanging fruit are pages that are already performing well for traffic and/or leads and, when optimized even further, can result in much bigger lifts.

5) You base your CRO tests on tactics instead of research.

When it comes to CRO, process is everything. Remove your ego and assumptions from the equation, stop relying on individual tactics to optimize your marketing, and instead take a systematic approach to CRO.

Your CRO process should always start with research. In fact, conducting research should be the step you spend the most time on. Why? Because the research and analysis you do in this step will lead you to the problems — and it’s only when you know where the problems lie that you can come up with a hypothesis for overcoming them.

Remember that test I just talked about that doubled leads for 50 top HubSpot blog posts in a week? Nick didn’t just wake up one day and realize our high-traffic blog posts might be leading to low-performing offers. He discovered this only by doing hours and hours of research into our lead gen strategy from the blog.

Paddy Moogan wrote a great post on Moz on where to look for data in the research stage. What does your sales process look like, for example? Have you ever reviewed the full funnel? “Try to find where the most common drop-off points are and take a deeper dive into why,” he suggests.

Here’s an (oversimplified) overview of what a CRO process should look like:

  • Step 1: Do your research.
  • Step 2: Form and validate your hypothesis.
  • Step 3: Establish your control, and create a treatment.
  • Step 4: Conduct the experiment.
  • Step 5: Analyze your experiment data.
  • Step 6: Conduct a follow-up experiment.

As you go through these steps, be sure you’re recording your hypothesis, test methodology, success criteria, and analysis in a replicable way. My team at HubSpot uses the template below, which was inspired by content from Brian Balfour’s online Reforge Growth programs. We’ve created an editable version in Google Sheets here that you can copy and customize yourself.

hubspot-experiment-template.png

Don’t forget the last step in the process: Conduct a follow-up experiment. What can you refine for your next test? How can you make improvements?

6) You give up after a "failed" test.

One of the most important pieces of advice I’ve ever gotten around CRO is this: “A test doesn’t ‘fail’ unless something breaks. You either get the result you want, or you learned something.”

It came from Sam Woods, a growth marketer, CRO, and copywriter at HubSpot, after I used the word “fail” a few too many times after months of unsuccessful tests on a single landing page.

test-doesnt-fail.png

What he taught me was a major part of the CRO mindset: Don’t give up after the first test. (Or the second, or the third.) Instead, approach every test systematically and objectively, putting aside your previous assumptions and any hope that the results would swing one way or the other.

As Peep Laja said, “Genuine CROs are always willing to change their minds.” Learn from tests that didn’t go the way you expected, use them to tweak your hypothesis, and then iterate, iterate, iterate.

I hope this list has inspired you to double down on your CRO skills and take a more systematic approach to your experiments. Mastering conversion rate optimization comes with a steep learning curve — and there’s really no cutting corners. You can save a whole lot of time (and money) by avoiding the mistakes I outlined above.

Have you ever made any of these CRO mistakes? Do you have any CRO mistakes to add to the list? Tell us about your experiences and ideas in the comments.


Sign up for The Moz Top 10, a semimonthly mailer updating you on the top ten hottest pieces of SEO news, tips, and rad links uncovered by the Moz team. Think of it as your exclusive digest of stuff you don't have time to hunt down but want to read!



from The Moz Blog http://ift.tt/2rWBbYK
via IFTTT

Monday, 26 June 2017

Form Validation Part 1: Constraint Validation in HTML

Paint by Numbers: Using Data to Produce Great Content

Posted by rjonesx.

It's not every day that I write about content. To be honest, it's probably a once-a-year kind of thing. I will readily admit that I'm a "links are king" kind of SEO, and have been since starting in this industry more than a decade ago. However, I do look over the fence from time to time to see if the grass is greener and, on occasion, I actually like what I see. Prior to joining Moz, I was a consultant at an agency like many of you reading this blog post. More often than not, one of the key concerns of my clients was what to write about. It seems that webmasters and business owners alike can easily acquire writer's block after trudging through the uninspiring task of turning a list of keywords into website copy. So where do you look when you have run out of words

Numbers.

Alright, stick with me here. I imagine for some of you the idea of poring over numbers to remedy writer's block would be like trying to stop a headache with a brick. It's adding insult to injury. What I hope to show you in the next couple of paragraphs is how data can be an incredible source of inspiration in writing, especially if you can hit a few key principles: expose, relate, surprise, and share.

Expose

Chances are your business or website generates some amount of unique, first party data that you can expose to the world. It might be from analytics, your rank tracker like Moz, or from raw user data if you operate a forum. I'll give you examples of how you might tap into these resources (especially when they don't seem obvious or plenteous) but let's start with a canonical example of one great use of first-party data in an industry that seems directly at odds with — dating.

The thought of quantifying and analyzing our love lives seems like an oxymoron of sorts. However, one of the most successful uses of data for content has been produced by the team at OK Cupid, whose "data"-tagged blog posts have earned thousands of solid backlinks and enviable traffic. The team at OK Cupid accomplishes this by tapping their huge resource for unique data, generated by their user base. Let's look at one quick example: Congrats Graduates: No One Gives a Sh*t.

22% of female and 16% of male millenials say a college degree is mandatory for dating.

The blog post is fairly straightforward (and not particularly long) but it used unique data that isn't really available to the average person. Because OK Cupid is in a privileged position, they can provide this kind of insight to their audience at large.

But maybe you don't have a million customers with profiles on your site; where can you look for first party data? Well, here are a couple of ideas of the types of data your company or organization might have which can easily be turned into interesting content:

  • Google Analytics, Search Console data and Adwords data: Do you see trends around holidays that are interesting? Perhaps you notice that more people search for certain keywords at certain times. This could be even more interesting if there's a local holiday (like a festival or event) that makes your data unique from the rest of the country.
  • Sales data: When do your sales go up or down? Do they coincide with events? Or do they happen to coincide with completely different types of keywords? Try using Google Correlate, which will identify keywords that follow the same patterns as your data.
  • Survey data: Use your sales or lead history to run surveys and generate insightful content.
    • A clothing store could compare responses to questions about personality by the colors of clothing that people purchase (Potential headline: Is It True What They Say About Red?)
    • A car parts store could compare the size of certain accessories to favorite sports (Potential headline: Big Trucks and Big Hits)
    • An insurance provider could compare the type of insurance requested vs. the level of education (Potential headline: What Smart People Do Differently with Insurance)

There are probably tons more sources of unique, first-party data that you or your business have generated over the years which can be turned into great content. If you dig through the data long enough, you'll hit pay dirt.

Relate

Data is foreign. It's a language almost no one speaks in their day-to-day conversations, a notation meant for machines. This consideration requires that we make data immediately relatable to our readers. We shouldn't just ask "What does the data say?", but instead "What does the data say to me?" How we make data relatable is simple — organize your data by how people identify themselves. This can be geographic, economic, biological, social, or cultural distinctions with which we regularly categorize ourselves.

Many of the best examples of this kind of strategy involve geography (perhaps because everyone lives somewhere, and it's pretty non-controversial to make generic claims about one location or another). Take a look at a map of your country and try not to look first towards where you live. I'm a North Carolinian, and I almost immediately find myself interested in anything that compares my state to others.

So maybe you aren't OK Cupid with millions of users and you can't find unique data to share — don't worry, there's still hope. The example below is a rather ingenious method of using Google Adwords data to build a geographical story that's relatable to any potential customer in the United States. The webmasters at Opulent used state-level Keyword Planner to visualize popularity across the country in a piece they call the "State of Style."

When I found this on Reddit's DataIsBeautiful (where most of these examples come from), I immediately checked to see what performed best in North Carolina. I honestly couldn't care less about popular fashion or jewelry brands, but my interest in North Carolina eclipsed that lack of interest. Geography-based data visualization has produced successful content related to in sports, politics, beer, and even knitting.

If you walk away with any practical ideas from this post, I think this example has got to be it. Fire up an Adwords campaign and find out how consumer demand breaks down in your industry at a state-by-state level. Are you a marketer and want to attract clients in a particular sector? Here's your chance to write a whitepaper on national demand. If you're a local business, you can target Google Keyword Planner to your city and compare it to other cities around the country.

Surprise

Perhaps the greatest opportunity with data-focused content is the chance to truly surprise your reader. There's something exciting about learning an interesting fact (who hasn't seen one of these lying around and didn't pick it up?). So, how do you make your data "pop?" How do you make numbers fascinating?

Perspective.

Let's start with a simple statistic:

The cost of ending polio between 2013 and 2018 is

$5.5 Billion Dollars.

How does that number feel to you? Does it feel big or little? Is it interesting on its own? Probably not, let's try and spice it up a bit.

$5.5 billion dollars doesn't seem that much when you realize people spend that amount on iPhones every 2 weeks. We could rid the world of polio for that much! Or, what if we present it like this...

In this light, it seems almost insane to spend that much money preventing just a couple more polio cases relative to the huge gains we could make on malaria. Of course, the statistics don't tell the full story. Polio is in the end-stages of eradication where the cost-per-case is much higher, and as malaria is attacked, it too will see cost-per-case increase. But the point remains the same: by giving the polio numbers some sort of context, some sort of forced perspective, we make the data far more intriguing and appealing.

So how would this work with content for your own site? Let's look at an example from BestPlay.co, which wrote a piece on Board Games are Getting Worse. Board games aren't a data-centric industry, but that doesn't keep them from producing awesome content with data. Here's a generic graph they provide in the piece which shows off average board game ratings.

There really isn't much to see here. There's nothing intrinsically shocking about the data as we look at it. So how do they add perspective to make their point and give the user intrigue? Simple — apply a historical perspective.

With this historical perspective, we can see board game scores getting better and better up until 2012, when they began to take a dive — the first multi-year dive in their recorded history. To draw users in, you use comparison to provide surprising perspectives.

Share

This final method is the one that I think is most overlooked. Once you've created your fancy piece of content, let your audience do some leg work for you by releasing the data set. There's an entire community of the Internet just looking for great data sets which could take advantage of your data and cite your content in their own publications. You can find everything from All of Donald Trump's Tweets to Everything Lost at TSA to Hand-drawn Pictures of Pineapples. While there is a good chance your data set won't ever be used, it can pick up a couple of extra links in the event that it does.

Putting it all together

What happens when a webmaster combines these types of methods — exposing unique data, making it relatable and surprising, even for a topic that seems averse to data? You get something like this: Jeans vs. Leggings.

This piece played the geography card for relatability:

They compared user interest in jeans to give perspective to the growth of demand for leggings:

Slice.com reveals their first-party data to make interesting, data-driven content that ultimately scores them links from sites like In Style Magazine, Shape.com, and the NY Post. Looking at fashion through the lens of data meant great traffic and great shares.

How do I get started?

Get down and dirty with the data. Don't wait until you end up with a nice report in your hand, but start slicing and dicing things looking for interesting patterns or results. You can start with the data you already have: Google Analytics, Google Search Console, Google Adwords, and, if you're a Moz customer, even your rank tracking data or keyword research data. If none of these avenues work, dig through the amazing data resources found on Reddit or WebHose. Look for a story in the numbers by relating the data to your audience and making comparisons to provide perspective. It isn't a foolproof formula, but it is pretty close. The right slice of data will cut straight through writer's block.


Sign up for The Moz Top 10, a semimonthly mailer updating you on the top ten hottest pieces of SEO news, tips, and rad links uncovered by the Moz team. Think of it as your exclusive digest of stuff you don't have time to hunt down but want to read!



from The Moz Blog http://ift.tt/2sHxVTT
via IFTTT

Passkeys: What the Heck and Why?

These things called  passkeys  sure are making the rounds these days. They were a main attraction at  W3C TPAC 2022 , gained support in  Saf...