JavaScript/Best Practices

From Wikibooks, open books for an open world
Jump to: navigation, search

Contents

This chapter will describe current Javascript community standards and best practices that every programmer should know.

document.write[edit]

This is deprecated. Use innerHTML or DOM manipulation methods instead.

In XHTML, document.write does not work, but you can achieve the same effects with DOM manipulation methods[1].

JavaScript protocol[edit]

Try to avoid links that exist solely for executing JavaScript code.

<a href="javascript:resumeFancyVersion()">See my résumé!</a>

Instead consider:

 <a href="resumePlainVersion.html" onclick="return !resumeFancyVersion()">See my résumé!</a>

Users with JavaScript enabled will be provided with the JavaScript-embellished version of content (perhaps using DHTML), whereas those without will be redirected to a XHTML page that provides it statically. This is more accessible than having an <a> element lacking a normal href attribute value. First, it uses proper language semantics. Second, it provides access to your content for users without JavaScript. Third, it detects whether the function execution succeeded and redirects JS-enabled readers too in case of a failure.

The onclick expression evaluates to a Boolean value. If the function succeeds to perform the desired effect and returns true, the onclick will return a failure and hyperlink not execute. When the function fails for whatever reason, the false, null or undefined value will evaluate to true and not prevent the link from being executed. Alternatively, if you do not wish to provide a static equivalent, you may embed the onclick attribute within an element with less demanding semantics:

<strong onclick="resumeFancyVersion()">See my résumé!</strong>

Thus no user agent will be confused upon reading a reduced <a> element.

Email validation[edit]

Many people use Javascript functions to immediately catch the most common sorts of errors in form entry. For example, some web forms ask people to enter the same thing twice. Other web forms ask people to enter an email address. Then they do a quick check to see if what was entered looks at least vaguely like an email address:

function isValidEmail(str) {
  // These comments use the following terms from RFC2822:
  // local-part, domain, domain-literal and dot-atom.
  // Does the address contain a local-part followed an @ followed by a domain?
  // Note the use of lastIndexOf to find the last @ in the address
  // since a valid email address may have a quoted @ in the local-part.
  // Does the domain name have at least two parts, i.e. at least one dot,
  // after the @? If not, is it a domain-literal?
  // This will accept some invalid email addresses
  // BUT it doesn't reject valid ones. 
  var atSym = str.lastIndexOf("@");
  if (atSym < 1) { return false; } // no local-part
  if (atSym == str.length - 1) { return false; } // no domain
  if (atSym > 64) { return false; } // there may only be 64 octets in the local-part
  if (str.length - atSym > 255) { return false; } // there may only be 255 octets in the domain

  // Is the domain plausible?
  var lastDot = str.lastIndexOf(".");
  // Check if it is a dot-atom such as example.com
  if (lastDot > atSym + 1 && lastDot < str.length - 1) { return true; }
  //  Check if could be a domain-literal.
  if (str.charAt(atSym + 1) == '[' &&  str.charAt(str.length - 1) == ']') { return true; }
  return false;
}

Unfortunately, some other "email validation" Javascript functions reject perfectly valid email addresses. For example, some incorrectly reject valid addresses that include "+" signs.

The original email address syntax (RFC 821) did allow "+" signs. RFC 822, published in the same month (August 1982), also allowed them. The current version of the syntax is given in RFC2821/RFC2822. Section 3 of RFC3696 gives useful examples of unusual valid email addresses.

After validation, many JavaScript programmers encode the email address using encodeURIComponent() to work-around certain client-side languages that can't seem to handle plus signs properly.[1][2]

The complexity of the quoting rules used for email addresses makes it impractical to test the local-part of an address or a domain literal completely. Given that there isn't necessarily a real mailbox corresponding to a valid local-part how much extra download time is worth spending on a complex validation script?

Examples valid according to RFC2822[edit]

  • me@example.com
  • a.nonymous@example.com
  • name+tag@example.com
  • a.name+tag@example.com
  • me.example@com
  • "spaces must be quoted"@example.com
  • !#$%&'*+-/=.?^_`{|}~@[1.0.0.127]
  • !#$%&'*+-/=.?^_`{|}~@[IPv6:0123:4567:89AB:CDEF:0123:4567:89AB:CDEF]
  • me(this is a comment)@example.com – comments are discouraged but not prohibited by RFC2822.

Examples invalid according to RFC2822s[edit]

  • me@
  • @example.com
  • me.@example.com
  • .me@example.com
  • me@example..com
  • me\@example.com
  • spaces\ must\ be\ within\ quotes\ even\ when\ escaped@example.com
  • a\@mustbeinquotes@example.com



Test page[edit]

COMMENT: This code has been incorrectly designed to reject certain emails that are actually valid. If changes to valid and invalid emails are accepted, then the following code should also be reviewed.

The following test page can be used to test an email validation function. Save the three files in the same directory and then open validEmail.htm in a web browser.

validEmail.js

function isValidEmail(str) {
  // These comments use the following terms from RFC2822:
  // local-part, domain, domain-literal and dot-atom.
  // Does the address contain a local-part followed an @ followed by a domain?
  // Note the use of lastIndexOf to find the last @ in the address
  // since a valid email address may have a quoted @ in the local-part.
  // Does the domain name have at least two parts, i.e. at least one dot,
  // after the @? If not, is it a domain-literal?
  // This will accept some invalid email addresses
  // BUT it doesn't reject valid ones. 
  var atSym = str.lastIndexOf("@");
  if (atSym < 1) { return false; } // no local-part
  if (atSym == str.length - 1) { return false; } // no domain
  if (atSym > 64) { return false; } // there may only be 64 octets in the local-part
  if (str.length - atSym > 255) { return false; } // there may only be 255 octets in the domain

  // Is the domain plausible?
  var lastDot = str.lastIndexOf(".");
  // Check if it is a dot-atom such as example.com
  if (lastDot > atSym + 1 && lastDot < str.length - 1) { return true; }
  //  Check if could be a domain-literal.
  if (str.charAt(atSym + 1) == '[' &&  str.charAt(str.length - 1) == ']') { return true; }
  return false;
}

function testIsValidEmail(str) {
  alert("'" + str + "' is " + (isValidEmail(str) ? "" : "NOT ") + " a valid email address.");
}

function checkSamples() {
  var validAddress = [
    'me@example.com',
    'a.nonymous@example.com',
    'name+tag@example.com',
    'name\\@tag@example.com',
    'spaces\\ are\\ allowed@example.com',
    '"spaces may be quoted"@example.com',
    '!#$%&\'*+-/=.?^_`{|}~@[1.0.0.127]',
    '!#$%&\'*+-/=.?^_`{|}~@[IPv6:0123:4567:89AB:CDEF:0123:4567:89AB:CDEF]',
    'me(this is a comment)@example.com'
  ];
  var invalidAddress = [
    'me@',
    '@example.com',
    'me.@example.com',
    '.me@example.com',
    'me@example..com',
    'me.example@com',
    'me\\@example.com'
  ];

  var results = new StringBuffer();

  var handlesValidAddressesCorrectly = true;
  results.append('<table border="1">');
  results.append('<caption>Does the function accept all the valid sample addresses?</caption>');
  results.append('<tr><th>Valid address</th><th>Function returns</th></tr>');
  for (var i = 0; i < validAddress.length; i++) {
    results.append('<tr><td>');
    results.append(validAddress[i]);
    results.append('</td><td>');
    if (isValidEmail(validAddress[i])) {
      results.append('<span class="good">true</span>');
    } else {
      handlesValidAddressesCorrectly = false;
      results.append('<strong class="fail">false</strong>');
    }
    results.append('</td></tr>');
  }
  results.append('</table>');

  var handlesInvalidAddressesCorrectly = true;
  results.append('<table border="1">');
  results.append('<caption>Does the function reject all the invalid sample addresses?</caption>');
  results.append('<tr><th>Valid address</th><th>Function returns</th></tr>');
  for (var i = 0; i < invalidAddress.length; i++) {
    results.append('<tr><td>');
    results.append(invalidAddress[i]);
    results.append('</td><td>');
    if (!isValidEmail(invalidAddress[i])) {
      results.append('<span class="good">false</span>');
    } else {
      handlesInvalidAddressesCorrectly = false;
      results.append('<em class="warning">true</em>');
    }
    results.append('</td></tr>');
  }
  results.append('</table>');

  var conclusion;
  if (handlesValidAddressesCorrectly) {
    if (handlesInvalidAddressesCorrectly) {
      conclusion = '<p><strong class="good">The function works correctly with all the sample addresses.</strong></p>';
    } else {
      conclusion = '<p><em class="warning">The function incorrectly accepts some invalid addresses.</em></p>';
    }
  } else {
    conclusion = '<p><strong class="fail">The function incorrectly rejects some valid addresses.</strong></p>';
  }

  document.getElementById('testResults').innerHTML = conclusion + results.toString();
}

function StringBuffer() {
  this.buffer = [];
}

StringBuffer.prototype.append = function append(string) {
  this.buffer.push(string);
  return this;
};

StringBuffer.prototype.toString = function toString() {
  return this.buffer.join("");
};

validEmail.css

body {
  background-color:#fff;
  color:#000
}

table {
  margin-bottom:2em
}

caption {
  margin-top:2em
}

.good {
  background-color:#0f0;
  color:#000
}

.warning {
  background-color:#fc9;
  color:#000
}

.fail {
  background-color:#f00;
  color:#fff
}

validEmail.htm

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html lang="en">
  <head>
    <title>Valid email test</title>
    <link rel="stylesheet" type="text/css" href="validEmail.css">
    <script type="text/javascript" src="validEmail.js"></script>
  </head>
  <body onload="checkSamples()">
    <h1>Unit test for email address validation functions</h1>
    <h2>Interactive test</h2>
    <form action="">
      <fieldset>
        <legend>Email address</legend>
        <label for="emailAddress">Email</label>
        <input type="text" size="40" value="" name="email" id="emailAddress">
        <input type="button" name="validate" value="Check address"
            onclick="testIsValidEmail(this.form.email.value)">
      </fieldset>
    </form>
    <h2>Selected samples</h2>
    <p>This section shows the results of testing the function with sample strings.
      The sample includes both valid strings and invalid strings 
      according to <a href="http://www.faqs.org/rfcs/rfc2822.html">RFC2822</a>.
    </p>
    <div id="testResults">You need to enable Javascript for this unit test to work.</div>
  </body>
</html>

use strict[edit]

Many JavaScript programmers recommend enabling ECMAScript 5's strict mode by putting the exact statement "use strict"; before any other statements:[3][4][5]

    "use strict";
    function ...


For further reading[edit]

JavaScript best practices:

Best practices in other languages: