catena v2.2.0
catena
Prototype Helper for JavaScript
If you haven't used Grunt before, be sure to check out the Getting Started guide, as it explains how to create a Gruntfile as well as install and use Grunt plugins. Once you're familiar with that process, you may install this plugin with this command:
npm install catena --save-dev
Once the plugin has been installed, it may be enabled inside your Gruntfile with this line of JavaScript:
grunt.loadNpmTasks('catena');
Task
You must have the java command line tool installed for the google-closure-compiler npm module.
Run this task with the grunt catena
command.
Task targets, files and options may be specified according to the Grunt Configuring tasks guide.
Description
catena is a framework that facilites the use of prototypes and forces coding conventions that make the maintenance of big projects easier.
Demo
Basic chess game built with catena. Click here for demo app.
Usage Example
// Project configuration.
grunt.config.init({
catena: {
options: {
externs: ['BLITTER', 'YUI', 'jQuery', 'd3'],
license: 'LICENSE'
},
dev {
src: ['js/'], // Must be directories.
dest: 'dist/app.js',
options: {
watch: true
}
},
deploy {
src: ['js/'],
dest: 'dist/app.js',
options: {
test: true,
deploy: true
}
}
}
});
options.watch
Type: Boolean
Default: false
Set watch to true to write dest file everytime time you change a file in one of the src directories. If deploy is true then watch will not run.
options.test
Type: Boolean
Default: false
Run static code analysis tools before deploying. If errors are found deploy will be canceled.
options.lint
Type: Boolean
Default: true
Analyze code with eslint when test is true.
options.deploy
Type: Boolean
Default: false
Optimize dest file for deployment. Will perform ADVANCED_COMPILATION with the closure compiler after pre optimization phase if minify is set to true.
options.minify
Type: Boolean
Default: true
dest file will be minified when deploy is true. If minify is false then dest file will be beautified instead.
options.minifyLanguageIn
Type: String
Default: ECMASCRIPT_2019
The specification parsed src files should conform to when minifying with closure compiler.
options.minifyLanguageOut
Type: String
Default: ECMASCRIPT_2015
The specification minified dest file should conform to when minifying with closure compiler.
options.license
Type: String
Path to file containing license agreement.
options.externs
Type: String[]
Default: []
List of globally exposed dependencies (libraries, frameworks, etc.) that prevent the closure compiler from throwing an error when minifying, externs are only applied when test or deploy is true.
When working with external modules in catena, you should access properties and invoke methods using string literals. This will prevent the closure compiler from minifying property names for external modules. The closure compiler is set to use ADVANCED_COMPILATION always, by using string literals the compiler will leave property names as they are.
// Wrong
BLITTER.getImageData('test-icon');
require('path').isAbsolute('/');
// Correct
BLITTER['getImageData']('test-icon');
require('path')['isAbsolute']('/');
Project Structure
All JavaScript files should be placed inside the src directories, all of them will be concatenated recursively. All projects must have a CLASS.Main module declared as the entry point of the application.
You should only declare one module per file. catena will parse all JavaScript files in the src directories individually when deploy argument is used. Parsing will fail if more than one module is declared in a file.
srcDir
│ Main.js
│
├─── dir-one
│ │ FileOne.js
│ └─ FileTwo.js
│
└─── dir-two
└─ FileThree.js
CLASS
References all of the modules that can be instantiated. Always remember to declare the append property for all CLASS modules as object literals. Properties declared in the append object are tied to the prototype of the CLASS module.
Here's a list of internal properties exposed by catena (inside the prototype of CLASS modules):
- $isClass
- $applied
- $parentName
- $name
// Parent.js
CLASS.Parent = function () {
// Constructor
};
CLASS.Parent.append = {
// Properties declared in append will be shared by
// all instances of the module in the prototype.
};
// Child.js
extend('Parent', 'Child');
CLASS.Child = function () {
// Call parent's constructor and inherit all of its properties.
this.super();
};
CLASS.Child.append = {
// Properties that share the same name to any of the Parent's
// append properties can be overwritten or overridden.
};
extend
Chain the child's prototype with the parent's prototype. Always declare at the top of the file.
extend (parentName: String, childName: String)
super
Call the parent's constructor.
super (args: Array)
// Example
// Point.js
CLASS.Point = function (x, y) {
this.x = x;
this.y = y;
};
// append omitted for brevity.
// Square.js
extend('Point', 'Square');
CLASS.Square = function (x, y, width, height) {
// Pass arguments into the parent's constructor.
this.super(x, y);
this.width = width;
this.height = height;
};
// append omitted for brevity.
abstract
Flag a method as abstract.
abstract ()
// Example
// Shape.js
CLASS.Shape = function () {};
CLASS.Shape.append = {
// If method is invoked directly, program will error out.
calculateArea: function () {
this.abstract();
}
};
// Square.js
extend('Shape', 'Square');
// Omitted constructor for brevity.
CLASS.Square.append = {
calculateArea: function () {
// Calculate the area of the square with overwritten method.
}
};
// Triangle.js
extend('Shape', 'Triangle');
// Omitted constructor for brevity.
CLASS.Triangle.append = {
calculateArea: function () {
// Calculate the area of the triangle.
}
};
Overriding Methods
Invoke a method from a parent class while keeping the same context (instance reference).
// Parent.js
CLASS.Parent = function () {};
CLASS.Parent.append = {
callMe: function () {}
};
// Child.js
extend('Parent', 'Child');
CLASS.Child = function () {
this.super(); // Call parent's constructor.
};
CLASS.Child.append = {
// The _$_ shorthand references all CLASS module prototypes.
callMe: function () {
_$_.Parent.callMe.call(this);
alert('With _$_ we can expand the functionality of Parent.callMe in Child.callMe');
}
};
CONST
Constants used inside the app should be declared here. Properties declared inside CONST cannot be changed after the CLASS.Main module is initialized. Nested objects and arrays are recursively frozen too.
Here's a list of constants exposed by catena (inside CONST):
- $DEV: Will always be true unless you run catena with the deploy argument. Useful for performing tests at runtime prior to having your code deployed.
CONST.GRAVITY = 9.8;
SINGLE
Singletons reference object literals, like the append property in CLASS modules. If the init or postInit method are declared in a SINGLE module, they will be invoked prior to CLASS.Main being instantiated.
Here's a list of internal properties exposed by catena (inside SINGLE modules):
- $isSingle
- $name
SINGLE.Mouse = {
// Declaring init or postInit as something other than a function will throw an error.
// init and postInit will be unreachable after being invoked at the start of the program.
init: function () {
// Initialize singleton properties.
},
// postInit is invoked after all init methods are invoked.
postInit: function () {
// Interact with other initialized singletons.
}
};
Access Modifiers
Public properties can be read, written and invoked externally.
this.x = 0;
Private properties can only be read, written and invoked internally.
this._x = 0;
Protected properties can only be read, written and invoked by instances of the same CLASS module.
this.__x = 0;
It is strongly advised to not start any references with $ as catena uses this convention internally to solve dependencies at runtime. The only internal properties you should access are the ones declared in CONST. Internal properties exposed in CLASS and SINGLE modules are used to solve dependencies at runtime and help with debugging when CONST.$DEV is true, you should never access them in your application as they will not be declared when deploying.
// Wrong
CLASS.Test = function () {
this.$testValue = 0;
};
CLASS.Test.append = {
$testMethod: function () {}
};
let $testFunction = function () {};
// Correct
if (CONST.$DEV) {
// Do expensive test.
}
Helper API
Functions that catena uses internally and are exposed to be used externally also.
Error Functions
throwError
All arguments are optional. Type will always be shown as uppercase. The index determines how far back in the call stack must be traveled to find the method that will be pretty printed beside the module's name in the message.
throwError (message: String, type: String, module: Object, index: Number)
throwArgumentError
For throwArgumentError the last two params are optional but the first two are required for the error message.
throwArgumentError (name: String, type: String, module: Object, index: Number)
Validation Functions
Type checking functions.
isNaN, isNull, isArray, isEmptyArray, isNonEmptyArray, isObject, isNumber, isString, isEmptyString, isNonEmptyString, isBoolean, isFunction, isUndefined
All of these functions require only one argument.
func (arg: *) : Boolean
// Example
isArray([]);
isInstance
isInstance is unique compared to the other validation functions as it requires two arguments instead of one.
isInstance (type: *, arg: *) : Boolean
// Example
isInstance(CLASS.Shape, new CLASS.Triangle());
Test Functions
testArray, testEmptyArray, testNonEmptyArray, testObject, testNumber, testString, testEmptyString, testNonEmptyString, testBoolean, testFunction
Functions that will check if type is valid and error the program out with throwArgumentError if type is invalid.
testArray (arg: *, argName: String, module: Object, errorIndex: Number)
testInstance, testOptionalInstance
testInstance and testOptionalInstance are unique as they require two more arguments than the other functions.
testInstance (type: *, arg: *, typeName: String, argName: String, module: Object, errorIndex: Number)
4 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
6 years ago
6 years ago
6 years ago
6 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago