Skip to content
zebraClass is a createClass extensions that uses the lazy-loading functionality to provide an easy-to-use interface for shared inheritance. Requires createClass.
JavaScript
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Failed to load latest commit information.
.gitattributes
LICENSE.md
README.md
package.json
zebraClass.js

README.md

zebraClass

zebraClass Logo
zebraClass is a createClass extensions that uses the lazy-loading functionality to provide an easy-to-use interface for shared inheritance. Requires createClass.

Use case 1: JavaScript Candy

If you are developing an application that has different versions of the same class for different usages but you want the same inheritance chain for both classes you're going to need this if you don't want to uglify your code by creating single files for each class and loading your classes in a zebra way.
Let's assume you're developing an application that allows you to edit and solve puzzles. You're going to create one controller for the Game and one for the Editor. Sounds quite simple- and it is. You're going to create the base class "Controller" and then derive your classes "GameController" and "EditorController" from that class. But let's assume you're going to create different controllers for different kinds of puzzles. So you've got a BaseController and derive both a CrosswordController and a SudokuController from these. Now things got a bit more tricky. One option would be to place the Game and Editor controllers on top of the concrete game controller like this: BaseController -> CrosswordController -> [Game/Editor]CrosswordController
But as soon as things get a bit trickier and you need to hook your concrete controllers inbetween those controllers (chances increase by abstracting your game logic, e.g. BaseController -> FieldController -> GridController -> CrosswordController), you'll need a zebra kind of inheritance:
SharedBaseController -> GameBaseController -> SharedCrosswordController -> GameCrosswordController

You could also do this by moving all of your controllers into a single file and include them zebra-style. But this would be the Java way, not the JavaScript style. In JavaScript you're loading a module and you want it to work. You don't want a thousand 20-lines files making things complicated and less structured. So you're sticking to the JavaScript-way and create both a shared.js and game.js/editor.js using zebraClass to easily manage these inheritances:

index.js:
// Tell zebraClass how to handle the game controller variations
// This will automatically create the class MyPuzzleApp.GameController.
// Even though it is generally possible, you should not add anything to it
// But instead use the classes
// MyPuzzleApp.ControllerBase and MyPuzzleApp.ControllerGame
zebraClass.add( 'MyPuzzleApp.Controller', [ 'Base', 'Game' ] );

require( './base' );
require( './game' );

zebraClass.initialize( 'MyPuzzleApp.Controller' );
base.js:
zebraClass( 'MyPuzzleApp.Controller' );

MyPuzzleApp.ControllerBase.prototype...  // Add your shared code here...



zebraClass( 'MyPuzzleApp.FieldController', 'MyPuzzleApp.Controller' );
...
zebraClass( 'MyPuzzleApp.GridController', 'MyPuzzleApp.FieldController' );
...
zebraClass( 'MyPuzzleApp.CrosswordController', 'MyPuzzleApp.GridController' );
...
game.js:
zebraClass( 'MyPuzzleApp.Controller' );
MyPuzzleApp.ControllerGame.prototype... // Add your game code here...

zebraClass( 'MyPuzzleApp.FieldController' );
...
zebraClass( 'MyPuzzleApp.GridController' );
...
zebraClass( 'MyPuzzleApp.CrosswordController' );
...

Use case 2: Multichain inheritance

Let's assume we've got the same controllers as above but we'd like to have both the game and the editor available. If we'd be using classic single-chain inheritance, we would not get any farer than the base controller class, since once we have added a game or an editor class, the next layer's base class wouldn't know whether to take the *Editor or the *Game class as its parent. Thus we're going to create two base-versions for both the *Editor and *Game classes (automatically handled by zebraClass): index.js:

// Tell zebraClass how to handle the game controller variation
zebraClass.add( 'MyPuzzleApp.Controller', [ 'Base', 'Game' ], 'Game[classname][variation]' );

include( './base' );
include( './game' );

zebraClass.initialize( 'MyPuzzleApp.Controller' );


// Tell zebraClass how to handle the editor controller variation
zebraClass.add( 'MyPuzzleApp.Controller', [ 'Base', 'Editor' ], 'Editor[classname][variation]' );

include( './base' );
include( './editor' );

zebraClass.initialize( 'MyPuzzleApp.Controller' );
base.js:
zebraClass( 'MyPuzzleApp.Controller', function(Controller,sClassName,sParentClassName) {

  // Add constructor
  Controller.prototype[sClassName]  = function(...) {
    this[sParentClassName](...);

    ...
  };

} );

zebraClass( 'MyPuzzleApp.FieldController', 'MyPuzzleApp.Controller', function( Controller, sClassName, sParentClassName ) {
  ...
} );

zebraClass( 'MyPuzzleApp.GridController', 'MyPuzzleApp.FieldController', function( Controller, sClassName, sParentClassName ) {
  ...
} );

zebraClass( 'MyPuzzleApp.CrosswordController', 'MyPuzzleApp.GridController', function( Controller, sClassName, sParentClassName ) {
  ...
} );
game.js:
zebraClass( 'MyPuzzleApp.Controller', function(Controller,sClassName,sParentClassName) {

  // Add constructor
  Controller.prototype[sClassName]  = function(...) {
    this[sParentClassName]();

    ...
  };

} );

zebraClass( 'MyPuzzleApp.FieldController', function( Controller, sClassName, sParentClassName ) {
  ...
} );

zebraClass( 'MyPuzzleApp.GridController', function( Controller, sClassName, sParentClassName ) {
  ...
} );

zebraClass( 'MyPuzzleApp.CrosswordController', function( Controller, sClassName, sParentClassName ) {
  ...
} );

Use case 3: Modular inheritance

Let's assume we've got the same controllers that we already used for multichain inheritance. But what we want to do now is adding modular functionality to the classes. Since we like it complicated we're not going to add the modular functionality at the end of the chain, but again for every single class within the inheritance chain:

index.js:
// Tell zebraClass how to handle the game controller variation
zebraClass.add( 'MyPuzzleApp.Controller', [ 'Base', 'MyModule', 'Game' ], 'Game[classname][variation]' );

include( './base' );

include( './my-module' );
// Since my-module doesn't add functionality to all classes,
// we have to let zebraClass take care of it.
// If you don't know whether or not a module is going to
// extend all classes, play it save and call this function.
zebraClass.finishVariation( 'MyPuzzleApp.Controller' );

include( './game' );

zebraClass.initialize( 'MyPuzzleApp.Controller' );

...
my-module.js:
// We're going to overwrite FieldController and GridController:

zebraClass( 'MyPuzzleApp.FieldController', function( Controller, sClassName, sParentClassName ) {
  ...
} );

zebraClass( 'MyPuzzleApp.GridController', function( Controller, sClassName, sParentClassName ) {
  ...
} );

Best use case (yet): Client/Server components

Let's assume you're developing UI components whose logic has to be both on your server and your client. But your servers and clients logic will divide at a certain point (the server for example might deal with sensitive data or require nodeJS modules to work, while the client will have code for actually displaying a component).
Thus you're going to create an inheritance chain that allows both the client and the server to place their logic at the right spot like this:

ui.js:
// Assume that we've got a global variable sClientOrServer
// It will be 'Server' if running nodeJS and 'Client' if running a clientside web application
zebraClass.add( 'UI.Component', [ 'Shared', sClientOrServer ] );

// Include was defined somewhere... simply load and execute the code
include( './shared', function() {

  include( './' + sClientOrServer.toLowerCase(), function() {

    zebraClass.initialize( 'UI.Component' );

    // UI has been loaded, now we can display stuff and stuff

  } );

} );

...
shared.js:
zebraClass( 'UI.Component', function( Component, sClassName, sParentClassName ) {
  Component.prototype[sClassName]   = function() {
    this[sParentClassName]();

    ...
  };

  ...
} );

zebraClass( 'UI.InputComponent', 'UI.Component', function( Component, sClassName, sParentClassName ) {
  Component.prototype.sValue        = null;
  Component.prototype[sClassName]   = function() {
    this[sParentClassName]();

    ...
  };
  Component.prototypeVirtual( 'validate' ); // ():bool

  ...
} );

zebraClass( 'UI.InputComponent.PasswordInputComponent', 'UI.InputComponent', function( Component, sClassName, sParentClassName ) {
  Component.prototype[sClassName]   = function() {
    this[sParentClassName]();

    ...
  };

  ...
} );

...
server.js:
zebraClass( 'UI.Component', function( Component, sClassName, sParentClassName ) {
  Component.prototype[sClassName]   = function() {
    this[sParentClassName]();

    ...
  };

  ...
} );

zebraClass( 'UI.InputComponent', function( Component, sClassName, sParentClassName ) {
  Component.prototype[sClassName]   = function() {
    this[sParentClassName]();

    ...
  };

  ...
} );

zebraClass( 'UI.InputComponent.PasswordInputComponent', function( Component, sClassName, sParentClassName ) {
  Component.prototype.sPasswordHash = null;
  Component.prototype.sSalt         = null;
  Component.prototype[sClassName]   = function( sPasswordHash, sSalt ) {
    this[sParentClassName]();

    this.sPasswordHash  = sPasswordHash;
    this.sSalt          = sSalt;
  };
  Component.prototype.validate      = function() {
    return require('crypto').createHash('md5').update( this.sSalt + this.sValue ).digest( 'hex' )===this.sPasswordHash; 
  };

  ...
} );

...
Something went wrong with that request. Please try again.