├── test
├── airenv
│ ├── jsclass
│ ├── app.xml
│ └── index.html
├── xulenv
│ ├── chrome
│ │ ├── content
│ │ │ ├── jsclass
│ │ │ └── main.xul
│ │ └── chrome.manifest
│ ├── defaults
│ │ └── preferences
│ │ │ └── prefs.js
│ └── application.ini
├── fixtures
│ └── common.js
├── phantom.js
├── browser.html
├── console.js
├── examples
│ ├── reformat.js
│ ├── benchmarks.js
│ ├── tracing.js
│ └── async.js
├── specs
│ ├── singleton_spec.js
│ ├── test
│ │ └── test_spec_helpers.js
│ ├── method_spec.js
│ ├── console_spec.js
│ ├── interface_spec.js
│ ├── forwardable_spec.js
│ ├── proxy_spec.js
│ ├── decorator_spec.js
│ ├── tsort_spec.js
│ └── comparable_spec.js
└── runner.js
├── source
├── dom
│ ├── _tail.js
│ ├── _head.js
│ ├── dom.js
│ ├── event.js
│ └── builder.js
├── test
│ ├── _tail.js
│ ├── reporters
│ │ ├── exit_status.js
│ │ ├── coverage.js
│ │ ├── phantomjs.js
│ │ ├── dot.js
│ │ ├── test_swarm.js
│ │ ├── composite.js
│ │ ├── json.js
│ │ ├── tap.js
│ │ └── error.js
│ ├── unit
│ │ ├── failure.js
│ │ ├── error.js
│ │ ├── observable.js
│ │ ├── test_result.js
│ │ ├── assertion_message.js
│ │ └── test_suite.js
│ ├── helpers.js
│ ├── ui
│ │ ├── terminal.js
│ │ └── browser.js
│ ├── unit.js
│ ├── context
│ │ ├── test.js
│ │ ├── context.js
│ │ ├── suite.js
│ │ ├── shared_behavior.js
│ │ └── life_cycle.js
│ ├── _head.js
│ ├── mocking
│ │ ├── dsl.js
│ │ └── matchers.js
│ ├── async_steps.js
│ └── coverage.js
├── console
│ ├── _tail.js
│ ├── windows.js
│ ├── phantom.js
│ ├── _head.js
│ ├── browser.js
│ ├── node.js
│ ├── rhino.js
│ ├── config.js
│ ├── browser_color.js
│ └── base.js
├── package
│ ├── browser.js
│ ├── _tail.js
│ ├── loaders
│ │ ├── server.js
│ │ ├── rhino.js
│ │ ├── wsh.js
│ │ ├── commonjs.js
│ │ ├── xulrunner.js
│ │ └── browser.js
│ ├── _head.js
│ ├── loader.js
│ └── dsl.js
├── assets
│ └── bullet_go.png
├── core
│ ├── _tail.js
│ ├── singleton.js
│ ├── _head.js
│ ├── interface.js
│ ├── keywords.js
│ ├── bootstrap.js
│ ├── class.js
│ ├── kernel.js
│ └── utils.js
├── forwardable.js
├── comparable.js
├── constant_scope.js
├── decorator.js
├── observable.js
├── proxy.js
├── deferrable.js
├── benchmark.js
├── tsort.js
├── state.js
└── command.js
├── site
├── config
│ ├── compass.rb
│ └── site.rb
├── site
│ ├── images
│ │ └── tracing.png
│ ├── stylesheets
│ │ └── github.css
│ └── javascripts
│ │ └── analytics.js
└── src
│ ├── pages
│ ├── platforms
│ │ └── node.haml
│ ├── singletons.haml
│ ├── index.haml
│ ├── interfaces.haml
│ ├── benchmark.haml
│ ├── hooks.haml
│ ├── binding.haml
│ ├── platforms.haml
│ ├── debugging.haml
│ ├── packages.haml
│ ├── packages
│ │ ├── autoload.haml
│ │ └── customloaders.haml
│ ├── license.haml
│ ├── forwardable.haml
│ ├── proxies.haml
│ ├── singletonmethods.haml
│ ├── classmethods.haml
│ ├── comparable.haml
│ ├── tsort.haml
│ ├── decorator.haml
│ ├── modifyingmodules.haml
│ ├── stacktrace.haml
│ ├── kernel.haml
│ ├── introduction.haml
│ └── console.haml
│ └── stylesheets
│ └── screen.sass
├── .gitignore
├── .travis.yml
├── Gemfile
├── index.js
├── Rakefile
├── LICENSE
└── README.markdown
/test/airenv/jsclass:
--------------------------------------------------------------------------------
1 | ../..
--------------------------------------------------------------------------------
/test/xulenv/chrome/content/jsclass:
--------------------------------------------------------------------------------
1 | ../../../..
--------------------------------------------------------------------------------
/source/dom/_tail.js:
--------------------------------------------------------------------------------
1 | exports.DOM = DOM;
2 | });
3 |
4 |
--------------------------------------------------------------------------------
/source/test/_tail.js:
--------------------------------------------------------------------------------
1 | exports.Test = Test;
2 | });
3 |
4 |
--------------------------------------------------------------------------------
/source/console/_tail.js:
--------------------------------------------------------------------------------
1 | exports.Console = Console;
2 | });
3 |
4 |
--------------------------------------------------------------------------------
/test/xulenv/chrome/chrome.manifest:
--------------------------------------------------------------------------------
1 | content xultestenv file:content/
2 |
--------------------------------------------------------------------------------
/source/package/browser.js:
--------------------------------------------------------------------------------
1 | Package.loader = Package.BrowserLoader;
2 |
3 |
--------------------------------------------------------------------------------
/site/config/compass.rb:
--------------------------------------------------------------------------------
1 | require "staticmatic/compass"
2 |
3 | project_type = :staticmatic
--------------------------------------------------------------------------------
/source/assets/bullet_go.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/snitko/js.class/master/source/assets/bullet_go.png
--------------------------------------------------------------------------------
/source/core/_tail.js:
--------------------------------------------------------------------------------
1 | JS.extend(exports, JS);
2 | if (global.JS) JS.extend(global.JS, JS);
3 | });
4 |
5 |
--------------------------------------------------------------------------------
/site/site/images/tracing.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/snitko/js.class/master/site/site/images/tracing.png
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | Gemfile.lock
2 | build
3 | node_modules
4 | site/site/stylesheets/screen.css
5 | site/site/*.html
6 |
7 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 |
3 | node_js:
4 | - "0.6"
5 | - "0.8"
6 | - "0.10"
7 | - "0.11"
8 |
9 |
--------------------------------------------------------------------------------
/test/xulenv/defaults/preferences/prefs.js:
--------------------------------------------------------------------------------
1 | pref("toolkit.defaultChromeURI", "chrome://xultestenv/content/main.xul");
2 |
--------------------------------------------------------------------------------
/source/package/_tail.js:
--------------------------------------------------------------------------------
1 | exports.Package = Package;
2 | exports.Packages = exports.packages = packages;
3 | exports.DSL = DSL;
4 | });
5 |
6 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source 'https://rubygems.org'
2 |
3 | gem 'staticmatic'
4 | gem 'compass', '~> 0.11.0'
5 | gem 'haml', '~> 3.1.0'
6 | gem 'RedCloth', '~> 3.0.0'
7 |
8 |
--------------------------------------------------------------------------------
/source/core/singleton.js:
--------------------------------------------------------------------------------
1 | JS.Singleton = new JS.Class('Singleton', {
2 | initialize: function(name, parent, methods) {
3 | return new (new JS.Class(name, parent, methods));
4 | }
5 | });
6 |
7 |
--------------------------------------------------------------------------------
/test/fixtures/common.js:
--------------------------------------------------------------------------------
1 | var Common = {name: 'CommonJS module'};
2 | var HTTP = {name: 'CommonJS HTTP lib'};
3 |
4 | if (typeof exports === 'object') {
5 | exports.Common = Common;
6 | exports.HTTP = HTTP;
7 | }
8 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | var path = require('path'),
2 | cleanup = (typeof JSCLASS_PATH === 'undefined');
3 |
4 | JSCLASS_PATH = path.dirname(__filename) + '/src';
5 | module.exports = require(JSCLASS_PATH + '/loader');
6 |
7 | if (cleanup) delete JSCLASS_PATH;
8 |
9 |
--------------------------------------------------------------------------------
/test/xulenv/application.ini:
--------------------------------------------------------------------------------
1 | [App]
2 | Vendor=JSClass
3 | Name=XULTestEnvironment
4 | Version=0.1
5 | BuildID=20101002
6 | Copyright=Copyright (c) 2010 Aurélio A. Heckert
7 | ID=xultestenv@jsclass.jcoglan.com
8 |
9 | [Gecko]
10 | MinVersion=1.8
11 | MaxVersion=1.9.2.*
12 |
13 |
--------------------------------------------------------------------------------
/source/dom/_head.js:
--------------------------------------------------------------------------------
1 | (function(factory) {
2 | var E = (typeof exports === 'object'),
3 | js = (typeof JS === 'undefined') ? require('./core') : JS;
4 |
5 | if (E) exports.JS = exports;
6 | factory(js, E ? exports : js);
7 |
8 | })(function(JS, exports) {
9 | 'use strict';
10 |
11 |
--------------------------------------------------------------------------------
/test/phantom.js:
--------------------------------------------------------------------------------
1 | JSCLASS_PATH = '../build/src'
2 | var pkg = require(JSCLASS_PATH + '/loader')
3 |
4 | pkg.require('JS.Test', function(Test) {
5 | var page = new WebPage(),
6 | reporter = new Test.Reporters.PhantomJS({}, page)
7 |
8 | page.open('test/browser.html')
9 | })
10 |
11 |
--------------------------------------------------------------------------------
/source/console/windows.js:
--------------------------------------------------------------------------------
1 | Console.extend({
2 | Windows: new JS.Class(Console.Base, {
3 | coloring: function() {
4 | return false;
5 | },
6 |
7 | echo: function(string) {
8 | WScript.Echo(string);
9 | },
10 |
11 | exit: function(status) {
12 | WScript.Quit(status);
13 | }
14 | })
15 | });
16 |
17 |
--------------------------------------------------------------------------------
/source/console/phantom.js:
--------------------------------------------------------------------------------
1 | Console.extend({
2 | Phantom: new JS.Class(Console.Base, {
3 | echo: function(string) {
4 | console.log(string);
5 | },
6 |
7 | envvar: function(name) {
8 | return require('system').env[name] || null;
9 | },
10 |
11 | exit: function(status) {
12 | phantom.exit(status);
13 | }
14 | })
15 | });
16 |
17 |
--------------------------------------------------------------------------------
/site/site/stylesheets/github.css:
--------------------------------------------------------------------------------
1 | .atn { color:#008080 }
2 | .atv { color:#008080 }
3 | .com { color:#999988 }
4 | .dec { color:#000000; font-weight:bold }
5 | .kwd { color:#000000; font-weight:bold }
6 | .lit { color:#009999 }
7 | .pln { color:#000000 }
8 | .pun { color:#666666 }
9 | .str { color:#dd1144 }
10 | .tag { color:#000080 }
11 | .typ { color:#445588 }
12 |
13 |
--------------------------------------------------------------------------------
/source/console/_head.js:
--------------------------------------------------------------------------------
1 | (function(factory) {
2 | var E = (typeof exports === 'object'),
3 | js = (typeof JS === 'undefined') ? require('./core') : JS,
4 |
5 | Enumerable = js.Enumerable || require('./enumerable').Enumerable;
6 |
7 | if (E) exports.JS = exports;
8 | factory(js, Enumerable, E ? exports : js);
9 |
10 | })(function(JS, Enumerable, exports) {
11 | 'use strict';
12 |
13 |
--------------------------------------------------------------------------------
/site/site/javascripts/analytics.js:
--------------------------------------------------------------------------------
1 | (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
2 | (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
3 | m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
4 | })(window,document,'script','//www.google-analytics.com/analytics.js','ga');
5 |
6 | ga('create', 'UA-873493-4', 'jcoglan.com');
7 | ga('send', 'pageview');
8 |
9 |
--------------------------------------------------------------------------------
/test/airenv/app.xml:
--------------------------------------------------------------------------------
1 |
2 |
npm install jsclass8 | 9 | After installing this package, you can either use the 10 | "@JS.require()@":/packages.html API to load components, or use the standard 11 | @require()@ function. 12 | 13 |
// Using JS.require()
14 |
15 | var JS = require('jsclass');
16 |
17 | JS.require('JS.Set', function(Set) {
18 | var s = new Set([1,2,3]);
19 | // ...
20 | });
21 |
22 |
23 | // Using require()
24 |
25 | var Set = require('jsclass/src/set');
26 | var s = new Set([1,2,3]);
27 |
--------------------------------------------------------------------------------
/source/core/interface.js:
--------------------------------------------------------------------------------
1 | JS.Interface = new JS.Class('Interface', {
2 | initialize: function(methods) {
3 | this.test = function(object, returnName) {
4 | var n = methods.length;
5 | while (n--) {
6 | if (typeof object[methods[n]] !== 'function')
7 | return returnName ? methods[n] : false;
8 | }
9 | return true;
10 | };
11 | },
12 |
13 | extend: {
14 | ensure: function() {
15 | var args = JS.array(arguments), object = args.shift(), face, result;
16 | while (face = args.shift()) {
17 | result = face.test(object, true);
18 | if (result !== true) throw new Error('object does not implement ' + result + '()');
19 | }
20 | }
21 | }
22 | });
23 |
24 |
--------------------------------------------------------------------------------
/source/test/helpers.js:
--------------------------------------------------------------------------------
1 | Test.extend({
2 | Helpers: new JS.Module({
3 | $R: function(start, end) {
4 | return new Range(start, end);
5 | },
6 |
7 | $w: function(string) {
8 | return string.split(/\s+/);
9 | },
10 |
11 | forEach: function(list, block, context) {
12 | for (var i = 0, n = list.length; i < n; i++) {
13 | block.call(context, list[i], i);
14 | }
15 | },
16 |
17 | its: function() {
18 | return new MethodChain();
19 | },
20 |
21 | map: function(list, block, context) {
22 | return new Enumerable.Collection(list).map(block, context)
23 | },
24 |
25 | repeat: function(n, block, context) {
26 | while (n--) block.call(context);
27 | }
28 | })
29 | });
30 |
31 |
--------------------------------------------------------------------------------
/source/console/browser.js:
--------------------------------------------------------------------------------
1 | Console.extend({
2 | Browser: new JS.Class(Console.Base, {
3 | backtraceFilter: function() {
4 | return new RegExp(window.location.href.replace(/(\/[^\/]+)/g, '($1)?') + '/?', 'g');
5 | },
6 |
7 | coloring: function() {
8 | if (this.envvar(Console.NO_COLOR)) return false;
9 | return Console.AIR;
10 | },
11 |
12 | echo: function(string) {
13 | if (window.runtime) return window.runtime.trace(string);
14 | if (window.console) return console.log(string);
15 | alert(string);
16 | },
17 |
18 | envvar: function(name) {
19 | return window[name] || null;
20 | },
21 |
22 | getDimensions: function() {
23 | if (Console.AIR) return this.callSuper();
24 | return [1024, 1];
25 | }
26 | })
27 | });
28 |
29 |
--------------------------------------------------------------------------------
/source/test/ui/terminal.js:
--------------------------------------------------------------------------------
1 | Test.UI.extend({
2 | Terminal: new JS.Class({
3 | getOptions: function() {
4 | var options = {},
5 | format = Console.envvar('FORMAT'),
6 | test = Console.envvar('TEST');
7 |
8 | if (Console.envvar('TAP')) options.format = 'tap';
9 |
10 | if (format) options.format = format;
11 | if (test) options.test = [test];
12 |
13 | delete options.argv;
14 | options.test = options.test || [];
15 | return options;
16 | },
17 |
18 | getReporters: function(options) {
19 | var R = Test.Reporters,
20 | Printer = R.get(options.format) || R.Dot;
21 |
22 | return [
23 | new R.Coverage(options),
24 | new Printer(options),
25 | new R.ExitStatus(options)
26 | ];
27 | }
28 | })
29 | });
30 |
31 |
--------------------------------------------------------------------------------
/source/test/unit.js:
--------------------------------------------------------------------------------
1 | var Test = new JS.Module('Test', {
2 | extend: {
3 | asyncTimeout: 5,
4 |
5 | filter: function(objects, suffix) {
6 | return Test.Runner.filter(objects, suffix);
7 | },
8 |
9 | Reporters: new JS.Module({
10 | extend: {
11 | METHODS: ['startSuite', 'startContext', 'startTest',
12 | 'update', 'addFault',
13 | 'endTest', 'endContext', 'endSuite'],
14 |
15 | _registry: {},
16 |
17 | register: function(name, klass) {
18 | this._registry[name] = klass;
19 | },
20 |
21 | get: function(name) {
22 | if (!name) return null;
23 | return this._registry[name] || null;
24 | }
25 | }
26 | }),
27 |
28 | UI: new JS.Module({}),
29 | Unit: new JS.Module({})
30 | }
31 | });
32 |
33 |
--------------------------------------------------------------------------------
/test/console.js:
--------------------------------------------------------------------------------
1 | if (this.ActiveXObject) load = function(path) {
2 | var fso = new ActiveXObject('Scripting.FileSystemObject'), file, runner;
3 | try {
4 | file = fso.OpenTextFile(path);
5 | runner = function() { eval(file.ReadAll()) };
6 | runner();
7 | } finally {
8 | try { if (file) file.Close() } catch (e) {}
9 | }
10 | };
11 |
12 | (function() {
13 | var $ = (typeof global === 'object') ? global : this,
14 | path = $.JSCLASS_PATH = 'build/src/';
15 |
16 | if (typeof phantom !== 'undefined') {
17 | $.JSCLASS_PATH = '../' + $.JSCLASS_PATH;
18 | $.CWD = '..';
19 | }
20 |
21 | if (typeof require === 'function') {
22 | $.JS = require('../' + path + 'loader');
23 | require('./runner');
24 | } else {
25 | load(path + 'loader.js');
26 | load('test/runner.js');
27 | }
28 | })();
29 |
30 |
--------------------------------------------------------------------------------
/source/test/unit/error.js:
--------------------------------------------------------------------------------
1 | Test.Unit.extend({
2 | Error: new JS.Class({
3 | initialize: function(testCase, exception) {
4 | if (typeof exception === 'string')
5 | exception = new Error(exception);
6 |
7 | this._testCase = testCase;
8 | this._exception = exception;
9 | },
10 |
11 | metadata: function() {
12 | return {
13 | test: this.testMetadata(),
14 | error: this.errorMetadata()
15 | }
16 | },
17 |
18 | testMetadata: function() {
19 | return this._testCase.metadata();
20 | },
21 |
22 | errorMetadata: function() {
23 | return {
24 | type: 'error',
25 | message: this._exception.name + ': ' + this._exception.message,
26 | backtrace: Console.filterBacktrace(this._exception.stack)
27 | };
28 | }
29 | })
30 | });
31 |
32 |
--------------------------------------------------------------------------------
/source/test/reporters/phantomjs.js:
--------------------------------------------------------------------------------
1 | // http://phantomjs.org/
2 |
3 | Test.Reporters.extend({
4 | PhantomJS: new JS.Class({
5 | initialize: function(options, page) {
6 | this._options = options || {};
7 |
8 | var format = Console.envvar('FORMAT');
9 |
10 | if (Console.envvar('TAP')) format = format || 'tap';
11 | this._options.format = this._options.format || format;
12 |
13 | var R = Test.Reporters,
14 | Printer = R.get(this._options.format) || R.Dot,
15 | reporter = new R.Composite(),
16 | bridge = new R.JSON.Reader(reporter);
17 |
18 | reporter.addReporter(new Printer(options));
19 | reporter.addReporter(new R.ExitStatus());
20 |
21 | page.onConsoleMessage = function(m) {
22 | if (!bridge.read(m)) console.log(m);
23 | };
24 | }
25 | })
26 | });
27 |
28 |
--------------------------------------------------------------------------------
/source/package/loaders/commonjs.js:
--------------------------------------------------------------------------------
1 | Package.CommonJSLoader = {
2 | usable: function() {
3 | return typeof require === 'function' &&
4 | typeof exports === 'object';
5 | },
6 |
7 | __FILE__: function() {
8 | return this._currentPath;
9 | },
10 |
11 | loadFile: function(path, fireCallbacks) {
12 | var file;
13 |
14 | if (typeof process !== 'undefined') {
15 | var cwd = process.cwd(),
16 | module = path.replace(/\.[^\.]+$/g, ''),
17 | path = require('path');
18 |
19 | file = path.resolve(module);
20 | }
21 | else if (typeof phantom !== 'undefined') {
22 | file = phantom.libraryPath.replace(/\/$/, '') + '/' +
23 | path.replace(/^\//, '');
24 | }
25 |
26 | this._currentPath = file + '.js';
27 | var module = require(file);
28 | fireCallbacks(module);
29 |
30 | return module;
31 | }
32 | };
33 |
34 |
--------------------------------------------------------------------------------
/test/examples/reformat.js:
--------------------------------------------------------------------------------
1 | // This script reads a JS.Test JSON output stream from stdin and reformats it
2 | // using any of the terminal-based test reporters, e.g. dot, spec, error, tap.
3 | //
4 | // Try it out on the JS.Test suite:
5 | //
6 | // $ node test/console -f json | node test/examples/reformat -f tap
7 |
8 | JSCLASS_PATH = 'build/src'
9 | var JS = require('../../' + JSCLASS_PATH + '/loader')
10 |
11 | JS.require('JS.Test', function(Test) {
12 | var options = require('nopt')({format: String}),
13 | R = Test.Reporters,
14 | Printer = R.get(options.format),
15 | reporter = new R.Composite(),
16 | reader = new R.JSON.Reader(reporter)
17 |
18 | reporter.addReporter(new Printer())
19 | reporter.addReporter(new R.ExitStatus())
20 |
21 | process.stdin.on('data', function(data) {
22 | data.toString().split('\n').forEach(reader.method('read'))
23 | })
24 | process.stdin.resume()
25 | })
26 |
27 |
--------------------------------------------------------------------------------
/test/xulenv/chrome/content/main.xul:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | var Camel = new JS.Singleton(Animal, {
12 | fillHumpsWithWater: function() { ... }
13 | });
14 |
15 | // You can call instance methods...
16 | Camel.speak('the desert'); // from Animal
17 | Camel.fillHumpsWithWater();
18 |
19 | var s = Camel.method('speak');
20 | s('drinking');
21 |
22 | Camel.klass.superclass // -> Animal
23 |
24 | @JS.Singleton@ just creates a class with the arguments you give it,
25 | immediately instantiates this new class and returns the instance. You can
26 | access the class through @Camel.klass@ as shown above.
27 |
28 |
--------------------------------------------------------------------------------
/source/test/_head.js:
--------------------------------------------------------------------------------
1 | (function(factory) {
2 | var E = (typeof exports === 'object'),
3 | js = (typeof JS === 'undefined') ? require('./core') : JS,
4 |
5 | Console = js.Console || require('./console').Console,
6 | DOM = js.DOM || require('./dom').DOM,
7 | Enumerable = js.Enumerable || require('./enumerable').Enumerable,
8 | SortedSet = js.SortedSet || require('./set').SortedSet,
9 | Range = js.Range || require('./range').Range,
10 | Hash = js.Hash || require('./hash').Hash,
11 | MethodChain = js.MethodChain || require('./method_chain').MethodChain,
12 | Comparable = js.Comparable || require('./comparable').Comparable,
13 | StackTrace = js.StackTrace || require('./stack_trace').StackTrace;
14 |
15 | if (E) exports.JS = exports;
16 | factory(js, Console, DOM, Enumerable, SortedSet, Range, Hash, MethodChain, Comparable, StackTrace, E ? exports : js);
17 |
18 | })(function(JS, Console, DOM, Enumerable, SortedSet, Range, Hash, MethodChain, Comparable, StackTrace, exports) {
19 | 'use strict';
20 |
21 |
--------------------------------------------------------------------------------
/source/package/loaders/xulrunner.js:
--------------------------------------------------------------------------------
1 | Package.XULRunnerLoader = {
2 | jsloader: '@mozilla.org/moz/jssubscript-loader;1',
3 | cssservice: '@mozilla.org/content/style-sheet-service;1',
4 | ioservice: '@mozilla.org/network/io-service;1',
5 |
6 | usable: function() {
7 | try {
8 | var CC = (Components || {}).classes;
9 | return !!(CC && CC[this.jsloader] && CC[this.jsloader].getService);
10 | } catch(e) {
11 | return false;
12 | }
13 | },
14 |
15 | setup: function() {
16 | var Cc = Components.classes, Ci = Components.interfaces;
17 | this.ssl = Cc[this.jsloader].getService(Ci.mozIJSSubScriptLoader);
18 | this.sss = Cc[this.cssservice].getService(Ci.nsIStyleSheetService);
19 | this.ios = Cc[this.ioservice].getService(Ci.nsIIOService);
20 | },
21 |
22 | loadFile: function(path, fireCallbacks) {
23 | Package.log('[LOAD] ' + path);
24 |
25 | this.ssl.loadSubScript(path);
26 | fireCallbacks();
27 | },
28 |
29 | loadStyle: function(path) {
30 | var uri = this.ios.newURI(path, null, null);
31 | this.sss.loadAndRegisterSheet(uri, this.sss.USER_SHEET);
32 | }
33 | };
34 |
35 |
--------------------------------------------------------------------------------
/source/console/node.js:
--------------------------------------------------------------------------------
1 | Console.extend({
2 | Node: new JS.Class(Console.Base, {
3 | backtraceFilter: function() {
4 | return new RegExp(process.cwd() + '/', 'g');
5 | },
6 |
7 | coloring: function() {
8 | return !this.envvar(Console.NO_COLOR) && require('tty').isatty(1);
9 | },
10 |
11 | envvar: function(name) {
12 | return process.env[name] || null;
13 | },
14 |
15 | exit: function(status) {
16 | process.exit(status);
17 | },
18 |
19 | getDimensions: function() {
20 | var width, height, dims;
21 | if (process.stdout.getWindowSize) {
22 | dims = process.stdout.getWindowSize();
23 | width = dims[0];
24 | height = dims[1];
25 | } else {
26 | dims = process.binding('stdio').getWindowSize();
27 | width = dims[1];
28 | height = dims[0];
29 | }
30 | return [width, height];
31 | },
32 |
33 | print: function(string) {
34 | require('sys').print(this.flushFormat() + string);
35 | },
36 |
37 | puts: function(string) {
38 | require('sys').puts(this.flushFormat() + string);
39 | }
40 | })
41 | });
42 |
43 |
--------------------------------------------------------------------------------
/source/test/reporters/test_swarm.js:
--------------------------------------------------------------------------------
1 | // https://github.com/jquery/testswarm
2 |
3 | Test.Reporters.extend({
4 | TestSwarm: new JS.Class({
5 | extend: {
6 | create: function(options, browser) {
7 | if (JS.ENV.TestSwarm) return new this(options, browser);
8 | }
9 | },
10 |
11 | initialize: function(options, browserReporter) {
12 | this._browserReporter = browserReporter;
13 |
14 | TestSwarm.serialize = function() {
15 | return browserReporter.serialize();
16 | };
17 | },
18 |
19 | startSuite: function(event) {},
20 |
21 | startContext: function(event) {},
22 |
23 | startTest: function(event) {},
24 |
25 | addFault: function(event) {},
26 |
27 | endTest: function(event) {
28 | TestSwarm.heartbeat();
29 | },
30 |
31 | endContext: function(event) {},
32 |
33 | update: function(event) {},
34 |
35 | endSuite: function(event) {
36 | TestSwarm.submit({
37 | fail: event.failures,
38 | error: event.errors,
39 | total: event.tests
40 | });
41 | }
42 | })
43 | });
44 |
45 | Test.Reporters.register('testswarm', Test.Reporters.TestSwarm);
46 |
47 |
--------------------------------------------------------------------------------
/site/src/pages/index.haml:
--------------------------------------------------------------------------------
1 | :textile
2 | h2. The cross-platform JavaScript class library
3 |
4 | @jsclass@ is a portable, modular JavaScript class library, influenced by the
5 | "Ruby":http://ruby-lang.org/ programming language. It provides a rich set of
6 | tools for building object-oriented JavaScript programs, and is designed to run
7 | on a wide variety of "client- and server-side platforms":/platforms.html.
8 |
9 | In particular, to support the writing of equally portable JavaScript code, it
10 | provides a "package manager":/packages.html and "testing framework":http://jstest.jcoglan.com
11 | that run on all supported platforms.
12 |
13 | h3. Features
14 |
15 | The library provides many of Ruby's powerful features, including:
16 |
17 | * An object system with classes, mixins, and singleton methods
18 | * Late-binding arguments-optional @super@ calls to parent classes and mixins
19 | * @included@, @extended@ and @inherited@ hooks
20 | * Context-preserving method binding
21 | * Reflection APIs for the object system
22 | * Conventions for object equality, comparison, iteration and hashing
23 | * Versions of various standard Ruby modules and data structures
24 |
25 |
--------------------------------------------------------------------------------
/source/comparable.js:
--------------------------------------------------------------------------------
1 | (function(factory) {
2 | var E = (typeof exports === 'object'),
3 | js = (typeof JS === 'undefined') ? require('./core') : JS;
4 |
5 | if (E) exports.JS = exports;
6 | factory(js, E ? exports : js);
7 |
8 | })(function(JS, exports) {
9 | 'use strict';
10 |
11 | var Comparable = new JS.Module('Comparable', {
12 | extend: {
13 | ClassMethods: new JS.Module({
14 | compare: function(one, another) {
15 | return one.compareTo(another);
16 | }
17 | }),
18 |
19 | included: function(base) {
20 | base.extend(this.ClassMethods);
21 | }
22 | },
23 |
24 | lt: function(other) {
25 | return this.compareTo(other) < 0;
26 | },
27 |
28 | lte: function(other) {
29 | return this.compareTo(other) < 1;
30 | },
31 |
32 | gt: function(other) {
33 | return this.compareTo(other) > 0;
34 | },
35 |
36 | gte: function(other) {
37 | return this.compareTo(other) > -1;
38 | },
39 |
40 | eq: function(other) {
41 | return this.compareTo(other) === 0;
42 | },
43 |
44 | between: function(a, b) {
45 | return this.gte(a) && this.lte(b);
46 | }
47 | });
48 |
49 | exports.Comparable = Comparable;
50 | });
51 |
52 |
--------------------------------------------------------------------------------
/source/console/rhino.js:
--------------------------------------------------------------------------------
1 | Console.extend({
2 | Rhino: new JS.Class(Console.Base, {
3 | backtraceFilter: function() {
4 | return new RegExp(java.lang.System.getProperty('user.dir') + '/', 'g');
5 | },
6 |
7 | envvar: function(name) {
8 | var env = java.lang.System.getenv();
9 | return env.get(name) || null;
10 | },
11 |
12 | getDimensions: function() {
13 | var proc = java.lang.Runtime.getRuntime().exec(['sh', '-c', 'stty -a < /dev/tty']),
14 | is = proc.getInputStream(),
15 | bite = 0,
16 | out = '',
17 | width, height;
18 |
19 | while (bite >= 0) {
20 | bite = is.read();
21 | if (bite >= 0) out += String.fromCharCode(bite);
22 | }
23 |
24 | var match = out.match(/rows\s+(\d+);\s+columns\s+(\d+)/);
25 | if (!match) return this._dimCache || this.callSuper();
26 |
27 | return this._dimCache = [parseInt(match[2], 10), parseInt(match[1], 10)];
28 | },
29 |
30 | print: function(string) {
31 | java.lang.System.out.print(this.flushFormat() + string);
32 | },
33 |
34 | puts: function(string) {
35 | java.lang.System.out.println(this.flushFormat() + string);
36 | }
37 | })
38 | });
39 |
40 |
--------------------------------------------------------------------------------
/source/core/keywords.js:
--------------------------------------------------------------------------------
1 | JS.Method.keyword('callSuper', function(method, env, receiver, args) {
2 | var methods = env.lookup(method.name),
3 | stackIndex = methods.length - 1,
4 | params = JS.array(args);
5 |
6 | if (stackIndex === 0) return undefined;
7 |
8 | var _super = function() {
9 | var i = arguments.length;
10 | while (i--) params[i] = arguments[i];
11 |
12 | stackIndex -= 1;
13 | if (stackIndex === 0) delete receiver.callSuper;
14 | var returnValue = methods[stackIndex].apply(receiver, params);
15 | receiver.callSuper = _super;
16 | stackIndex += 1;
17 |
18 | return returnValue;
19 | };
20 |
21 | return _super;
22 | });
23 |
24 | JS.Method.keyword('blockGiven', function(method, env, receiver, args) {
25 | var block = Array.prototype.slice.call(args, method.arity),
26 | hasBlock = (typeof block[0] === 'function');
27 |
28 | return function() { return hasBlock };
29 | });
30 |
31 | JS.Method.keyword('yieldWith', function(method, env, receiver, args) {
32 | var block = Array.prototype.slice.call(args, method.arity);
33 |
34 | return function() {
35 | if (typeof block[0] !== 'function') return;
36 | return block[0].apply(block[1] || null, arguments);
37 | };
38 | });
39 |
40 |
--------------------------------------------------------------------------------
/site/src/pages/interfaces.haml:
--------------------------------------------------------------------------------
1 | :textile
2 | h2. Interfaces
3 |
4 | Though not found in Ruby, I've decided to include @Interface@ support in
5 | JS.Class. Interfaces are found in Java and can be very useful in JavaScript
6 | when used judiciously. The idea of an interface is that you create a set of
7 | method names with no implementations. You can then insist that objects/classes
8 | implement the named methods; if you require an object to have a certain set of
9 | methods, you can then throw an exception if it does not.
10 |
11 | To create an interface, just pass in an array of method names:
12 |
13 | var IntComparable = new JS.Interface([ 14 | 'compareTo', 'lt', 'lte', 'gt', 'gte', 'eq' 15 | ]); 16 | 17 | var IntStateMachine = new JS.Interface([ 18 | 'getInitialState', 'changeState' 19 | ]);20 | 21 | You can then test any object to find out whether it implements the required 22 | interfaces: 23 | 24 |
JS.Interface.ensure(someObject, IntComparable, IntStateMachine);25 | 26 | @JS.Interface.ensure@ tests its first argument against all the supplied 27 | interfaces. If it fails one of the tests, an error is thrown that tells you 28 | the name of the first method found to be missing from the object. 29 | -------------------------------------------------------------------------------- /source/dom/dom.js: -------------------------------------------------------------------------------- 1 | var DOM = { 2 | ELEMENT_NODE: 1, 3 | ATTRIBUTE_NODE: 2, 4 | TEXT_NODE: 3, 5 | CDATA_SECTION_NODE: 4, 6 | ENTITY_REFERENCE_NODE: 5, 7 | ENTITY_NODE: 6, 8 | PROCESSING_INSTRUCTION_NODE: 7, 9 | COMMENT_NODE: 8, 10 | DOCUMENT_NODE: 9, 11 | DOCUMENT_TYPE_NODE: 10, 12 | DOCUMENT_FRAGMENT_NODE: 11, 13 | NOTATION_NODE: 12, 14 | 15 | ENV: this, 16 | 17 | toggleClass: function(node, className) { 18 | if (this.hasClass(node, className)) this.removeClass(node, className); 19 | else this.addClass(node, className); 20 | }, 21 | 22 | hasClass: function(node, className) { 23 | var classes = node.className.split(/\s+/); 24 | return JS.indexOf(classes, className) >= 0; 25 | }, 26 | 27 | addClass: function(node, className) { 28 | if (this.hasClass(node, className)) return; 29 | node.className = node.className + ' ' + className; 30 | }, 31 | 32 | removeClass: function(node, className) { 33 | var pattern = new RegExp('\\b' + className + '\\b\\s*', 'g'); 34 | node.className = node.className.replace(pattern, ''); 35 | } 36 | }; 37 | 38 | -------------------------------------------------------------------------------- /source/core/bootstrap.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | var methodsFromPrototype = function(klass) { 3 | var methods = {}, 4 | proto = klass.prototype; 5 | 6 | for (var field in proto) { 7 | if (!proto.hasOwnProperty(field)) continue; 8 | methods[field] = JS.Method.create(klass, field, proto[field]); 9 | } 10 | return methods; 11 | }; 12 | 13 | var classify = function(name, parentName) { 14 | var klass = JS[name], 15 | parent = JS[parentName]; 16 | 17 | klass.__inc__ = []; 18 | klass.__dep__ = []; 19 | klass.__fns__ = methodsFromPrototype(klass); 20 | klass.__tgt__ = klass.prototype; 21 | 22 | klass.prototype.constructor = 23 | klass.prototype.klass = klass; 24 | 25 | JS.extend(klass, JS.Class.prototype); 26 | klass.include(parent || JS.Kernel); 27 | klass.setName(name); 28 | 29 | klass.constructor = klass.klass = JS.Class; 30 | }; 31 | 32 | classify('Method'); 33 | classify('Module'); 34 | classify('Class', 'Module'); 35 | 36 | var eigen = JS.Kernel.instanceMethod('__eigen__'); 37 | 38 | eigen.call(JS.Method).resolve(); 39 | eigen.call(JS.Module).resolve(); 40 | eigen.call(JS.Class).include(JS.Module.__meta__); 41 | })(); 42 | 43 | JS.NotImplementedError = new JS.Class('NotImplementedError', Error); 44 | 45 | -------------------------------------------------------------------------------- /source/test/unit/observable.js: -------------------------------------------------------------------------------- 1 | Test.Unit.extend({ 2 | Observable: new JS.Module({ 3 | addListener: function(channelName, block, context) { 4 | if (block === undefined) throw new Error('No callback was passed as a listener'); 5 | 6 | this.channels()[channelName] = this.channels()[channelName] || []; 7 | this.channels()[channelName].push([block, context]); 8 | 9 | return block; 10 | }, 11 | 12 | removeListener: function(channelName, block, context) { 13 | var channel = this.channels()[channelName]; 14 | if (!channel) return; 15 | 16 | var i = channel.length; 17 | while (i--) { 18 | if (channel[i][0] === block) { 19 | channel.splice(i,1); 20 | return block; 21 | } 22 | } 23 | return null; 24 | }, 25 | 26 | notifyListeners: function(channelName, args) { 27 | var args = JS.array(arguments), 28 | channelName = args.shift(), 29 | channel = this.channels()[channelName]; 30 | 31 | if (!channel) return 0; 32 | 33 | for (var i = 0, n = channel.length; i < n; i++) 34 | channel[i][0].apply(channel[i][1] || null, args); 35 | 36 | return channel.length; 37 | }, 38 | 39 | channels: function() { 40 | return this.__channels__ = this.__channels__ || []; 41 | } 42 | }) 43 | }); 44 | 45 | -------------------------------------------------------------------------------- /site/src/stylesheets/screen.sass: -------------------------------------------------------------------------------- 1 | body 2 | background: #fff 3 | border-top: 2em solid #95c3f0 4 | color: #30303c 5 | font: 300 18px/1.4 Open Sans, FreeSans, Helvetica, Arial, sans-serif 6 | margin: 0 7 | padding: 0 8 | 9 | .nav 10 | float: left 11 | margin: 2em 12 | 13 | h1 14 | font-size: 2em 15 | font-weight: 400 16 | margin: 0 17 | 18 | a 19 | color: #30303c 20 | 21 | h4, li 22 | font-size: 90% 23 | 24 | .content 25 | margin: 2em 0 3em 20em 26 | width: 40em 27 | 28 | h2 29 | font-size: 1.8em 30 | font-weight: 300 31 | margin: 0 0 1em 32 | 33 | h3 34 | font-size: 1em 35 | font-weight: bold 36 | margin: 2.8em 0 1.4em 37 | 38 | p, ul, pre 39 | margin: 1.4em 0 40 | 41 | ul 42 | margin: 1em 0 0 2em 43 | padding: 0 44 | list-style: circle outside 45 | 46 | ul ul 47 | font-size: 0.9em 48 | margin-top: 0 49 | 50 | a 51 | color: #3094c0 52 | text-decoration: none 53 | 54 | a:hover 55 | text-decoration: underline 56 | 57 | pre, code 58 | font-family: Inconsolata, Monaco, Lucida Console, Courier New, monospace 59 | 60 | pre, blockquote 61 | border-left: 1em solid #f0f0f0 62 | font-size: 0.9em 63 | margin-left: 0 64 | padding-left: 2em 65 | 66 | .footer 67 | border-top: 1px solid #d0d0d0 68 | clear: both 69 | margin: 2em 0 0 2em 70 | padding: 1em 0 2em 18em 71 | width: 40em 72 | 73 | -------------------------------------------------------------------------------- /source/console/config.js: -------------------------------------------------------------------------------- 1 | Console.BROWSER = (typeof window !== 'undefined'); 2 | Console.NODE = (typeof process === 'object') && !Console.BROWSER; 3 | Console.PHANTOM = (typeof phantom !== 'undefined'); 4 | Console.AIR = (Console.BROWSER && typeof runtime !== 'undefined'); 5 | Console.RHINO = (typeof java !== 'undefined' && typeof java.lang !== 'undefined'); 6 | Console.WSH = (typeof WScript !== 'undefined'); 7 | 8 | var useColor = false, ua; 9 | if (Console.BROWSER) { 10 | ua = navigator.userAgent; 11 | if (window.console && (/Firefox/.test(ua) || /Chrome/.test(ua))) 12 | useColor = true; 13 | } 14 | 15 | if (Console.PHANTOM) Console.adapter = new Console.Phantom(); 16 | else if (useColor) Console.adapter = new Console.BrowserColor(); 17 | else if (Console.BROWSER) Console.adapter = new Console.Browser(); 18 | else if (Console.NODE) Console.adapter = new Console.Node(); 19 | else if (Console.RHINO) Console.adapter = new Console.Rhino(); 20 | else if (Console.WSH) Console.adapter = new Console.Windows(); 21 | else Console.adapter = new Console.Base(); 22 | 23 | for (var type in Console.ESCAPE_CODES) { 24 | for (var key in Console.ESCAPE_CODES[type]) (function(type, key) { 25 | Console.define(key, function() { 26 | Console.adapter.format(type, key, arguments); 27 | }); 28 | })(type, key); 29 | } 30 | 31 | Console.extend(Console); 32 | 33 | -------------------------------------------------------------------------------- /source/test/context/context.js: -------------------------------------------------------------------------------- 1 | Test.extend({ 2 | Context: new JS.Module({ 3 | extend: { 4 | included: function(base) { 5 | base.extend(Test.Context.Context, {_resolve: false}); 6 | base.include(Test.Context.LifeCycle, {_resolve: false}); 7 | base.extend(Test.Context.Test, {_resolve: false}); 8 | base.include(Console); 9 | }, 10 | 11 | Context: new JS.Module({ 12 | context: function(name, block) { 13 | var klass = new JS.Class(name.toString(), this, {}, {_resolve: false}); 14 | klass.__eigen__().resolve(); 15 | block.call(klass); 16 | return klass; 17 | }, 18 | 19 | cover: function(module) { 20 | var logger = new Test.Coverage(module); 21 | this.before_all_callbacks.push(logger.method('attach')); 22 | this.after_all_callbacks.push(logger.method('detach')); 23 | Test.Unit.TestCase.reports.push(logger); 24 | } 25 | }) 26 | } 27 | }), 28 | 29 | describe: function(name, block) { 30 | var klass = new JS.Class(name.toString(), Test.Unit.TestCase, {}, {_resolve: false}); 31 | klass.include(Test.Context, {_resolve: false}); 32 | klass.__eigen__().resolve(); 33 | 34 | block.call(klass); 35 | return klass; 36 | } 37 | }); 38 | 39 | Test.Context.Context.alias({describe: 'context'}); 40 | 41 | Test.extend({ 42 | context: Test.describe 43 | }); 44 | 45 | -------------------------------------------------------------------------------- /source/test/reporters/composite.js: -------------------------------------------------------------------------------- 1 | Test.Reporters.extend({ 2 | Composite: new JS.Class({ 3 | initialize: function(reporters) { 4 | this._reporters = reporters || []; 5 | this._queue = []; 6 | this._pointer = 0; 7 | }, 8 | 9 | addReporter: function(reporter) { 10 | if (!reporter) return; 11 | this._reporters.push(reporter); 12 | }, 13 | 14 | removeReporter: function(reporter) { 15 | var index = JS.indexOf(this._reporters, reporter); 16 | if (index >= 0) this._reporters.splice(index, 1); 17 | }, 18 | 19 | flush: function() { 20 | var queue = this._queue, method, event, i, n, fn; 21 | while (queue[this._pointer] !== undefined) { 22 | method = queue[this._pointer][0]; 23 | event = queue[this._pointer][1]; 24 | for (i = 0, n = this._reporters.length; i < n; i++) { 25 | fn = this._reporters[i][method]; 26 | if (fn) fn.call(this._reporters[i], event); 27 | } 28 | this._pointer += 1; 29 | } 30 | } 31 | }) 32 | }); 33 | 34 | (function() { 35 | var methods = Test.Reporters.METHODS, 36 | n = methods.length; 37 | 38 | while (n--) 39 | (function(i) { 40 | var method = methods[i]; 41 | Test.Reporters.Composite.define(method, function(event) { 42 | this._queue[event.eventId] = [method, event]; 43 | this.flush(); 44 | }); 45 | })(n); 46 | })(); 47 | 48 | -------------------------------------------------------------------------------- /source/core/class.js: -------------------------------------------------------------------------------- 1 | JS.Class = JS.makeClass(JS.Module); 2 | 3 | JS.extend(JS.Class.prototype, { 4 | initialize: function(name, parent, methods, options) { 5 | if (typeof name !== 'string') { 6 | options = arguments[2]; 7 | methods = arguments[1]; 8 | parent = arguments[0]; 9 | name = undefined; 10 | } 11 | if (typeof parent !== 'function') { 12 | options = methods; 13 | methods = parent; 14 | parent = Object; 15 | } 16 | JS.Module.prototype.initialize.call(this, name); 17 | options = options || {}; 18 | 19 | var klass = JS.makeClass(parent); 20 | JS.extend(klass, this); 21 | 22 | klass.prototype.constructor = 23 | klass.prototype.klass = klass; 24 | 25 | klass.__eigen__().include(parent.__meta__, {_resolve: options._resolve}); 26 | klass.setName(name); 27 | 28 | klass.__tgt__ = klass.prototype; 29 | 30 | var parentModule = (parent === Object) 31 | ? {} 32 | : (parent.__fns__ ? parent : new JS.Module(parent.prototype, {_resolve: false})); 33 | 34 | klass.include(JS.Kernel, {_resolve: false}) 35 | .include(parentModule, {_resolve: false}) 36 | .include(methods, {_resolve: false}); 37 | 38 | if (options._resolve !== false) klass.resolve(); 39 | 40 | if (typeof parent.inherited === 'function') 41 | parent.inherited(klass); 42 | 43 | return klass; 44 | } 45 | }); 46 | 47 | -------------------------------------------------------------------------------- /site/src/pages/benchmark.haml: -------------------------------------------------------------------------------- 1 | :textile 2 | h2. Benchmark 3 | 4 | @JS.Benchmark@ provides a tool for measuring the execution time of blocks 5 | of JavaScript code. To take a measurement, you just supply a name for the 6 | measurement, the number of times to run it, and the function you want to 7 | execute: 8 | 9 |
JS.Benchmark.measure('String#join', 20, {
10 | test: function() {
11 | ['a', 'list', 'of', 'strings'].join(' ');
12 | }
13 | });
14 |
15 | @JS.Benchmark@ will take your @test@ function, run it the given number of
16 | times, and print the mean and standard deviation of the function's execution
17 | time to the "console":/console.html.
18 |
19 | If the operation you're benchmarking requires some setup, you probably don't
20 | want to include the setup time in the measurement. To help you measure the
21 | right thing, you can place any setup code in its own function. State can be
22 | shared between the @setup@ and @test@ functions by assigning values to @this@:
23 |
24 | JS.Benchmark.measure('Module#ancestors', 10, {
25 | setup: function() {
26 | this.module = new JS.Module({ include: [JS.Comparable, JS.Enumerable] });
27 | },
28 | test: function() {
29 | this.module.ancestors();
30 | }
31 | });
32 |
33 | The time reported by @Benchmark@ will not include the time it took to run the
34 | @setup@ function, only the @test@ function is included.
35 |
--------------------------------------------------------------------------------
/source/test/ui/browser.js:
--------------------------------------------------------------------------------
1 | Test.UI.extend({
2 | Browser: new JS.Class({
3 | getOptions: function() {
4 | var qs = (location.search || '').replace(/^\?/, ''),
5 | pairs = qs.split('&'),
6 | options = {},
7 | parts, key, value;
8 |
9 | for (var i = 0, n = pairs.length; i < n; i++) {
10 | parts = pairs[i].split('=');
11 | key = decodeURIComponent(parts[0]);
12 | value = decodeURIComponent(parts[1]);
13 |
14 | if (/\[\]$/.test(parts[0])) {
15 | key = key.replace(/\[\]$/, '');
16 | if (!(options[key] instanceof Array)) options[key] = [];
17 | options[key].push(value);
18 | } else {
19 | options[key] = value;
20 | }
21 | }
22 |
23 | if (options.test)
24 | options.test = [].concat(options.test);
25 | else
26 | options.test = [];
27 |
28 | return options;
29 | },
30 |
31 | getReporters: function(options) {
32 | var reporters = [],
33 | R = Test.Reporters,
34 | reg = R._registry,
35 | browser = new R.Browser(options),
36 | reporter;
37 |
38 | reporters.push(new R.Coverage());
39 | reporters.push(browser);
40 |
41 | for (var name in reg) {
42 | reporter = reg[name] && reg[name].create && reg[name].create(options, browser);
43 | if (reporter) reporters.push(reporter);
44 | }
45 |
46 | return reporters;
47 | }
48 | })
49 | });
50 |
51 |
--------------------------------------------------------------------------------
/source/test/reporters/json.js:
--------------------------------------------------------------------------------
1 | Test.Reporters.extend({
2 | JSON: new JS.Class({
3 | include: Console,
4 |
5 | _log: function(eventName, data) {
6 | if (!JS.ENV.JSON) return;
7 | this.puts(JSON.stringify({jstest: [eventName, data]}));
8 | },
9 |
10 | extend: {
11 | create: function() {
12 | if (!JS.ENV.navigator) return;
13 | if (/\bPhantomJS\b/.test(navigator.userAgent)) return new this();
14 | },
15 |
16 | Reader: new JS.Class({
17 | initialize: function(reporter) {
18 | this._reporter = new Test.Reporters.Composite([reporter]);
19 | },
20 |
21 | read: function(message) {
22 | if (!JS.ENV.JSON) return false;
23 | try {
24 | var data = JSON.parse(message),
25 | payload = data.jstest,
26 | method = payload[0],
27 | event = payload[1];
28 |
29 | this._reporter[method](event);
30 | return true;
31 | }
32 | catch (e) {
33 | return false;
34 | }
35 | }
36 | })
37 | }
38 | })
39 | });
40 |
41 | (function() {
42 | var methods = Test.Reporters.METHODS,
43 | n = methods.length;
44 |
45 | while (n--)
46 | (function(i) {
47 | var method = methods[i];
48 | Test.Reporters.JSON.define(method, function(event) {
49 | this._log(method, event);
50 | });
51 | })(n);
52 | })();
53 |
54 | Test.Reporters.register('json', Test.Reporters.JSON);
55 |
56 |
--------------------------------------------------------------------------------
/site/src/pages/hooks.haml:
--------------------------------------------------------------------------------
1 | :textile
2 | h2. Metaprogramming hooks
3 |
4 | Ruby defines a few hook methods that you can use to detect when a class is
5 | subclassed or when a module is mixed in. These hooks are called @inherited()@,
6 | @included()@ and @extended()@.
7 |
8 | If a class has a class method called @inherited()@ it will be called whenever
9 | you create a subclass of it:
10 |
11 | var ChildDetector = new JS.Class({
12 | extend: {
13 | inherited: function(klass) {
14 | // Do stuff with child class
15 | }
16 | }
17 | });
18 |
19 | The hook receives the new child class as an argument. Note that @class@ is a
20 | reserved word in JavaScript and should not be used as a variable name. The
21 | child class will have all its methods in place when @inherited()@ gets called,
22 | so you can use them within your callback function.
23 |
24 | In the same vein, if you @include()@ a module that has a singleton method
25 | called @included@, that method will be called. This effectively allows you to
26 | redefine the meaning of @include@ for individual modules.
27 |
28 | The @extended()@ hook works in much the same way as @included()@, except that
29 | it will be called when the module is used to @extend()@ an object.
30 |
31 | // This will call MyMod.extended(obj) 32 | obj.extend(MyMod);33 | 34 | Again, you can use this to redefine how @extend()@ works with individual 35 | modules, so they can change the behaviour of the objects they extend. 36 | -------------------------------------------------------------------------------- /site/src/pages/binding.haml: -------------------------------------------------------------------------------- 1 | :textile 2 | h2. Method binding 3 | 4 | Ruby has method objects (@Method@ and @UnboundMethod@) for passing references 5 | to an object's methods, so you can call a method without having a reference to 6 | the object. JavaScript has functions and treats them as first-class objects, 7 | so you can get a reference to a method and call it later: 8 | 9 |
var rex = new Dog('Rex');
10 | var spk = rex.speak; // a reference, we are not calling the method
11 | spk('biscuits');
12 | // -> "MY NAME IS AND I LIKE BISCUITS!"
13 |
14 | Where did Rex's name go? The thing is, we've not called @spk@ through the
15 | object @rex@, so @this@ inside the function no longer refers to the right
16 | thing. JS.Class gives each object a @method()@ method, that returns a method
17 | by name, bound to its source object. This method is simply a JavaScript
18 | function that you can call on its own and maintain the binding of @this@:
19 |
20 | var speak = rex.method('speak');
21 | speak('biscuits');
22 | // -> "MY NAME IS REX AND I LIKE BISCUITS!"
23 |
24 | You can also do this with class methods, since classes are objects too:
25 |
26 | var User = new JS.Class({
27 | extend: {
28 | create: function(name) {
29 | return new this(name);
30 | }
31 | },
32 | initialize: function(name) {
33 | this.username = name;
34 | }
35 | });
36 |
37 | var u = User.method('create');
38 | u('James') // -> {username: 'James'}
39 |
40 |
--------------------------------------------------------------------------------
/source/test/context/suite.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | var suite = Test.Unit.TestCase.suite;
3 |
4 | Test.Unit.TestCase.extend({
5 | // Tweaks to standard method so we don't get superclass methods and we don't
6 | // get weird default tests
7 | suite: function(filter) {
8 | return suite.call(this, filter, false, false);
9 | }
10 | });
11 | })();
12 |
13 | Test.Unit.TestSuite.include({
14 | run: function(result, continuation, callback, context) {
15 | if (this._metadata.fullName)
16 | callback.call(context, this.klass.STARTED, this);
17 |
18 | var withIvars = function(ivarsFromCallback) {
19 | this.forEach(function(test, resume) {
20 | if (ivarsFromCallback && test.setValuesFromCallbacks)
21 | test.setValuesFromCallbacks(ivarsFromCallback);
22 |
23 | test.run(result, resume, callback, context);
24 |
25 | }, function() {
26 | var afterCallbacks = function() {
27 | if (this._metadata.fullName)
28 | callback.call(context, this.klass.FINISHED, this);
29 |
30 | continuation.call(context);
31 | };
32 | if (ivarsFromCallback && first.runAllCallbacks)
33 | first.runAllCallbacks('after', afterCallbacks, this);
34 | else
35 | afterCallbacks.call(this);
36 |
37 | }, this);
38 | };
39 |
40 | var first = this._tests[0], ivarsFromCallback = null;
41 |
42 | if (first && first.runAllCallbacks)
43 | first.runAllCallbacks('before', withIvars, this);
44 | else
45 | withIvars.call(this, null);
46 | }
47 | });
48 |
49 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | require 'rubygems'
2 | require 'httparty'
3 | require 'nokogiri'
4 | require 'set'
5 |
6 | def mdc_url(type)
7 | 'http://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Global_Objects/' + type
8 | end
9 |
10 | def document(url)
11 | puts "Fetching #{url} ..."
12 | Nokogiri.parse(HTTParty.get(url))
13 | rescue
14 | document(url)
15 | end
16 |
17 | MDC_URLS = %w(Array Boolean Date EvalError Error Function Math
18 | Number Object RangeError ReferenceError RegExp
19 | String SyntaxError TypeError URIError).
20 | map(&method(:mdc_url))
21 |
22 | ELEMENT_URL = 'http://developer.mozilla.org/en/DOM/element'
23 | EVENT_URL = 'http://developer.mozilla.org/en/DOM/event'
24 | STYLE_URL = 'http://developer.mozilla.org/en/DOM/CSS'
25 |
26 | class MethodSet < SortedSet
27 | def add_method(link)
28 | name = link.text.strip.gsub(/^event\./, '')
29 | add(name) if name =~ /^[a-z][a-zA-Z0-9\_\$]*$/
30 | end
31 |
32 | def import(url, selector)
33 | document(url).search(selector).each(&method(:add_method))
34 | end
35 | end
36 |
37 | namespace :import do
38 | task :method_chain do
39 | methods = MethodSet.new
40 |
41 | MDC_URLS.each { |url| methods.import(url, 'dt a') }
42 |
43 | methods.import(ELEMENT_URL, 'td code a:first-child')
44 |
45 | document(ELEMENT_URL).search('#pageText>p').last.search('a').map do |link|
46 | methods.import(link[:href], 'td code a:first-child')
47 | end
48 |
49 | methods.import(EVENT_URL, 'dt a')
50 | methods.import(STYLE_URL, 'li a')
51 |
52 | p methods.entries
53 | end
54 | end
55 |
56 |
--------------------------------------------------------------------------------
/site/src/pages/platforms.haml:
--------------------------------------------------------------------------------
1 | :textile
2 | h2. Supported platforms
3 |
4 | One of the main goals of @jsclass@ is to help you write programs that work
5 | across a wide variety of JavaScript platforms. It does this by abstracting
6 | some platform-specific details such as "module loading":/packages.html, and by
7 | providing cross-platform "testing tools":http://jstest.jcoglan.com.
8 |
9 | @jsclass@ currently runs without modification on all the following
10 | environments:
11 |
12 | h3. Web browsers
13 |
14 | * "Chrome":http://www.google.com/chrome
15 | * "Firefox":http://www.getfirefox.com/
16 | * "Internet Explorer":http://www.microsoft.com/windows/internet-explorer/default.aspx
17 | * "Opera":http://www.opera.com/
18 | * "Safari":http://www.apple.com/safari/
19 |
20 | h3. Headless DOM environments
21 |
22 | * "PhantomJS":http://www.phantomjs.org/ (both in the browser and the scripting
23 | runtime)
24 |
25 | h3. Server-side platforms
26 |
27 | * "Node.js":http://nodejs.org/
28 | * "Narwhal":http://narwhaljs.org/
29 | * "RingoJS":http://ringojs.org/
30 |
31 | h3. Database shells
32 |
33 | * "MongoDB":http://www.mongodb.org/
34 |
35 | h3. GUI frameworks
36 |
37 | * "Mozilla XULRunner":https://developer.mozilla.org/en/xulrunner
38 | * "Adobe AIR":http://www.adobe.com/products/air/
39 |
40 | h3. Shell environments
41 |
42 | * "V8 shell":http://code.google.com/p/v8/
43 | * "Mozilla Rhino":http://www.mozilla.org/rhino/
44 | * "Mozilla SpiderMonkey":http://www.mozilla.org/js/spidermonkey/
45 | * "Windows Script Host":http://msdn.microsoft.com/en-us/library/9bbdkx3k(VS.85).aspx
46 |
47 |
--------------------------------------------------------------------------------
/source/constant_scope.js:
--------------------------------------------------------------------------------
1 | (function(factory) {
2 | var E = (typeof exports === 'object'),
3 | js = (typeof JS === 'undefined') ? require('./core') : JS;
4 |
5 | if (E) exports.JS = exports;
6 | factory(js, E ? exports : js);
7 |
8 | })(function(JS, exports) {
9 | 'use strict';
10 |
11 | var ConstantScope = new JS.Module('ConstantScope', {
12 | extend: {
13 | included: function(base) {
14 | base.__consts__ = new JS.Module();
15 | base.extend(this.ClassMethods);
16 | base.__eigen__().extend(this.ClassMethods);
17 |
18 | base.include(base.__consts__);
19 | base.extend(base.__consts__);
20 |
21 | base.include(this.extract(base.__fns__));
22 | base.extend(this.extract(base.__eigen__().__fns__));
23 | },
24 |
25 | ClassMethods: new JS.Module({
26 | define: function(name, callable) {
27 | var constants = this.__consts__ || this.__tgt__.__consts__;
28 |
29 | if (/^[A-Z]/.test(name))
30 | constants.define(name, callable);
31 | else
32 | this.callSuper();
33 |
34 | if (JS.isType(callable, JS.Module)) {
35 | callable.include(ConstantScope);
36 | callable.__consts__.include(constants);
37 | }
38 | }
39 | }),
40 |
41 | extract: function(methods, base) {
42 | var constants = {}, key, object;
43 | for (key in methods) {
44 | if (!/^[A-Z]/.test(key)) continue;
45 |
46 | object = methods[key];
47 | constants[key] = object;
48 | delete methods[key];
49 | }
50 | return constants;
51 | }
52 | }
53 | });
54 |
55 | exports.ConstantScope = ConstantScope;
56 | });
57 |
58 |
--------------------------------------------------------------------------------
/test/specs/singleton_spec.js:
--------------------------------------------------------------------------------
1 | JS.ENV.SingletonSpec = JS.Test.describe(JS.Singleton, function() { with(this) {
2 | before(function() { with(this) {
3 | this.mixin = new JS.Module()
4 | this.parent = new JS.Class()
5 | }})
6 |
7 | describe("with no ancestors", function() { with(this) {
8 | before(function() { with(this) {
9 | this.singleton = new JS.Singleton({ foo: function() { return "foo" } })
10 | }})
11 |
12 | it("creates an object that inherits from Kernel", function() { with(this) {
13 | assertEqual( [JS.Kernel, singleton.klass, singleton.__eigen__()],
14 | singleton.__eigen__().ancestors() )
15 | }})
16 |
17 | it("creates an object with the right methods", function() { with(this) {
18 | assertEqual( "foo", singleton.foo() )
19 | }})
20 | }})
21 |
22 | describe("with a parent class", function() { with(this) {
23 | before(function() { with(this) {
24 | this.singleton = new JS.Singleton(parent)
25 | }})
26 |
27 | it("creates an object that inherits from the parent", function() { with(this) {
28 | assertEqual( [JS.Kernel, parent, singleton.klass, singleton.__eigen__()],
29 | singleton.__eigen__().ancestors() )
30 | }})
31 | }})
32 |
33 | describe("with a mixin", function() { with(this) {
34 | before(function() { with(this) {
35 | this.singleton = new JS.Singleton({ include: mixin })
36 | }})
37 |
38 | it("creates an object that inherits from the mixin", function() { with(this) {
39 | assertEqual( [JS.Kernel, mixin, singleton.klass, singleton.__eigen__()],
40 | singleton.__eigen__().ancestors() )
41 | }})
42 | }})
43 | }})
44 |
45 |
--------------------------------------------------------------------------------
/test/examples/benchmarks.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | var $ = (typeof this.global === 'object') ? this.global : this
3 | $.JSCLASS_PATH = 'build/min/'
4 | })()
5 |
6 | if (typeof require === 'function')
7 | var JS = require('../../' + JSCLASS_PATH + 'loader')
8 | else
9 | load(JSCLASS_PATH + 'loader.js')
10 |
11 |
12 | JS.require('JS.Module', 'JS.Class', 'JS.Benchmark', 'JS.Set', 'JS.OrderedSet', 'JS.SortedSet',
13 | function(Module, Class, bm, Set, OrderedSet, SortedSet) {
14 |
15 | var sets = [SortedSet, OrderedSet, Set],
16 | i = sets.length
17 |
18 | while (i--) {
19 | bm.measure(sets[i] + ' creation', 10, {
20 | test: function() {
21 | var set = new sets[i](), n = 1000
22 | while (n--) set.add(n)
23 | }
24 | })
25 | }
26 |
27 | bm.measure('Class creation', 300, {
28 | test: function() {
29 | new Class({
30 | method1: function() {},
31 | method1: function() {},
32 | method1: function() {}
33 | })
34 | }
35 | })
36 |
37 | bm.measure('Module#ancestors', 5000, {
38 | setup: function() {
39 | var included = new Module({ include: new Module({ include: new Module() }) })
40 | this.module = new Module()
41 | this.module.include(included)
42 | },
43 | test: function() {
44 | this.module.ancestors()
45 | }
46 | })
47 |
48 | bm.measure('Module#ancestors (cached)', 5000, {
49 | setup: function() {
50 | var included = new Module({ include: new Module({ include: new Module() }) })
51 | this.module = new Module()
52 | this.module.include(included)
53 | this.module.ancestors()
54 | },
55 | test: function() {
56 | this.module.ancestors()
57 | }
58 | })
59 | })
60 |
61 |
--------------------------------------------------------------------------------
/source/dom/event.js:
--------------------------------------------------------------------------------
1 | DOM.Event = {
2 | _registry: [],
3 |
4 | on: function(element, eventName, callback, context) {
5 | if (element === undefined) return;
6 |
7 | if (element !== DOM.ENV &&
8 | element.nodeType !== DOM.ELEMENT_NODE &&
9 | element.nodeType !== DOM.DOCUMENT_NODE)
10 | return;
11 |
12 | var wrapped = function() { callback.call(context, element) };
13 |
14 | if (element.addEventListener)
15 | element.addEventListener(eventName, wrapped, false);
16 | else if (element.attachEvent)
17 | element.attachEvent('on' + eventName, wrapped);
18 |
19 | this._registry.push({
20 | _element: element,
21 | _type: eventName,
22 | _callback: callback,
23 | _context: context,
24 | _handler: wrapped
25 | });
26 | },
27 |
28 | detach: function(element, eventName, callback, context) {
29 | var i = this._registry.length, register;
30 | while (i--) {
31 | register = this._registry[i];
32 |
33 | if ((element && element !== register._element) ||
34 | (eventName && eventName !== register._type) ||
35 | (callback && callback !== register._callback) ||
36 | (context && context !== register._context))
37 | continue;
38 |
39 | if (register._element.removeEventListener)
40 | register._element.removeEventListener(register._type, register._handler, false);
41 | else if (register._element.detachEvent)
42 | register._element.detachEvent('on' + register._type, register._handler);
43 |
44 | this._registry.splice(i,1);
45 | register = null;
46 | }
47 | }
48 | };
49 |
50 | DOM.Event.on(DOM.ENV, 'unload', DOM.Event.detach, DOM.Event);
51 |
52 |
--------------------------------------------------------------------------------
/source/test/reporters/tap.js:
--------------------------------------------------------------------------------
1 | Test.Reporters.extend({
2 | TAP: new JS.Class({
3 | extend: {
4 | HOSTNAME: 'testling',
5 |
6 | create: function(options) {
7 | if (!JS.ENV.location) return;
8 | var parts = location.hostname.split('.');
9 | if (JS.indexOf(parts, this.HOSTNAME) >= 0) return new this(options);
10 | }
11 | },
12 |
13 | include: Console,
14 |
15 | startSuite: function(event) {
16 | this._testId = 0;
17 | this.puts('1..' + event.size);
18 | },
19 |
20 | startContext: function(event) {},
21 |
22 | startTest: function(event) {
23 | this._testPassed = true;
24 | this._faults = [];
25 | },
26 |
27 | addFault: function(event) {
28 | this._testPassed = false;
29 | this._faults.push(event);
30 | },
31 |
32 | endTest: function(event) {
33 | var line = this._testPassed ? 'ok' : 'not ok';
34 | line += ' ' + ++this._testId + ' - ' + this._format(event.fullName);
35 | this.puts(line);
36 |
37 | var fault, message, parts, j, m;
38 | for (var i = 0, n = this._faults.length; i < n; i++) {
39 | fault = this._faults[i];
40 | var message = fault.error.message;
41 | if (fault.error.backtrace) message += '\n' + fault.error.backtrace;
42 | parts = message.split(/[\r\n]/);
43 | for (j = 0, m = parts.length; j < m; j++)
44 | this.puts(' ' + parts[j]);
45 | }
46 | },
47 |
48 | endContext: function(event) {},
49 |
50 | update: function(event) {},
51 |
52 | endSuite: function(event) {},
53 |
54 | _format: function(string) {
55 | return string.replace(/[\s\t\r\n]+/g, ' ');
56 | }
57 | })
58 | });
59 |
60 | Test.Reporters.register('tap', Test.Reporters.TAP);
61 |
62 |
--------------------------------------------------------------------------------
/test/specs/test/test_spec_helpers.js:
--------------------------------------------------------------------------------
1 | JS.ENV.TestSpecHelpers = new JS.Module({
2 | suite: function(tests) {
3 | return new JS.Class("TestedSuite", JS.Test.Unit.TestCase, tests).suite()
4 | },
5 |
6 | createTestEnvironment: function() {
7 | this.result = new JS.Test.Unit.TestResult()
8 | this.faults = []
9 | this.result.addListener(JS.Test.Unit.TestResult.FAULT, this.faults.push, this.faults)
10 | },
11 |
12 | runTests: function(tests, resume) {
13 | if (tests) this.testcase = this.suite(tests)
14 | JS.Test.showStack = false
15 | this.testcase.run(this.result, function() {
16 | if (resume) resume()
17 | JS.Test.showStack = true
18 | }, function() {})
19 | },
20 |
21 | assertTestResult: function(runs, assertions, failures, errors) { with(this) {
22 | __wrapAssertion__(function() { with(this) {
23 | assertEqual( runs, result.runCount(), "Incorrect run count" )
24 | assertEqual( assertions, result.assertionCount(), "Incorrect assertion count" )
25 | assertEqual( failures, result.failureCount(), "Incorrect failure count" )
26 | assertEqual( errors, result.errorCount(), "Incorrect error count" )
27 |
28 | assertEqual( failures + errors, faults.length )
29 | }})
30 | }},
31 |
32 | assertMessage: function(index, message) { with(this) {
33 | if (typeof index === "string") {
34 | message = index
35 | index = 1
36 | }
37 |
38 | var f = faults[index-1],
39 | t = f.testMetadata(),
40 | e = f.errorMetadata();
41 |
42 | assertEqual( message, (e.type === "failure" ? "Failure" : "Error") + ":\n" +
43 | t.shortName + "(" + t.context.pop() + "):\n" +
44 | e.message )
45 | }}
46 | })
47 |
48 |
--------------------------------------------------------------------------------
/source/package/dsl.js:
--------------------------------------------------------------------------------
1 | var DSL = {
2 | __FILE__: function() {
3 | return Package.loader.__FILE__();
4 | },
5 |
6 | pkg: function(name, path) {
7 | var pkg = path
8 | ? Package._getByPath(path)
9 | : Package._getByName(name);
10 | pkg.provides(name);
11 | return pkg;
12 | },
13 |
14 | file: function(filename) {
15 | var files = [], i = arguments.length;
16 | while (i--) files[i] = resolve(arguments[i]);
17 | return Package._getByPath.apply(Package, files);
18 | },
19 |
20 | load: function(path, fireCallbacks) {
21 | Package.loader.loadFile(path, fireCallbacks);
22 | },
23 |
24 | autoload: function(pattern, options) {
25 | Package._autoload(pattern, options);
26 | }
27 | };
28 |
29 | DSL.files = DSL.file;
30 | DSL.loader = DSL.file;
31 |
32 | var packages = function(declaration) {
33 | declaration.call(DSL);
34 | };
35 |
36 | var parseLoadArgs = function(args) {
37 | var files = [], i = 0;
38 |
39 | while (typeof args[i] === 'string'){
40 | files.push(args[i]);
41 | i += 1;
42 | }
43 |
44 | return {files: files, callback: args[i], context: args[i+1]};
45 | };
46 |
47 | exports.load = function(path, callback) {
48 | var args = parseLoadArgs(arguments),
49 | n = args.files.length;
50 |
51 | var loadNext = function(index) {
52 | if (index === n) return args.callback.call(args.context);
53 | Package.loader.loadFile(args.files[index], function() {
54 | loadNext(index + 1);
55 | });
56 | };
57 | loadNext(0);
58 | };
59 |
60 | exports.require = function() {
61 | var args = parseLoadArgs(arguments);
62 |
63 | Package.when({complete: args.files}, function(objects) {
64 | if (!args.callback) return;
65 | args.callback.apply(args.context, objects && objects.complete);
66 | });
67 |
68 | return this;
69 | };
70 |
71 |
--------------------------------------------------------------------------------
/test/runner.js:
--------------------------------------------------------------------------------
1 | JS.ENV.CWD = (typeof CWD === 'undefined') ? '.' : CWD
2 |
3 | JS.cache = false
4 | if (JS.ENV.JS_DEBUG) JS.debug = true
5 |
6 | JS.packages(function() { with(this) {
7 | autoload(/^(.*)Spec$/, {from: CWD + '/test/specs', require: 'JS.$1'})
8 |
9 | pkg('Test.UnitSpec').requires('JS.Set', 'JS.Observable')
10 | pkg('ClassSpec').requires('ModuleSpec')
11 |
12 | file(CWD + '/test/specs/test/test_spec_helpers.js').provides('TestSpecHelpers')
13 |
14 | pkg('Test.UnitSpec').requires('TestSpecHelpers')
15 | pkg('Test.MockingSpec').requires('TestSpecHelpers')
16 | }})
17 |
18 | JS.require('JS', 'JS.Test', function(js, Test) {
19 | js.extend(JS, js)
20 | JS.Test = Test
21 |
22 | var specs = [ 'Test.UnitSpec',
23 | 'Test.ContextSpec',
24 | 'Test.MockingSpec',
25 | 'Test.FakeClockSpec',
26 | 'Test.AsyncStepsSpec',
27 | 'ModuleSpec',
28 | 'ClassSpec',
29 | 'MethodSpec',
30 | 'KernelSpec',
31 | 'SingletonSpec',
32 | 'InterfaceSpec',
33 | 'CommandSpec',
34 | 'ComparableSpec',
35 | 'ConsoleSpec',
36 | 'ConstantScopeSpec',
37 | 'DecoratorSpec',
38 | 'EnumerableSpec',
39 | 'ForwardableSpec',
40 | 'HashSpec',
41 | 'LinkedListSpec',
42 | 'MethodChainSpec',
43 | 'DeferrableSpec',
44 | 'ObservableSpec',
45 | 'PackageSpec',
46 | 'ProxySpec',
47 | 'RangeSpec',
48 | 'SetSpec',
49 | 'StateSpec',
50 | 'TSortSpec' ]
51 |
52 | specs = Test.filter(specs, 'Spec')
53 | specs.push(function() { Test.autorun() })
54 | JS.require.apply(JS, specs)
55 | })
56 |
57 |
--------------------------------------------------------------------------------
/source/test/unit/test_result.js:
--------------------------------------------------------------------------------
1 | Test.Unit.extend({
2 | TestResult: new JS.Class({
3 | include: Test.Unit.Observable,
4 |
5 | extend: {
6 | CHANGED: 'Test.Unit.TestResult.CHANGED',
7 | FAULT: 'Test.Unit.TestResult.FAULT'
8 | },
9 |
10 | initialize: function() {
11 | this._runCount = this._assertionCount = 0;
12 | this._failures = [];
13 | this._errors = [];
14 | },
15 |
16 | addRun: function() {
17 | this._runCount += 1;
18 | this.notifyListeners(this.klass.CHANGED, this);
19 | },
20 |
21 | addFailure: function(failure) {
22 | this._failures.push(failure);
23 | this.notifyListeners(this.klass.FAULT, failure);
24 | this.notifyListeners(this.klass.CHANGED, this);
25 | },
26 |
27 | addError: function(error) {
28 | this._errors.push(error);
29 | this.notifyListeners(this.klass.FAULT, error);
30 | this.notifyListeners(this.klass.CHANGED, this);
31 | },
32 |
33 | addAssertion: function() {
34 | this._assertionCount += 1;
35 | this.notifyListeners(this.klass.CHANGED, this);
36 | },
37 |
38 | passed: function() {
39 | return this._failures.length === 0 && this._errors.length === 0;
40 | },
41 |
42 | runCount: function() {
43 | return this._runCount;
44 | },
45 |
46 | assertionCount: function() {
47 | return this._assertionCount;
48 | },
49 |
50 | failureCount: function() {
51 | return this._failures.length;
52 | },
53 |
54 | errorCount: function() {
55 | return this._errors.length;
56 | },
57 |
58 | metadata: function() {
59 | return {
60 | passed: this.passed(),
61 | tests: this.runCount(),
62 | assertions: this.assertionCount(),
63 | failures: this.failureCount(),
64 | errors: this.errorCount()
65 | };
66 | }
67 | })
68 | });
69 |
70 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | JS.Class: Ruby-style JavaScript
2 | http://jsclass.jcoglan.com
3 | Copyright (c) 2007-2013 James Coglan and contributors
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | Parts of the Software build on techniques from the following open-source
16 | projects:
17 |
18 | * The Prototype framework, (c) 2005-2010 Sam Stephenson (MIT license)
19 | * Alex Arnell's Inheritance library, (c) 2006 Alex Arnell (MIT license)
20 | * Base, (c) 2006-2010 Dean Edwards (MIT license)
21 |
22 | The Software contains direct translations to JavaScript of these open-source
23 | Ruby libraries:
24 |
25 | * Ruby standard library modules, (c) Yukihiro Matsumoto and contributors (Ruby license)
26 | * Test::Unit, (c) 2000-2003 Nathaniel Talbott (Ruby license)
27 | * Context, (c) 2008 Jeremy McAnally (MIT license)
28 | * EventMachine::Deferrable, (c) 2006-07 Francis Cianfrocca (Ruby license)
29 |
30 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
31 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
32 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
33 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
34 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
35 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
36 | THE SOFTWARE.
37 |
38 |
39 |
--------------------------------------------------------------------------------
/test/specs/method_spec.js:
--------------------------------------------------------------------------------
1 | JS.ENV.MethodSpec = JS.Test.describe(JS.Method, function() { with(this) {
2 | before(function() { with(this) {
3 | this.callable = function(a,b) { return "something" }
4 | this.theModule = new JS.Module({ im_a_method: callable })
5 | this.theMethod = theModule.instanceMethod("im_a_method")
6 | }})
7 |
8 | it("should be bootstrapped properly", function() { with(this) {
9 | assertKindOf( JS.Class, JS.Method )
10 | assertKindOf( JS.Module, JS.Method )
11 | assertKindOf( JS.Kernel, JS.Method )
12 | assertEqual( "Method", JS.Method.displayName )
13 | }})
14 |
15 | describe("#module", function() { with(this) {
16 | it("refers to the module hosting the method", function() { with(this) {
17 | assertEqual( theModule, theMethod.module )
18 | }})
19 | }})
20 |
21 | describe("#name", function() { with(this) {
22 | it("returns the name of the method", function() { with(this) {
23 | assertEqual( "im_a_method", theMethod.name )
24 | }})
25 | }})
26 |
27 | describe("#callable", function() { with(this) {
28 | it("refers to the JavaScript function the method represents", function() { with(this) {
29 | assertEqual( callable, theMethod.callable )
30 | }})
31 | }})
32 |
33 | describe("#arity", function() { with(this) {
34 | it("gives the number of arguments the method accepts", function() { with(this) {
35 | assertEqual( 2, theMethod.arity )
36 | }})
37 | }})
38 |
39 | describe("#contains", function() { with(this) {
40 | it("returns true if the method's source includes the word", function() { with(this) {
41 | assert( theMethod.contains("return") )
42 | assert( theMethod.contains("something") )
43 | }})
44 |
45 | it("returns false if the method's source does not include the word", function() { with(this) {
46 | assert( !theMethod.contains("nothing") )
47 | }})
48 | }})
49 | }})
50 |
51 |
--------------------------------------------------------------------------------
/source/test/context/shared_behavior.js:
--------------------------------------------------------------------------------
1 | Test.Context.extend({
2 | SharedBehavior: new JS.Class(JS.Module, {
3 | extend: {
4 | createFromBehavior: function(beh) {
5 | var mod = new this();
6 | mod._behavior = beh;
7 | return mod;
8 | },
9 |
10 | moduleName: function(name) {
11 | return name.toLowerCase()
12 | .replace(/[\s:',\.~;!#=\(\)&]+/g, '_')
13 | .replace(/\/(.?)/g, function(m,a) { return '.' + a.toUpperCase() })
14 | .replace(/(?:^|_)(.)/g, function(m,a) { return a.toUpperCase() });
15 | }
16 | },
17 |
18 | included: function(arg) {
19 | this._behavior.call(arg);
20 | }
21 | })
22 | });
23 |
24 | Test.Unit.TestCase.extend({
25 | shared: function(name, block) {
26 | name = Test.Context.SharedBehavior.moduleName(name);
27 | JS.ENV[name] = Test.Context.SharedBehavior.createFromBehavior(block);
28 | },
29 |
30 | use: function(sharedName) {
31 | if (JS.isType(sharedName, Test.Context.SharedBehavior) ||
32 | JS.isType(sharedName, JS.Module))
33 | this.include(sharedName);
34 |
35 | else if (JS.isType(sharedName, 'string')) {
36 | var name = Test.Context.SharedBehavior.moduleName(sharedName),
37 | beh = JS.ENV[name];
38 |
39 | if (!beh) throw new Error('Could not find example group named "' + sharedName + '"');
40 | this.include(beh);
41 | }
42 | }
43 | });
44 |
45 | (function() {
46 | var alias = function(method, aliases) {
47 | var extension = {};
48 | for (var i = 0, n = aliases.length; i < n; i++)
49 | extension[aliases[i]] = Test.Unit.TestCase[method];
50 | Test.Unit.TestCase.extend(extension);
51 | };
52 |
53 | alias('shared', ['sharedBehavior', 'shareAs', 'shareBehaviorAs', 'sharedExamplesFor']);
54 | alias('use', ['uses', 'itShouldBehaveLike', 'behavesLike', 'usesExamplesFrom']);
55 | })();
56 |
57 |
--------------------------------------------------------------------------------
/source/console/browser_color.js:
--------------------------------------------------------------------------------
1 | Console.extend({
2 | BrowserColor: new JS.Class(Console.Browser, {
3 | COLORS: {
4 | green: 'limegreen'
5 | },
6 |
7 | __queue__: [],
8 | __state__: {},
9 |
10 | format: function(type, name) {
11 | name = name.replace(/^bg/, '');
12 |
13 | var state = JS.extend({}, this.__state__),
14 | color = this.COLORS[name] || name,
15 | no = /^no/.test(name);
16 |
17 | if (type === 'reset')
18 | state = {};
19 | else if (no)
20 | delete state[type];
21 | else if (type === 'weight')
22 | state.weight = 'font-weight: ' + name;
23 | else if (type === 'style')
24 | state.style = 'font-style: ' + name;
25 | else if (type === 'underline')
26 | state.underline = 'text-decoration: underline';
27 | else if (type === 'color')
28 | state.color = 'color: ' + color;
29 | else if (type === 'background')
30 | state.background = 'background-color: ' + color;
31 | else
32 | state = null;
33 |
34 | if (state) {
35 | this.__state__ = state;
36 | this.__queue__.push(state);
37 | }
38 | },
39 |
40 | print: function(string) {
41 | this.__queue__.push(string)
42 | },
43 |
44 | puts: function(string) {
45 | this.print(string);
46 | var buffer = '', formats = [], item;
47 | while (item = this.__queue__.shift()) {
48 | if (typeof item === 'string') {
49 | buffer += '%c' + item;
50 | formats.push(this._serialize(this.__state__));
51 | } else {
52 | this.__state__ = item;
53 | }
54 | }
55 | console.log.apply(console, [buffer].concat(formats));
56 | },
57 |
58 | _serialize: function(state) {
59 | var rules = [];
60 | for (var key in state) rules.push(state[key]);
61 | return rules.join('; ');
62 | }
63 | })
64 | });
65 |
66 |
--------------------------------------------------------------------------------
/test/examples/tracing.js:
--------------------------------------------------------------------------------
1 | if (this.ActiveXObject) load = function(path) {
2 | var fso = new ActiveXObject('Scripting.FileSystemObject'), file, runner;
3 | try {
4 | file = fso.OpenTextFile(path);
5 | runner = function() { eval(file.ReadAll()) };
6 | runner();
7 | } finally {
8 | try { if (file) file.Close() } catch (e) {}
9 | }
10 | };
11 |
12 | (function() {
13 | var $ = (typeof this.global === 'object') ? this.global : this
14 | $.JSCLASS_PATH = 'build/src/'
15 | })()
16 |
17 | if (typeof require === 'function') {
18 | var JS = require('../../' + JSCLASS_PATH + 'loader')
19 | } else {
20 | load(JSCLASS_PATH + 'loader.js')
21 | }
22 |
23 | Foo = {}
24 |
25 | JS.require('JS.Class', 'JS.Method', 'JS.Console', 'JS.Hash', 'JS.OrderedHash', 'JS.TSort',
26 | function(Class, Method, Console, Hash, OrderedHash, TSort) {
27 | Tasks = new Class({
28 | include: TSort,
29 |
30 | initialize: function(table) {
31 | this.table = table;
32 | },
33 |
34 | tsortEachNode: function(block, context) {
35 | for (var task in this.table) {
36 | if (this.table.hasOwnProperty(task))
37 | block.call(context, task);
38 | }
39 | },
40 |
41 | tsortEachChild: function(task, block, context) {
42 | var tasks = this.table[task];
43 | for (var i = 0, n = tasks.length; i < n; i++)
44 | block.call(context, tasks[i]);
45 | }
46 | })
47 |
48 | var tasks = new Tasks({
49 | 'eat breakfast': ['serve'],
50 | 'serve': ['cook'],
51 | 'cook': ['buy bacon', 'buy eggs'],
52 | 'buy bacon': [],
53 | 'buy eggs': []
54 | })
55 |
56 | var hash = new OrderedHash(['foo', 4, 'bar', 5])
57 |
58 | Method.tracing([Hash, TSort], function() {
59 | tasks.tsort()
60 | hash.hasKey('foo')
61 | Console.puts(Console.nameOf(Foo))
62 | hash.select(function() { throw new Error('fail') })
63 | })
64 |
65 | hash.hasKey('something')
66 | })
67 |
68 |
--------------------------------------------------------------------------------
/source/core/kernel.js:
--------------------------------------------------------------------------------
1 | JS.Kernel = new JS.Module('Kernel', {
2 | __eigen__: function() {
3 | if (this.__meta__) return this.__meta__;
4 | var name = this.toString() + '.';
5 | this.__meta__ = new JS.Module(name, null, {_target: this});
6 | return this.__meta__.include(this.klass, {_resolve: false});
7 | },
8 |
9 | equals: function(other) {
10 | return this === other;
11 | },
12 |
13 | extend: function(module, options) {
14 | var resolve = (options || {})._resolve;
15 | this.__eigen__().include(module, {_extended: this, _resolve: resolve});
16 | return this;
17 | },
18 |
19 | hash: function() {
20 | return JS.Kernel.hashFor(this);
21 | },
22 |
23 | isA: function(module) {
24 | return (typeof module === 'function' && this instanceof module) ||
25 | this.__eigen__().includes(module);
26 | },
27 |
28 | method: function(name) {
29 | var cache = this.__mct__ = this.__mct__ || {},
30 | value = cache[name],
31 | field = this[name];
32 |
33 | if (typeof field !== 'function') return field;
34 | if (value && field === value._value) return value._bound;
35 |
36 | var bound = JS.bind(field, this);
37 | cache[name] = {_value: field, _bound: bound};
38 | return bound;
39 | },
40 |
41 | methods: function() {
42 | return this.__eigen__().instanceMethods();
43 | },
44 |
45 | tap: function(block, context) {
46 | block.call(context, this);
47 | return this;
48 | },
49 |
50 | toString: function() {
51 | if (this.displayName) return this.displayName;
52 | var name = this.klass.displayName || this.klass.toString();
53 | return '#<' + name + ':' + this.hash() + '>';
54 | }
55 | });
56 |
57 | (function() {
58 | var id = 1;
59 |
60 | JS.Kernel.hashFor = function(object) {
61 | if (object.__hash__ !== undefined) return object.__hash__;
62 | object.__hash__ = (new JS.Date().getTime() + id).toString(16);
63 | id += 1;
64 | return object.__hash__;
65 | };
66 | })();
67 |
68 |
--------------------------------------------------------------------------------
/source/decorator.js:
--------------------------------------------------------------------------------
1 | (function(factory) {
2 | var E = (typeof exports === 'object'),
3 | js = (typeof JS === 'undefined') ? require('./core') : JS;
4 |
5 | if (E) exports.JS = exports;
6 | factory(js, E ? exports : js);
7 |
8 | })(function(JS, exports) {
9 | 'use strict';
10 |
11 | var Decorator = new JS.Class('Decorator', {
12 | initialize: function(decoree, methods) {
13 | var decorator = new JS.Class(),
14 | delegators = {},
15 | method, func;
16 |
17 | for (method in decoree.prototype) {
18 | func = decoree.prototype[method];
19 | if (typeof func === 'function' && func !== decoree) func = this.klass.delegate(method);
20 | delegators[method] = func;
21 | }
22 |
23 | decorator.include(new JS.Module(delegators), {_resolve: false});
24 | decorator.include(this.klass.InstanceMethods, {_resolve: false});
25 | decorator.include(methods);
26 | return decorator;
27 | },
28 |
29 | extend: {
30 | delegate: function(name) {
31 | return function() {
32 | return this.component[name].apply(this.component, arguments);
33 | };
34 | },
35 |
36 | InstanceMethods: new JS.Module({
37 | initialize: function(component) {
38 | this.component = component;
39 | this.klass = this.constructor = component.klass;
40 | var method, func;
41 | for (method in component) {
42 | if (this[method]) continue;
43 | func = component[method];
44 | if (typeof func === 'function') func = Decorator.delegate(method);
45 | this[method] = func;
46 | }
47 | },
48 |
49 | extend: function(source) {
50 | this.component.extend(source);
51 | var method, func;
52 | for (method in source) {
53 | func = source[method];
54 | if (typeof func === 'function') func = Decorator.delegate(method);
55 | this[method] = func;
56 | }
57 | }
58 | })
59 | }
60 | });
61 |
62 | exports.Decorator = Decorator;
63 | });
64 |
65 |
--------------------------------------------------------------------------------
/site/src/pages/debugging.haml:
--------------------------------------------------------------------------------
1 | :textile
2 | h2. Debugging support
3 |
4 | The 2.1 release introduced support for "WebKit's @displayName@":http://www.alertdebugging.com/2009/04/29/building-a-better-javascript-profiler-with-webkit/
5 | property for profiling and debugging JavaScript. Essentially, it is a
6 | workaround for the fact that JavaScript objects and functions do not have
7 | names directly attached to them, and can be referenced by any number of
8 | variables, thus making objects and functions basically anonymous.
9 |
10 | WebKit's profiler and debugger improves the situation by using the
11 | @displayName@ assigned to a @Function@ object if one has been assigned.
12 | JS.Class generates display names for methods and inner classes, so long as you
13 | specify a name for the outermost class. Class (and module) names are optional
14 | and are specified using the first argument to the @Class@ and @Module@
15 | constructors. For example:
16 |
17 | Foo = new JS.Module('Foo', {
18 | sleep: function() { /* ... */ },
19 |
20 | extend: {
21 | eatFood: function() { /* ... */ },
22 |
23 | InnerClass: new JS.Class({
24 | haveVisions: function() { /* ... */ }
25 | })
26 | }
27 | });
28 |
29 | The name does not have to be the same as the variable you assign the module to,
30 | although it will probably be more helpful if the names _are_ the same. The
31 | name given is not used for variable assignment, though.
32 |
33 | Given the above definition, we now find @displayName@ set on the methods and
34 | the inner class:
35 |
36 | Foo.instanceMethod('sleep').displayName
37 | // -> "Foo#sleep"
38 |
39 | Foo.eatFood.displayName
40 | // -> "Foo.eatFood"
41 |
42 | Foo.InnerClass.displayName
43 | // -> "Foo.InnerClass"
44 |
45 | Foo.InnerClass.instanceMethod('haveVisions').displayName
46 | // -> "Foo.InnerClass#haveVisions"
47 |
48 | Further debugging support is provided by the "@StackTrace@":/stacktrace.html module.
49 |
50 |
--------------------------------------------------------------------------------
/source/observable.js:
--------------------------------------------------------------------------------
1 | (function(factory) {
2 | var E = (typeof exports === 'object'),
3 | js = (typeof JS === 'undefined') ? require('./core') : JS;
4 |
5 | if (E) exports.JS = exports;
6 | factory(js, E ? exports : js);
7 |
8 | })(function(JS, exports) {
9 | 'use strict';
10 |
11 | var Observable = new JS.Module('Observable', {
12 | extend: {
13 | DEFAULT_METHOD: 'update'
14 | },
15 |
16 | addObserver: function(observer, context) {
17 | (this.__observers__ = this.__observers__ || []).push({_block: observer, _context: context});
18 | },
19 |
20 | removeObserver: function(observer, context) {
21 | this.__observers__ = this.__observers__ || [];
22 | context = context;
23 | var i = this.countObservers();
24 | while (i--) {
25 | if (this.__observers__[i]._block === observer && this.__observers__[i]._context === context) {
26 | this.__observers__.splice(i,1);
27 | return;
28 | }
29 | }
30 | },
31 |
32 | removeObservers: function() {
33 | this.__observers__ = [];
34 | },
35 |
36 | countObservers: function() {
37 | return (this.__observers__ = this.__observers__ || []).length;
38 | },
39 |
40 | notifyObservers: function() {
41 | if (!this.isChanged()) return;
42 | var i = this.countObservers(), observer, block, context;
43 | while (i--) {
44 | observer = this.__observers__[i];
45 | block = observer._block;
46 | context = observer._context;
47 | if (typeof block === 'function') block.apply(context, arguments);
48 | else block[context || Observable.DEFAULT_METHOD].apply(block, arguments);
49 | }
50 | },
51 |
52 | setChanged: function(state) {
53 | this.__changed__ = !(state === false);
54 | },
55 |
56 | isChanged: function() {
57 | if (this.__changed__ === undefined) this.__changed__ = true;
58 | return !!this.__changed__;
59 | }
60 | });
61 |
62 | Observable.alias({
63 | subscribe: 'addObserver',
64 | unsubscribe: 'removeObserver'
65 | }, true);
66 |
67 | exports.Observable = Observable;
68 | });
69 |
70 |
--------------------------------------------------------------------------------
/site/src/pages/packages.haml:
--------------------------------------------------------------------------------
1 | :textile
2 | h2. Package management
3 |
4 | @jsclass@ comes with a package manager that makes it really easy to load
5 | libraries into your application on demand on all @jsclass@'s "supported
6 | platforms":/platforms.html. You can tell it which file each module in your
7 | application lives in, and what other modules it depends on, and it takes care
8 | of resolving dependencies and loading your code for you. This means your
9 | application code only needs to specify which objects it needs, rather than
10 | which scripts to download in order to run.
11 |
12 | For example, say I want to do something with the "@Console@":/console.html
13 | module. I just need to @require@ it, and supply a function to run once all the
14 | requisite code has loaded:
15 |
16 | JS.require('JS.Console', function(Console) {
17 | Console.puts('Hello, world!');
18 | });
19 |
20 | The package system only loads what is needed to get the objects you want, and
21 | it makes sure that each file is only downloaded once. Where possible, files
22 | are downloaded in parallel to improve performance, but it makes sure
23 | interdependent scripts run in the right order.
24 |
25 | It is designed to be able to load any code from any domain, including from
26 | libraries that have their own package systems. It does not require the files
27 | it loads to follow any particular package format, but it can load files that
28 | export global variables or a CommonJS module. It expects the arguments to
29 | @JS.require()@ to either be the names of global objects, or exported
30 | properties of a CommonJS module. Whichever format the loaded files use,
31 | @JS.require()@ will yield the referenced objects as parameters to the callback
32 | function.
33 |
34 | * "Basic usage examples":/packages/introduction.html
35 | * "Pattern-based loading with @autoload@":/packages/autoload.html
36 | * "Custom loader functions":/packages/customloaders.html
37 |
38 | You can use the "@jsbuild@ command-line
39 | tool":http://github.com/jcoglan/jsbuild to bundle your packages for
40 | production.
41 |
42 |
--------------------------------------------------------------------------------
/site/src/pages/packages/autoload.haml:
--------------------------------------------------------------------------------
1 | :textile
2 | h2. Short-hand setup using @autoload@
3 |
4 | As your application grows you may find that your package configuration becomes
5 | repetitive. For example, you may have a set of test scripts that mirror the
6 | set of classes in your application:
7 |
8 | JS.packages(function() { with(this) {
9 |
10 | file('tests/widget_spec.js')
11 | .provides('WidgetSpec')
12 | .requires('MyApp.Widget');
13 |
14 | file('tests/blog_post_spec.js')
15 | .provides('BlogPostSpec')
16 | .requires('MyApp.BlogPost');
17 |
18 | file('tests/users/profile_spec.js')
19 | .provides('Users.ProfileSpec')
20 | .requires('MyApp.Users.Profile');
21 | });
22 |
23 | If you run into this situation you can use the @autoload()@ function to set up
24 | packages for objects whose name matches a certain pattern. For example you
25 | could compress the above configuration like this:
26 |
27 | JS.packages(function() { with(this) {
28 | autoload(/^(.*)Spec$/, {from: 'tests', require: 'MyApp.$1'});
29 | });
30 |
31 | @autoload()@ expects three parameters. The first is a regex that is used to
32 | match package names. The @from@ option is a directory path where packages with
33 | that name pattern live, for example this rule would make the package loader
34 | look in @tests/users/profile_spec.js@ to find the @Users.ProfileSpec@ module.
35 | The @require@ option lets you specify an object the package depends on, using
36 | match results from the regex. The above rule would mean that
37 | @Users.ProfileSpec@ has a dependency on @MyApp.Users.Profile@.
38 |
39 | If you @require()@ a package that doesn't have an explicit configuration, the
40 | autoloader will try to figure out where to load it from by matching its name
41 | against the set of patterns it knows about. A naming convention is adopted for
42 | converting object names to paths: dots convert to path separators, and
43 | camelcase names are converted to underscored style. Thus @Users.ProfileSpec@
44 | becomes @users/profile_spec.js@.
45 |
--------------------------------------------------------------------------------
/source/test/mocking/dsl.js:
--------------------------------------------------------------------------------
1 | Test.Mocking.Stub.include({
2 | given: function() {
3 | var matcher = new Test.Mocking.Parameters(arguments, this._expected);
4 | this._argMatchers.push(matcher);
5 | this._currentMatcher = matcher;
6 | return this;
7 | },
8 |
9 | raises: function(exception) {
10 | this._currentMatcher._exception = exception;
11 | return this;
12 | },
13 |
14 | returns: function() {
15 | this._currentMatcher.returns(arguments);
16 | return this;
17 | },
18 |
19 | yields: function() {
20 | this._currentMatcher.yields(arguments);
21 | return this;
22 | },
23 |
24 | atLeast: function(n) {
25 | this._currentMatcher.setMinimum(n);
26 | return this;
27 | },
28 |
29 | atMost: function(n) {
30 | this._currentMatcher.setMaximum(n);
31 | return this;
32 | },
33 |
34 | exactly: function(n) {
35 | this._currentMatcher.setExpected(n);
36 | return this;
37 | }
38 | });
39 |
40 | Test.Mocking.Stub.alias({
41 | raising: 'raises',
42 | returning: 'returns',
43 | yielding: 'yields'
44 | });
45 |
46 | Test.Mocking.extend({
47 | DSL: new JS.Module({
48 | stub: function() {
49 | return Test.Mocking.stub.apply(Test.Mocking, arguments);
50 | },
51 |
52 | expect: function() {
53 | var stub = Test.Mocking.stub.apply(Test.Mocking, arguments);
54 | stub.expected();
55 | this.addAssertion();
56 | return stub;
57 | },
58 |
59 | anything: function() {
60 | return new Test.Mocking.Anything();
61 | },
62 |
63 | anyArgs: function() {
64 | return new Test.Mocking.AnyArgs();
65 | },
66 |
67 | instanceOf: function(type) {
68 | return new Test.Mocking.InstanceOf(type);
69 | },
70 |
71 | match: function(type) {
72 | return new Test.Mocking.Matcher(type);
73 | },
74 |
75 | arrayIncluding: function() {
76 | return new Test.Mocking.ArrayIncluding(arguments);
77 | },
78 |
79 | objectIncluding: function(elements) {
80 | return new Test.Mocking.ObjectIncluding(elements);
81 | }
82 | })
83 | });
84 |
85 | Test.Unit.TestCase.include(Test.Mocking.DSL);
86 | Test.Unit.mocking = Test.Mocking;
87 |
88 |
--------------------------------------------------------------------------------
/source/test/reporters/error.js:
--------------------------------------------------------------------------------
1 | Test.Reporters.extend({
2 | Error: new JS.Class({
3 | include: Console,
4 |
5 | NAMES: {
6 | failure: 'Failure',
7 | error: 'Error'
8 | },
9 |
10 | startSuite: function(event) {
11 | this._faults = [];
12 | this._start = event.timestamp;
13 |
14 | this.consoleFormat('bold');
15 | this.puts('Loaded suite: ' + event.children.join(', '));
16 | this.reset();
17 | this.puts('');
18 | },
19 |
20 | startContext: function(event) {},
21 |
22 | startTest: function(event) {},
23 |
24 | addFault: function(event) {
25 | this._faults.push(event);
26 | this._printFault(this._faults.length, event);
27 | },
28 |
29 | update: function(event) {},
30 |
31 | endTest: function(event) {},
32 |
33 | endContext: function(event) {},
34 |
35 | endSuite: function(event) {
36 | this._printSummary(event);
37 | },
38 |
39 | _printFault: function(index, fault) {
40 | this.consoleFormat('bold', 'red');
41 | this.puts(index + ') ' + this.NAMES[fault.error.type] + ': ' + fault.test.fullName);
42 | this.reset();
43 | this.puts(fault.error.message);
44 | if (fault.error.backtrace) {
45 | this.grey();
46 | this.puts(fault.error.backtrace);
47 | }
48 | this.reset();
49 | this.puts('');
50 | },
51 |
52 | _printSummary: function(event) {
53 | var runtime = (event.timestamp - this._start) / 1000;
54 | this.reset();
55 | this.puts('Finished in ' + runtime + ' seconds');
56 |
57 | var color = event.passed ? 'green' : 'red';
58 | this.consoleFormat(color);
59 | this.puts(this._plural(event.tests, 'test') + ', ' +
60 | this._plural(event.assertions, 'assertion') + ', ' +
61 | this._plural(event.failures, 'failure') + ', ' +
62 | this._plural(event.errors, 'error'));
63 | this.reset();
64 | this.puts('');
65 | },
66 |
67 | _plural: function(number, noun) {
68 | return number + ' ' + noun + (number === 1 ? '' : 's');
69 | }
70 | })
71 | });
72 |
73 | Test.Reporters.register('error', Test.Reporters.Error);
74 |
75 |
--------------------------------------------------------------------------------
/source/core/utils.js:
--------------------------------------------------------------------------------
1 | var JS = {ENV: global};
2 |
3 | JS.END_WITHOUT_DOT = /([^\.])$/;
4 |
5 | JS.array = function(enumerable) {
6 | var array = [], i = enumerable.length;
7 | while (i--) array[i] = enumerable[i];
8 | return array;
9 | };
10 |
11 | JS.bind = function(method, object) {
12 | return function() {
13 | return method.apply(object, arguments);
14 | };
15 | };
16 |
17 | JS.Date = JS.ENV.Date;
18 |
19 | JS.extend = function(destination, source, overwrite) {
20 | if (!destination || !source) return destination;
21 | for (var field in source) {
22 | if (destination[field] === source[field]) continue;
23 | if (overwrite === false && destination.hasOwnProperty(field)) continue;
24 | destination[field] = source[field];
25 | }
26 | return destination;
27 | };
28 |
29 | JS.indexOf = function(list, item) {
30 | if (list.indexOf) return list.indexOf(item);
31 | var i = list.length;
32 | while (i--) {
33 | if (list[i] === item) return i;
34 | }
35 | return -1;
36 | };
37 |
38 | JS.isType = function(object, type) {
39 | if (typeof type === 'string')
40 | return typeof object === type;
41 |
42 | if (object === null || object === undefined)
43 | return false;
44 |
45 | return (typeof type === 'function' && object instanceof type) ||
46 | (object.isA && object.isA(type)) ||
47 | object.constructor === type;
48 | };
49 |
50 | JS.makeBridge = function(parent) {
51 | var bridge = function() {};
52 | bridge.prototype = parent.prototype;
53 | return new bridge();
54 | };
55 |
56 | JS.makeClass = function(parent) {
57 | parent = parent || Object;
58 |
59 | var constructor = function() {
60 | return this.initialize
61 | ? this.initialize.apply(this, arguments) || this
62 | : this;
63 | };
64 | constructor.prototype = JS.makeBridge(parent);
65 |
66 | constructor.superclass = parent;
67 |
68 | constructor.subclasses = [];
69 | if (parent.subclasses) parent.subclasses.push(constructor);
70 |
71 | return constructor;
72 | };
73 |
74 | JS.match = function(category, object) {
75 | if (object === undefined) return false;
76 | return typeof category.test === 'function'
77 | ? category.test(object)
78 | : category.match(object);
79 | };
80 |
81 |
--------------------------------------------------------------------------------
/site/src/pages/license.haml:
--------------------------------------------------------------------------------
1 | :textile
2 | h2. License
3 |
4 | Copyright © 2007–2013 James Coglan and contributors
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in all
14 | copies or substantial portions of the Software.
15 |
16 | Parts of the Software build on techniques from the following open-source
17 | projects:
18 |
19 | * "Prototype":http://prototypejs.org/, © 2005–2010 Sam Stephenson
20 | (MIT license)
21 | * Alex Arnell's "Inheritance":http://www.twologic.com/projects/inheritance/
22 | library, © 2006 Alex Arnell (MIT license)
23 | * "Base":http://dean.edwards.name/weblog/2006/03/base/, © 2006–2010
24 | Dean Edwards (MIT license)
25 |
26 | The Software contains direct translations to JavaScript of these open-source
27 | Ruby libraries:
28 |
29 | * "Ruby standard library":http://www.ruby-lang.org/en/ modules, ©
30 | Yukihiro Matsumoto and contributors (Ruby license)
31 | * "Test::Unit":http://test-unit.rubyforge.org/, © 2000–2003
32 | Nathaniel Talbott (Ruby license)
33 | * "Context":http://github.com/jm/context, © 2008 Jeremy McAnally (MIT
34 | license)
35 | * "EventMachine::Deferrable":http://rubyeventmachine.com/, ©
36 | 2006–07 Francis Cianfrocca (Ruby license)
37 |
38 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
39 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
40 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
41 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
42 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
43 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
44 | SOFTWARE.
45 |
46 |
--------------------------------------------------------------------------------
/source/test/unit/assertion_message.js:
--------------------------------------------------------------------------------
1 | Test.Unit.extend({
2 | AssertionMessage: new JS.Class({
3 | extend: {
4 | Literal: new JS.Class({
5 | initialize: function(value) {
6 | this._value = value;
7 | this.toString = this.inspect;
8 | },
9 |
10 | inspect: function() {
11 | return this._value.toString();
12 | }
13 | }),
14 |
15 | literal: function(value) {
16 | return new this.Literal(value);
17 | },
18 |
19 | Template: new JS.Class({
20 | extend: {
21 | create: function(string) {
22 | var parts = string ? string.match(/\(\?\)|(?=[^\\])\?|(?:(?!\(\?\))(?:\\\?|[^\?]))+/g) : [];
23 | return new this(parts);
24 | }
25 | },
26 |
27 | initialize: function(parts) {
28 | this._parts = new Enumerable.Collection(parts);
29 | this.count = this._parts.findAll(function(e) { return e === '?' || e === '(?)' }).length;
30 | },
31 |
32 | result: function(parameters) {
33 | if (parameters.length !== this.count) throw 'The number of parameters does not match the number of substitutions';
34 | var params = JS.array(parameters);
35 | return this._parts.collect(function(e) {
36 | if (e === '(?)') return params.shift().replace(/^\[/, '(').replace(/\]$/, ')');
37 | if (e === '?') return params.shift();
38 | return e.replace(/\\\?/g, '?');
39 | }).join('');
40 | }
41 | })
42 | },
43 |
44 | initialize: function(head, template, parameters) {
45 | this._head = head;
46 | this._templateString = template;
47 | this._parameters = new Enumerable.Collection(parameters);
48 | },
49 |
50 | template: function() {
51 | return this._template = this._template || this.klass.Template.create(this._templateString);
52 | },
53 |
54 | toString: function() {
55 | var messageParts = [], head, tail;
56 | if (this._head) messageParts.push(this._head);
57 | tail = this.template().result(this._parameters.collect(function(e) {
58 | return Console.convert(e);
59 | }, this));
60 | if (tail !== '') messageParts.push(tail);
61 | return messageParts.join('\n');
62 | }
63 | })
64 | });
65 |
66 |
--------------------------------------------------------------------------------
/source/proxy.js:
--------------------------------------------------------------------------------
1 | (function(factory) {
2 | var E = (typeof exports === 'object'),
3 | js = (typeof JS === 'undefined') ? require('./core') : JS;
4 |
5 | if (E) exports.JS = exports;
6 | factory(js, E ? exports : js);
7 |
8 | })(function(JS, exports) {
9 | 'use strict';
10 |
11 | var Proxy = new JS.Module('Proxy', {
12 | extend: {
13 | Virtual: new JS.Class({
14 | initialize: function(klass) {
15 | var bridge = function() {},
16 | proxy = new JS.Class(),
17 | delegators = {},
18 | method, func;
19 |
20 | bridge.prototype = klass.prototype;
21 |
22 | for (method in klass.prototype) {
23 | func = klass.prototype[method];
24 | if (typeof func === 'function' && func !== klass) func = this.klass.forward(method);
25 | delegators[method] = func;
26 | }
27 |
28 | proxy.include({
29 | initialize: function() {
30 | var args = arguments,
31 | subject = null;
32 |
33 | this.__getSubject__ = function() {
34 | subject = new bridge;
35 | klass.apply(subject, args);
36 | return (this.__getSubject__ = function() { return subject; })();
37 | };
38 | },
39 | klass: klass,
40 | constructor: klass
41 | }, {_resolve: false});
42 |
43 | proxy.include(new JS.Module(delegators), {_resolve: false});
44 | proxy.include(this.klass.InstanceMethods);
45 | return proxy;
46 | },
47 |
48 | extend: {
49 | forward: function(name) {
50 | return function() {
51 | var subject = this.__getSubject__();
52 | return subject[name].apply(subject, arguments);
53 | };
54 | },
55 |
56 | InstanceMethods: new JS.Module({
57 | extend: function(source) {
58 | this.__getSubject__().extend(source);
59 | var method, func;
60 | for (method in source) {
61 | func = source[method];
62 | if (typeof func === 'function') func = Proxy.Virtual.forward(method);
63 | this[method] = func;
64 | }
65 | }
66 | })
67 | }
68 | })
69 | }
70 | });
71 |
72 | exports.Proxy = Proxy;
73 | });
74 |
75 |
--------------------------------------------------------------------------------
/test/specs/console_spec.js:
--------------------------------------------------------------------------------
1 | JS.require('JS.Console', function(Console) {
2 |
3 | JS.ENV.ConsoleSpec = JS.Test.describe(Console, function() { with(this) {
4 | describe("convert", function() { with(this) {
5 | it("strigifies undefined", function() { with(this) {
6 | assertEqual( "undefined", Console.convert(undefined) )
7 | }})
8 |
9 | it("strigifies null", function() { with(this) {
10 | assertEqual( "null", Console.convert(null) )
11 | }})
12 |
13 | it("strigifies booleans", function() { with(this) {
14 | assertEqual( "true", Console.convert(true) )
15 | assertEqual( "false", Console.convert(false) )
16 | }})
17 |
18 | it("strigifies numbers", function() { with(this) {
19 | assertEqual( "0", Console.convert(0) )
20 | assertEqual( "5", Console.convert(5) )
21 | }})
22 |
23 | it("stringifies strings", function() { with(this) {
24 | assertEqual( '""', Console.convert("") )
25 | assertEqual( '"hi"', Console.convert("hi") )
26 | }})
27 |
28 | it("strigifies arrays", function() { with(this) {
29 | assertEqual( "[]", Console.convert([]) )
30 | assertEqual( "[ 1, 2, 3 ]", Console.convert([1,2,3]) )
31 | }})
32 |
33 | it("strigifies circular arrays", function() { with(this) {
34 | var a = [1,2]
35 | a.push(a)
36 | assertEqual( "[ 1, 2, #circular ]", Console.convert(a) )
37 | }})
38 |
39 | it("strigifies objects", function() { with(this) {
40 | assertEqual( "{}", Console.convert({}) )
41 | assertEqual( "{ \"foo\": \"bar\" }", Console.convert({foo: "bar"}) )
42 | }})
43 |
44 | it("strigifies recursive objects", function() { with(this) {
45 | assertEqual( "{ \"foo\": { \"bar\": [ 1, 2, 3 ] } }", Console.convert({foo: {bar: [1,2,3]}}) )
46 | }})
47 |
48 | it("strigifies circular objects", function() { with(this) {
49 | var o = {foo: "bar"}
50 | o.bar = o
51 | assertEqual( "{ \"bar\": #circular, \"foo\": \"bar\" }", Console.convert(o) )
52 | }})
53 |
54 | it("strigifies DOM nodes", function() { with(this) {
55 | var node = {
56 | nodeType: 0,
57 | toString: function() { return "[object HTMLFormElement]" }
58 | }
59 | assertEqual( "[object HTMLFormElement]", Console.convert(node) )
60 | }})
61 | }})
62 | }})
63 |
64 | })
65 |
66 |
--------------------------------------------------------------------------------
/test/specs/interface_spec.js:
--------------------------------------------------------------------------------
1 | JS.ENV.InterfaceSpec = JS.Test.describe(JS.Interface, function() { with(this) {
2 | before(function() { with(this) {
3 | this.face = new JS.Interface(["foo", "bar"])
4 | }})
5 |
6 | describe("#test", function() { with(this) {
7 | it("returns true iff the object implements all the methods", function() { with(this) {
8 | assert( face.test({foo: function() {}, bar: function() {}}) )
9 | }})
10 |
11 | it("returns false iff one of the names does not map to a function", function() { with(this) {
12 | assert( !face.test({foo: function() {}, bar: true}) )
13 | assert( !face.test({foo: true, bar: function() {}}) )
14 | }})
15 |
16 | it("returns false iff one of the names is missing", function() { with(this) {
17 | assert( !face.test({foo: function() {}}) )
18 | assert( !face.test({bar: function() {}}) )
19 | }})
20 | }})
21 |
22 | describe(".ensure", function() { with(this) {
23 | it("passes iff the object implements all the methods", function() { with(this) {
24 | assertNothingThrown(function() {
25 | face.test({foo: function() {}, bar: function() {}})
26 | })
27 | }})
28 |
29 | it("throws an error iff one of the names does not map to a function", function() { with(this) {
30 | assertThrows(Error, function() {
31 | JS.Interface.ensure({foo: function() {}, bar: true}, face)
32 | })
33 | assertThrows(Error, function() {
34 | JS.Interface.ensure({foo: true, bar: function() {}}, face)
35 | })
36 | }})
37 |
38 | it("throws an error iff one of the names is missing", function() { with(this) {
39 | assertThrows(Error, function() {
40 | JS.Interface.ensure({foo: function() {}}, face)
41 | })
42 | assertThrows(Error, function() {
43 | JS.Interface.ensure({bar: function() {}}, face)
44 | })
45 | }})
46 |
47 | it("throws an error iff the object does not fully implement one of the interfaces", function() { with(this) {
48 | assertThrows(Error, function() {
49 | JS.Interface.ensure({foo: function() {}}, new JS.Interface(["foo"]), new JS.Interface(["bar"]))
50 | })
51 | assertThrows(Error, function() {
52 | JS.Interface.ensure({bar: function() {}}, new JS.Interface(["foo"]), new JS.Interface(["bar"]))
53 | })
54 | }})
55 | }})
56 | }})
57 |
58 |
--------------------------------------------------------------------------------
/site/src/pages/forwardable.haml:
--------------------------------------------------------------------------------
1 | :textile
2 | h2. Forwardable
3 |
4 | What was it the "Gang of Four":http://en.wikipedia.org/wiki/Design_Patterns
5 | said? _Prefer delegation_. Delegation is the act of getting the receiver of a
6 | method call to respond by simply passing that call along to some other object.
7 | @JS.Forwardable@, a port of Ruby's "@Forwardable@ module":http://ruby-doc.org/core/classes/Forwardable.html,
8 | allows you to easily define instance methods that do just that.
9 |
10 | Let's say you have a class that wraps a collection of objects, which it stores
11 | as an array in one of its instance properties. You might implement some
12 | methods for manipulating the array through the class' own interface:
13 |
14 | var RecordCollection = new JS.Class({
15 | initialize: function() {
16 | this.records = [];
17 | },
18 | push: function(record) {
19 | return this.records.push(record);
20 | },
21 | shift: function() {
22 | return this.records.shift();
23 | }
24 | });
25 |
26 | Instead, you can @extend@ the class with @JS.Forwardable@, then use
27 | @defineDelegators()@ to create the methods. @defineDelegators@ takes the name
28 | of the instance property to delegate calls to as the first argument, and the
29 | names of the delegated methods as the other arguments:
30 |
31 | var RecordCollection = new JS.Class({
32 | extend: JS.Forwardable,
33 | initialize: function() {
34 | this.records = [];
35 | }
36 | });
37 | RecordCollection.defineDelegators('records', 'push', 'shift');
38 |
39 | var recs = new RecordCollection();
40 | recs.push('The White Stripes - Icky Thump');
41 | recs.push('Battles - Mirrored');
42 |
43 | recs.shift() // -> "The White Stripes - Icky Thump"
44 |
45 | If you need to define extra class methods, you need to change the notation
46 | slightly:
47 |
48 | var RecordCollection = new JS.Class({
49 | extend: [JS.Forwardable, {
50 | // class methods go here
51 | }],
52 | initialize: function() {
53 | this.records = [];
54 | }
55 | });
56 |
57 | If you want to give the delegating method a different name, use the
58 | @defineDelegator()@ (singular) method instead:
59 |
60 | // Add an instance method called 'add' that calls records.push
61 | RecordCollection.defineDelegator('records', 'push', 'add');
62 |
--------------------------------------------------------------------------------
/source/test/mocking/matchers.js:
--------------------------------------------------------------------------------
1 | Test.Mocking.extend({
2 | Anything: new JS.Class({
3 | equals: function() { return true },
4 | toString: function() { return 'anything' }
5 | }),
6 |
7 | AnyArgs: new JS.Class({
8 | equals: function() { return Enumerable.ALL_EQUAL },
9 | toString: function() { return '*arguments' }
10 | }),
11 |
12 | ArrayIncluding: new JS.Class({
13 | initialize: function(elements) {
14 | this._elements = Array.prototype.slice.call(elements);
15 | },
16 |
17 | equals: function(array) {
18 | if (!JS.isType(array, Array)) return false;
19 | var i = this._elements.length, j;
20 | loop: while (i--) {
21 | j = array.length;
22 | while (j--) {
23 | if (Enumerable.areEqual(this._elements[i], array[j]))
24 | continue loop;
25 | }
26 | return false;
27 | }
28 | return true;
29 | },
30 |
31 | toString: function() {
32 | var name = Console.convert(this._elements);
33 | return 'arrayIncluding(' + name + ')';
34 | }
35 | }),
36 |
37 | ObjectIncluding: new JS.Class({
38 | initialize: function(elements) {
39 | this._elements = elements;
40 | },
41 |
42 | equals: function(object) {
43 | if (!JS.isType(object, Object)) return false;
44 | for (var key in this._elements) {
45 | if (!Enumerable.areEqual(this._elements[key], object[key]))
46 | return false;
47 | }
48 | return true;
49 | },
50 |
51 | toString: function() {
52 | var name = Console.convert(this._elements);
53 | return 'objectIncluding(' + name + ')';
54 | }
55 | }),
56 |
57 | InstanceOf: new JS.Class({
58 | initialize: function(type) {
59 | this._type = type;
60 | },
61 |
62 | equals: function(object) {
63 | return JS.isType(object, this._type);
64 | },
65 |
66 | toString: function() {
67 | var name = Console.convert(this._type),
68 | an = /^[aeiou]/i.test(name) ? 'an' : 'a';
69 | return an + '(' + name + ')';
70 | }
71 | }),
72 |
73 | Matcher: new JS.Class({
74 | initialize: function(type) {
75 | this._type = type;
76 | },
77 |
78 | equals: function(object) {
79 | return JS.match(this._type, object);
80 | },
81 |
82 | toString: function() {
83 | var name = Console.convert(this._type);
84 | return 'matching(' + name + ')';
85 | }
86 | })
87 | });
88 |
89 |
--------------------------------------------------------------------------------
/source/deferrable.js:
--------------------------------------------------------------------------------
1 | (function(factory) {
2 | var E = (typeof exports === 'object'),
3 | js = (typeof JS === 'undefined') ? require('./core') : JS;
4 |
5 | if (E) exports.JS = exports;
6 | factory(js, E ? exports : js);
7 |
8 | })(function(JS, exports) {
9 | 'use strict';
10 |
11 | var Deferrable = new JS.Module('Deferrable', {
12 | extend: {
13 | Timeout: new JS.Class(Error)
14 | },
15 |
16 | callback: function(block, context) {
17 | if (this.__deferredStatus__ === 'success')
18 | return block.apply(context, this.__deferredValue__);
19 |
20 | if (this.__deferredStatus__ === 'failure')
21 | return;
22 |
23 | this.__callbacks__ = this.__callbacks__ || [];
24 | this.__callbacks__.push([block, context]);
25 | },
26 |
27 | errback: function(block, context) {
28 | if (this.__deferredStatus__ === 'failure')
29 | return block.apply(context, this.__deferredValue__);
30 |
31 | if (this.__deferredStatus__ === 'success')
32 | return;
33 |
34 | this.__errbacks__ = this.__errbacks__ || [];
35 | this.__errbacks__.push([block, context]);
36 | },
37 |
38 | timeout: function(milliseconds) {
39 | this.cancelTimeout();
40 | var self = this, error = new Deferrable.Timeout();
41 | this.__timeout__ = JS.ENV.setTimeout(function() { self.fail(error) }, milliseconds);
42 | },
43 |
44 | cancelTimeout: function() {
45 | if (!this.__timeout__) return;
46 | JS.ENV.clearTimeout(this.__timeout__);
47 | delete this.__timeout__;
48 | },
49 |
50 | setDeferredStatus: function(status, args) {
51 | this.__deferredStatus__ = status;
52 | this.__deferredValue__ = args;
53 |
54 | this.cancelTimeout();
55 |
56 | switch (status) {
57 | case 'success':
58 | if (!this.__callbacks__) return;
59 | var callback;
60 | while (callback = this.__callbacks__.pop())
61 | callback[0].apply(callback[1], args);
62 | break;
63 |
64 | case 'failure':
65 | if (!this.__errbacks__) return;
66 | var errback;
67 | while (errback = this.__errbacks__.pop())
68 | errback[0].apply(errback[1], args);
69 | break;
70 | }
71 | },
72 |
73 | succeed: function() {
74 | return this.setDeferredStatus('success', arguments);
75 | },
76 |
77 | fail: function() {
78 | return this.setDeferredStatus('failure', arguments);
79 | }
80 | });
81 |
82 | exports.Deferrable = Deferrable;
83 | });
84 |
85 |
--------------------------------------------------------------------------------
/source/benchmark.js:
--------------------------------------------------------------------------------
1 | (function(factory) {
2 | var E = (typeof exports === 'object'),
3 | js = (typeof JS === 'undefined') ? require('./core') : JS,
4 |
5 | Console = js.Console || require('./console').Console;
6 |
7 | if (E) exports.JS = exports;
8 | factory(js, Console, E ? exports : js);
9 |
10 | })(function(JS, Console, exports) {
11 | 'use strict';
12 |
13 | var Benchmark = new JS.Module('Benchmark', {
14 | include: Console,
15 | N: 5,
16 |
17 | measure: function(name, runs, functions) {
18 | var envs = [], env,
19 | times = [],
20 | block = functions.test;
21 |
22 | var i = runs * Benchmark.N;
23 | while (i--) {
24 | env = {};
25 | if (functions.setup) functions.setup.call(env);
26 | envs.push(env);
27 | }
28 |
29 | var n = Benchmark.N, start, end;
30 | while (n--) {
31 | i = runs;
32 | start = new JS.Date().getTime();
33 | while (i--) block.call(envs.pop());
34 | end = new JS.Date().getTime();
35 | times.push(end - start);
36 | }
37 | this.printResult(name, times);
38 | },
39 |
40 | printResult: function(name, times) {
41 | var average = this.average(times);
42 | this.reset();
43 | this.print(' ');
44 | this.consoleFormat('bgblack', 'white');
45 | this.print('BENCHMARK');
46 | this.reset();
47 | this.print(' [' + this.format(average) + ']');
48 | this.consoleFormat('cyan');
49 | this.puts(' ' + name);
50 | this.reset();
51 | },
52 |
53 | format: function(average) {
54 | var error = (average.value === 0) ? 0 : 100 * average.error / average.value;
55 | return Math.round(average.value) +
56 | 'ms \u00B1 ' + Math.round(error) + '%';
57 | },
58 |
59 | average: function(list) {
60 | return { value: this.mean(list), error: this.stddev(list) };
61 | },
62 |
63 | mean: function(list, mapper) {
64 | var values = [],
65 | mapper = mapper || function(x) { return x },
66 | n = list.length,
67 | sum = 0;
68 |
69 | while (n--) values.push(mapper(list[n]));
70 |
71 | n = values.length;
72 | while (n--) sum += values[n];
73 | return sum / values.length;
74 | },
75 |
76 | stddev: function(list) {
77 | var square = function(x) { return x*x };
78 | return Math.sqrt(this.mean(list, square) - square(this.mean(list)));
79 | }
80 | });
81 |
82 | Benchmark.extend(Benchmark);
83 |
84 | exports.Benchmark = Benchmark;
85 | });
86 |
87 |
--------------------------------------------------------------------------------
/site/src/pages/proxies.haml:
--------------------------------------------------------------------------------
1 | :textile
2 | h2. Proxy
3 |
4 | In general, a proxy is an object that controls access to another object
5 | (referred to as the _real subject_). It has exactly the same interface as the
6 | real subject and does not modify the subject's behaviour. All it does is
7 | forward method calls onto the subject and it returns the results of such calls.
8 | Proxies are often used to restrict access to a subject, to provide a local
9 | interface to a remote object (such as a web service API), or to allow the
10 | instantiation of memory-intensive objects on demand.
11 |
12 | h3. Virtual proxies
13 |
14 | A virtual proxy is an object that acts as a stand-in for its real subject. The
15 | subject is not initialized until it is really needed, allowing for 'lazy
16 | instantiation' of objects that are expensive to create.
17 |
18 | JS.Class provides a module called @JS.Proxy.Virtual@. This allows you create
19 | proxies for classes without needing to write all the forwarding and
20 | instantiation methods yourself. @JS.Proxy.Virtual@ inspects the proxied class
21 | and automatically creates proxies for all its instance methods. This saves you
22 | time and reduces code duplication.
23 |
24 | Consider the following example: we have a @Dog@ class, which keeps track of
25 | how many times it has been instantiated through its class property @instances@.
26 | We then create a @DogProxy@ class from @Dog@, and instantiate it. At this
27 | point we see that @Dog.instances == 0@. Then we call @rex.bark()@, which
28 | instantiates @rex@'s subject and calls @bark()@ on it. @Dog.instances@ now
29 | equals @1@. Calling further methods on @rex@ will not create any more @Dog@
30 | instances.
31 |
32 | var Dog = new JS.Class({
33 | extend: {
34 | instances: 0
35 | },
36 | initialize: function(name) {
37 | this.name = name;
38 | this.klass.instances++;
39 | },
40 | bark: function() {
41 | return this.name + ' says WOOF!';
42 | }
43 | });
44 |
45 | var DogProxy = new JS.Proxy.Virtual(Dog);
46 |
47 | var rex = new DogProxy('Rex');
48 | Dog.instances // -> 0
49 |
50 | rex.bark() // -> "Rex says WOOF!"
51 | Dog.instances // -> 1
52 |
53 | This pattern is particularly suited to creating parts of a UI that are
54 | initially hidden - you can create proxies for them on page load, but they
55 | won't add any of their HTML to the page until you choose to show them.
56 |
--------------------------------------------------------------------------------
/site/src/pages/packages/customloaders.haml:
--------------------------------------------------------------------------------
1 | :textile
2 | h2. Custom loader functions
3 |
4 | Some libraries, such as the "Google Ajax
5 | APIs":http://code.google.com/apis/ajax/, have their own systems for loading
6 | code on demand that involve more than simply knowing the path to a script
7 | file. Our package system allows you to specify packages that use a loader
8 | function rather than a path to load themselves; the function should take a
9 | callback and call it when the library in question is done loading. For
10 | example, here's how you'd incorporate Google Maps into your library:
11 |
12 | JS.packages(function() { with(this) {
13 | file('http://www.google.com/jsapi?key=MY_GOOGLE_KEY')
14 | .provides('google.load');
15 |
16 | loader(function(cb) { google.load('maps', '2.x', {callback: cb}) })
17 | .provides('GMap2', 'GClientGeocoder')
18 | .requires('google.load');
19 | }});
20 |
21 | The callback (@cb@) is a function generated by the package system that
22 | continues to load and run dependent code once the custom loader has finished
23 | its work. If you don't call @cb@ (or pass it to a function that will call it
24 | for you as above), code that depends on this library will not run.
25 |
26 | @JS.packages@ also provides post-load setup hooks that let you run some code
27 | after a file loads. For example, a strategy for loading
28 | "YUI3":http://developer.yahoo.com/yui/3/ might involve loading the seed file,
29 | creating a new global instance of the library, then using YUI's own loader
30 | functions to load further modules. Some sample code:
31 |
32 | JS.packages(function() { with(this) {
33 | file('http://yui.yahooapis.com/3.0.0pr2/build/yui/yui-min.js')
34 | .setup(function() { window.yui3 = YUI() })
35 | .provides('YUI', 'yui3');
36 |
37 | loader(function(cb) { yui3.use('node', cb) })
38 | .provides('yui3.Node')
39 | .requires('yui3');
40 | }});
41 |
42 | Loader functions can also be used to generate library objects that are
43 | expensive to create without necessarily loading code from external files. Just
44 | remember to call @cb@ yourself when the generated object is ready:
45 |
46 | JS.packages(function() { with(this) {
47 | loader(function(cb) {
48 | window.ChocolateFactory = new WonkaVenture();
49 | // Perform other expensive setup operations
50 | cb();
51 | })
52 | .provides('ChocolateFactory');
53 | }});
54 |
--------------------------------------------------------------------------------
/site/src/pages/singletonmethods.haml:
--------------------------------------------------------------------------------
1 | :textile
2 | h2. Singleton methods
3 |
4 | In Ruby, singleton methods are methods attached to a single object rather than
5 | defined in a class. If you add a singleton method to a Ruby object, @super@
6 | refers to the method from the object's class definition. @jsclass@ allows the
7 | same behaviour. Recall our @Animal@ class:
8 |
9 | var Animal = new Class({
10 | initialize: function(name) {
11 | this.name = name;
12 | },
13 | speak: function(things) {
14 | return 'My name is ' + this.name + ' and I like ' + things;
15 | }
16 | });
17 |
18 | We can create a new animal and @extend@ it with singleton methods. All
19 | @Class@-derived objects have an @extend()@ method for doing this:
20 |
21 | var cow = new Animal('Daisy');
22 |
23 | cow.extend({
24 | speak: function(stuff) {
25 | return 'Mooo! ' + this.callSuper();
26 | },
27 | getName: function() {
28 | return this.name;
29 | }
30 | });
31 |
32 | cow.getName() // -> "Daisy"
33 |
34 | cow.speak('grass')
35 | // -> "Mooo! My name is Daisy and I like grass"
36 |
37 | h3. Modules as object extensions
38 |
39 | As well as passing simple objects into the @extend()@ method, you can use
40 | modules. The receiving object will then gain all the methods of the module. If
41 | we extend using "@Observable@":/observable.html, for example:
42 |
43 | cow.extend(Observable);
44 |
45 | cow.addObserver(function() {
46 | alert('This cow is observable!');
47 | });
48 |
49 | cow.notifyObservers();
50 |
51 | This alerts @"This cow is observable!"@, as expected. Using modules to extend
52 | objects has some interesting inheritance consequences, which are more
53 | thoroughly exposed in "the inheritance article":/inheritance.html. In short,
54 | all singleton methods are stored in a module attached to the object - this is
55 | known as an eigenclass or metaclass in Ruby circles. By using a module to
56 | extend an object, the module is mixed into the eigenclass, making it part of
57 | the inheritance tree. So we can override @notifyObservers()@, for example, to
58 | duplicate every observer added to an object. @callSuper()@ calls out to the
59 | module we used to extend the object.
60 |
61 | cow.extend({
62 | notifyObservers: function() {
63 | this.callSuper();
64 | this.callSuper();
65 | }
66 | });
67 |
68 | // alerts "This cow is observable!" twice
69 | cow.notifyObservers();
70 |
71 |
--------------------------------------------------------------------------------
/source/dom/builder.js:
--------------------------------------------------------------------------------
1 | DOM.Builder = new JS.Class('DOM.Builder', {
2 | extend: {
3 | addElement: function(name) {
4 | this.define(name, function() {
5 | return this.makeElement(name, arguments);
6 | });
7 | DOM[name] = function() {
8 | return new DOM.Builder().makeElement(name, arguments);
9 | };
10 | },
11 |
12 | addElements: function(list) {
13 | var i = list.length;
14 | while (i--) this.addElement(list[i]);
15 | }
16 | },
17 |
18 | initialize: function(parent) {
19 | this._parentNode = parent;
20 | },
21 |
22 | makeElement: function(name, children) {
23 | var element, child, attribute;
24 | if ( document.createElementNS ) {
25 | // That makes possible to mix HTML within SVG or XUL.
26 | element = document.createElementNS('http://www.w3.org/1999/xhtml', name);
27 | } else {
28 | element = document.createElement(name);
29 | }
30 | for (var i = 0, n = children.length; i < n; i++) {
31 | child = children[i];
32 | if (typeof child === 'function') {
33 | child(new this.klass(element));
34 | } else if (JS.isType(child, 'string')) {
35 | element.appendChild(document.createTextNode(child));
36 | } else {
37 | for (attribute in child)
38 | element[attribute] = child[attribute];
39 | }
40 | }
41 | if (this._parentNode) this._parentNode.appendChild(element);
42 | return element;
43 | },
44 |
45 | concat: function(text) {
46 | if (!this._parentNode) return;
47 | this._parentNode.appendChild(document.createTextNode(text));
48 | }
49 | });
50 |
51 | DOM.Builder.addElements([
52 | 'a', 'abbr', 'address', 'applet', 'area', 'article', 'aside', 'audio', 'b',
53 | 'base', 'bdo', 'blockquote', 'body', 'br', 'button', 'canvas', 'caption',
54 | 'cite', 'code', 'col', 'colgroup', 'command', 'datalist', 'dd', 'del',
55 | 'details', 'device', 'dfn', 'div', 'dl', 'dt', 'em', 'embed', 'fieldset',
56 | 'figcaption', 'figure', 'footer', 'form', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
57 | 'head', 'header', 'hgroup', 'hr', 'html', 'i', 'iframe', 'img', 'input',
58 | 'ins', 'kbd', 'keygen', 'label', 'legend', 'li', 'link', 'map', 'mark',
59 | 'marquee', 'menu', 'meta', 'meter', 'nav', 'noscript', 'object', 'ol',
60 | 'optgroup', 'option', 'output', 'p', 'param', 'pre', 'progress', 'q', 'rp',
61 | 'rt', 'ruby', 'samp', 'script', 'section', 'select', 'small', 'source',
62 | 'span', 'strong', 'style', 'sub', 'sup', 'summary', 'table', 'tbody', 'td',
63 | 'textarea', 'tfoot', 'th', 'thead', 'time', 'title', 'tr', 'track', 'ul',
64 | 'var', 'video', 'wbr'
65 | ]);
66 |
67 |
--------------------------------------------------------------------------------
/site/src/pages/classmethods.haml:
--------------------------------------------------------------------------------
1 | :textile
2 | h2. Class methods
3 |
4 | In Ruby, modules and classes are just another type of object; they are objects
5 | that are responsible for storing methods and making new objects. Class methods
6 | are methods attached to (and called on) an individual class object, rather
7 | than its instances. They are really just a special case of "singleton methods":/singletonmethods.html.
8 | To add class methods when defining a class, wrap them up in an @extend@ block:
9 |
10 | var User = new JS.Class({
11 | extend: {
12 | find: function(id) {
13 | // Return a User with id
14 | },
15 | create: function(name) {
16 | return new this(name);
17 | }
18 | },
19 | initialize: function(name) {
20 | this.username = name;
21 | }
22 | });
23 |
24 | We could equally add the methods after the class was created:
25 |
26 | var User = new JS.Class({
27 | initialize: function(name) {
28 | this.username = name;
29 | }
30 | });
31 |
32 | User.extend({
33 | find: function(id) {
34 | // Return a User with id
35 | },
36 | create: function(name) {
37 | return new this(name);
38 | }
39 | });
40 |
41 | These two syntaxes apply equally to creating and extending "@Modules@":/modules.html.
42 | Within a class method, the keyword @this@ refers to the class itself - see the
43 | @User.create()@ method:
44 |
45 | var james = User.create('James');
46 | james.username // -> 'James'
47 | james.klass // -> User
48 |
49 | When you create a subclass, it will inherit any class methods of its parent,
50 | and you can use @callSuper()@ too:
51 |
52 | var LoudUser = new JS.Class(User, {
53 | extend: {
54 | create: function(name) {
55 | return this.callSuper(name.toUpperCase());
56 | }
57 | }
58 | });
59 |
60 | var me = LoudUser.create('James');
61 | me.username // -> 'JAMES'
62 | me.klass // -> LoudUser
63 |
64 | var you = LoudUser.find(24) // inherited from User
65 |
66 | Note how @this@, even in @callSuper@ methods, always refers to the same thing
67 | as in the original method call. We get back a @LoudUser@, not a @User@.
68 |
69 | Note that class methods are not the same as Java's static methods. If you want
70 | to call a class method from an instance of the class, you need to get a
71 | reference to the class through the object's @klass@ property.
72 |
73 | User.define('copy', function() {
74 | return this.klass.create(this.username);
75 | });
76 |
77 |
--------------------------------------------------------------------------------
/test/specs/forwardable_spec.js:
--------------------------------------------------------------------------------
1 | JS.require('JS.Forwardable', function(Forwardable) {
2 |
3 | JS.ENV.ForwardableSpec = JS.Test.describe(Forwardable, function() { with(this) {
4 | define("Subject", new JS.Class({
5 | initialize: function() {
6 | this.name = "something";
7 | },
8 | setName: function(name) {
9 | this.name = name;
10 | },
11 | getName: function() {
12 | return this.name;
13 | },
14 | multiply: function(a,b) {
15 | return a*b;
16 | }
17 | }))
18 |
19 | define("forwardableClass", function() { with(this) {
20 | return new JS.Class({extend: Forwardable,
21 | initialize: function() {
22 | this.subject = new Subject()
23 | }
24 | })
25 | }})
26 |
27 | before(function() { with(this) {
28 | this.forwarderClass = forwardableClass()
29 | this.forwarder = new forwarderClass()
30 | }})
31 |
32 | describe("#defineDelegator", function() { with(this) {
33 | it("defines a forwarding method", function() { with(this) {
34 | forwarderClass.defineDelegator("subject", "getName")
35 | assertRespondTo( forwarder, "getName" )
36 | assertEqual( "something", forwarder.getName() )
37 | }})
38 |
39 | it("passes arguments through when forwarding calls", function() { with(this) {
40 | forwarderClass.defineDelegator("subject", "multiply")
41 | assertEqual( 20, forwarder.multiply(4,5) )
42 | }})
43 |
44 | it("uses the named property as the forwarding target", function() { with(this) {
45 | forwarder.target = {getName: function() { return "target name" }}
46 | forwarderClass.defineDelegator("target", "getName")
47 | assertEqual( "target name", forwarder.getName() )
48 | }})
49 |
50 | it("defines a forwarding method under a different name", function() { with(this) {
51 | forwarderClass.defineDelegator("subject", "getName", "subjectName")
52 | assertRespondTo( forwarder, "subjectName" )
53 | assertEqual( "something", forwarder.subjectName() )
54 | }})
55 |
56 | it("defines forwarding methods for mutators", function() { with(this) {
57 | forwarderClass.defineDelegator("subject", "getName")
58 | forwarderClass.defineDelegator("subject", "setName")
59 | forwarder.setName("nothing")
60 | assertEqual( "nothing", forwarder.getName() )
61 | }})
62 | }})
63 |
64 | describe("#defineDelegators", function() { with(this) {
65 | it("defines multiple forwarding methods", function() { with(this) {
66 | forwarderClass.defineDelegators("subject", "getName", "setName")
67 | forwarder.setName("nothing")
68 | assertEqual( "nothing", forwarder.getName() )
69 | }})
70 | }})
71 | }})
72 |
73 | })
74 |
75 |
--------------------------------------------------------------------------------
/site/src/pages/comparable.haml:
--------------------------------------------------------------------------------
1 | :textile
2 | h2. Comparable
3 |
4 | @JS.Comparable@ is a module that helps you manipulate objects that can be
5 | ordered relative to each other. It is designed to work exactly like
6 | "@Comparable@":http://ruby-doc.org/core/classes/Comparable.html
7 | in Ruby, only without the nice method names like @"<="@ that Ruby allows. To
8 | use it, your class must define an instance method called @compareTo@, that
9 | tells it how to compare itself to other objects. As an example, let's create a
10 | class to represent to-do list items. These can be ordered according to their
11 | priority.
12 |
13 | var TodoItem = new JS.Class({
14 | include: JS.Comparable,
15 |
16 | initialize: function(task, priority) {
17 | this.task = task;
18 | this.priority = priority;
19 | },
20 |
21 | // Must return -1 if this object is 'less than' other,
22 | // +1 if it is 'greater than' other, or 0 if they are equal
23 | compareTo: function(other) {
24 | if (this.priority < other.priority) return -1;
25 | if (this.priority > other.priority) return 1;
26 | return 0;
27 | }
28 | });
29 |
30 | @TodoItem@ now has the following instance methods:
31 |
32 | * @lt(other)@ - returns @true@ iff the receiver is less than @other@
33 | * @lte(other)@ - returns @true@ iff the receiver is less than or equal to@other@
34 | * @gt(other)@ - returns @true@ iff the receiver is greater than @other@
35 | * @gte(other)@ - returns @true@ iff the receiver is greater than or equal to @other@
36 | * @eq(other)@ - returns @true@ iff the receiver is equal to @other@
37 | * @between(a,b)@ - returns @true@ iff the receiver is between @a@ and @b@ inclusive
38 | * @compareTo(other)@ - returns @-1@/@0@/@1@ to indicate result of comparison
39 |
40 | @TodoItem@ also has a class method called @compare@ that you should use for
41 | sorting.
42 |
43 | Let's create some items and see how they behave:
44 |
45 | var items = [
46 | new TodoItem('Go to work', 5),
47 | new TodoItem('Buy milk', 3),
48 | new TodoItem('Pay the rent', 2),
49 | new TodoItem('Write code', 1),
50 | new TodoItem('Head down the pub', 4)
51 | ];
52 |
53 | items[2].lt(items[1]) // -> true
54 | items[0].gte(items[3]) // -> true
55 | items[4].eq(items[4]) // -> true
56 | items[1].between(items[3], items[4]) // -> true
57 |
58 | items.sort(TodoItem.compare)
59 | // -> [
60 | // {task: 'Write code', ...},
61 | // {task: 'Pay the rent', ...},
62 | // {task: 'Buy milk', ...},
63 | // {task: 'Head down the pub', ...},
64 | // {task: 'Go to work', ...}
65 | // ]
66 |
67 |
--------------------------------------------------------------------------------
/site/src/pages/tsort.haml:
--------------------------------------------------------------------------------
1 | :textile
2 | h2. TSort
3 |
4 | @JS.TSort@ is a JavaScript version of Ruby's @TSort@ module, which provides
5 | "topological sort":http://en.wikipedia.org/wiki/Topological_sorting capability
6 | to data structures. The canonical example of this is determining how a
7 | set of dependent tasks should be sorted such that each task comes after those
8 | it depends on in the list. One way to represent this information may be as
9 | a task table:
10 |
11 | var tasks = new Tasks({
12 | 'eat breakfast': ['serve'],
13 | 'serve': ['cook'],
14 | 'cook': ['buy bacon', 'buy eggs'],
15 | 'buy bacon': [],
16 | 'buy eggs': []
17 | });
18 |
19 | (This example is borrowed from the excellent "Getting to know the Ruby
20 | standard library":http://endofline.wordpress.com/2010/12/22/ruby-standard-library-tsort/
21 | blog.)
22 |
23 | This table represents each task involved in serving breakfast. The tasks are
24 | the keys in the table, and each task maps to the list of tasks which must be
25 | done immediately before it. We want to to sort all our tasks into a linear
26 | list so we can process them in the correct order, and @TSort@ lets us do that.
27 |
28 | To use @TSort@, our @Tasks@ class must implement two methods. @tsortEachNode@
29 | must take a callback function and context and yield each task in the set to
30 | the callback. @tsortEachChild@ must accept an individual task and yield each
31 | of its direct children. We can implement this simply:
32 |
33 | var Tasks = new JS.Class({
34 | include: JS.TSort,
35 |
36 | initialize: function(table) {
37 | this.table = table;
38 | },
39 |
40 | tsortEachNode: function(block, context) {
41 | for (var task in this.table) {
42 | if (this.table.hasOwnProperty(task))
43 | block.call(context, task);
44 | }
45 | },
46 |
47 | tsortEachChild: function(task, block, context) {
48 | var tasks = this.table[task];
49 | for (var i = 0, n = tasks.length; i < n; i++)
50 | block.call(context, tasks[i]);
51 | }
52 | });
53 |
54 | Once we've told @TSort@ how to traverse our list of tasks, it can sort the
55 | dependent tasks out for us:
56 |
57 | tasks.tsort() 58 | // -> ['buy bacon', 'buy eggs', 'cook', 'serve', 'eat breakfast']59 | 60 | We now have a flat list of the tasks and can iterate over them in order, 61 | knowing that each task will have all its dependencies run before it in the 62 | list. 63 | 64 | Note that @TSort@ sorts based on the how objects are related to each other 65 | in a graph, not by how they compare using "comparison":/comparable.html 66 | methods. 67 | -------------------------------------------------------------------------------- /source/tsort.js: -------------------------------------------------------------------------------- 1 | (function(factory) { 2 | var E = (typeof exports === 'object'), 3 | js = (typeof JS === 'undefined') ? require('./core') : JS, 4 | 5 | Hash = js.Hash || require('./hash').Hash; 6 | 7 | if (E) exports.JS = exports; 8 | factory(js, Hash, E ? exports : js); 9 | 10 | })(function(JS, Hash, exports) { 11 | 'use strict'; 12 | 13 | var TSort = new JS.Module('TSort', { 14 | extend: { 15 | Cyclic: new JS.Class(Error) 16 | }, 17 | 18 | tsort: function() { 19 | var result = []; 20 | this.tsortEach(result.push, result); 21 | return result; 22 | }, 23 | 24 | tsortEach: function(block, context) { 25 | this.eachStronglyConnectedComponent(function(component) { 26 | if (component.length === 1) 27 | block.call(context, component[0]); 28 | else 29 | throw new TSort.Cyclic('topological sort failed: ' + component.toString()); 30 | }); 31 | }, 32 | 33 | stronglyConnectedComponents: function() { 34 | var result = []; 35 | this.eachStronglyConnectedComponent(result.push, result); 36 | return result; 37 | }, 38 | 39 | eachStronglyConnectedComponent: function(block, context) { 40 | var idMap = new Hash(), 41 | stack = []; 42 | 43 | this.tsortEachNode(function(node) { 44 | if (idMap.hasKey(node)) return; 45 | this.eachStronglyConnectedComponentFrom(node, idMap, stack, function(child) { 46 | block.call(context, child); 47 | }); 48 | }, this); 49 | }, 50 | 51 | eachStronglyConnectedComponentFrom: function(node, idMap, stack, block, context) { 52 | var nodeId = idMap.size, 53 | stackLength = stack.length, 54 | minimumId = nodeId, 55 | component, i; 56 | 57 | idMap.store(node, nodeId); 58 | stack.push(node); 59 | 60 | this.tsortEachChild(node, function(child) { 61 | if (idMap.hasKey(child)) { 62 | var childId = idMap.get(child); 63 | if (child !== undefined && childId < minimumId) minimumId = childId; 64 | } else { 65 | var subMinimumId = this.eachStronglyConnectedComponentFrom(child, idMap, stack, block, context); 66 | if (subMinimumId < minimumId) minimumId = subMinimumId; 67 | } 68 | }, this); 69 | 70 | if (nodeId === minimumId) { 71 | component = stack.splice(stackLength, stack.length - stackLength); 72 | i = component.length; 73 | while (i--) idMap.store(component[i], undefined); 74 | block.call(context, component); 75 | } 76 | 77 | return minimumId; 78 | }, 79 | 80 | tsortEachNode: function() { 81 | throw new JS.NotImplementedError('tsortEachNode'); 82 | }, 83 | 84 | tsortEachChild: function() { 85 | throw new JS.NotImplementedError('tsortEachChild'); 86 | } 87 | }); 88 | 89 | exports.TSort = TSort; 90 | }); 91 | 92 | -------------------------------------------------------------------------------- /site/src/pages/decorator.haml: -------------------------------------------------------------------------------- 1 | :textile 2 | h2. Decorator 3 | 4 | The @JS.Decorator@ module gives you a means of implementing "the decorator pattern":http://en.wikipedia.org/wiki/Decorator_pattern 5 | with minimal boilerplate and code duplication. When creating a decorator class, 6 | you only need to define methods that differ in some way from the methods in 7 | the decorated object (the _component_). This means you don't have to write 8 | lots of forwarding methods by hand, which saves you time, filesize, and 9 | reduces code duplication. 10 | 11 | Let's take a quick example: 12 | 13 |
// Basic Bike class. Bikes cost $10 per gear.
14 |
15 | var Bike = new JS.Class({
16 | initialize: function(model, gears) {
17 | this.model = model;
18 | this.gears = gears;
19 | },
20 | getModel: function() {
21 | return this.model;
22 | },
23 | getPrice: function() {
24 | return 10 * this.gears;
25 | },
26 | applyBrakes: function(force) {
27 | // slow the bike down...
28 | }
29 | });
30 |
31 | // Disk brake decorator. Disk brakes add to the price,
32 | // and make the bike's brakes more powerful.
33 |
34 | var DiskBrakeDecorator = new JS.Decorator(Bike, {
35 | getPrice: function() {
36 | return this.component.getPrice() + 50;
37 | },
38 | applyBrakes: function(force) {
39 | this.component.applyBrakes(8 * force);
40 | }
41 | });
42 |
43 | @DiskBrakeDecorator@ gets versions of all @Bike@'s instance methods that
44 | forward the method call onto the component and return the result. e.g.,
45 | @DiskBrakeDecorator@'s @getModel()@ method looks like:
46 |
47 | getModel: function() {
48 | return this.component.getModel();
49 | };
50 |
51 | Any methods you don't redefine in the decorator class will look similar to
52 | this. Let's try our new classes out:
53 |
54 | var bike = new Bike('Specialized Rock Hopper', 21);
55 | bike.getPrice() // -> 210
56 |
57 | bike = new DiskBrakeDecorator(bike);
58 | bike.getPrice() // -> 260
59 | bike.getModel() // -> "Specialized Rock Hopper"
60 |
61 | Within your decorator methods, use @this.component@ to refer to the decorated
62 | object. If a decorator defines new methods, they will be passed through by any
63 | other decorators you wrap an object with.
64 |
65 | var HornDecorator = new JS.Decorator(Bike, {
66 | beepHorn: function(noise) {
67 | return noise.toUpperCase();
68 | }
69 | });
70 |
71 | var bike = new Bike('Specialized Rock Hopper', 21);
72 |
73 | // Let's wrap a HornDecorator with a DiskBrakeDecorator
74 | bike = new HornDecorator(bike);
75 | bike = new DiskBrakeDecorator(bike);
76 |
77 | bike.beepHorn('beep!') // -> "BEEP!"
78 |
--------------------------------------------------------------------------------
/source/console/base.js:
--------------------------------------------------------------------------------
1 | Console.extend({
2 | Base: new JS.Class({
3 | __buffer__: '',
4 | __format__: '',
5 |
6 | backtraceFilter: function() {
7 | if (typeof version === 'function' && version() > 100) {
8 | return /.*/;
9 | } else {
10 | return null;
11 | }
12 | },
13 |
14 | coloring: function() {
15 | return !this.envvar(Console.NO_COLOR);
16 | },
17 |
18 | echo: function(string) {
19 | if (typeof console !== 'undefined') return console.log(string);
20 | if (typeof print === 'function') return print(string);
21 | },
22 |
23 | envvar: function(name) {
24 | return null;
25 | },
26 |
27 | exit: function(status) {
28 | if (typeof system === 'object' && system.exit) system.exit(status);
29 | if (typeof quit === 'function') quit(status);
30 | },
31 |
32 | format: function(type, name, args) {
33 | if (!this.coloring()) return;
34 | var escape = Console.ESCAPE_CODES[type][name];
35 |
36 | for (var i = 0, n = args.length; i < n; i++)
37 | escape = escape.replace('%' + (i+1), args[i]);
38 |
39 | this.__format__ += Console.escape(escape);
40 | },
41 |
42 | flushFormat: function() {
43 | var format = this.__format__;
44 | this.__format__ = '';
45 | return format;
46 | },
47 |
48 | getDimensions: function() {
49 | var width = this.envvar('COLUMNS') || Console.DEFAULT_WIDTH,
50 | height = this.envvar('ROWS') || Console.DEFAULT_HEIGHT;
51 |
52 | return [parseInt(width, 10), parseInt(height, 10)];
53 | },
54 |
55 | print: function(string) {
56 | var coloring = this.coloring(),
57 | width = this.getDimensions()[0],
58 | esc = Console.escape,
59 | length, prefix, line;
60 |
61 | while (string.length > 0) {
62 | length = this.__buffer__.length;
63 | prefix = (length > 0 && coloring) ? esc('1F') + esc((length + 1) + 'G') : '';
64 | line = string.substr(0, width - length);
65 |
66 | this.__buffer__ += line;
67 |
68 | if (coloring) this.echo(prefix + this.flushFormat() + line);
69 |
70 | if (this.__buffer__.length === width) {
71 | if (!coloring) this.echo(this.__buffer__);
72 | this.__buffer__ = '';
73 | }
74 | string = string.substr(width - length);
75 | }
76 | },
77 |
78 | puts: function(string) {
79 | var coloring = this.coloring(),
80 | esc = Console.escape,
81 | length = this.__buffer__.length,
82 | prefix = (length > 0 && coloring) ? esc('1F') + esc((length + 1) + 'G') : this.__buffer__;
83 |
84 | this.echo(prefix + this.flushFormat() + string);
85 | this.__buffer__ = '';
86 | }
87 | })
88 | });
89 |
90 |
--------------------------------------------------------------------------------
/source/test/unit/test_suite.js:
--------------------------------------------------------------------------------
1 | Test.Unit.extend({
2 | TestSuite: new JS.Class({
3 | include: Enumerable,
4 |
5 | extend: {
6 | STARTED: 'Test.Unit.TestSuite.STARTED',
7 | FINISHED: 'Test.Unit.TestSuite.FINISHED',
8 |
9 | forEach: function(tests, block, continuation, context) {
10 | var looping = false,
11 | pinged = false,
12 | n = tests.length,
13 | i = -1,
14 | breakTime = new JS.Date().getTime(),
15 | setTimeout = Test.FakeClock.REAL.setTimeout;
16 |
17 | var ping = function() {
18 | pinged = true;
19 | var time = new JS.Date().getTime();
20 |
21 | if (Console.BROWSER && (time - breakTime) > 1000) {
22 | breakTime = time;
23 | looping = false;
24 | setTimeout(iterate, 0);
25 | }
26 | else if (!looping) {
27 | looping = true;
28 | while (looping) iterate();
29 | }
30 | };
31 |
32 | var iterate = function() {
33 | i += 1;
34 | if (i === n) {
35 | looping = false;
36 | return continuation && continuation.call(context);
37 | }
38 | pinged = false;
39 | block.call(context, tests[i], ping);
40 | if (!pinged) looping = false;
41 | };
42 |
43 | ping();
44 | }
45 | },
46 |
47 | initialize: function(metadata, tests) {
48 | this._metadata = metadata;
49 | this._tests = tests;
50 | },
51 |
52 | forEach: function(block, continuation, context) {
53 | this.klass.forEach(this._tests, block, continuation, context);
54 | },
55 |
56 | run: function(result, continuation, callback, context) {
57 | if (this._metadata.fullName)
58 | callback.call(context, this.klass.STARTED, this);
59 |
60 | this.forEach(function(test, resume) {
61 | test.run(result, resume, callback, context)
62 |
63 | }, function() {
64 | if (this._metadata.fullName)
65 | callback.call(context, this.klass.FINISHED, this);
66 |
67 | continuation.call(context);
68 |
69 | }, this);
70 | },
71 |
72 | size: function() {
73 | if (this._size !== undefined) return this._size;
74 | var totalSize = 0, i = this._tests.length;
75 | while (i--) totalSize += this._tests[i].size();
76 | return this._size = totalSize;
77 | },
78 |
79 | empty: function() {
80 | return this._tests.length === 0;
81 | },
82 |
83 | metadata: function(root) {
84 | var data = JS.extend({size: this.size()}, this._metadata);
85 | if (root) {
86 | delete data.fullName;
87 | delete data.shortName;
88 | delete data.context;
89 | }
90 | return data;
91 | }
92 | })
93 | });
94 |
95 |
--------------------------------------------------------------------------------
/test/specs/proxy_spec.js:
--------------------------------------------------------------------------------
1 | JS.require('JS.Proxy', function(Proxy) {
2 |
3 | JS.ENV.ProxySpec = JS.Test.describe(Proxy, function() { with(this) {
4 | describe(Proxy.Virtual, function() { with(this) {
5 | before(function() { with(this) {
6 | this.instances = 0
7 |
8 | this.Subject = new JS.Class({
9 | initialize: function(name, age) {
10 | instances += 1
11 | this.name = name
12 | this.age = age
13 | },
14 |
15 | setName: function(a, b) {
16 | this.name = a + " " + b
17 | },
18 |
19 | getName: function() { return this.name },
20 |
21 | getAge: function() { return this.age }
22 | })
23 |
24 | this.Proxy = new Proxy.Virtual(Subject)
25 | this.proxyInstance = new Proxy("jcoglan", 26)
26 | }})
27 |
28 | it("creates classes", function() { with(this) {
29 | assertKindOf( JS.Class, Proxy )
30 | }})
31 |
32 | it("does not instantiate the wrapped class immediately", function() { with(this) {
33 | assertEqual( 0, instances )
34 | }})
35 |
36 | it("instantiates the wrapped class when a method is called", function() { with(this) {
37 | proxyInstance.getName()
38 | assertEqual( 1, instances )
39 | }})
40 |
41 | it("instantiates the wrapped class once per proxy instance", function() { with(this) {
42 | proxyInstance.getName()
43 | proxyInstance.getName()
44 | assertEqual( 1, instances )
45 | new Proxy("bart", 10).getName()
46 | assertEqual( 2, instances )
47 | }})
48 |
49 | it("passes constructor arguments down to the subject", function() { with(this) {
50 | assertEqual( "jcoglan", proxyInstance.getName() )
51 | assertEqual( 26, proxyInstance.getAge() )
52 | }})
53 |
54 | it("passes method arguments down to the subject", function() { with(this) {
55 | proxyInstance.setName("some", "words")
56 | assertEqual( "some words", proxyInstance.getName() )
57 | }})
58 |
59 | describe("a singleton method", function() { with(this) {
60 | before(function() { with(this) {
61 | proxyInstance.extend({
62 | newMethod: function() { return this.name.toUpperCase() }
63 | })
64 | }})
65 |
66 | it("can access the subject's instance variables", function() { with(this) {
67 | assertEqual( "JCOGLAN", proxyInstance.newMethod() )
68 | }})
69 | }})
70 |
71 | describe("a singleton method that calls super", function() { with(this) {
72 | before(function() { with(this) {
73 | proxyInstance.extend({
74 | getAge: function() { return this.callSuper() * 2 }
75 | })
76 | }})
77 |
78 | it("calls the subject's implementation of the method", function() { with(this) {
79 | assertEqual( 52, proxyInstance.getAge() )
80 | }})
81 | }})
82 | }})
83 | }})
84 |
85 | })
86 |
87 |
--------------------------------------------------------------------------------
/README.markdown:
--------------------------------------------------------------------------------
1 | # JS.Class - Ruby-style JavaScript
2 |
3 | [http://jsclass.jcoglan.com](http://jsclass.jcoglan.com)
4 |
5 | JS.Class is a JavaScript library for building object-oriented programs using
6 | Ruby idioms. It implements Ruby's core object/module/class system in JavaScript,
7 | as well as several standard Ruby libraries and various other extensions.
8 |
9 |
10 | ## Development
11 |
12 | To hack on JS.Class you'll need to be able to build it and run the tests. You
13 | need Node.js and Ruby installed, then run:
14 |
15 | cd path/to/js.class
16 | npm install
17 | bundle install
18 | bundle exec jake
19 |
20 | This will build the project and create files in the `build` directory.
21 |
22 |
23 | ### Running the tests
24 |
25 | To run the tests, you need to run several tasks. Make sure all the target server
26 | platforms work:
27 |
28 | $ JS=(v8 node phantomjs spidermonkey rhino narwhal ringo mongo)
29 | $ for js in "${JS[@]}"; do echo "$js" ; $js test/console.js ; echo $? ; done
30 |
31 | Some interpreters will skip the tests that use asynchronous APIs.
32 |
33 | Using Node.js, check that all the output formats work:
34 |
35 | $ FMT=(dot error json progress spec tap xml)
36 | $ for fmt in "${FMT[@]}" ; do node test/console.js -f $fmt ; done
37 |
38 | Check the tests work in the PhantomJS browser. All the output formats should
39 | work here too -- change the format by editing `test/phantom.js`.
40 |
41 | $ phantomjs test/phantom.js
42 |
43 | Run the test suite in as many web browsers as you can:
44 |
45 | $ open test/browser.html
46 |
47 | For desktop application platforms, run it in XULRunner and AIR:
48 |
49 | $ xulrunner -app test/xulenv/application.ini
50 | $ adl test/airenv/app.xml
51 |
52 | Finally, JS.Class supports several distributed test environments, including
53 | Buster.JS, Karma, Testem, Testling CI and TestSwarm. Here's how to test on
54 | each platform; some of these require running commands in multiple terminals and
55 | I've numbered the prompts to indicate this.
56 |
57 | #### Buster.JS
58 |
59 | 1 $ python -m SimpleHTTPServer 8000
60 | 2 $ ./node_modules/.bin/buster-server
61 | 3 $ open http://localhost:1111
62 | 4 $ ./node_modules/.bin/buster-test
63 |
64 | #### Karma
65 |
66 | 1 $ python -m SimpleHTTPServer 8000
67 | 2 $ ./node_modules/.bin/testacular start test/testacular.js
68 | 3 $ open http://localhost:8080
69 | 4 $ ./node_modules/.bin/testacular run
70 |
71 | #### Testem
72 |
73 | 1 $ ./node_modules/.bin/testem -f test/testem.json
74 | 2 $ open http://localhost:7357
75 |
76 | Tests should run automatically in all connected browsers when you edit files.
77 |
78 | #### TestSwarm
79 |
80 | TestSwarm integration requires a hosted server and is tested during the release
81 | process.
82 |
83 |
84 | ## License
85 |
86 | Distributed under the MIT license.
87 | Copyright (c) 2007-2013 James Coglan
88 |
89 |
--------------------------------------------------------------------------------
/site/src/pages/modifyingmodules.haml:
--------------------------------------------------------------------------------
1 | :textile
2 | h2. Modifying existing classes and modules
3 |
4 | Just like in Ruby, classes and modules are open for modification at any time,
5 | so you can add and change methods in existing classes. All classes and modules
6 | in @jsclass@ have some special methods for modifying the methods they contain.
7 | The following is written to apply to modifying classes, but it applies equally
8 | to modifying modules -- classes are just modules that can be instantiated.
9 |
10 | The first of these methods is called @define()@. This adds a single named
11 | method to the class/module. If you're modifying a class, the method instantly
12 | becomes available in instances of the class, and in its subclasses.
13 |
14 | Animal.define('sleep', function() {
15 | return this.name + ' is sleeping';
16 | });
17 |
18 | rex.sleep()
19 | // -> "Rex is sleeping"
20 |
21 | Note that if the class already has a method with the same name as the one
22 | you're defining, the old method will be overwritten. Methods in parent classes
23 | and in mixins can be accessed using @callSuper()@.
24 |
25 | If you want to create aliases for methods that already exist, use the @alias()@
26 | method. The new alias goes on the left-hand-side, the existing method name on
27 | the right.
28 |
29 | Animal.alias({
30 | talk: 'speak',
31 | rest: 'sleep'
32 | });
33 |
34 | The other option available to you is to use @include()@. This method has a
35 | couple of roles; if you supply a "@Module@":/modules.html, the module is mixed
36 | into the class and becomes part of its inheritance tree. If you supply any
37 | other type of object, the methods from the object are copied into the class
38 | itself, overwriting any pre-existing methods with similar names.
39 |
40 | // Replace Dog's speak method (#1)
41 | Dog.include({
42 | speak: function(stuff) {
43 | return this.callSuper('lots of ' + stuff) + '!';
44 | }
45 | });
46 |
47 | rex.speak('cats')
48 | // -> "My name is Rex and I like lots of cats!"
49 |
50 | // Mix in a module, altering the class's ancestry
51 | // callSuper() in Dog#speak will now call this method
52 | var Speaker = new Module({
53 | speak: function(stuff) {
54 | return 'I can talk about ' + stuff + '!';
55 | }
56 | });
57 |
58 | Dog.include(Speaker);
59 | rex.speak('cats')
60 | // -> "I can talk about lots of cats!!"
61 |
62 | Notice how including @Speaker@ does not overwrite the @speak@ method in the
63 | @Dog@ class (marked @#1@ above). That method is defined in @Dog@ and will
64 | persist until overwritten directly in the @Dog@ class. @Speaker@ merely
65 | injects another method called @speak@ into the inheritance chain for the @Dog@
66 | class. For more information, refer to the "explanation of Ruby's method lookup
67 | algorithm":/inheritance.html.
68 |
--------------------------------------------------------------------------------
/site/src/pages/stacktrace.haml:
--------------------------------------------------------------------------------
1 | :textile
2 | h2. StackTrace
3 |
4 | @JS.StackTrace@ is a module you can use to inspect what an application is
5 | doing internally while it runs. It provides an interface for monitoring method
6 | calls, which you can use to build monitoring and debugging tools.
7 |
8 | The @StackTrace@ module supports the "@Observable@":/observable.html interface
9 | for monitoring what the stack is doing:
10 |
11 | JS.StackTrace.addObserver(monitor);12 | 13 | @monitor@ should be an object that responds to the @update()@ method. This 14 | method takes two arguments: an event name, and an event object. So, the 15 | object should look something like this: 16 | 17 |
monitor = {
18 | update: function(event, data) {
19 | if (event === 'call') // ...
20 | }
21 | };
22 |
23 | There are three types of event, which tell you when a function is called, when
24 | a function returns, and when an error is thrown.
25 |
26 | h3. @call@ event
27 |
28 | The @call@ event fires when a function is called. The @data@ object in this
29 | case represents the data surrounding the method call. It has the following
30 | properties:
31 |
32 | * @object@ - The object receiving the method call
33 | * @method@ - The @Method@ object for the current method call
34 | * @env@ - The @Class@ or @Module@ where the method is being executed
35 | * @args@ - An @Array@ of the arguments to the method call
36 | * @leaf@ - Boolean indicating whether the call is a leaf; it's a leaf if no
37 | other method calls are logged while it is running. This is always @true@
38 | when a method is first called.
39 |
40 | h3. @return@ event
41 |
42 | The @return@ event fires when a function returns. The @data@ object is the
43 | same object that's passed to the @call@ event, with one extra property:
44 |
45 | * @result@ - The return value of the method call
46 |
47 | h3. @error@ event
48 |
49 | This event fires when an exception is thrown. The @data@ object is just the
50 | error that was raised.
51 |
52 | h3. Enabling tracing
53 |
54 | Since tracing incurs a performance cost, JS.Class does not trace anything
55 | by default. When you want to trace a module or class, you pass a list of the
56 | modules you want to trace to @Method.trace()@, and use @Method.untrace()@ to
57 | stop tracing them.
58 |
59 | JS.Method.trace([JS.Hash, JS.Range]);60 | 61 | h3. Call stack logging 62 | 63 | There is a logger you can use to print the call stack to the "@Console@":/console.html. 64 | To use it, just pass a list of modules to trace and a function to @JS.Method.tracing()@. 65 | This enables tracing for the given modules, runs the function, then disables 66 | tracing again. 67 | 68 |
JS.Method.tracing([JS.Hash], function() {
69 | var hash = new JS.OrderedHash(['foo', 4, 'bar', 5]);
70 | hash.hasKey('foo');
71 | });
72 |
73 | !/images/tracing.png!
74 |
--------------------------------------------------------------------------------
/source/test/async_steps.js:
--------------------------------------------------------------------------------
1 | Test.extend({
2 | AsyncSteps: new JS.Class(JS.Module, {
3 | define: function(name, method) {
4 | this.callSuper(name, function() {
5 | var args = [name, method].concat(JS.array(arguments));
6 | this.__enqueue__(args);
7 | });
8 | },
9 |
10 | included: function(klass) {
11 | klass.include(Test.AsyncSteps.Sync);
12 | if (!klass.includes(Test.Context)) return;
13 |
14 | klass.extend({
15 | it: function(name, opts, block) {
16 | if (typeof opts === 'function') {
17 | block = opts;
18 | opts = {};
19 | }
20 | this.callSuper(name, opts, function(resume) {
21 | this.exec(block, function(error) {
22 | Test.Unit.TestCase.processError(this, error);
23 | this.sync(resume);
24 | });
25 | });
26 | }
27 | });
28 | },
29 |
30 | extend: {
31 | Sync: new JS.Module({
32 | __enqueue__: function(args) {
33 | this.__stepQueue__ = this.__stepQueue__ || [];
34 | this.__stepQueue__.push(args);
35 | if (this.__runningSteps__) return;
36 | this.__runningSteps__ = true;
37 |
38 | var setTimeout = Test.FakeClock.REAL.setTimeout;
39 | setTimeout(this.method('__runNextStep__'), 1);
40 | },
41 |
42 | __runNextStep__: function(error) {
43 | if (error !== undefined) return this.addError(error);
44 |
45 | var step = this.__stepQueue__.shift(), n;
46 |
47 | if (!step) {
48 | this.__runningSteps__ = false;
49 | if (!this.__stepCallbacks__) return;
50 |
51 | n = this.__stepCallbacks__.length;
52 | while (n--) this.__stepCallbacks__.shift().call(this);
53 |
54 | return;
55 | }
56 |
57 | var methodName = step.shift(),
58 | method = step.shift(),
59 | parameters = step.slice(),
60 | block = function() { method.apply(this, parameters) };
61 |
62 | parameters[method.length - 1] = this.method('__runNextStep__');
63 | if (!this.exec) return block.call(this);
64 | this.exec(block, function() {}, this.method('__endSteps__'));
65 | },
66 |
67 | __endSteps__: function(error) {
68 | Test.Unit.TestCase.processError(this, error);
69 | this.__stepQueue__ = [];
70 | this.__runNextStep__();
71 | },
72 |
73 | addError: function() {
74 | this.callSuper();
75 | this.__endSteps__();
76 | },
77 |
78 | sync: function(callback) {
79 | if (!this.__runningSteps__) return callback.call(this);
80 | this.__stepCallbacks__ = this.__stepCallbacks__ || [];
81 | this.__stepCallbacks__.push(callback);
82 | }
83 | })
84 | }
85 | }),
86 |
87 | asyncSteps: function(methods) {
88 | return new this.AsyncSteps(methods);
89 | }
90 | });
91 |
92 |
--------------------------------------------------------------------------------
/site/src/pages/kernel.haml:
--------------------------------------------------------------------------------
1 | :textile
2 | h2. The Kernel module
3 |
4 | Ruby's @Kernel@ module defines the methods common to all objects. Similarly,
5 | the @JS.Kernel@ module defines methods shared by all objects (including class
6 | and module objects). Every object created using @JS.Class@ has these methods,
7 | though they may be overridden depending on the object's type.
8 |
9 | h3. @object.__eigen__()@
10 |
11 | Returns the @object@'s "metamodule", a module used to store any "singleton methods":/singletonmethods.html
12 | attached to the object. Calling @__eigen__()@ on a class or module returns a
13 | module used to store its class methods.
14 |
15 | h3. @object.enumFor(methodName, *args)@
16 |
17 | Returns an @Enumerator@ (see "@Enumerable@":/enumerable.html) for the object
18 | using the given @methodName@ and optional arguments. For example, the
19 | @Enumerator@ generated by @Enumerable#forEachCons@ is generated as follows:
20 |
21 | forEachCons: function(n, block, context) {
22 | if (!block) return this.enumFor('forEachCons', n);
23 | // forEachCons implementation details ...
24 | }
25 |
26 | h3. @object.equals(other)@
27 |
28 | Returns @true@ iff @object@ and @other@ are the same object. This can be
29 | overridden to provide more meaningful equality tests. If you want to use an
30 | object as a key in a "@Hash@":/hash.html you must also override @Kernel#hash()@
31 | (see below).
32 |
33 | h3. @object.extend(module)@
34 |
35 | Adds the methods from @module@ as "singleton methods":/singletonmethods.html
36 | to @object@.
37 |
38 | h3. @object.hash()@
39 |
40 | Returns a hashcode for the object, which is used by "@Hash@":/hash.html when
41 | storing keys.The default implementation returns a unique hexadecimal number
42 | for each object. If two objects are equal according to the @equals()@ method,
43 | they must both return the same hashcode otherwise they will not work correctly
44 | as keys in a @Hash@.
45 |
46 | h3. @object.isA(type)@
47 |
48 | Returns @true@ iff @object@ is an instance of the given @type@, which should
49 | be a class or module. If @type@ is anywhere in @object@'s inheritance
50 | hierarchy this method will return @true@.
51 |
52 | h3. @object.method(name)@
53 |
54 | Returns a copy of the method with the given name from @object@, as a
55 | standalone function bound to @object@'s instance variables. See "method binding":/binding.html.
56 |
57 | h3. @object.tap(block, context)@
58 |
59 | Calls the function @block@ in the given (optional) @context@, passing @object@
60 | as a parameter to @block@, and returns @object@. Useful for inspecting
61 | intermediate values in a long method chain. For example:
62 |
63 | list .tap(function(x) { console.log("original: ", x) })
64 | .toArray() .tap(function(x) { console.log("array: ", x) })
65 | .select(condition) .tap(function(x) { console.log("evens: ", x) })
66 | .map(square) .tap(function(x) { console.log("squares: ", x) });
67 |
68 |
--------------------------------------------------------------------------------
/test/examples/async.js:
--------------------------------------------------------------------------------
1 | JSCLASS_PATH = "build/min/"
2 | var JS = require("../../" + JSCLASS_PATH + "loader")
3 |
4 | JS.require("JS.Test", "JS.MethodChain", function(Test, MC) {
5 |
6 | Test.describe("Asynchronous testing", function() { with(this) {
7 | describe("with a simple test", function() { with(this) {
8 | it("allows async assertions", function(resume) { with(this) {
9 | setTimeout(function() {
10 | resume(function() { assert(false) })
11 | }, 1000)
12 | }})
13 | }})
14 |
15 | describe("with nested resume blocks", function() { with(this) {
16 | define("wait", function(resume, block) { with(this) {
17 | setTimeout(function() { resume(block) }, 1000)
18 | }})
19 |
20 | it("keeps running until you use a resume block with no continuation", function(resume) { with(this) {
21 | var startTime = new Date().getTime();
22 |
23 | wait(resume, function(resume) {
24 | assert(true)
25 | wait(resume, function(resume) {
26 | assert(true)
27 | wait(resume, function() {
28 | var endTime = new Date().getTime();
29 | assertInDelta( 4, endTime - startTime, 0.1 )
30 | })
31 | })
32 | })
33 | }})
34 | }})
35 |
36 | describe("with an async before block", function() { with(this) {
37 | before(function(resume) { with(this) {
38 | var self = this
39 | setTimeout(function() {
40 | self.value = 2
41 | resume()
42 | }, 1000);
43 | }})
44 |
45 | it("waits for the before block to resume", function() { with(this) {
46 | assertEqual( 2, value )
47 | }})
48 |
49 | describe("with another nested block", function() { with(this) {
50 | before(function(resume) { with(this) {
51 | var self = this
52 | setTimeout(function() {
53 | self.value *= 4
54 | resume()
55 | }, 500)
56 | }})
57 |
58 | it("runs both before blocks sequentially", function() { with(this) {
59 | assertEqual( 80, this.value )
60 | }})
61 | }})
62 | }})
63 |
64 | describe("with an async before all block", function() { with(this) {
65 | before("all", function(resume) { with(this) {
66 | var self = this
67 | setTimeout(function() {
68 | self.value = 20
69 | resume()
70 | }, 1000);
71 | }})
72 |
73 | it("waits for the before all block to resume", function() { with(this) {
74 | assertEqual( 2, value )
75 | }})
76 |
77 | describe("with another nested all block", function() { with(this) {
78 | before("all", function(resume) { with(this) {
79 | var self = this
80 | setTimeout(function() {
81 | self.value *= 4
82 | resume()
83 | }, 500)
84 | }})
85 |
86 | it("runs both before all blocks sequentially", function() { with(this) {
87 | assertEqual( 8, value )
88 | }})
89 | }})
90 | }})
91 | }})
92 |
93 | Test.autorun()
94 | })
95 |
96 |
--------------------------------------------------------------------------------
/test/specs/decorator_spec.js:
--------------------------------------------------------------------------------
1 | JS.require('JS.Decorator', function(Decorator) {
2 |
3 | JS.ENV.DecoratorSpec = JS.Test.describe(Decorator, function() { with(this) {
4 | var Bicycle = new JS.Class({
5 | initialize: function(model, gears) {
6 | this.model = model;
7 | this.gears = gears;
8 | },
9 | getModel: function() {
10 | return this.model;
11 | },
12 | getPrice: function() {
13 | return 10 * this.gears;
14 | }
15 | });
16 |
17 | var HeadlightDecorator = new Decorator(Bicycle, {
18 | getPrice: function() {
19 | return 5 + this.component.getPrice();
20 | }
21 | });
22 |
23 | var PedalsDecorator = new Decorator(Bicycle, {
24 | getPrice: function() {
25 | return 24 + this.component.getPrice();
26 | },
27 | rotatePedals: function() {
28 | return 'Turning the pedals';
29 | }
30 | });
31 |
32 | define("Bicycle", Bicycle)
33 | define("HeadlightDecorator", HeadlightDecorator)
34 | define("PedalsDecorator", PedalsDecorator)
35 |
36 | before(function() { with(this) {
37 | this.bicycle = new Bicycle("Trek", 24)
38 | this.withHeadlights = new HeadlightDecorator(bicycle)
39 | this.withPedals = new PedalsDecorator(bicycle)
40 | this.withBoth = new HeadlightDecorator(withPedals)
41 | }})
42 |
43 | it("creates classes", function() { with(this) {
44 | assertKindOf( JS.Class, HeadlightDecorator )
45 | }})
46 |
47 | it("generates objects of the decorated type", function() { with(this) {
48 | assertKindOf( Bicycle, withHeadlights )
49 | assertKindOf( Bicycle, withBoth )
50 | }})
51 |
52 | it("generates the same API of the decorated class", function() { with(this) {
53 | assertRespondTo( withHeadlights, "getModel" )
54 | assertRespondTo( withHeadlights, "getPrice" )
55 | }})
56 |
57 | it("adds methods specified in the decorating class", function() { with(this) {
58 | assertRespondTo( withPedals, "rotatePedals" )
59 | assertEqual( "Turning the pedals", withPedals.rotatePedals() )
60 | }})
61 |
62 | it("passes undefined method calls down to the component", function() { with(this) {
63 | assertEqual( "Trek", withHeadlights.getModel() )
64 | assertEqual( "Trek", withPedals.getModel() )
65 | }})
66 |
67 | it("allows decorators to call down to the decoree using this.component", function() { with(this) {
68 | assertEqual( 240, bicycle.getPrice() )
69 | assertEqual( 245, withHeadlights.getPrice() )
70 | assertEqual( 264, withPedals.getPrice() )
71 | }})
72 |
73 | it("allows decorators to be composed", function() { with(this) {
74 | assertEqual( 269, withBoth.getPrice() )
75 | }})
76 |
77 | it("allows decorators to wrap any object", function() { with(this) {
78 | var subject = {
79 | getPrice: function() { return 50 },
80 | getSizes: function() { return ['S', 'M', 'L', 'XL'] }
81 | }
82 | var decorated = new PedalsDecorator(subject)
83 | assertEqual( 74, decorated.getPrice() )
84 | assertEqual( decorated.getSizes(), subject.getSizes() )
85 | }})
86 | }})
87 |
88 | })
89 |
90 |
--------------------------------------------------------------------------------
/test/specs/tsort_spec.js:
--------------------------------------------------------------------------------
1 | JS.require('JS.TSort', 'JS.Hash', function(TSort, Hash) {
2 |
3 | JS.ENV.TSortSpec = JS.Test.describe(TSort, function() { with(this) {
4 | before(function() { with(this) {
5 | this.Hash = new JS.Class(Hash, {
6 | include: TSort,
7 |
8 | tsortEachNode: function(block, context) {
9 | return this.forEachKey(block, context)
10 | },
11 |
12 | tsortEachChild: function(node, block, context) {
13 | var list = this.fetch(node);
14 | for (var i = 0, n = list.length; i < n; i++)
15 | block.call(context, list[i]);
16 | }
17 | })
18 | }})
19 |
20 | describe("with primitive data", function() { with(this) {
21 | describe("with no cycles", function() { with(this) {
22 | before(function() { with(this) {
23 | this.hash = new Hash([ 1, [2,3],
24 | 2, [3],
25 | 3, [],
26 | 4, [] ])
27 | }})
28 |
29 | it("sorts the elements topologically", function() { with(this) {
30 | assertEqual( [3,2,1,4], hash.tsort() )
31 | }})
32 |
33 | it("identifies strongly connected nodes", function() { with(this) {
34 | assertEqual( [[3],[2],[1],[4]], hash.stronglyConnectedComponents() )
35 | }})
36 | }})
37 |
38 | describe("when there are cycles", function() { with(this) {
39 | before(function() { with(this) {
40 | this.hash = new Hash([ 1, [2,3,4],
41 | 2, [3],
42 | 3, [2],
43 | 4, [1] ])
44 | }})
45 |
46 | it("raises an error", function() { with(this) {
47 | assertThrows(TSort.Cyclic, function() { hash.tsort() })
48 | }})
49 |
50 | it("identifies strongly connected nodes", function() { with(this) {
51 | assertEqual( [[2,3],[1,4]], hash.stronglyConnectedComponents() )
52 | }})
53 | }})
54 | }})
55 |
56 | describe("with object data", function() { with(this) {
57 | include(JS.Test.Helpers)
58 |
59 | before(function() { with(this) {
60 | this.TodoItem = new JS.Class("TodoItem", {
61 | initialize: function(priority) {
62 | this.priority = priority
63 | },
64 | equals: function(other) {
65 | return this.priority == other.priority
66 | },
67 | hash: function() {
68 | return this.priority
69 | }
70 | })
71 | }})
72 |
73 | describe("with no cycles", function() { with(this) {
74 | before(function() { with(this) {
75 | this.hash = new Hash([ new TodoItem(1), [new TodoItem(2),new TodoItem(3)],
76 | new TodoItem(2), [new TodoItem(3)],
77 | new TodoItem(3), [],
78 | new TodoItem(4), [] ])
79 | }})
80 |
81 | it("sorts the elements topologically", function() { with(this) {
82 | assertEqual( [3,2,1,4], map(hash.tsort(), "priority") )
83 | }})
84 |
85 | it("identifies strongly connected nodes", function() { with(this) {
86 | assertEqual( [[new TodoItem(3)],[new TodoItem(2)],[new TodoItem(1)],[new TodoItem(4)]],
87 | hash.stronglyConnectedComponents() )
88 | }})
89 | }})
90 | }})
91 | }})
92 |
93 | })
94 |
95 |
--------------------------------------------------------------------------------
/source/package/loaders/browser.js:
--------------------------------------------------------------------------------
1 | Package.BrowserLoader = {
2 | HOST_REGEX: /^(https?\:)?\/\/[^\/]+/i,
3 |
4 | usable: function() {
5 | return !!Package._getObject('window.document.getElementsByTagName') &&
6 | typeof phantom === 'undefined';
7 | },
8 |
9 | __FILE__: function() {
10 | var scripts = document.getElementsByTagName('script'),
11 | src = scripts[scripts.length - 1].src,
12 | url = window.location.href;
13 |
14 | if (/^\w+\:\/+/.test(src)) return src;
15 | if (/^\//.test(src)) return window.location.origin + src;
16 | return url.replace(/[^\/]*$/g, '') + src;
17 | },
18 |
19 | cacheBust: function(path) {
20 | if (exports.cache !== false) return path;
21 | var token = new JS.Date().getTime();
22 | return path + (/\?/.test(path) ? '&' : '?') + token;
23 | },
24 |
25 | fetch: function(path) {
26 | var originalPath = path;
27 | path = this.cacheBust(path);
28 |
29 | this.HOST = this.HOST || this.HOST_REGEX.exec(window.location.href);
30 | var host = this.HOST_REGEX.exec(path);
31 |
32 | if (!this.HOST || (host && host[0] !== this.HOST[0])) return null;
33 | Package.log('[FETCH] ' + path);
34 |
35 | var source = new Package.Deferred(),
36 | self = this,
37 | xhr = window.ActiveXObject
38 | ? new ActiveXObject('Microsoft.XMLHTTP')
39 | : new XMLHttpRequest();
40 |
41 | xhr.open('GET', path, true);
42 | xhr.onreadystatechange = function() {
43 | if (xhr.readyState !== 4) return;
44 | xhr.onreadystatechange = self._K;
45 | source.succeed(xhr.responseText + '\n//@ sourceURL=' + originalPath);
46 | xhr = null;
47 | };
48 | xhr.send(null);
49 | return source;
50 | },
51 |
52 | loadFile: function(path, fireCallbacks, source) {
53 | if (!source) path = this.cacheBust(path);
54 |
55 | var self = this,
56 | head = document.getElementsByTagName('head')[0],
57 | script = document.createElement('script');
58 |
59 | script.type = 'text/javascript';
60 |
61 | if (source)
62 | return source.callback(function(code) {
63 | Package.log('[EXEC] ' + path);
64 | var execute = new Function('code', 'eval(code)');
65 | execute(code);
66 | fireCallbacks();
67 | });
68 |
69 | Package.log('[LOAD] ' + path);
70 | script.src = path;
71 |
72 | script.onload = script.onreadystatechange = function() {
73 | var state = script.readyState, status = script.status;
74 | if ( !state || state === 'loaded' || state === 'complete' ||
75 | (state === 4 && status === 200) ) {
76 | fireCallbacks();
77 | script.onload = script.onreadystatechange = self._K;
78 | head = null;
79 | script = null;
80 | }
81 | };
82 | head.appendChild(script);
83 | },
84 |
85 | loadStyle: function(path) {
86 | var link = document.createElement('link');
87 | link.rel = 'stylesheet';
88 | link.type = 'text/css';
89 | link.href = this.cacheBust(path);
90 |
91 | document.getElementsByTagName('head')[0].appendChild(link);
92 | },
93 |
94 | _K: function() {}
95 | };
96 |
97 |
--------------------------------------------------------------------------------
/site/src/pages/introduction.haml:
--------------------------------------------------------------------------------
1 | :textile
2 | h2. Getting started with @jsclass@
3 |
4 | You can use @jsclass@ on any of the "supported platforms":/platforms.html
5 | without any custom configuration. To start using @jsclass@ in your project,
6 | you'll need to download it using the link on the left.
7 |
8 | If you're using @jsclass@ on Node and do not need to support other
9 | environments, you can "install it with @npm@":/platforms/node.html and avoid a
10 | lot of the boilerplate shown below.
11 |
12 | The download contains two directories, @src@ and @min@. Both contain the same
13 | files; @src@ contains the @jsclass@ source code, and @min@ contains a minified
14 | version suitable for production use on the web. Pick which version you want to
15 | use, and copy it into your project. You can then load @jsclass@ into your
16 | pages as follows.
17 |
18 | 19 |20 | 21 | On server-side platforms, you need to set the global variable @JSCLASS_PATH@ 22 | like this: 23 | 24 |
(function() {
25 | var $ = (typeof global === 'object') ? global : this;
26 | $.JSCLASS_PATH = 'path/to/jsclass/src';
27 | })();
28 |
29 | Then use the platform's module loading API to load the @jsclass@ package
30 | loader:
31 |
32 | // On CommonJS:
33 | var JS = require('./' + JSCLASS_PATH + '/loader');
34 |
35 | // On other platforms, this creates JS as a global:
36 | load(JSCLASS_PATH + '/loader.js');
37 |
38 | @JSCLASS_PATH@ tells the @loader@ script where you're storing the @jsclass@
39 | library, and will be interpreted relative to the current working directory.
40 | @loader.js@ is a script that knows how to load packages on any platform, while
41 | @loader-browser.js@ only contains code for loading packages in the browser,
42 | and is thus a little smaller.
43 |
44 | This may look like quite a lot of boilerplate, but once you've done this you
45 | can use a single mechanism to load @jsclass@ components (and any other
46 | components you make) on any platform your code needs to run on. That mechanism
47 | is the @JS.require()@ function.
48 |
49 | All components that are part of the @jsclass@ library can be loaded using the
50 | @JS.require()@ function, passing in the name of the object(s) you want to use
51 | and a callback to run once they're ready.
52 |
53 | JS.require('JS.Hash', 'JS.Observable', function(Hash, Observable) {
54 | // ...
55 | });
56 |
57 | The @JS.require()@ function is aware of dependencies and will load everything
58 | you need to use the objects you want. One some platforms, package loading is
59 | asynchronous, and you should be aware of that when structuring your code. If
60 | an object you want to use is already loaded, @JS.require()@ will not reload
61 | it.
62 |
63 | You're now ready to start using @jsclass@. Dive into the reference
64 | documentation linked on the left of this page, and find out how you can "use
65 | the @jsclass@ package system":/packages.html to manage dependencies in your
66 | own projects.
67 |
68 |
--------------------------------------------------------------------------------
/site/src/pages/console.haml:
--------------------------------------------------------------------------------
1 | :textile
2 | h2. Console
3 |
4 | Most JavaScript environments have some sort of console that allows running
5 | programs to print output, log messages and the like. In many web browsers,
6 | this capability is provided by the @window.console@ object, and in server-side
7 | environments the program may write output to the terminal. @JS.Console@
8 | provides a single interface to all these facilities, using the most
9 | appropriate device wherever it is being used.
10 |
11 | When writing output to a terminal, @JS.Console@ also supports many ANSI
12 | formatting commands for changing the color and other attributes of printed
13 | text.
14 |
15 | The main two output methods are as follows:
16 |
17 | * @JS.Console.puts(string)@ - prints @string@ to the console with a line break
18 | afterward, so that subsequent messages appear on the next line.
19 | * @JS.Console.print(string)@ - prints @string@ without applying line breaks,
20 | allowing multiple @print()@ calls to write output to the same line if the
21 | console supports this. In environments that don't support this, the output
22 | is buffered until the next @puts()@ call.
23 |
24 | The output devices used are as follows:
25 |
26 | * In web browsers that support @window.console@, the development console is
27 | used to log output.
28 | * In web browsers with no @console@ object, the @alert()@ function is used.
29 | * In Adobe AIR applications running under @adl@, the @runtime.trace()@
30 | function is used to write to the terminal.
31 | * In programs running in a terminal, output is sent to @STDOUT@.
32 |
33 | In addition, the following formatting commands are available for formatting
34 | output in environments that support ANSI escape codes for formatting text.
35 | All commands are methods of @JS.Console@, for example you can switch to bold
36 | text by calling @JS.Console.bold()@. Text printed after the command has been
37 | issued will have the formatting applied to it. In environments that do not
38 | support formatting these methods have no effect.
39 |
40 | * @reset()@ resets all formatting attributes
41 | * @bold()@, @normal()@ set the weight of the text
42 | * @underline()@, @noline()@ apply and remove underlining
43 | * @blink()@, @noblink()@ apply and remove blinking text
44 | * @black()@, @red()@, @green()@, @yellow()@, @blue()@, @magenta()@, @cyan()@,
45 | @white()@, @nocolor()@ set the current text color
46 | * Color methods can be prefixed with @bg@, for example @bgyellow()@, to set
47 | the current background color
48 |
49 | Multiple formatting instructions can be combined using this method:
50 |
51 | JS.Console.consoleFormat('bold', 'red');
52 |
53 | The @consoleFormat()@ method takes a list of formatting instructions as input.
54 | It calls @reset()@ and then applies the given formats.
55 |
56 | Finally note that @JS.Console@ can be used as a mixin so all the above methods
57 | appear in the current class:
58 |
59 | Logger = new JS.Class({
60 | include: JS.Console,
61 |
62 | info: function(message) {
63 | this.bgblack();
64 | this.white();
65 | this.print('INFO');
66 | this.reset();
67 | this.puts(' ' + message);
68 | }
69 | });
70 |
--------------------------------------------------------------------------------
/source/state.js:
--------------------------------------------------------------------------------
1 | (function(factory) {
2 | var E = (typeof exports === 'object'),
3 | js = (typeof JS === 'undefined') ? require('./core') : JS;
4 |
5 | if (E) exports.JS = exports;
6 | factory(js, E ? exports : js);
7 |
8 | })(function(JS, exports) {
9 | 'use strict';
10 |
11 | var State = new JS.Module('State', {
12 | __getState__: function(state) {
13 | if (typeof state === 'object') return state;
14 | if (typeof state === 'string') return (this.states || {})[state];
15 | return {};
16 | },
17 |
18 | setState: function(state) {
19 | this.__state__ = this.__getState__(state);
20 | State.addMethods(this.__state__, this.klass);
21 | },
22 |
23 | inState: function() {
24 | var i = arguments.length;
25 | while (i--) {
26 | if (this.__state__ === this.__getState__(arguments[i])) return true;
27 | }
28 | return false;
29 | },
30 |
31 | extend: {
32 | ClassMethods: new JS.Module({
33 | states: function(block) {
34 | this.define('states', State.buildCollection(this, block));
35 | }
36 | }),
37 |
38 | included: function(klass) {
39 | klass.extend(this.ClassMethods);
40 | },
41 |
42 | stub: function() { return this; },
43 |
44 | buildStubs: function(stubs, collection, states) {
45 | var state, method;
46 | for (state in states) {
47 | collection[state] = {};
48 | for (method in states[state]) stubs[method] = this.stub;
49 | }
50 | },
51 |
52 | findStates: function(collections, name) {
53 | var i = collections.length, results = [];
54 | while (i--) {
55 | if (collections[i].hasOwnProperty(name))
56 | results.push(collections[i][name]);
57 | }
58 | return results;
59 | },
60 |
61 | buildCollection: function(module, states) {
62 | var stubs = {},
63 | collection = {},
64 | superstates = module.lookup('states'),
65 | state, klass, methods, name, mixins, i, n;
66 |
67 | this.buildStubs(stubs, collection, states);
68 |
69 | for (i = 0, n = superstates.length; i < n; i++)
70 | this.buildStubs(stubs, collection, superstates[i]);
71 |
72 | for (state in collection) {
73 | klass = new JS.Class(states[state]);
74 | mixins = this.findStates(superstates, state);
75 |
76 | i = mixins.length;
77 | while (i--) {
78 | if (mixins[i]) klass.include(mixins[i].klass);
79 | }
80 |
81 | methods = {};
82 | for (name in stubs) {
83 | if (!klass.prototype[name]) methods[name] = stubs[name];
84 | }
85 | klass.include(methods);
86 | collection[state] = new klass;
87 | }
88 | if (module.__tgt__) this.addMethods(stubs, module.__tgt__.klass);
89 | return collection;
90 | },
91 |
92 | addMethods: function(state, klass) {
93 | if (!klass) return;
94 |
95 | var methods = {},
96 | proto = klass.prototype,
97 | method;
98 |
99 | for (method in state) {
100 | if (proto[method]) continue;
101 | klass.define(method, this.wrapped(method));
102 | }
103 | },
104 |
105 | wrapped: function(method) {
106 | return function() {
107 | var func = (this.__state__ || {})[method];
108 | return func ? func.apply(this, arguments) : this;
109 | };
110 | }
111 | }
112 | });
113 |
114 | exports.State = State;
115 | });
116 |
117 |
--------------------------------------------------------------------------------
/source/test/coverage.js:
--------------------------------------------------------------------------------
1 | Test.extend({
2 | Coverage: new JS.Class({
3 | initialize: function(module) {
4 | this._module = module;
5 | this._methods = new Hash([]);
6 |
7 | var storeMethods = function(module) {
8 | var methods = module.instanceMethods(false),
9 | i = methods.length;
10 | while (i--) this._methods.store(module.instanceMethod(methods[i]), 0);
11 | };
12 | storeMethods.call(this, module);
13 | storeMethods.call(this, module.__eigen__());
14 | },
15 |
16 | attach: function() {
17 | var module = this._module;
18 | StackTrace.addObserver(this);
19 | JS.Method.trace([module, module.__eigen__()]);
20 | },
21 |
22 | detach: function() {
23 | var module = this._module;
24 | JS.Method.untrace([module, module.__eigen__()]);
25 | StackTrace.removeObserver(this);
26 | },
27 |
28 | update: function(event, frame) {
29 | if (event !== 'call') return;
30 | var pair = this._methods.assoc(frame.method);
31 | if (pair) pair.setValue(pair.value + 1);
32 | },
33 |
34 | report: function() {
35 | var methods = this._methods.entries().sort(function(a,b) {
36 | return b.value - a.value;
37 | });
38 | var covered = this._methods.all(function(pair) { return pair.value > 0 });
39 |
40 | this.printTable(methods, function(row, i) {
41 | if (row[1] === 0) return ['bgred', 'white'];
42 | return (i % 2 === 0) ? ['bold'] : [];
43 | });
44 | return covered;
45 | },
46 |
47 | printTable: function(table, formatter) {
48 | var widths = [],
49 | table = [['Method', 'Calls']].concat(table),
50 | C = Console,
51 | i = table.length,
52 | j, string;
53 |
54 | while (i--) {
55 | j = table[i].length;
56 | while (j--) {
57 | widths[j] = widths[j] || 0;
58 | string = (table[i][j] === undefined ? '' : table[i][j]).toString();
59 | widths[j] = Math.max(string.length, widths[j]);
60 | }
61 | }
62 |
63 | var divider = '+', j = widths.length;
64 | while (j--) divider = '+' + this.repeat('-', widths[j] + 2) + divider;
65 | divider = ' ' + divider;
66 | C.reset();
67 | C.puts();
68 | C.puts(divider);
69 |
70 | var printRow = function(row, format) {
71 | var data = table[row];
72 | C.reset();
73 | C.print(' ');
74 | for (var i = 0, n = data.length; i < n; i++) {
75 | C.reset();
76 | C.print('|');
77 | C.consoleFormat.apply(C, format);
78 | C.print(' ' + this.pad(data[i], widths[i]) + ' ');
79 | }
80 | C.reset();
81 | C.puts('|');
82 | };
83 | printRow.call(this, 0, ['bold']);
84 | C.reset();
85 | C.puts(divider);
86 |
87 | for (var i = 1, n = table.length; i < n; i++) {
88 | var format = formatter ? formatter(table[i], i) : [];
89 | printRow.call(this, i, format);
90 | }
91 | C.reset();
92 | C.puts(divider);
93 | },
94 |
95 | pad: function(string, width) {
96 | string = (string === undefined ? '' : string).toString();
97 | return string + this.repeat(' ', width - string.length);
98 | },
99 |
100 | repeat: function(string, n) {
101 | var result = '';
102 | while (n--) result += string;
103 | return result;
104 | }
105 | })
106 | });
107 |
--------------------------------------------------------------------------------
/test/specs/comparable_spec.js:
--------------------------------------------------------------------------------
1 | JS.require('JS.Comparable', function(Comparable) {
2 |
3 | JS.ENV.ComparableSpec = JS.Test.describe(Comparable, function() { with(this) {
4 | include(JS.Test.Helpers)
5 |
6 | define("TodoItem", new JS.Class({
7 | include: Comparable,
8 | initialize: function(position, task) {
9 | this.position = position;
10 | this.task = task || "";
11 | },
12 | compareTo: function(other) {
13 | if (this.position < other.position)
14 | return -1;
15 | else if (this.position > other.position)
16 | return 1;
17 | else
18 | return 0;
19 | }
20 | }))
21 |
22 | describe("sorting", function() { with(this) {
23 | before(function() { with(this) {
24 | this.todos = map([8,2,7,5,3,7,6], function(id) { return new TodoItem(id) })
25 | }})
26 |
27 | it("uses the #compareTo method to sort", function() { with(this) {
28 | todos.sort(TodoItem.compare)
29 | assertEqual( [2,3,5,6,7,7,8], map(todos, 'position') )
30 | }})
31 | }})
32 |
33 | describe("#lt", function() { with(this) {
34 | it("returns true if A < B", function() { with(this) {
35 | assert( new TodoItem(1).lt(new TodoItem(2)) )
36 | }})
37 |
38 | it("returns false if A = B", function() { with(this) {
39 | assert( !new TodoItem(2).lt(new TodoItem(2)) )
40 | }})
41 |
42 | it("returns false if A > B", function() { with(this) {
43 | assert( !new TodoItem(3).lt(new TodoItem(2)) )
44 | }})
45 | }})
46 |
47 | describe("#lte", function() { with(this) {
48 | it("returns true if A < B", function() { with(this) {
49 | assert( new TodoItem(1).lte(new TodoItem(2)) )
50 | }})
51 |
52 | it("returns true if A = B", function() { with(this) {
53 | assert( new TodoItem(2).lte(new TodoItem(2)) )
54 | }})
55 |
56 | it("returns false if A > B", function() { with(this) {
57 | assert( !new TodoItem(3).lte(new TodoItem(2)) )
58 | }})
59 | }})
60 |
61 | describe("#eq", function() { with(this) {
62 | it("returns false if A < B", function() { with(this) {
63 | assert( !new TodoItem(1).eq(new TodoItem(2)) )
64 | }})
65 |
66 | it("returns true if A = B", function() { with(this) {
67 | assert( new TodoItem(2).eq(new TodoItem(2)) )
68 | }})
69 |
70 | it("returns false if A > B", function() { with(this) {
71 | assert( !new TodoItem(3).eq(new TodoItem(2)) )
72 | }})
73 | }})
74 |
75 | describe("#gt", function() { with(this) {
76 | it("returns false if A < B", function() { with(this) {
77 | assert( !new TodoItem(1).gt(new TodoItem(2)) )
78 | }})
79 |
80 | it("returns false if A = B", function() { with(this) {
81 | assert( !new TodoItem(2).gt(new TodoItem(2)) )
82 | }})
83 |
84 | it("returns true if A > B", function() { with(this) {
85 | assert( new TodoItem(3).gt(new TodoItem(2)) )
86 | }})
87 | }})
88 |
89 | describe("#gte", function() { with(this) {
90 | it("returns false if A < B", function() { with(this) {
91 | assert( !new TodoItem(1).gte(new TodoItem(2)) )
92 | }})
93 |
94 | it("returns true if A = B", function() { with(this) {
95 | assert( new TodoItem(2).gte(new TodoItem(2)) )
96 | }})
97 |
98 | it("returns true if A > B", function() { with(this) {
99 | assert( new TodoItem(3).gte(new TodoItem(2)) )
100 | }})
101 | }})
102 | }})
103 |
104 | })
105 |
106 |
--------------------------------------------------------------------------------
/source/command.js:
--------------------------------------------------------------------------------
1 | (function(factory) {
2 | var E = (typeof exports === 'object'),
3 | js = (typeof JS === 'undefined') ? require('./core') : JS,
4 |
5 | Enumerable = js.Enumerable || require('./enumerable').Enumerable,
6 | Observable = js.Observable || require('./observable').Observable;
7 |
8 | if (E) exports.JS = exports;
9 | factory(js, Enumerable, Observable, E ? exports : js);
10 |
11 | })(function(JS, Enumerable, Observable, exports) {
12 | 'use strict';
13 |
14 | var Command = new JS.Class('Command', {
15 | initialize: function(functions) {
16 | if (typeof functions === 'function')
17 | functions = {execute: functions};
18 | this._functions = functions;
19 | this._stack = this._functions.stack || null;
20 | },
21 |
22 | execute: function(push) {
23 | if (this._stack) this._stack._restart();
24 | var exec = this._functions.execute;
25 | if (exec) exec.apply(this);
26 | if (this._stack && push !== false) this._stack.push(this);
27 | },
28 |
29 | undo: function() {
30 | var exec = this._functions.undo;
31 | if (exec) exec.apply(this);
32 | },
33 |
34 | extend: {
35 | Stack: new JS.Class({
36 | include: [Observable || {}, Enumerable || {}],
37 |
38 | initialize: function(options) {
39 | options = options || {};
40 | this._redo = options.redo || null;
41 | this.clear();
42 | },
43 |
44 | forEach: function(block, context) {
45 | if (!block) return this.enumFor('forEach');
46 | block = Enumerable.toFn(block);
47 |
48 | for (var i = 0, n = this._stack.length; i < n; i++) {
49 | if (this._stack[i] !== undefined)
50 | block.call(context, this._stack[i], i);
51 | }
52 | return this;
53 | },
54 |
55 | clear: function() {
56 | this._stack = [];
57 | this.length = this.pointer = 0;
58 | },
59 |
60 | _restart: function() {
61 | if (this.pointer === 0 && this._redo && this._redo.execute)
62 | this._redo.execute();
63 | },
64 |
65 | push: function(command) {
66 | this._stack.splice(this.pointer, this.length);
67 | this._stack.push(command);
68 | this.length = this.pointer = this._stack.length;
69 | if (this.notifyObservers) this.notifyObservers(this);
70 | },
71 |
72 | stepTo: function(position) {
73 | if (position < 0 || position > this.length) return;
74 | var i, n;
75 |
76 | switch (true) {
77 | case position > this.pointer :
78 | for (i = this.pointer, n = position; i < n; i++)
79 | this._stack[i].execute(false);
80 | break;
81 |
82 | case position < this.pointer :
83 | if (this._redo && this._redo.execute) {
84 | this._redo.execute();
85 | for (i = 0, n = position; i < n; i++)
86 | this._stack[i].execute(false);
87 | } else {
88 | for (i = 0, n = this.pointer - position; i < n; i++)
89 | this._stack[this.pointer - i - 1].undo();
90 | }
91 | break;
92 | }
93 | this.pointer = position;
94 | if (this.notifyObservers) this.notifyObservers(this);
95 | },
96 |
97 | undo: function() {
98 | this.stepTo(this.pointer - 1);
99 | },
100 |
101 | redo: function() {
102 | this.stepTo(this.pointer + 1);
103 | }
104 | })
105 | }
106 | });
107 |
108 | exports.Command = Command;
109 | });
110 |
111 |
--------------------------------------------------------------------------------
/source/test/context/life_cycle.js:
--------------------------------------------------------------------------------
1 | Test.Context.LifeCycle = new JS.Module({
2 | extend: {
3 | included: function(base) {
4 | base.extend(this.ClassMethods);
5 |
6 | base.before_all_callbacks = [];
7 | base.before_each_callbacks = [];
8 | base.after_all_callbacks = [];
9 | base.after_each_callbacks = [];
10 | base.before_should_callbacks = {};
11 |
12 | base.extend({
13 | inherited: function(child) {
14 | this.callSuper();
15 | child.before_all_callbacks = [];
16 | child.before_each_callbacks = [];
17 | child.after_all_callbacks = [];
18 | child.after_each_callbacks = [];
19 | child.before_should_callbacks = {};
20 | }
21 | });
22 | },
23 |
24 | ClassMethods: new JS.Module({
25 | before: function(period, block) {
26 | if ((typeof period === 'function') || !block) {
27 | block = period;
28 | period = 'each';
29 | }
30 |
31 | this['before_' + (period + '_') + 'callbacks'].push(block);
32 | },
33 |
34 | after: function(period, block) {
35 | if ((typeof period === 'function') || !block) {
36 | block = period;
37 | period = 'each';
38 | }
39 |
40 | this['after_' + (period + '_') + 'callbacks'].push(block);
41 | },
42 |
43 | gatherCallbacks: function(callbackType, period) {
44 | var outerCallbacks = (typeof this.superclass.gatherCallbacks === 'function')
45 | ? this.superclass.gatherCallbacks(callbackType, period)
46 | : [];
47 |
48 | var mine = this[callbackType + '_' + (period + '_') + 'callbacks'];
49 |
50 | return (callbackType === 'before')
51 | ? outerCallbacks.concat(mine)
52 | : mine.concat(outerCallbacks);
53 | }
54 | })
55 | },
56 |
57 | setup: function(resume) {
58 | if (this.klass.before_should_callbacks[this._methodName])
59 | this.klass.before_should_callbacks[this._methodName].call(this);
60 |
61 | this.runCallbacks('before', 'each', resume);
62 | },
63 |
64 | teardown: function(resume) {
65 | this.runCallbacks('after', 'each', resume);
66 | },
67 |
68 | runCallbacks: function(callbackType, period, continuation) {
69 | var callbacks = this.klass.gatherCallbacks(callbackType, period);
70 |
71 | Test.Unit.TestSuite.forEach(callbacks, function(callback, resume) {
72 | this.exec(callback, resume, continuation);
73 | }, continuation, this);
74 | },
75 |
76 | runAllCallbacks: function(callbackType, continuation, context) {
77 | var previousIvars = this.instanceVariables();
78 | this.runCallbacks(callbackType, 'all', function() {
79 |
80 | var ivars = this.instanceVariables().inject({}, function(hash, ivar) {
81 | if (previousIvars.member(ivar)) return hash;
82 | hash[ivar] = this[ivar];
83 | return hash;
84 | }, this);
85 |
86 | if (continuation) continuation.call(context, ivars);
87 | });
88 | },
89 |
90 | setValuesFromCallbacks: function(values) {
91 | for (var key in values)
92 | this[key] = values[key];
93 | },
94 |
95 | instanceVariables: function() {
96 | var ivars = [];
97 | for (var key in this) {
98 | if (this.hasOwnProperty(key)) ivars.push(key);
99 | }
100 | return new Enumerable.Collection(ivars);
101 | }
102 | });
103 |
104 | (function() {
105 | var m = Test.Context.LifeCycle.ClassMethods.method('instanceMethod');
106 |
107 | Test.Context.LifeCycle.ClassMethods.include({
108 | setup: m('before'),
109 | teardown: m('after')
110 | });
111 | })();
112 |
113 |
--------------------------------------------------------------------------------