diff --git a/src/lang/array.js b/src/lang/array.js index ffe3ee9..7855ad6 100644 --- a/src/lang/array.js +++ b/src/lang/array.js @@ -1,4 +1,4 @@ -/** section: Language, alias of: Array.from +/** section: Language, alias of: Array.from, related to: Array * $A(iterable) -> Array * * Accepts an array-like collection (anything with numeric indices) and returns @@ -29,7 +29,7 @@ if (Prototype.Browser.WebKit) { }; } -/** section: Language +/** section: Language, related to: Array * $w(string) -> Array * - string (String): A string with zero or more spaces. * @@ -43,11 +43,94 @@ function $w(string) { return string ? string.split(/\s+/) : []; } +/** alias of: $A + * Array.from(iterable) -> Array +**/ Array.from = $A; /** section: Language * class Array + * + * Prototype extends all native JavaScript arrays with quite a few powerful + * methods. + * + * This is done in two ways: + * + * * It mixes in the [[Enumerable]] module, which brings a ton of methods in + * already. + * * It adds quite a few extra methods, which are documented in this section. + * + * With Prototype, arrays become much, much more than the trivial objects we + * used to manipulate, limiting ourselves to using their `length` property and + * their `[]` indexing operator. They become very powerful objects that + * greatly simplify the code for 99% of the common use cases involving them. + * + *

Why you should stop using for…in to iterate

+ * + * Many JavaScript authors have been misled into using the `for…in` JavaScript + * construct to loop over array elements. This kind of code just won’t work + * with Prototype. + * + * The ECMA 262 standard, which defines ECMAScript 3rd edition, supposedly + * implemented by all major browsers including MSIE, defines ten methods + * on Array (§15.4.4), including nice methods like `concat`, `join`, `pop`, and + * `push`. + * + * This same standard explicitely defines that the `for…in` construct (§12.6.4) + * exists to enumerate the properties of the object appearing on the right side + * of the `in` keyword. Only properties specifically marked as _non-enumerable_ + * are ignored by such a loop. By default, the `prototype` and `length` + * properties are so marked, which prevents you from enumerating over array + * methods when using for…in. This comfort led developers to use `for…in` as a + * shortcut for indexing loops, when it is not its actual purpose. + * + * However, Prototype has no way to mark the methods it adds to + * `Array.prototype` as non-enumerable. Therefore, using `for…in` on arrays + * when using Prototype will enumerate all extended methods as well, such as + * those coming from the [[Enumerable]] module, and those Prototype puts in the + * Array namespace (listed further below). + * + *

What you should use instead

+ * + * You can revert to vanilla loops: + * + * for (var index = 0; index < myArray.length; ++index) { + * var item = myArray[index]; + * // Your code working on item here... + * } + * + * Or you can use iterators, such as [[Array#each]]: + * + * myArray.each(function(item) { + * // Your code working on item here... + * }); + * + * + * The inability to use `for...in` on arrays is not much of a burden: as you’ll + * see, most of what you used to loop over arrays for can be concisely done + * using the new methods provided by Array or the mixed-in [[Enumerable]] + * module. So manual loops should be fairly rare. + * + * + *

A note on performance

+ * + * Should you have a very large array, using iterators with lexical closures + * (anonymous functions that you pass to the iterators and that get invoked at + * every loop iteration) in methods like [[Array#each]] — _or_ relying on + * repetitive array construction (such as uniq), may yield unsatisfactory + * performance. In such cases, you’re better off writing manual indexing loops, + * but take care then to cache the length property and use the prefix `++` + * operator: + * + * // Custom loop with cached length property: maximum full-loop + * // performance on very large arrays! + * for (var index = 0, len = myArray.length; index < len; ++index) { + * var item = myArray[index]; + * // Your code working on item here... + * } + * **/ + (function() { var arrayProto = Array.prototype, slice = arrayProto.slice, diff --git a/src/lang/date.js b/src/lang/date.js index e9759de..30abdf8 100644 --- a/src/lang/date.js +++ b/src/lang/date.js @@ -1,12 +1,13 @@ /** section: Language * class Date + * + * Extensions to the built-in `Date` object. **/ /** - * Date#toJSON -> String + * Date#toJSON() -> String * * Produces a string representation of the date in ISO 8601 format. - * **/ Date.prototype.toJSON = function() { return '"' + this.getUTCFullYear() + '-' + diff --git a/src/lang/hash.js b/src/lang/hash.js index 7441673..e4391cd 100644 --- a/src/lang/hash.js +++ b/src/lang/hash.js @@ -1,9 +1,11 @@ -/** section: Language +/** section: Language, related to: Hash * $H([object]) -> Hash * - * Creates a Hash (which is synonymous to “map” or “associative array” for our purposes). - * A convenience wrapper around the Hash constructor, with a safeguard that lets you pass - * an existing Hash object and get it back untouched (instead of uselessly cloning it). + * Creates a `Hash`. + * + * `$H` is a convenience wrapper around the Hash constructor, with a safeguard + * that lets you pass an existing Hash object and get it back untouched + * (instead of uselessly cloning it). **/ function $H(object) { return new Hash(object); @@ -11,6 +13,24 @@ function $H(object) { /** section: Language * class Hash + * + * A set of key/value pairs. + * + * `Hash` can be thought of as an associative array, binding unique keys to + * values (which are not necessarily unique), though it can not guarantee + * consistent order its elements when iterating. Because of the nature of + * JavaScript, every object is in fact a hash; but `Hash` adds a number of + * methods that let you enumerate keys and values, iterate over key/value + * pairs, merge two hashes together, and much more. + * + *

Creating a hash

+ * + * There are two ways to construct a Hash instance: the first is regular + * JavaScript object instantiation with the `new` keyword, and the second is + * using the [[$H]] function. There is one difference between them: if a `Hash` + * is passed to `$H`, it will be returned as-is, wherease the same hash passed + * to `new Hash` will be _cloned_ instead. + * **/ var Hash = Class.create(Enumerable, (function() { /** @@ -35,7 +55,7 @@ var Hash = Class.create(Enumerable, (function() { /** * Hash#set(key, value) -> value * - * Sets the hash’s `key` property to value and returns value. + * Sets the hash’s `key` property to `value` and returns `value`. **/ function set(key, value) { return this._object[key] = value; diff --git a/src/lang/number.js b/src/lang/number.js index dd41dae..d56b68c 100644 --- a/src/lang/number.js +++ b/src/lang/number.js @@ -1,5 +1,16 @@ /** section: Language * class Number + * + * Extensions to the built-in `Number` object. + * + * Prototype extends native JavaScript numbers in order to provide: + * + * * [[ObjectRange]] compatibility, through [[Number#succ]]. + * * Ruby-like numerical loops with [[Number#times]]. + * * Simple utility methods such as [[Number#toColorPart]] and + * [[Number#toPaddedString]]. + * * Instance-method aliases of many functions in the `Math` namespace. + * **/ Object.extend(Number.prototype, (function() { /** diff --git a/src/lang/object.js b/src/lang/object.js index 6d5122c..68b89bc 100644 --- a/src/lang/object.js +++ b/src/lang/object.js @@ -1,5 +1,12 @@ /** section: Language * class Object + * + * Extensions to the built-in `Object` object. + * + * Because it is dangerous and invasive to augment `Object.prototype` (i.e., + * add instance methods to objects), all these methods are static methods that + * take an `Object` as their first parameter. + * **/ (function() { diff --git a/src/lang/periodical_executer.js b/src/lang/periodical_executer.js index 9d18d53..266f324 100644 --- a/src/lang/periodical_executer.js +++ b/src/lang/periodical_executer.js @@ -1,5 +1,15 @@ /** section: Language * class PeriodicalExecuter + * + * A class that oversees the calling of a particular function periodically. + * + * `PeriodicalExecuter` shields you from multiple parallel executions of the + * `callback` function, should it take longer than the given interval to + * execute. + * + * This is especially useful if you use one to interact with the user at + * given intervals (e.g. use a prompt or confirm call): this will avoid + * multiple message boxes all waiting to be actioned. **/ var PeriodicalExecuter = Class.create({ /** @@ -8,18 +18,7 @@ var PeriodicalExecuter = Class.create({ * - frequency (Number): the amount of time, in sections, to wait in between * callbacks. * - * Creates an object that oversees the calling of a particular function via - * `window.setInterval`. - * - * The only notable advantage provided by `PeriodicalExecuter` is that it - * shields you against multiple parallel executions of the `callback` - * function, should it take longer than the given interval to execute (it - * maintains an internal “running” flag, which is shielded against - * exceptions in the callback function). - * - * This is especially useful if you use one to interact with the user at - * given intervals (e.g. use a prompt or confirm call): this will avoid - * multiple message boxes all waiting to be actioned. + * Creates an `PeriodicalExecuter`. **/ initialize: function(callback, frequency) { this.callback = callback; diff --git a/src/lang/range.js b/src/lang/range.js index 36362e3..8affa84 100644 --- a/src/lang/range.js +++ b/src/lang/range.js @@ -1,5 +1,21 @@ /** section: Language * class ObjectRange + * + * Ranges represent an interval of values. The value type just needs to be + * "compatible" — that is, to implement a `succ` method letting us step from + * one value to the next (its successor). + * + * Prototype provides such a method for [[Number]] and [[String]], but you + * are (of course) welcome to implement useful semantics in your own objects, + * in order to enable ranges based on them. + * + * `ObjectRange` mixes in [[Enumerable]], which makes ranges very versatile. + * It takes care, however, to override the default code for `include`, to + * achieve better efficiency. + * + * While `ObjectRange` does provide a constructor, the preferred way to obtain + * a range is to use the [[$R]] utility function, which is strictly equivalent + * (only way more concise to use). **/ /** section: Language diff --git a/src/lang/regexp.js b/src/lang/regexp.js index bf01c43..4b1b7ce 100644 --- a/src/lang/regexp.js +++ b/src/lang/regexp.js @@ -1,5 +1,7 @@ /** section: Language * class RegExp + * + * Extensions to the built-in `RegExp` object. **/ /** alias of: RegExp#test diff --git a/src/lang/string.js b/src/lang/string.js index e378ce8..beaad09 100644 --- a/src/lang/string.js +++ b/src/lang/string.js @@ -1,5 +1,13 @@ /** section: Language * class String + * + * Extensions to the built-in `String` class. + * + * Prototype enhances the `String` object with a series of useful methods for + * ranging from the trivial to the complex. Tired of stripping trailing + * whitespace? Try [[String#strip]]. Want to replace `replace`? Have a look at + * [[String#sub]] and [[String#gsub]]. Need to parse a query string? We have + * [[String#toQueryParams]]. **/ Object.extend(String, { /** diff --git a/src/lang/template.js b/src/lang/template.js index 1603518..5d06e3d 100644 --- a/src/lang/template.js +++ b/src/lang/template.js @@ -1,5 +1,107 @@ /** section: Language * class Template + * + * A class for sophisticated string interpolation. + * + * Any time you have a group of similar objects and you need to produce + * formatted output for these objects, maybe inside a loop, you typically + * resort to concatenating string literals with the object's fields. There's + * nothing wrong with this approach, except that it is hard to visualize the + * output immediately just by glancing at the concatenation expression. The + * `Template` class provides a much nicer and clearer way of achieving this + * formatting. + * + *

Straightforward templates

+ * + * The Template class uses a basic formatting syntax, similar to what is used + * in Ruby. The templates are created from strings that have embedded symbols + * in the form (e.g., `#{fieldName}`) that will be replaced by actual values + * when the template is applied (evaluated) to an object. + * + * + * // the template (our formatting expression) + * var myTemplate = new Template( + * 'The TV show #{title} was created by #{author}.'); + * + * // our data to be formatted by the template + * var show = { + * title: 'The Simpsons', + * author: 'Matt Groening', + * network: 'FOX' + * }; + * + * // let's format our data + * myTemplate.evaluate(show); + * // -> "The TV show The Simpsons was created by Matt Groening." + * + *

Templates are meant to be reused

+ * + * As the example illustrates, `Template` objects are not tied to specific + * data. The data is bound to the template only during the evaluation of the + * template, without affecting the template itself. The next example shows the + * same template being used with a handful of distinct objects. + * + * + * // creating a few similar objects + * var conversion1 = { from: 'meters', to: 'feet', factor: 3.28 }; + * var conversion2 = { from: 'kilojoules', to: 'BTUs', factor: 0.9478 }; + * var conversion3 = { from: 'megabytes', to: 'gigabytes', factor: 1024 }; + * + * // the template + * var templ = new Template( + * 'Multiply by #{factor} to convert from #{from} to #{to}.'); + * + * // let's format each object + * [conversion1, conversion2, conversion3].each( function(conv){ + * templ.evaluate(conv); + * }); + * // -> Multiply by 3.28 to convert from meters to feet. + * // -> Multiply by 0.9478 to convert from kilojoules to BTUs. + * // -> Multiply by 1024 to convert from megabytes to gigabytes. + * + *

Escape sequence

+ * + * There's always the chance that one day you'll need to have a literal in your + * template that looks like a symbol, but is not supposed to be replaced. For + * these situations there's an escape character: the backslash (`\`). + * + * // NOTE: you're seeing two backslashes here because the backslash + * // is also an escape character in JavaScript strings, so a literal + * // backslash is represented by two backslashes. + * var t = new Template( + * 'in #{lang} we also use the \\#{variable} syntax for templates.'); + * var data = { lang:'Ruby', variable: '(not used)' }; + * t.evaluate(data); + * // -> in Ruby we also use the #{variable} syntax for templates. + * + * + * + *

Custom syntaxes

+ * + * The default syntax of the template strings will probably be enough for most + * scenarios. In the rare occasion where the default Ruby-like syntax is + * inadequate, there's a provision for customization. `Template`'s + * constructor accepts an optional second argument that is a regular expression + * object to match the replaceable symbols in the template string. Let's put + * together a template that uses a syntax similar to the ubiquitous `<%= %>` + * constructs: + * + * // matches symbols like '<%= field %>' + * var syntax = /(^|.|\r|\n)(\<%=\s*(\w+)\s*%\>)/; + * + * var t = new Template( + * '
Name: <%= name %>, Age: <%=age%>
', syntax); + * t.evaluate( {name: 'John Smith', age: 26} ); + * // ->
Name: John Smith, Age: 26
+ * + * There are important constraints to any custom syntax. Any syntax must + * provide at least three groupings in the regular expression. The first + * grouping is to capture what comes before the symbol, to detect the backslash + * escape character (no, you cannot use a different character). The second + * grouping captures the entire symbol and will be completely replaced upon + * evaluation. Lastly, the third required grouping captures the name of the + * field inside the symbol. + * **/ var Template = Class.create({ /**