oxjs/examples/documentation/oxdoc_tutorial/js/example.js

627 lines
20 KiB
JavaScript
Raw Normal View History

2012-06-23 18:26:55 +00:00
/*
2012-06-24 15:32:45 +00:00
An `OxDoc` comment is an inline or multi-line comment that starts with `@`:
2012-05-30 17:23:47 +00:00
```
2012-06-23 14:47:08 +00:00
//@ ...
2012-05-30 17:23:47 +00:00
/*@
2012-06-23 14:47:08 +00:00
...
*/
2012-05-30 17:23:47 +00:00
```
The syntax is simple: almost every line has the form `name <type> summary`. If
it doesn't, its meaning depends on its context.
*/
2012-06-24 15:32:45 +00:00
'use strict';
2012-05-30 13:20:41 +00:00
this.My = {};
2012-05-30 17:23:47 +00:00
/*
If the first line of the comment doesn't match `name <type> summary`, it is a
2012-06-23 14:47:08 +00:00
section definition. Here, it marks a section named 'Primitives'.
2012-05-30 17:23:47 +00:00
*/
//@ Primitives
2012-06-23 14:47:08 +00:00
/*
This inline comment documents an item by providing its name, type and one-line
summary.
*/
2012-06-02 09:21:54 +00:00
//@ My.REQUEST_TIMEOUT <number> Request timeout, in milliseconds
My.REQUEST_TIMEOUT = 60000;
2012-05-30 13:20:41 +00:00
2012-06-23 14:47:08 +00:00
/*
In a multiline comment, lines that follow the inital definition are indented, as
they refer to the item defined in the line above. Lines that don't match `name
<type> summary` are parsed as a description. Like the summary, the description
2012-06-24 15:32:45 +00:00
can contain some `markdown` (see Ox.parseMarkdown).
2012-06-23 14:47:08 +00:00
*/
2012-05-30 13:20:41 +00:00
/*@
2012-06-02 09:21:54 +00:00
My.MAGIC_CONSTANT <number> Magic constant, needed for HTTP requests
2012-06-23 14:47:08 +00:00
Please note that the value of `My.MAGIC_CONSTANT` (either `23` or `42`) is
browser-dependent.
2012-05-30 13:20:41 +00:00
*/
2012-06-23 14:47:08 +00:00
My.MAGIC_CONSTANT = navigator.userAgent.length % 2 ? 23 : 42;
2012-05-30 13:20:41 +00:00
2012-06-23 14:47:08 +00:00
/*
This defines a new section named 'Objects'.
*/
2012-05-30 17:23:47 +00:00
//@ Objects
2012-06-23 14:47:08 +00:00
2012-06-02 09:21:54 +00:00
/*
2012-06-24 15:32:45 +00:00
A line that starts with `#` is an inline comment that will be ignored by the
parser.
The following lines document properties of the `My.favorites` object. This
example shows all possible values for `type`. These values can be shortened
it's sufficient to specify their first character.
A line that starts with `>` is an inline test statement, followed by the
expected result. (Yes, it's that simple!)
2012-06-02 09:21:54 +00:00
*/
2012-05-30 13:20:41 +00:00
/*@
2012-06-24 15:32:45 +00:00
My.favorite <object> Collection of favorites
# Properties ---------------------------------------------------------------
2012-06-02 09:21:54 +00:00
array <a> My favorite array
boolean <b> My favorite boolean value
date <d> My favorite date
error <e> My favorite error
function <f> My favorite function
arguments <g> My favorite arguments
htmlelement <h> My favorite HTML element
nodelist <l> My favorite nodelist
number <n> My favorite number
object <o> My favorite object
regexp <r> My favorite regular expression
string <s> My favorite string
undefined <u> Undefined is an all-time favorite
2012-06-23 21:46:58 +00:00
window <w> So is the DOM window
other <+> And the document
2012-06-02 09:21:54 +00:00
any <*> Favorite of the day
# Events -------------------------------------------------------------------
event <!> Fires when My.favorite['function'] is called
2012-06-24 15:32:45 +00:00
# Tests --------------------------------------------------------------------
2012-06-24 13:41:06 +00:00
> My.favorite.array.length + My.favorite.string.length
0
> My.favorite['function'].length + My.favorite.arguments.length
0
> My.favorite.number + Object.keys(My.favorite.object).length
0
> My.favorite.regexp.toString()
'/(?:)/'
2012-06-02 09:21:54 +00:00
*/
2012-06-24 13:41:06 +00:00
My.favorite = (function() {
var favorite = {
2012-06-02 09:21:54 +00:00
array: [],
boolean: false,
date: new Date(),
error: new Error(),
'function': function() {
2012-06-24 13:41:06 +00:00
My.Event.trigger(favorite, 'event');
2012-06-02 09:21:54 +00:00
},
arguments: (function() { return arguments; }()),
htmlelement: document.createElement('a'),
nodelist: document.getElementsByTagName('a'),
number: 0,
object: {},
regexp: new RegExp(),
string: '',
'undefined': void 0,
'window': window,
other: document
},
2012-06-24 13:41:06 +00:00
keys = Object.keys(favorite);
favorite.any = favorite[
2012-06-25 07:29:50 +00:00
keys[Math.floor(+new Date / 86400) % keys.length]
2012-06-23 21:46:58 +00:00
];
2012-06-24 13:41:06 +00:00
return favorite;
2012-06-02 09:21:54 +00:00
}());
2012-06-16 12:28:43 +00:00
2012-06-23 14:47:08 +00:00
/*
Documentation can be nested. In other words, one can document the properties of
2012-06-24 15:32:45 +00:00
a property (of a property...).
If all elements of an array are of a known type (in this case `string`), one can
mark the type as `<[s]>` instead of just `<a>`.
2012-06-23 14:47:08 +00:00
*/
2012-06-02 09:21:54 +00:00
/*@
2012-06-23 14:47:08 +00:00
My.HTMLUtils <o> HTML Utilities
namedEntities <[s]> Named HTML entities
replace <o> Entity decoding utilities
namedEntities <a> Can be passed to `String.prototype.replace`
0 <r> Matches named entities
1 <f> Decodes named entities
numericEntities <a> Can be passed to `String.prototype.replace`
0 <r> Matches numeric entities
1 <f> Decodes numeric entities
2012-06-24 13:41:06 +00:00
> ''.replace.apply('&amp;', My.HTMLUtils.replace.namedEntities)
'&'
> ''.replace.apply('&#x2620;', My.HTMLUtils.replace.numericEntities)
'☠'
2012-05-30 13:20:41 +00:00
*/
My.HTMLUtils = (function() {
2012-06-23 14:47:08 +00:00
var chars = '"&\'<>',
entities = ['&quot;', '&amp;', '&apos;', '&lt;', '&gt;'];
return {
2012-06-23 14:47:08 +00:00
namedEntities: entities,
replace: {
2012-06-23 14:47:08 +00:00
namedEntities: [
new RegExp('(' + entities.join('|') + ')', 'g'),
function(match) {
2012-06-23 14:47:08 +00:00
return chars[entities.indexOf(match)];
}
],
2012-06-23 14:47:08 +00:00
numericEntities: [
/&#([0-9A-FX]+);/gi,
function(match, code) {
2012-06-23 14:47:08 +00:00
return String.fromCharCode(
/^X/i.test(code)
? parseInt(code.slice(1), 16)
: parseInt(code, 10)
);
}
],
}
};
}());
2012-05-30 13:20:41 +00:00
2012-06-23 14:47:08 +00:00
/*
The beginning of another section, named 'Functions'.
*/
2012-05-30 17:23:47 +00:00
//@ Functions
2012-06-23 14:47:08 +00:00
/*
In the case of a function, the indented lines don't document properties, but the
function's signature, return value and arguments. Signature and return value are
just a special case of `name <type> summary`, where `name` has the form
2012-06-24 15:32:45 +00:00
`(arguments) ->`.
If an item can be of more than one type (in this case `string` or `function`),
this is documented as `<s|f>`. If it has a default value (in this case the
2012-06-25 07:16:46 +00:00
string `'GET'`), this is documented as `<s|'GET'>`. For a function-type argument
(usually a callback function), there is no return value to document, only the
arguments it gets passed.
2012-06-23 14:47:08 +00:00
*/
2012-05-30 13:20:41 +00:00
/*@
2012-06-23 14:47:08 +00:00
My.readURL <f> Asynchronously reads a remote resource
2012-05-30 13:20:41 +00:00
(url[, method], callback) -> <u> undefined
2012-06-23 14:47:08 +00:00
Please note that the return value of `My.readURL` may change in the
future.
url <s|f> Remote URL, or function that returns one
method <s|'GET'> Request method ('GET', 'POST', 'PUT' or 'DELETE')
callback <f> Callback function
result <s|null> Response text, or `null` in case of an error
error <o|null> Error object, or `null` in case of success
code <n> Error code
text <s> Error text
2012-05-30 13:20:41 +00:00
*/
My.readURL = function(url, method, callback) {
var request = new XMLHttpRequest();
2012-06-23 14:47:08 +00:00
if (Ox.isFunction(url)) {
url = url();
}
2012-05-30 13:20:41 +00:00
if (arguments.length == 2) {
callback = method;
method = 'GET';
}
request.open(method, url, true);
2012-06-23 14:47:08 +00:00
request.onreadystatechange = function() {
2012-05-30 13:20:41 +00:00
if (request.readyState == 4) {
if (request.status == 200) {
2012-06-23 14:47:08 +00:00
callback(request.responseText, null);
2012-05-30 13:20:41 +00:00
} else {
2012-06-23 14:47:08 +00:00
callback(null, {
code: request.status,
text: request.statusText
});
2012-05-30 13:20:41 +00:00
}
}
};
request.send();
};
2012-06-23 14:47:08 +00:00
/*
If a function's return value depends on the absence or presence of optional
2012-06-24 15:32:45 +00:00
arguments, there can be multiple `(arguments) -> <type> summary` lines.
To test asynchronous functions, just call `Ox.test(actual, expected)` in the
2012-06-24 13:41:06 +00:00
callback.
2012-06-23 14:47:08 +00:00
*/
2012-05-30 13:20:41 +00:00
/*@
My.isOdd <f> Synchronously or asynchronously computes if a given number is odd
(number) -> <b> True if the number is odd
(number, callback) -> <u> undefined
number <n> Any number
callback <f> Callback function
isOdd <b> True if the number is odd
ms <n> Time it took to compute the result, in milliseconds
2012-06-24 13:41:06 +00:00
> My.isOdd(0)
false
> My.isOdd(1, function(isOdd, ms) { Ox.test(isOdd, true); })
undefined
2012-05-30 13:20:41 +00:00
*/
My.isOdd = function(number, callback) {
var time = +new Date, isOdd = !!(number % 2);
if (callback) {
callback(isOdd, +new Date - time);
} else {
return isOdd;
}
2012-05-30 17:23:47 +00:00
};
2012-05-30 13:20:41 +00:00
/*
2012-06-23 14:47:08 +00:00
Another case for multiple `(arguments) -> <type> summary` lines are functions
whose signature cannot be represented in `(required[, optional])` notation. For
a range function `(stop)` or `(start, stop)` or `(start, stop, step)` the
notation `([start, ]stop[, step])` would be ambigious, since you cannot call it
with `(stop, step)`.
2012-05-30 13:20:41 +00:00
*/
/*@
My.range <f> Returns a python-style range
(b) -> <[n]> Integers from 0 (inclusive) to b (exclusive)
(a, b) -> <[n]> Integers from a (inclusice) to b (exclusive)
2012-05-30 17:23:47 +00:00
(a, b, c) -> <[n]> Numbers from a (inclusive) to b (exclusive), growing by c
2012-06-24 13:41:06 +00:00
> My.range(2)
[0, 1]
> My.range(1, 3)
[1, 2]
> My.range(2, 6, 2)
[2, 4]
2012-05-30 13:20:41 +00:00
*/
My.range = function() {
var a = [];
Ox.loop.apply(null, Ox.toArray(arguments).concat(function(i) {
a.push(i);
}));
return a;
};
2012-06-23 14:47:08 +00:00
/*
2012-06-24 13:41:06 +00:00
As functions are objects in JavaScript, they may have their own properties or
methods that need documentation. These get prefixed with `.`, in order to
differentiate them from arguments.
2012-06-23 14:47:08 +00:00
*/
/*@
My.localStorage <f> Returns a localStorage handler for a given namespace
(ns) -> storage <f> localStorage handler
() -> <o> Returns all key:value pairs
(key) -> <*> Returns one value
(key, value) -> <f> Sets one value, returns the handler
({key: value, ...}) -> <f> Sets one or more values, returns the handler
key <s> Any string
value <*> Any value that can be JSON-serialized
.delete <f> Delete method
() -> <f> Deletes all key:value pairs, returns the handler
(key[, ...]) -> <f> Deletes one or more pairs, returns the handler
key <s> Any string
ns <s> Namespace
2012-06-24 13:41:06 +00:00
> Ox.typeOf((My.test = {storage: My.localStorage('My')}).storage)
'function'
> My.test.storage({foo: 'bar'})('baz')
undefined
> My.test.storage('bar', 'baz')('bar')
'baz'
> My.test.storage.delete('bar')('foo')
'bar'
2012-06-25 07:16:46 +00:00
> My.test.storage.delete()()
{}
*/
2012-06-24 13:41:06 +00:00
My.localStorage = function(ns) {
function storage(key, value) {
var ret;
if (arguments.length == 0) {
ret = {};
Ox.forEach(localStorage, function(value, key) {
if (Ox.startsWith(key, ns + '.')) {
ret[key.slice(ns.length + 1)] = JSON.parse(value);
}
});
2012-06-24 13:41:06 +00:00
} else if (arguments.length == 1 && !Ox.isObject(key)) {
value = localStorage[ns + '.' + key];
ret = Ox.isUndefined(value) ? void 0 : JSON.parse(value);
} else {
Ox.forEach(Ox.makeObject(arguments), function(value, key) {
localStorage[ns + '.' + key] = JSON.stringify(value);
});
ret = storage;
}
return ret;
}
storage.delete = function() {
var keys = arguments.length == 0 ? Object.keys(storage())
: Ox.toArray(arguments)
keys.forEach(function(key) {
delete localStorage[ns + '.' + key];
});
return storage;
};
2012-06-24 13:41:06 +00:00
return storage;
};
2012-05-30 17:23:47 +00:00
2012-06-23 14:47:08 +00:00
/*
2012-06-24 13:41:06 +00:00
And another section, named 'UI Elements'.
2012-06-23 14:47:08 +00:00
*/
2012-06-23 18:26:55 +00:00
//@ UI Elements
2012-06-23 14:47:08 +00:00
2012-06-23 18:26:55 +00:00
/*
When documenting a constructor function, the returned object may come with a lot
more documentation than the function itself. In this case, one may want to
document the contructor's arguments first, then the signature and return value,
2012-06-24 15:32:45 +00:00
follwed by the documentation of the returned object
If an event has properties (i.e. passes an object to its handler), these
properties can be documented as well, just like regular object properties.
2012-06-23 18:26:55 +00:00
*/
2012-06-23 14:47:08 +00:00
/*@
My.Box <f> A very simple colored box
options <o> Options
color <[n]> RGB value
self <o> Shared private object
([options[, self]]) -> <o> Box object
2012-06-23 18:26:55 +00:00
change <!> Fires when the color of the box changes
2012-06-25 07:29:50 +00:00
color <[n]> RGB value
2012-06-24 13:41:06 +00:00
> My.Box({color: [0, 255, 0]}).getHSL()
[120, 1, 0.5]
> My.Box().setHSL(240, 1, 0.5).options('color')
[0, 0, 255]
> My.Box().toGrayscale().options('color')
[85, 85, 85]
2012-06-23 14:47:08 +00:00
*/
My.Box = function(options, self) {
self = self || {};
var that = Ox.Element({}, self)
.defaults({color: [255, 0, 0]})
.options(options || {})
.update(setColor)
.css({width: '256px', height: '256px'});
2012-06-23 18:26:55 +00:00
setColor();
2012-06-23 14:47:08 +00:00
function setColor() {
that.css({background: 'rgb(' + self.options.color.join(', ') + ')'});
2012-06-23 18:26:55 +00:00
if (arguments.length) {
that.triggerEvent('change', {color: self.options.color});
}
2012-06-23 14:47:08 +00:00
}
2012-06-23 18:26:55 +00:00
/*
2012-06-24 15:32:45 +00:00
Sometimes, it can be more convenient to document properties at the place
where they are defined. A name prefixed with a `.` signals that what follows
is not a standalone item, but a property of the previous one (or, in case
the previous item is a function that returns an object, a property of the
2012-06-23 18:26:55 +00:00
retuned object).
*/
/*@
.getHSL <f> Returns the color of the box as HSL value
() -> <[n]> HSL value
*/
that.getHSL = function() {
2012-06-24 13:41:06 +00:00
return Ox.hsl(self.options.color);
2012-06-23 18:26:55 +00:00
};
/*@
.setHSL <f> Sets the color of the box to a given HSL value
(hsl) -> <o> The Box object
hsl <[n]> HSL value
*/
that.setHSL = function(hsl) {
return that.options({color: Ox.rgb(hsl)});
};
2012-06-23 14:47:08 +00:00
/*@
.toGrayscale <f> Changes the color of the box to grayscale.
2012-06-23 18:26:55 +00:00
() -> <o> The Box object
2012-06-23 14:47:08 +00:00
*/
that.toGrayscale = function() {
return that.options({
2012-06-24 13:41:06 +00:00
color: Ox.repeat([Math.round(Ox.avg(self.options.color))], 3)
2012-06-23 18:26:55 +00:00
});
2012-06-23 14:47:08 +00:00
};
return that;
};
2012-06-23 21:46:58 +00:00
/*
If an object extends or inherits from another one, one can specify its "class"
2012-06-24 15:32:45 +00:00
(the name of the constuctor of the object it inherits from). Here,
2012-06-23 21:46:58 +00:00
`My.ExtendedBox` extends `My.Box`. All events and properties of the latter,
2012-06-24 15:32:45 +00:00
unless redefined, will be present on the former.
2012-06-23 21:46:58 +00:00
*/
2012-06-23 18:26:55 +00:00
/*@
2012-06-23 21:46:58 +00:00
My.ExtendedBox <f> An extended box with random color
2012-06-23 18:26:55 +00:00
options <o> Options
height <n> Height in px
width <n> Width in px
self <o> Shared private object
([options[, self]]) -> <o:My.Box> Extended Box object
2012-06-24 13:41:06 +00:00
> My.ExtendedBox().options({color: [0, 255, 0]}).getHSL()
[120, 1, 0.5]
> My.ExtendedBox().setHSL(240, 1, 0.5).options('color')
[0, 0, 255]
> [My.ExtendedBox().options('width'), My.ExtendedBox().options('height')]
[256, 256]
2012-06-23 18:26:55 +00:00
*/
My.ExtendedBox = function(options, self) {
self = self || {};
var that = My.Box({}, self)
.defaults({
height: 256,
width: 256
})
.options(options || {})
.update(function(key, value) {
if (key == 'width' || key == 'height') {
setSize();
}
});
randomize();
setSize();
function randomize() {
that.options({
color: Ox.range(3).map(function() {
return Ox.random(256);
})
});
}
function setSize() {
that.css({
width: self.options.width + 'px',
height: self.options.height + 'px'
})
}
/*@
.randomize <f> Randomizes the colors of the box
() -> <o> The Extended Box object
*/
that.randomize = randomize;
return that;
};
2012-06-24 13:41:06 +00:00
/*
2012-06-25 07:29:50 +00:00
The next item will get added to the 'Objects' section.
2012-06-24 13:41:06 +00:00
*/
//@ Objects
/*
2012-06-24 15:32:45 +00:00
Whenever code cannot be tested with just a series of one-liners, a `<script>`
tag can be added before the tests. Since the script will be evaluated in the
global context, it's a good idea not to create or leave any clutter.
2012-06-24 13:41:06 +00:00
*/
/*@
My.Event <o> Provides basic event handling
bind <f> Adds an event handler
(object[, event], callback) -> <o> My.Event
object <o> Any object that triggers events
event <s> Event name (if missing, add handler for all events)
callback <f> Event handler
data <o|null> Event data (if any)
trigger <f> Triggers an event
(object, event[, data]) -> <o> My.Event
object <o> The object that triggers the event
event <s> Event name
data <o> Event data (optional)
unbind <f> Removes one or more event handlers
(object[, event[, callback]]) -> <o> My.Event
object <o> The object that triggers the event
event <s> Event name (if missing, remove handlers for all events)
callback <f> Event handler (if missing, remove all handlers)
<script>
My.test = {
array: [],
handler: function(data) {
My.test.array.push(data.id);
},
object: (function() {
var id = 0,
that = {
ping: function() {
My.Event.trigger(that, 'ping', {id: id});
return id++;
}
};
return that;
}())
};
setTimeout(function() {
delete My.test;
}, 1000);
</script>
> My.test.object.ping() == 0
true
> Ox.methods(My.Event.bind(My.test.object, 'ping', My.test.handler))
['bind', 'trigger', 'unbind']
> My.test.object.ping() == 1
true
> Ox.methods(My.Event.unbind(My.test.object, 'ping', My.test.handler))
['bind', 'trigger', 'unbind']
> My.test.object.ping() == 2
true
> My.test.array
[1]
*/
My.Event = (function() {
var handlers = [], that = {};
that.bind = function(object, event, callback) {
if (arguments.length == 2) {
callback = event;
event = null;
}
handlers.push({object: object, event: event, callback: callback});
return that;
};
that.trigger = function(object, event, data) {
handlers.forEach(function(handler) {
if (handler.object === object && handler.event === event) {
handler.callback(data || null);
}
});
return that;
};
that.unbind = function(object, event, callback) {
handlers.forEach(function(handler, i) {
if (
handler.object === object
&& (!event || !handler.event || handler.event === event)
&& (!callback || handler.callback === callback)
) {
handlers.splice(i, 1);
}
});
return that;
};
return that;
}());
2012-05-30 17:23:47 +00:00
//@
2012-06-24 13:41:06 +00:00
2012-06-23 18:26:55 +00:00
/*
And finally, this is how everything gets parsed and displayed, in less than 30
lines of code. Note that it would be more efficient to parse the source once
2012-06-23 18:26:55 +00:00
```
var doc = Ox.doc(source);
```
and use
```
Ox.SyntaxHighlighter({
showLineNumbers: true,
source: source
})
```
and
```
Ox.TreeList({data: doc})
```
and
```
Ox.DocPanel({
expanded: true,
items: doc,
2012-06-24 13:41:06 +00:00
getModule: function() {
return 'My';
},
path: path,
2012-06-24 13:41:06 +00:00
showTests: true,
stripComments: true
2012-06-23 18:26:55 +00:00
});
```
but the thing we want to demonstrate here is that we can just pass files to
Ox.SyntaxHighlighter and Ox.DocPanel, and they'll do the rest.
*/
2012-05-30 17:23:47 +00:00
Ox.load('UI', function() {
2012-06-23 14:47:08 +00:00
var file = 'example.js',
2012-06-25 07:16:46 +00:00
path = Ox.PATH + '../examples/documentation/oxdoc_tutorial/js/';
2012-06-23 14:47:08 +00:00
Ox.get(path + file, function(source) {
Ox.TabPanel({
2012-06-23 18:26:55 +00:00
content: function(id) {
return id == 'source' ? Ox.SyntaxHighlighter({
file: path + file,
showLineNumbers: true,
}).css({overflowY: 'scroll'})
: id == 'items' ? Ox.TreeList({data: Ox.doc(source)})
: Ox.DocPanel({
expanded: true,
files: [file],
getModule: function() { return 'My'; },
path: path,
2012-06-24 13:41:06 +00:00
showTests: true,
stripComments: true
2012-06-23 18:26:55 +00:00
});
2012-06-23 14:47:08 +00:00
},
tabs: [
2012-06-24 13:41:06 +00:00
{id: 'source', title: 'Source Code'},
2012-06-23 18:26:55 +00:00
{id: 'items', title: 'Parsed Documentation'},
{id: 'panel', title: 'Documentation Browser'}
2012-06-23 14:47:08 +00:00
]
}).appendTo(Ox.$body);
});
2012-05-30 17:23:47 +00:00
});