iz.js

Summary
iz.js
IZ Object SystemThe IZ Object System, or iz, provides a mechanism for the creation of robust javascript objects.
Class definition methods
iz.Package(classname, inheritance, class_function)Creates a new package prototype and registers it’s namespace.
Class.has(attributename, definition)Defines a new object attribute.
Class.mixin(class_or_object, overlay_attributes)Adds the functionality of the given class or object to Class by copying all the methods and attributes of the provided class onto the current object.
SUPER(obj, methodname)SUPER is a special function used to retrieve a superclass’ version of a given method bound to the provided object.
IZ utility routines
iz.Use(classname, callback)Ensures that classname is loaded and available to be used.
iz.Tetchy(true_or_false)Tells IZ whether it should be particularly tetchy about common misuses / coding mistakes.
iz.Module(classname)Returns the class with the name provided.
iz.add_search_path(path)On server-side environments such as node, adds a path to be searched when loading IZ based modules using iz.Use().
iz.lock_search_path();Prevents further additions to the search path list.
proto.new_object(args)Creates a new instance of the package class.
iz.get_metadata(obj)Inspects an object and returns information about it.
Class.CONSTRUCT(args, [object_to_localize])CONSTRUCT is called immediately after a new object is created.
Class._on_object_createThe _on_object_create method is used to run any object level initialization required immediately upon creation of a new object.
Object instance methods
obj.does(classname)Introspection routine, returns true if obj can do what classname does.
obj.isa(classname)Introspection routine, returns true if obj is a member or subclass of the class provided, false otherwise.
obj.can(methodname)Introspection routine, returns true if obj has a method called ‘methodname’
obj.get_default(attributename)Returns the default value for a given attribute.
obj.get_current_state()The get_current_state method returns a flat ‘generic’ javascript object representing the state of the object.
obj.localize()localize returns an object to use in place of the original.

IZ Object System

The IZ Object System, or iz, provides a mechanism for the creation of robust javascript objects.  Some features are:

  • Multi-level class-like inheritance
  • Mixins
  • object intro-spection
  • simple attribute creation
  • protected attributes that may only be get and set via method calls
  • attribute type checking (including user-defined checking)
  • deep object localization.

IZ is designed to make working with Objects in Javascript exceedingly easy.  It provides a clean way to create objects and object hierarchies in Javascript.  IZ does this in a way that will be familiar to most developers who have worked with object oriented concepts in other languages.  Behind the scenes, the IZ object system does this in a very Javascript native way and in the most efficient way possible, using prototypical inheritence and other Javascript native mechanisms.

The end result is a very usable and approachable way of writing and using objects in javascript, with complete support for features and abilities that, while possible, are complex to implement in standard javascript.

Example

var Parrot = iz.Use('Animal.Bird.Parrot');
var myparrot = new Parrot({ mimic : 'duck' });
myparrot.makesound(); // quack!
myparrot.mimic('cow');
myparrot.makesound(); // moo!

Note that while we use the term ‘Class’ frequently in this documentation, it is important to know that Javascript has no notion of a Class or Class namespace.  IZ borrows the term and nomenclature for familiarity and ease of understanding only.  IZ uses prototypical inheritance and uses ‘Class names’ only as a mechanism for naming and retrieving prototypes.

For more information on Javascript’s prototypical inheritance (and many other goodies), please refer to Douglas Crockford’s excellent book, ‘Javascript: The Good Parts.’

Summary
Class definition methods
iz.Package(classname, inheritance, class_function)Creates a new package prototype and registers it’s namespace.
Class.has(attributename, definition)Defines a new object attribute.
Class.mixin(class_or_object, overlay_attributes)Adds the functionality of the given class or object to Class by copying all the methods and attributes of the provided class onto the current object.
SUPER(obj, methodname)SUPER is a special function used to retrieve a superclass’ version of a given method bound to the provided object.
IZ utility routines
iz.Use(classname, callback)Ensures that classname is loaded and available to be used.
iz.Tetchy(true_or_false)Tells IZ whether it should be particularly tetchy about common misuses / coding mistakes.
iz.Module(classname)Returns the class with the name provided.
iz.add_search_path(path)On server-side environments such as node, adds a path to be searched when loading IZ based modules using iz.Use().
iz.lock_search_path();Prevents further additions to the search path list.
proto.new_object(args)Creates a new instance of the package class.
iz.get_metadata(obj)Inspects an object and returns information about it.
Class.CONSTRUCT(args, [object_to_localize])CONSTRUCT is called immediately after a new object is created.
Class._on_object_createThe _on_object_create method is used to run any object level initialization required immediately upon creation of a new object.
Object instance methods
obj.does(classname)Introspection routine, returns true if obj can do what classname does.
obj.isa(classname)Introspection routine, returns true if obj is a member or subclass of the class provided, false otherwise.
obj.can(methodname)Introspection routine, returns true if obj has a method called ‘methodname’
obj.get_default(attributename)Returns the default value for a given attribute.
obj.get_current_state()The get_current_state method returns a flat ‘generic’ javascript object representing the state of the object.
obj.localize()localize returns an object to use in place of the original.

Class definition methods

iz.Package(classname, inheritance, class_function)

iz.Package = function (packagename,
inheritance,
closure)

Creates a new package prototype and registers it’s namespace.

Inheritance can be defined using two mechanisms.  First, ‘extends’, provides a base class for the class to be created.  This can be an IZ classname, a function or another object.  This will be used as the prototype for objects of this class.  Note that when providing a function to extends, node’s util.inherits() is used to accomplish the inheritance.  The second mechanism for inheritance is the mixin.  While only a single item may be specified for ‘extends’, multiple mixins may be specified using an array.  You can provide an IZ class name or an object as a mixin.  Note that you may use ‘extends’ and ‘mixin’ on the same class in the same iz.Package definition.

The function in the iz.Package() call is used to create a prototype object for the given class.  The Class object passed to the function will have all the inheritance and mixin behavior specified in the inheritance argument already added and will be ready for your own definitions.  This allows you to override any inherited behavior within your class definition.  It is vital that you remember to return the Class at the end of the function.  This is used as the prototype for all objects created using this class.  As such, any state created within the class_function would be shared by ALL objects created using this class.

The second argument to the class_function is ‘SUPER’ which is a method used to access the superclass’ methods for this class.  Additional information about SUPER is available later in this documentation.

Note also that in the case where you are not inheriting anything, you can omit the inheritance argument altogether.

Parameters

classnameNamespace of class to be created
inheritanceObject containing inheritance details
class_functionFunction reference containing the body of the class to be created.

Returns

Class creation function

Example

iz.Package( 'MyAwesomeClass',
              {
                   extends: 'MyOtherClass',
                   mixin: [ 'Classname', my_useful_object ]
              },

              // Class is passed in and Class must be returned.
              function (Class, SUPER) {
                    // add things to Class

                    // it's very important to return Class;
                    return Class;
              });

Class.has(attributename, definition)

Defines a new object attribute.

Parameters

attributenameAttribute name to add
definitionAttribute definition object literal

When you define an attribute, an accessor method is created for that attribute.  This accessor is created using the attributename provided.  To obtain the value of the attribute, simply call the accessor.  To set the value, call the accessor with the new value as the only argument.  For example, to set and read an attribute called ‘age’:

// sets age to 27
this.age(27);

// returns 27.
this.age();

Attribute Definition

The definition provides information about the attribute.  The available options are:

isadefines a type requirement for this element.  valid options are: boolean, number, string, function, array, object and classname (iz class name)
readonlySets attribute to be read-only (true/false) Read only values can only be set in the new() call.  All other attempts to set a value will fail.
defaultprovides a default value for the field if none is provided during the new() call.  Note that the default will be shared by all instances of the class, and thus only simple types should use default.  To set defaults on complex attributes (objects, arrays, etc.) should use a builder.
builderfunction to be used to get a default value.  ‘this’ will be the object the builder is being called on, and the first argument will be the field name the builder is being called on.  called as: this.builder(fieldname) Should return the value to be used as the default.  This is usually preferable to default on attributes of complex types such as objects and arrays.
check_valuefunction that is used to override the type checking with custom behavior.  Called with attribute details as first argument, attribute name as second and value as the third.  Note that setting this overrides IZ’s internal type checking.  Called as: this.check_value(attribute_details, fieldname, value)
privateboolean indicating that this attribute is private. accessors for private attributes are not added to the final object.  See the example below for how to use private attributes.

Note that the builder / default values are not set until the value is requested, not at object creation.  This can have unexpected results if your builder routine is time-sensitive (and might be a good reason to create an init- type method instead).

Returns

accessor function for this attribute (not bound to this) Unless you are dealing with private attributes, it is safe to ignore the return value of has();

Example

// within Package definition
Class.has('friends', {
                       isa: 'array',
                       builder: function (name) {
                                                   // create an array if we didn't get one.
                                                   return new Array();
                                                }

                   })

It is possible to create multiple attributes with a single ‘has’ call by passing an object with attribute name / definition pairs.  This is best understood using an example:

Class.has({
    server: { isa: 'object' },
    port: { isa: 'number', default: 80 },
    max_size: { isa: 'number', default: 1000000 },
});

As mentioned above, it is possible to create private attributes that are only accessible by class methods.  The mechanism for doing this is straightforward, if somewhat non-intuitive.  The has operator returns the accessor function for the given attribute.  Using this along with javascript’s bind() routine allows you to tie the method to the given instance.  A code example will make this clearer:

// within the Package definition

// this creates an age attribute, but you can not call object.age() as the accessor is never added.
var age_prv = Class.has('age', { isa: 'number', private: true });

Class.do_something_with_age = function(new_age) {

    // this gets us an accessor for age that we can work with.
    // note that this must be done in each method that needs access to age, as it must be bound to this
    var age = age_prv.bind(this);

    // this sets age.
    age(new_age);
}

Class.mixin(class_or_object, overlay_attributes)

Adds the functionality of the given class or object to Class by copying all the methods and attributes of the provided class onto the current object.  Mixins allow you to merge multiple objects or clases together.

This can be especially useful when you want to create functionality that would apply to many different types or classes of object.  Creating a class intended to be mixed in will allow you to add that functionality to any object regardless of it’s inheritance structure.  In IZ mixins can be applied at the class level or at the instance level, allowing run-time addition of defined functionality.

Parameters

class_or_objectClass or Object to mix in
oberlay_attributesoptional array of attributes to mix in.

Returns

The original object.

Example

// do.stuff is a subclass of thingdoer
iz.Package('do.stuff', { extends: 'thingdoer' }, function (Class, SUPER) {

       // Also, a duck.
       Class.mixin('Duck');

       Class.do_things = function () {
           console.log('doing things!  also: ' + Class.quack() );
       };
       return Class;
   });
  • OR -
var stuffdoer = new iz.Module('do.stuff')();
// mix in OtherThings into this instance only
stuffdoer.mixin('OtherThings');

Note that an optional overlay_attributes parameter can be added to specify the specific attributes to copy.  This parameter is an array of attributes to be copied from the source object.  Great care should be taken if you provide overlay_attributes as many methods have expectations about what is available on the object they are attached to.  Therefore this second parameter should only be used when you are intimately familiar with the package in question.

If you are creating classes to be included via the mixin mechanism, you can control what attributes are mixed in from your class.  You do this by adding an attribute called ‘overlay_attributes’ to your object or class.  Within this attribute, place an array containing the list of attribute names you want to have mixed-in to the recipient class or object.  Only those attributes specified will be mixed-in.  (Note that if overlay_attributes is provided to the mixin call, it will override your object’s overlay_attributes attribute)

SUPER(obj, methodname)

SUPER is a special function used to retrieve a superclass’ version of a given method bound to the provided object.  SUPER is provided as the second argument to the iz.Package’s class creation function, as such it can be used within any method created within the class.  It is usually called as follows:

iz.Package('Bird.Duck', { extends: 'Bird'}, function(Class, SUPER) {

    Class.take_off = function() {
        SUPER(this, 'take_off')();
        // do other Duck related take-off things
    };
});

A note about SUPER: Access to an object’s superclass methods can be difficult to accomplish reliably in javascript.  IZ’s SUPER sidesteps these issues and creates a reliable mechanism for accessing the methods an object inherits, provided it is called as a function as described above.  SUPER can, in fact, be called as a method, obj.SUPER(methodname), however doing this within a method will behave unreliably.  It is provided only for the rare situation where you need to access the superclass from outside a named method or in a situation where the method is created after object creation.  It is recommended that you avoid the obj.SUPER() calling semantics unless you know exactly what you are doing.

IZ utility routines

iz.Use(classname, callback)

iz.Use = function (packagename,
callback)

Ensures that classname is loaded and available to be used.

Use will load the class if it has not been loaded before, otherwise it uses the class that has already been loaded.  After loading, you can instantiate:

iz.Use('My.Module');
var mymodule = new iz.Module('My.Module');

Under node.js, this will load the class using require().  Note that when the module has already been loaded, it will not be reloaded and iz.Use() will instead behave like iz.Module() in these cases.  Note also that while iz.Use() does not currently load files in the browser environment, you may use IZ in the browser if you preload your dependencies.  (we are open to ideas to reliably accomplish in-browser loading)

Class naming and filenames

Class names are separated by periods ‘.’ representing containing namespace.  When the object is being loaded with ‘Use’ this namespace is translated to a path.  Each class component preceeding a . is treated as a directory.  For example, to load ‘MyApp.Animals.MegaDuck’, IZ would treat the file to be loaded as ‘MyApp/Animals/MegaDuck.js’

Path concerns: Under node, IZ uses standard node module loading behavior, meaning that your modules need to be in a directory that node’s require can find.  This means that your modules need to be rooted in a node_modules directory somewhere or in your NODE_PATH.

Parameters

classnameClass to be loaded
callbackoptional callback to call upon module load.

Returns nothing

var MegaDuckClass = iz.Use('My.Animals.MegaDuck');

Note that when running under node.js, class loading is synchronous so the callback mechanism is unneccessary.  In systems that do not support synchronous require, the callback may be used to ensure code that depends on the module is not run until it is available.

iz.Tetchy(true_or_false)

iz.Tetchy = function (tetchy)

Tells IZ whether it should be particularly tetchy about common misuses / coding mistakes.  IZ will generally try to ‘do the right thing’ when in ambiguous or confusing situations.

If iz.Tetchy() is set to true, IZ will be very picky about how you write your code and will throw exceptions when it encounters things it considers likely to be an error.

For example, If you pass misnamed or unknown attributes to an object constructor, with iz.Tetchy() set to false, IZ will simply ignore these extra fields.  With iz.Tetchy() set to true, it will throw an exception if it encounters a key it doesn’t recognize.  Another example is that IZ will throw an exception if you attempt to inherit from a class that has not been loaded.

Generally speaking, Tetchy will enforce some rules that might be helpful during development / debugging.  Tetchy does add a very small amount of overhead in various situations.  This overhead is minimal and will not have much, if any, effect on a normal application.  Likewise if you wrote your code well, it is not necessary, so it is by default set to false.

Note also that Tetchy is set globally for all IZ derived objects so turning it on in an application that uses modules you did not write can be problematic.

iz.Module(classname)

iz.Module = function (packagename)

Returns the class with the name provided.

Almost always used with a preceeding ‘new’.  (See Example)

Parameters

classnameName of the class to obtain.  NEW: classname should be sent through .Use function first either directly or indirectly through other means (iz.Package, etc)

Returns

Prototype Function for the class provided.

Example

var bad_duck = new iz.Module('Somewhere.Animals.SuperDuck')({ "disposition": "evil" });

iz.add_search_path(path)

On server-side environments such as node, adds a path to be searched when loading IZ based modules using iz.Use().  In most cases, the normal path resolution provided by node is adequate.  In many cases, setting NODE_PATH appropriately is preferable.  If neither is adequate, adding a search path may be your only alternative.  Note that this adds overhead to every iz.Use() call so it’s recommended not to use this unless you know what you are doing.

iz.lock_search_path();

iz.lock_search_paths = function()

Prevents further additions to the search path list.  If iz.Tetchy() is true, then additional attempts to call iz.add_search_path() will throw an exception.  When Tetchy is off, additional attempts will simply be ignored.  As with iz.add_search_path(), this option is best not used unless you know exactly what you are doing.

proto.new_object(args)

proto.new_object = function (args,
object_to_localize)

Creates a new instance of the package class.

Note: This method should not be called directly, instead use the new operator.  Refer to the example for proper way to create an instance of a class.

Parameters

argsObject literal containing attributes to be set upon object creation.

Returns

Instance of the prototype class.

Example

var good_duck = new iz.Module('Somewhere.Animals.SuperDuck')({ "disposition": "good" });

iz.get_metadata(obj)

iz.get_metadata = function(obj)

Inspects an object and returns information about it.  Things that should not be modified directly are localized.

Parameters

objan IZ class.

Returns

A javascript object containing attributes for ‘classname’, ‘does’ and ‘attributes’ which describe the class’s structure.

Class.CONSTRUCT(args, [object_to_localize])

CONSTRUCT is called immediately after a new object is created.  The arguments passed in the ‘new’ call are passed in to the CONSTRUCT method.  The default CONSTRUCT is sufficient in most cases and is what is responsible for setting initial attribute values and for calling superclass constructors.  Note that classes used as mixins do not have their CONSTRUCT method called.

NOTE: Overriding CONSTRUCT can cause unusual behavior, it is best to leave CONSTRUCT alone unless you need special class-level initialization during the new call.  This is NOT the way to create a factory, or to do other random class hijinks.  In other words, if you are thinking about overriding CONSTRUCT, think hard.  Then think again.  Then write an init() method instead.  If you can’t make that work, write a passing test for your module then add CONSTRUCT... and remember to call the superclass’ CONSTRUCT, and return ‘this’.

Parameters

argsObject literal containing attributes to be set upon object creation.

Returns

Instance of the object class to be returned to the caller.

Class._on_object_create

The _on_object_create method is used to run any object level initialization required immediately upon creation of a new object.  This is not intended to be a general purpose initialization routine, but instead a mechanism to do any initialization that may be necessary for inherited classes, etc.  For example, when inheriting from stream.Readable, you must call the stream.Readable construct mechanism so that it can initialize it’s own state, the _on_object_craete method is the place to do that.

module.exports = iz.Package('MyInputStream', { extends: stream.Readable }, function(Class, SUPER) {

    Class._on_object_create = function(args) {
        // initialize ourselves using the stream.Readable
        stream.Readable.apply(this, args.readable_options);
    };

    return Class;
});

Object instance methods

obj.does(classname)

Introspection routine, returns true if obj can do what classname does.  This checks both for subclassing as well as mixins.

Parameters

classnameClass to inquire about

Returns

boolTrue / False

Example

if (obj.does('Duck')) {
   // Here we only care if you can act like a duck
   obj.quack();
}

obj.isa(classname)

Introspection routine, returns true if obj is a member or subclass of the class provided, false otherwise.  Useful for checking actual class membership.  NOTE in most cases you probably want to use <obj.does()> instead.  Note - Calling without an argument will return the object’s class name.

Parameters

classnameClass to inquire about

Returns

boolTrue / False

Example

if (!obj.isa('Duck')) {
      // here you MUST be a duck or a duck subclass.
      throw new Error("HEY! Only REAL ducks are allowed here!");
}

obj.can(methodname)

Introspection routine, returns true if obj has a method called ‘methodname’

Parameters

methodnamemethod to inquire about

Returns

boolTrue / False

Example

if (obj.can('quack')) {
    // Here we only care if you can quack
    obj.quack();
}

obj.get_default(attributename)

Returns the default value for a given attribute.  Uses the ‘default’ value or the ‘builder’ function as set in the attribute definition.  Note that this returns the default, it does not set the field to that default.  If you want to do that, you will need to set the value to the result of this call.

Parameters

attributenameattribute to inquire about

Returns

valuedefault value the field would have.

Example

var friend_default = obj.get_default('friend');

obj.get_current_state()

The get_current_state method returns a flat ‘generic’ javascript object representing the state of the object.  This object only contains the attributes and their values as key-value pairs.  Keep in mind that the returned object has no connection to the original whatsoever.  As such, modifying its values will have no effect on the original object, nor will it’s values keep in sync with the original.  This is, in short, primarily useful for inspection, as IZ object attributes are private and will not be visible via things like node’s util.inspect() routine.

The get_current_state method also provides a simple way to clone an object, as you can call get_current_state and pass the resulting object to the class’ constructor.

Note that get_current_state by default only loads attributes created with has(), if you have other private data or state it is recommended that you override this method in your class;

// clone my_message_object
var cloned_msg_obj = new Message( my_message_object.get_current_state() );

// cloned_msg_obj and my_message_object now have the same state.
// We can use node's util.inspect to verify this
console.log("my_message_object: "  + util.inspect(my_message_object.get_current_state()));
console.log("cloned_msg_obj: "  + util.inspect(cloned_msg_obj.get_current_state()));

obj.localize()

localize returns an object to use in place of the original.  It should have the state of the original, but any writes should affect only the new object.  Ideally a localized object should see changes made to the original if they have not been overridden in the localized object.  That is to say, a localized object should be ‘copy on write’ wherever possible.  This method implements a generic localization routine for IZ objects, you may override this method if you need special handling.

var original = {};
original.name = 'foo';
original.age = 18;

// localize my object
var localized_obj = iz.localize(original);

// outputs localized_obj.name = foo
console.log("localized_obj.name = " + localized_obj.name);

// setting name only affects name, the original age sticks around.
localized_obj.name = 'bar';

// outputs original.name = foo
console.log("original.name = " + original.name);

// outputs localized_obj.name = bar
console.log("localized_obj.name = " + localized_obj.name);

// outputs localized_obj.age = 18
console.log("localized_obj.age = " + localized_obj.age);

// setting the original in the parent is reflected in the localized copy, so long
// as the localized copy has not yet been overridden. (happy birthday!)
original.age = 19;

// outputs localized_obj.age = 19
console.log("localized_obj.age = " + localized_obj.age);
iz.Package = function (packagename,
inheritance,
closure)
Creates a new package prototype and registers it’s namespace.
iz.Use = function (packagename,
callback)
Ensures that classname is loaded and available to be used.
iz.Tetchy = function (tetchy)
Tells IZ whether it should be particularly tetchy about common misuses / coding mistakes.
iz.Module = function (packagename)
Returns the class with the name provided.
iz.lock_search_paths = function()
Prevents further additions to the search path list.
proto.new_object = function (args,
object_to_localize)
Creates a new instance of the package class.
iz.get_metadata = function(obj)
Inspects an object and returns information about it.
Close