World Library  
Flag as Inappropriate
Email this Article

Unobtrusive JavaScript

Article Id: WHEBN0009136218
Reproduction Date:

Title: Unobtrusive JavaScript  
Author: World Heritage Encyclopedia
Language: English
Subject: JavaScript, JavaScript library, Responsive web design, JSLint, Nodeclipse
Collection:
Publisher: World Heritage Encyclopedia
Publication
Date:
 

Unobtrusive JavaScript


Unobtrusive JavaScript is a general approach to the use of JavaScript in web pages. Though the term is not formally defined, its basic principles are generally understood to include:

A new paradigm

JavaScript historically has had a reputation for being a clumsy language unsuitable for serious application development.[3][4] This has been largely due to inconsistent implementations of the language itself and the Document Object Model (DOM)[5] in various browsers, and the widespread use of buggy copy-and-paste code. Runtime errors were so common (and so difficult to debug) that few programmers even tried to fix them, as long as the script behaved more or less the way it was supposed to;[6] scripts often failed completely in some browsers.

Some web developers have been promoting graceful degradation since 1994.[7]

The recent emergence of Ajax interfaces has made it desirable. Whereas JavaScript was once reserved for relatively simple and non-critical tasks such as form validation and decorative novelties, it is now being used to write large, complex codebases that are often part of a site's core functionality. Run time errors and unpredictable behavior are no longer minor annoyances; they are fatal flaws.

Advocates of unobtrusive JavaScript see it as part of the larger Web standards movement; much as the demand for cross-browser compatibility has driven the increasing emphasis on standardized markup and style, the increasing demand for rich Internet applications is driving the movement toward better practices with the use of JavaScript. The concept of unobtrusiveness in relation to JavaScript programming was coined in 2002 by Stuart Langridge[8] in the article "Unobtrusive DHTML, and the power of unordered lists".[9] In the article Langridge argues for a way to keep all JavaScript code, including event handlers, outside of the HTML. Stuart Langridge has since expanded upon this thought in book[10] and article format.[11]

Other authors have tried to refine and define the essential elements of the unobtrusive paradigm. David Flanagan's seminal JavaScript: The Definitive Guide says that while there is no specific formula, there are three main goals:

  1. To separate JavaScript from HTML markup, as well as keeping modules of JavaScript independent of other modules.
  2. Unobtrusive JavaScript should degrade gracefully - all content should be available without all or any of the JavaScript running successfully.
  3. Unobtrusive JavaScript should not degrade the accessibility of the HTML, and ideally should improve it, whether the user has personal disabilities or are using an unusual, or unusually configured, browser.[12]

The Web Standards Project describes four benefits of unobtrusive DOM scripting in their JavaScript Manifesto.

  1. Usability: An unobtrusive DOM script does not draw the attention of the user - visitors use it without thinking about it.
  2. Graceful degradation: Unobtrusive DOM scripts never generate error messages, in any browser, even when they fail. If features cannot be presented properly, they silently disappear.
  3. Accessibility: If any script fails, the page still delivers its core functions and information via the markup, stylesheets and/or server-side scripting.
  4. Separation: For the benefit of other and future web developers, all JavaScript code is maintained separately, without impacting other files of script, markup or code.[13]

For the Paris Web Conference in 2007, Christian Heilmann identified seven rules of Unobtrusive JavaScript.

  1. Do not make any assumptions: Defensive programming techniques should allow for the possibilities that JavaScript may not run, the browser may not support expected methods, the HTML may have changed, unexpected input devices may be in use and other scripts may either not be present or may be encroaching on the global namespace.
  2. Find your hooks and relationships, such as IDs and other aspects of the expected HTML.
  3. Leave traversing individual DOM objects to the experts, such as to the CSS handler built into the browser where possible.
  4. Understand browsers and users, particularly how they fail, what assumptions they make, and unusual configurations or usages.
  5. Understand events, including how they 'bubble' and the features of the Event object that is passed to most event handlers.
  6. Play well with other scripts by avoiding global function and variable names.
  7. Work for the next developer by using self-explanatory variable and function names, creating logical and readable code, making dependencies obvious, and commenting any code that still might confuse.[14]

Separation of behavior from markup

Traditionally, JavaScript was often placed inline together with an HTML document's markup. For example, the following is a typical implementation of JavaScript form validation when written inline:


Adherents to "Unobtrusive JavaScript" argue that the purpose of markup is to describe a document's structure, not its programmatic behavior and that combining the two negatively impacts a site's maintainability for similar reasons that combining content and presentation does. They also argue that inline event handlers are harder to use and maintain, when one needs to set handlers for several events on a single element, when one wants to set the same event handler on several elements, or when one is using event delegation. Nor can they be used with custom events.

The unobtrusive solution is to register the necessary event handlers programmatically, rather than inline. Rather than adding the attribute explicitly as above, the relevant element(s) are simply identified, for example by class, id or some other means in the markup:


A script that runs when the page is first loaded into the browser can then look for the relevant element(s) and set them up accordingly:

window. = function() {
    document.getElementById('date'). = validateDate;
};

Namespaces

Unobtrusive JavaScript should add as little as possible to the global object or global namespace of the environment in which it runs. Other scripts may override any variable or function that is created in the global namespace, and this can lead to unexpected failures that are difficult to debug. JavaScript does not have a built-in explicit namespace mechanism, but the desired effects are easy to produce using the language's facilities. Flanagan suggests the use of the developer's own domain name, dotted segments reversed, as a single global name to publish that is very likely to be unique, in the style developed in the Java language.[15]

var org;
if (!org) {
    org = {};
} else if (typeof org != 'object') {
    throw new Error("org already exists and is not an object.");
}
if (!org.example) {
    org.example = {};
} else if (typeof org.example != 'object') {
    throw new Error("org.example already exists and is not an object.");
}

While variables, functions and objects of all kinds can be further defined within such namespace objects, it is usually recommended to use closures within the namespace to further isolate what will become private variables and functions, as well as to export what will be the public interface of each module of functionality. The code above could be followed directly by the following:[14]

org.example.Highlight = function() {
    // Define private data and functions
    var highlightId = 'x';
    function setHighlight(color) { 
        document.getElementById(highlightId).style.color = color;
    }
    
    // Return public pointers to functions or properties
    // that are to be public.
    return {
        goGreen: function() { setHighlight('green'); },
        goBlue:  function() { setHighlight('blue'); }
    }
}(); //End closure definition and invoke it.

From any other module, these public methods could be invoked in either way as follows

org.example.Highlight.goBlue();

var h = org.example.Highlight;
h.goGreen();

In this way, each module-writer's code is contained in private or in a unique namespace and cannot interfere with or intrude upon any other code at any time.

Degrading gracefully

Writing an event listener that detects the loading of the HTML page and then adds relevant listeners to other events on the page, as well as other behaviors as required, can solve the problem of separating JavaScript functionality from HTML markup. The use of client-side JavaScript libraries such as jQuery, MooTools or Prototype can simplify this process and help ensure that individual browser and browser version implementation details are hidden and catered for. Keeping most of the JavaScript out of the default namespace helps ensure that it is as unobtrusive as possible in that sense. A further criterion of unobtrusive JavaScript that is often cited is to ensure that added behavior degrades gracefully on those browsers with unexpected configurations, and those on which the user may have disabled JavaScript altogether.[12]

This requirement is a basic tenet of web accessibility, to ensure that JavaScript-enhanced websites are not only usable by people of all abilities and disabilities but that all users - whatever their computing platform - get equal access to all the site's information and functionality. Sometimes there is extra work involved in achieving this, but web accessibility is not an optional extra in many countries. For example, in the UK, the Equality Act 2010, while it does not refer explicitly to website accessibility, makes it illegal to discriminate against people with disabilities and applies to anyone providing any service in the public, private and voluntary sectors.[16] While a lot of effort may be put into designing and implementing a slick client-side user interface in unobtrusive JavaScript, it will not remain unobtrusive to a user without client-side scripting if they find that they cannot access published information. To meet this goal, it is often necessary to implement equivalent, albeit clunkier, server-side functionality that will be available without the use of JavaScript at all.

Take, for example, a webpage where thumbnail images need JavaScript behaviours so that full-size images will appear in front of the page when the mouse is rolled over them or they are clicked. First, server-side markup should ensure that the relevant full-size image is served to users without JavaScript who click on a thumbnail. In this case the basic HTML markup may look like the following, for each thumbnail:


  Image 1 shows... etc

This will work as it is without JavaScript. Unobtrusive JavaScript, in this case, during page-load, could find all the a elements that have a class of manual-link and remove them from the page DOM. It could then find all the images of class thumb and attach an or an event handler that is specified in-line to provide the slick behaviour. For example, when invoked the event handler may send an Ajax request to the server for the full-size image, then add a div to the page DOM invoking existing CSS so it appears in front of existing content, which itself may become partially greyed out. The div will need a close button, perhaps a visual 'spinner' to show that data is loading, etc. Finally, when the Ajax data arrives, the handler hides the spinner and inserts the full-size image into the new div for display.

This way, all the client-side functionality depends on the same JavaScript function. If that function succeeds, it begins by removing the basic, manual behavior, and goes on to add the client-side scripted behavior. If the script fails for whatever reason, the manual behavior remains in place and remains functional.

Best practices

Though the essence of unobtrusive JavaScript is the concept of an added separate behavior layer, advocates of the paradigm generally subscribe to a number of related principles, such as:

  • DOM Scripting, i.e. adherence to the W3C DOM and event model, and avoidance of browser-specific extensions.
  • Capability detection, i.e. testing for specific functionality before it is used.[17] In particular this is seen as the opposite of browser detection.
  • More generally, JavaScript best practices often parallel those in other programming languages, such as encapsulation and abstraction layers, avoidance of global variables, meaningful naming conventions, use of appropriate design patterns, and systematic testing. Such principles are essential to large-scale software development, but have not been widely observed in JavaScript programming in the past; their adoption is seen as an essential component of JavaScript's transition from a "toy" language to a tool for serious development.

See also

References


-- Module:Hatnote -- -- -- -- This module produces hatnote links and links to related articles. It -- -- implements the and meta-templates and includes -- -- helper functions for other Lua hatnote modules. --


local libraryUtil = require('libraryUtil') local checkType = libraryUtil.checkType local mArguments -- lazily initialise Module:Arguments local yesno -- lazily initialise Module:Yesno

local p = {}


-- Helper functions


local function getArgs(frame) -- Fetches the arguments from the parent frame. Whitespace is trimmed and -- blanks are removed. mArguments = require('Module:Arguments') return mArguments.getArgs(frame, {parentOnly = true}) end

local function removeInitialColon(s) -- Removes the initial colon from a string, if present. return s:match('^:?(.*)') end

function p.findNamespaceId(link, removeColon) -- Finds the namespace id (namespace number) of a link or a pagename. This -- function will not work if the link is enclosed in double brackets. Colons -- are trimmed from the start of the link by default. To skip colon -- trimming, set the removeColon parameter to true. checkType('findNamespaceId', 1, link, 'string') checkType('findNamespaceId', 2, removeColon, 'boolean', true) if removeColon ~= false then link = removeInitialColon(link) end local namespace = link:match('^(.-):') if namespace then local nsTable = mw.site.namespaces[namespace] if nsTable then return nsTable.id end end return 0 end

function p.formatPages(...) -- Formats a list of pages using formatLink and returns it as an array. Nil -- values are not allowed. local pages = {...} local ret = {} for i, page in ipairs(pages) do ret[i] = p._formatLink(page) end return ret end

function p.formatPageTables(...) -- Takes a list of page/display tables and returns it as a list of -- formatted links. Nil values are not allowed. local pages = {...} local links = {} for i, t in ipairs(pages) do checkType('formatPageTables', i, t, 'table') local link = t[1] local display = t[2] links[i] = p._formatLink(link, display) end return links end

function p.makeWikitextError(msg, helpLink, addTrackingCategory) -- Formats an error message to be returned to wikitext. If -- addTrackingCategory is not false after being returned from -- Module:Yesno, and if we are not on a talk page, a tracking category -- is added. checkType('makeWikitextError', 1, msg, 'string') checkType('makeWikitextError', 2, helpLink, 'string', true) yesno = require('Module:Yesno') local title = mw.title.getCurrentTitle() -- Make the help link text. local helpText if helpLink then helpText = ' (help)' else helpText = end -- Make the category text. local category if not title.isTalkPage and yesno(addTrackingCategory) ~= false then category = 'Hatnote templates with errors' category = string.format( '%s:%s', mw.site.namespaces[14].name, category ) else category = end return string.format( '%s', msg, helpText, category ) end


-- Format link -- -- Makes a wikilink from the given link and display values. Links are escaped -- with colons if necessary, and links to sections are detected and displayed -- with " § " as a separator rather than the standard MediaWiki "#". Used in -- the template.


function p.formatLink(frame) local args = getArgs(frame) local link = args[1] local display = args[2] if not link then return p.makeWikitextError( 'no link specified', 'Template:Format hatnote link#Errors', args.category ) end return p._formatLink(link, display) end

function p._formatLink(link, display) -- Find whether we need to use the colon trick or not. We need to use the -- colon trick for categories and files, as otherwise category links -- categorise the page and file links display the file. checkType('_formatLink', 1, link, 'string') checkType('_formatLink', 2, display, 'string', true) link = removeInitialColon(link) local namespace = p.findNamespaceId(link, false) local colon if namespace == 6 or namespace == 14 then colon = ':' else colon = end -- Find whether a faux display value has been added with the | magic -- word. if not display then local prePipe, postPipe = link:match('^(.-)|(.*)$') link = prePipe or link display = postPipe end -- Find the display value. if not display then local page, section = link:match('^(.-)#(.*)$') if page then display = page .. ' § ' .. section end end -- Assemble the link. if display then return string.format('%s', colon, link, display) else return string.format('%s%s', colon, link) end end


-- Hatnote -- -- Produces standard hatnote text. Implements the template.


function p.hatnote(frame) local args = getArgs(frame) local s = args[1] local options = {} if not s then return p.makeWikitextError( 'no text specified', 'Template:Hatnote#Errors', args.category ) end options.extraclasses = args.extraclasses options.selfref = args.selfref return p._hatnote(s, options) end

function p._hatnote(s, options) checkType('_hatnote', 1, s, 'string') checkType('_hatnote', 2, options, 'table', true) local classes = {'hatnote'} local extraclasses = options.extraclasses local selfref = options.selfref if type(extraclasses) == 'string' then classes[#classes + 1] = extraclasses end if selfref then classes[#classes + 1] = 'selfref' end return string.format( '
%s
', table.concat(classes, ' '), s )

end

return p-------------------------------------------------------------------------------- -- Module:Hatnote -- -- -- -- This module produces hatnote links and links to related articles. It -- -- implements the and meta-templates and includes -- -- helper functions for other Lua hatnote modules. --


local libraryUtil = require('libraryUtil') local checkType = libraryUtil.checkType local mArguments -- lazily initialise Module:Arguments local yesno -- lazily initialise Module:Yesno

local p = {}


-- Helper functions


local function getArgs(frame) -- Fetches the arguments from the parent frame. Whitespace is trimmed and -- blanks are removed. mArguments = require('Module:Arguments') return mArguments.getArgs(frame, {parentOnly = true}) end

local function removeInitialColon(s) -- Removes the initial colon from a string, if present. return s:match('^:?(.*)') end

function p.findNamespaceId(link, removeColon) -- Finds the namespace id (namespace number) of a link or a pagename. This -- function will not work if the link is enclosed in double brackets. Colons -- are trimmed from the start of the link by default. To skip colon -- trimming, set the removeColon parameter to true. checkType('findNamespaceId', 1, link, 'string') checkType('findNamespaceId', 2, removeColon, 'boolean', true) if removeColon ~= false then link = removeInitialColon(link) end local namespace = link:match('^(.-):') if namespace then local nsTable = mw.site.namespaces[namespace] if nsTable then return nsTable.id end end return 0 end

function p.formatPages(...) -- Formats a list of pages using formatLink and returns it as an array. Nil -- values are not allowed. local pages = {...} local ret = {} for i, page in ipairs(pages) do ret[i] = p._formatLink(page) end return ret end

function p.formatPageTables(...) -- Takes a list of page/display tables and returns it as a list of -- formatted links. Nil values are not allowed. local pages = {...} local links = {} for i, t in ipairs(pages) do checkType('formatPageTables', i, t, 'table') local link = t[1] local display = t[2] links[i] = p._formatLink(link, display) end return links end

function p.makeWikitextError(msg, helpLink, addTrackingCategory) -- Formats an error message to be returned to wikitext. If -- addTrackingCategory is not false after being returned from -- Module:Yesno, and if we are not on a talk page, a tracking category -- is added. checkType('makeWikitextError', 1, msg, 'string') checkType('makeWikitextError', 2, helpLink, 'string', true) yesno = require('Module:Yesno') local title = mw.title.getCurrentTitle() -- Make the help link text. local helpText if helpLink then helpText = ' (help)' else helpText = end -- Make the category text. local category if not title.isTalkPage and yesno(addTrackingCategory) ~= false then category = 'Hatnote templates with errors' category = string.format( '%s:%s', mw.site.namespaces[14].name, category ) else category = end return string.format( '%s', msg, helpText, category ) end


-- Format link -- -- Makes a wikilink from the given link and display values. Links are escaped -- with colons if necessary, and links to sections are detected and displayed -- with " § " as a separator rather than the standard MediaWiki "#". Used in -- the template.


function p.formatLink(frame) local args = getArgs(frame) local link = args[1] local display = args[2] if not link then return p.makeWikitextError( 'no link specified', 'Template:Format hatnote link#Errors', args.category ) end return p._formatLink(link, display) end

function p._formatLink(link, display) -- Find whether we need to use the colon trick or not. We need to use the -- colon trick for categories and files, as otherwise category links -- categorise the page and file links display the file. checkType('_formatLink', 1, link, 'string') checkType('_formatLink', 2, display, 'string', true) link = removeInitialColon(link) local namespace = p.findNamespaceId(link, false) local colon if namespace == 6 or namespace == 14 then colon = ':' else colon = end -- Find whether a faux display value has been added with the | magic -- word. if not display then local prePipe, postPipe = link:match('^(.-)|(.*)$') link = prePipe or link display = postPipe end -- Find the display value. if not display then local page, section = link:match('^(.-)#(.*)$') if page then display = page .. ' § ' .. section end end -- Assemble the link. if display then return string.format('%s', colon, link, display) else return string.format('%s%s', colon, link) end end


-- Hatnote -- -- Produces standard hatnote text. Implements the template.


function p.hatnote(frame) local args = getArgs(frame) local s = args[1] local options = {} if not s then return p.makeWikitextError( 'no text specified', 'Template:Hatnote#Errors', args.category ) end options.extraclasses = args.extraclasses options.selfref = args.selfref return p._hatnote(s, options) end

function p._hatnote(s, options) checkType('_hatnote', 1, s, 'string') checkType('_hatnote', 2, options, 'table', true) local classes = {'hatnote'} local extraclasses = options.extraclasses local selfref = options.selfref if type(extraclasses) == 'string' then classes[#classes + 1] = extraclasses end if selfref then classes[#classes + 1] = 'selfref' end return string.format( '
%s
', table.concat(classes, ' '), s )

end

return p
  1. ^
  2. ^
  3. ^
  4. ^
  5. ^
  6. ^
  7. ^ Aaron Gustafson. "Understanding Progressive Enhancement". 2008.
  8. ^
  9. ^
  10. ^ (Reference to the first edition, since it shows how the author pioneered the concept.)
  11. ^ E.g.:
  12. ^ a b
  13. ^
  14. ^ a b
  15. ^
  16. ^
  17. ^ http://dev.opera.com/articles/view/using-capability-detection/
This article was sourced from Creative Commons Attribution-ShareAlike License; additional terms may apply. World Heritage Encyclopedia content is assembled from numerous content providers, Open Access Publishing, and in compliance with The Fair Access to Science and Technology Research Act (FASTR), Wikimedia Foundation, Inc., Public Library of Science, The Encyclopedia of Life, Open Book Publishers (OBP), PubMed, U.S. National Library of Medicine, National Center for Biotechnology Information, U.S. National Library of Medicine, National Institutes of Health (NIH), U.S. Department of Health & Human Services, and USA.gov, which sources content from all federal, state, local, tribal, and territorial government publication portals (.gov, .mil, .edu). Funding for USA.gov and content contributors is made possible from the U.S. Congress, E-Government Act of 2002.
 
Crowd sourced content that is contributed to World Heritage Encyclopedia is peer reviewed and edited by our editorial staff to ensure quality scholarly research articles.
 
By using this site, you agree to the Terms of Use and Privacy Policy. World Heritage Encyclopedia™ is a registered trademark of the World Public Library Association, a non-profit organization.
 


Copyright © World Library Foundation. All rights reserved. eBooks from Project Gutenberg are sponsored by the World Library Foundation,
a 501c(4) Member's Support Non-Profit Organization, and is NOT affiliated with any governmental agency or department.