Documenting your code is somewhat like testing; we all know we should do it, we’re not really sure how, and most folks, if we're honest, simply don’t, but those who do are huge proponents of it. This tutorial will get you up to speed on one of the best ways to tackle it: YUIDoc.
What is YUIDoc?
YUIDoc will generate API documentation based on comments that you write.
YUIDoc is a NodeJS app that will generate API documentation (in the form of HTML), based on comments that you write in your JavaScript source code. Actually, it's not just for JavaScript: any programming language that supports block comments delimited by /* */
works for YUIDoc. As you might guess, YUIDoc is one of the tools that Yahoo! publishes along with their YUI Library.
To install YUIDoc, you’ll need NodeJS and the Node package manager (npm) installed first. Then, you can install YUIDoc via npm -g install yuidocjs
. You’ll use it by running yuidoc <path to js folder>
; more on this later.
It's All About the Tags
So, you know that YUIDoc gets its documentation from the multiline comments in the source file. Of course, you might have comments that aren’t part of the documentation. For YUIDoc to recognize a comment as significant, it must start with a double start: /**
. So:
/** YUIDoc will process this */ /* But not this */
Of course, it's what’s inside that counts (inside the comment blocks, that is). Each must include one and only one primary tag; it can also include zero or more secondary tags. Really, YUIDoc is that simple: add comments with the right tags to your code, and presto: documentation! So let’s learn some tags. Here’s how we’re going to do this: we’ll go over the tags, and where they are used, with simple examples of their usages; then, we’ll write and document some code so you have a better idea of how the tags work together.
Primary Tags
Before getting into the primary tags, remember that each comment block can only have a single primary tag. These describe what a given chunk of code is.
@module
The @module
tag describes a group of relate classes. (Yes, yes, JavaScript doesn’t have classes: YUIDoc is referring to constructor functions.) If you were using YUIDoc to document BackboneJS, the Backbone
object would be a module, because it holds the Model
, Collection
, View
, and other classes. Right after the tag, you put the name of the module.
/** @module Backbone */ var Backbone = Backbone || {};
@class
The @class
tag aptly describes a single class. In the YUI Library, this usually means a constructor function, but if you prefer to use a different pattern and call that your class, you can do that, too. Every comment with a @class
tag should also have a @static
or @constructor
tag (secondary tags which we’ll discuss shortly).
/** @class Model */ function Model () {}
If your class is part of a module, you don’t have to do anything within the @class
comment to designate that: just make sure that there’s an @module
comment block at the top of that file.
@method
Of course, every class will have at least a few methods, and you’ll use the @method
tag to describe them. The method name will go after the tag, and you’ll use the secondary tags @return
and @params
to describe the method.
/** @method render */ View.prototype.render = function (data) {}
@property
The @property
tag is used to tag the properties of a class. You’ll want to use the @type
and @default
secondary tags with this one, for sure.
/** @property templateString */ this.templateString = "div";
@event
If you have special custom events that a class can fire, you’ll want to use the @event
tag to describe them. Here’s what the YUIDoc documentation has to say:
An
@event
block is somewhat similar to a@method
block, except that@return
is irrelevant, and@param
is used to describe properties hanging off the event object that callbacks listening for the event receive.
Secondary Tags
Comment blocks can have more than one secondary tag; they’ll often have a handful, and sometimes even more than one of the same type. Let’s look at some of the ones you’ll use often.
@submodule
If you’re dividing your modules into submodules (maybe a submodule per file, maybe not), the @submodule
tag is at your service.
/** @module Util @submodule array */ Util.array = {};
@extends
The @extends
tag is useful when you have superclass/subclass relationships. You can claim what class is the parent of the currently documented class:
/** @class AppView @extends Backbone.View */ var AppView = Backbone.View.extend({});
@constructor
If a class can be instantiated, that means it needs a constructor function. If you’re using the standard prototypal pattern in JavaScript, the class declaration is also the constructor. That means you’ll often see something like this:
/** @class Recipe @constructor */ function Recipe () {}
In fact, you probably recall me saying that every @class
tag should have either a @constructor
or @static
secondary tag.
@static
Speaking of @static
, here it is. A class is considered static when you cannot create an instance of it. A good example of this is the built-in Math
object: you never create an instance of it (new Math()
), you call its methods from the class itself.
/** @class MathHelpers @static */ var MathHelpers = {};
A method can also be static: if a class can be instantiated, but also has some class-level methods, these methods are considered static (they’re called on the class, not the instance).
/** @class Person @constructor */ function Person () {} /** @method all @static */ Person.all = function () {};
In this example, you can create a Person
instance, but the all
method is static.
@final
This tag is used for properties or attributes, and marks said property as a constant: it should not be changed. While JavaScript doesn’t have real constants in its current state, your coding pattern or style guide might use them in principle, so this will be useful for that.
/** @property DATE_FORMAT @final */ var DATE_FORMAT = "%B %d, %Y";
@param
Here’s an important one: the @param
tag is used to define the parameters of a @method
(including a @constructor
) or an @event
. There are three bits of info that go after the @param
tag: the name of the parameter, the type (which is optional), and the description. These can either be in the order name type description
or type name description
; but in either case, the type must be surrounded by curly braces.
/** @method greet @param person {string} The name of the person to greet */ function greet (person) {}
There are a few ways to customize the name
part as well. Putting it in square brackets marks it as optional, while putting =someVal
after it shows what the default value is (obviously, only optional parameters have a default value). Then, if it is a placeholder for more than one argument, append *
to show that. (Obviously, name*
is a placeholder for 1 or more arguments, while [name]*
is a placeholder for 0 or more).
/** @class Template @constructor @param template {String} The template string @param [data={}] {Object} The object whose properties will be rendered in the template */ function Template (template, data) {}
@return
Most of your methods will want to return a value, so this is the tag that describes that value. Don’t forget to tell it what type the value is, and give it a description.
/** @method toHTML @param [template=Recipe.defaultTemplate] {Template} A template object @return {String} The recipe contents formatted in HTML with the default or passed-in template. */ Recipe.prototype.toHTML = function (template) { return "whatever"; };
@type
Remember the @property
primary tag? You’ll want to define what type those properties are, right? Well, the @type
tag is just what you need. Specify the type after the tag; you can also offer multiple types by separating them with vertical bars:
/** @property URL @type String */ URL: "http://net.tutsplus.com", /** @property person @type String|Person|Object */ this.person = new Person();
@private
/ @protected
Traditional programming languages offer private properties or methods: these aren’t accessible from outside of the instance. Just like constants, JavaScript has them by practice only, but you can use @private
to tag these if you use them. Note that YUIDoc doesn’t show private properties in the docs it generates (that makes sense), so this allows you to document a feature for your own benefit and not have it show up in the docs.
/** @method _toString @private */ var _toString = Object.prototype.toString.call;
Protected properties and methods are half-way between public and private: they’re only accessible from within instances and instances of subclasses. If that’s a thing you do in JavaScript, here’s your tag: @protected
.
@requires
If a module depends on one or more other modules, you can use @requires
to mark that:
/** @module MyFramework.localstorage @requires MyFramework */
Note that @requires
could also take a list of dependancies, separated by commas.
@default
When declaring a @property
, you might find it useful to give it a @default
value. @default
should always be used with @type
.
/** @property element @type String @default "div" */ element: "div",
@uses
Like we’ve said, JavaScript doesn’t really have classes, but it's flexible enough to create the illusion of classes, and even sub-classes. What’s even more cool is that it's flexible enough to have mixins or modules: this is where one class “borrows” properties or methods from another class. And it's not inheritance either, because you can mix in parts of more than one class (Of course, YUI has the ability to do this, but so do Dojo and other libraries). If you’re doing this, you’ll find @uses
very useful: it lets you declare what classes a given class is mixing in parts of.
/** @class ModalWindow @uses Window @uses DragDroppable */ var ModalWindow = new Class({ mixes: [Window, DragDroppable], ... });
Note: I just made up that mixin syntax, but I’m pretty sure I’ve seen something similar somewhere.
@example
Want to include an example of how to use a particular piece of code? Use the @example
tag, and then write the example below, indenting it one level. You can add as many examples as you’d like.
/** @method greet @example person.greet("Jane"); */ Person.prototype.greet = function (name) {};
@chainable
You’re probably familiar with chainable methods from jQuery. You know, where you can call a method off a method call, because the methods return the object? Mark your methods as such with @chainable
.
/** @method addClass @chainable */ jQuery.prototype.addClass = function (class) { // stuff; return this; }
@deprecated
/ @since
/ @beta
These three tags are all about support for the code (and it could be any code: module, class, method, or something else). Use @deprecated
to mark some functionality as no longer the best way to do it (deprecated functionality will probably be removed in a future version of the code). Optionally, you can include a message that explains what the current way to do it is.
/** @method toJSON @deprecated Pass the object to `JSON.parse` instead */ Something.toJSON = function () {};
The @since
tag just tells readers what version the given code what added in. And @beta
marks beta code: YUI suggests that @beta
code might “undergo backwards-incompatible changes in the near future.”
/** @class Tooltip @since 1.2.3 @constructor */ function Tooltip () {}
@extension
/ @extensionfor
/ extension_for
The @extension
tag (and its aliases) is pretty much the opposite of @uses
. Use it to mark which classes the extension class can be mixed into. Of course, realize that this doesn’t mean it's always mixed in, just that it can be.
/** @class Draggable @extensionfor ModalWindow */
Comments and Markdown
Before we look at an actual example, let me point out two more things about the documentation comment blocks.
First, you’ll often want to add a bit more information about your code than what the tags offer. Maybe you want to describe the purpose of the methods, or how a class fits into the bigger picture. Add these comments at the top of the comment block, above any of the tags. YUIDoc will notice them and include them in the documentation.
/** The `Router` class is used for . . . @class Router @static */ var Router = {};
Second, you’ll be pleased to know that these comments, as well as any descriptions or message written after the tags, can be written in Markdown, and YUIDoc will convert it to the correct HTML. You can even indent example code blocks in your comments and get syntax highlighting!
An Example
Now that you’ve learned the tags, let’s actually write some code and document it. Let’s create a Store
module, which holds two classes: Item
and Cart
. Each Item
instance will be a type of item in the store inventory: it will have a name, a price, and a quantity. A Cart
instance can add items to the cart and calculate the total price of the items in the cart (tax included). It's fairly simple, but gives us enough varied functionality to use many of the tags we’ve discussed. I’ve put all the following code in store.js
.
We start by creating the module:
/** * This module contains classes for running a store. * @module Store */ var Store = Store || {};
Now, let’s create a “constant”: the tax rate.
/** * `TAX_RATE` is stored as a percentage. Value is 13. * @property TAX_RATE * @static * @final * @type Number */ Store.TAX_RATE = 13;
This is a constant (@final
) @property
of @type
Number. Notice I’ve included @static
: this is because, for some reason, when we generate the documentation for this file, YUIDoc will display this as a property of our Item
class: it seems that YUIDoc doesn’t support having a property on a module. I guess I could create a static class to hold this constant (and other constants that might come if we further developed this), but I’ve left it this way for a reminder: to use a tool like YUIDoc to its fullest potential, you might have to change the way you code. You’ll have to decide if that’s what you want to do.
Now, for the Item
class:
/** * @class Item * @constructor * @param name {String} Item name * @param price {Number} Item price * @param quantity {Number} Item quantity (the number available to buy) */ Store.Item = function (name, price, quantity) { /** * @property name * @type String */ this.name = name; /** * @property price * @type String */ this.price = price * 100; /** * @property quantity * @type Number */ this.quantity = quantity; /** * @property id * @type Number */ this.id = Store.Item._id++; Store.Item.list[this.id] = this; };
As you can see, this constructor has three parameters. Then, there are three properties inside the constructor that we’re also describing. Since we want to give every Item
a unique ID, we need to store a static (class-level) property to increment the ID, and another static property, an object that tracks the Item
s by their ID.
/** * `_id` is incremented when a new item is created, so every item has a unique ID * @property id * @type Number * @static * @private */ Store.Item._id = 1; /** * @property list * @static * @type Object */ Store.Item.list = {};
How about the Cart
class?
/** * @class Cart * @constructor * @param name {String} Customer name */ Store.Cart = function (name) { /** * @property name * @type String */ this.name = name; /** * @property items * @type Object * @default {} */ this.items = {}; };
There’s not really anything new here: notice that we’re declaring that the default (or initial) state of the items
property is an empty object.
Now, the methods. For the addItem
, one of the parameters is optional, so we declare it as so, and give it a default value of 1. Also, notice that we make the method @chainable
.
/** * Adds 1 or more of a given item to the cart, if the chosen quantity * is available. If not, none are added. * * @method addItem * @param item {Object} An `Item` Object * @param [quantity=1] {Number} The number of items to add to the cart * @chainable */ Store.Cart.prototype.addItem = function (item, quantity) { quantity = quantity || 1; if (item.quantity >= quantity) { this.items[item.id] = this.items[item.id] || 0; this.items[item.id] += quantity; item.quantity -= quantity; } return this; };
Finally, we want to be able to return the total price, including taxes. Notice that we’re doing the price math in cents, and then converting to dollars and rounding to two decimal places.
/** * @method total * @return {Number} tax-included total value of cart contents */ Store.Cart.prototype.total = function () { var subtotal, id; subtotal = 0; for (id in this.items) { if(this.items.hasOwnProperty(id)) { subtotal += Store.Item.list[id].price * this.items[id]; } } return parseFloat(((subtotal * (1 + Store.TAX_RATE / 100)) / 100).toFixed(2)); };
If you want to test this code out, here are a simple tests:
var apple, pear, book, desk, assertEquals; assertEquals = function (one, two, msg) { console.log(((one === two) ? "PASS : " : "FAIL : ") + msg); }; apple = new Store.Item('Granny Smith Apple', 1.00, 5); pear = new Store.Item('Barlett Pear', 2.00, 3); book = new Store.Item('On Writing Well', 15.99, 2); desk = new Store.Item('IKEA Gallant', 123.45, 1); cart = new Store.Cart('Andrew'); cart.addItem(apple, 1).addItem(book, 3).addItem(desk, 1); assertEquals(apple.quantity, 4, "adding 1 apple removes 1 from the item quantity"); assertEquals(book.quantity, 2, "trying to add more books than there are means none are added"); assertEquals(cart.total(), 140.63, "total price for 1 apple and 1 desk is 140.63");
Generating the Documentation
Now that we’ve written the code and comment blocks, it's time to generate the documentation.
If you’ve installed it globally via npm, you’ll be able to simply run yuidoc {path to js}
. In my case, that’s
yuidoc .
Now, you’ll see that you have an out
directory in that folder; open out/index.html
, and you’ll see the documentation. Here’s what part of the Cart
class documentation will look like:
Configuring Output
There are several configuration options you can set when using YUIDoc. Sure, you can set them as command line flags, but I’d rather set them in a JSON config file. In your project directory, create a file named yuidoc.json
. First, there’s a bunch of general project information you can set; this doesn’t really effect the output too much, but it's good to document them:
{ "name": "Documenting JavaScript with YUIDoc", "description": "A tutorial about YUIDoc, for Nettuts+", "version": "1.0.0", "url": "http://net.tutsplus.com" }
Then, there are a number of actual options you can set. Here are a couple of interesting ones;
-
linkNatives
: set this to “true” to link native types like String or Number to the MDN docs. -
outdir
: use this one to rename theout
directory -
paths
: use this to set which paths YUIDoc searches for JavaScript files. -
exclude
: set this to a comma-separated list of files you want YUIDoc to ignore.
As long as you set the paths
options, you can run yuidoc -c yuidoc.json
and YUIDoc will run. Even if you don’t set paths
and just run yuidoc .
, YUIDoc will see that config file and apply it.
Here’s my total config file for this project:
{ "name": "Documenting JavaScript with YUIDoc", "description": "A tutorial about YUIDoc, for Nettuts+", "version": "1.0.0", "url": "http://net.tutsplus.com", "options": { "linkNatives": "true", "outdir": "./docs", "paths": "." } }
Evaluation
Based on the tags YUIDoc offers, you can see that it was made for JavaScript written in traditional OOP style, as well as specially for YUI widgets and such (in fact, I’ve left out several tags that were YUI-specific). Because of all this, you might find that several tags just aren’t that useful to you. Then, you have to ask yourself whether you’re willing to change your coding style to better match the way YUIDoc “thinks.” But even if you aren’t going to change, I think you’ll find that most YUIDoc tags will fit in just fine.
The bigger question to me is whether you like to have your documentation inline with your code.
The example code we wrote above is 120 lines with comments, 40 lines without. Obviously, that’s super simple code, and pretty much any real world example would be more balanced; however, reading such interspersed code could be difficult. Personally, I think I’m going to give YUIDoc a fair trial: I’ll be documenting my JavaScript as I write it (or at least, along side it) for the next few weeks. I’ll be interested to see if or how it affects my coding style and workflow.
You know the routine: love it or hate it, let me know in the comments!
Comments