5

I'm organizing my code into 20-60 line modules, usually in the module pattern. I want a well formed object oriented JavaScript library.

Is this the best way to do this? The code has been tested and works.

I like it because a programmer can pull modules from the library and use them as needed, they are self contained.

Here is Tool, Message, Effect and Text, all contained in NS.

Question?

Is this a good way ( best practice ) to organize my library?

Note

So far, there is 0 consensus in the comments and answers...very frustrating.

Outer Module Pattern

var NS = ( function ( window, undefined ) 
{ 
/* All Modules below here */ 
} )( window );

Tools

/**
 *Tools
 *    getTimeLapse - benchmark for adding
 */

var Tool = ( function () 
{
    var Tool = function ( ) 
    {
    };
    Tool.prototype.getTimeLapse = function( numberOfAdds ) 
    {
        var end_time;
        var start_time = new Date().getTime();
        var index = 0;           
        while ( index <= numberOfAdds )
        {
            index++;
        }
        end_time = new Date().getTime();
        return ( end_time - start_time );
    };
    return Tool;
} () );

Message

/**
 *Message
 *    element - holds the element to send the message to via .innerHTML
 *    type - determines the message to send
 */

var Message = ( function () 
{
    var messages = 
    {
        name:         'Please enter a valid name',
        email:        'Please enter a valid email',
        email_s:      'Please enter a valid email.',
        pass:         'Please enter passoword, 6-40 characters',
        url:          'Please enter a valid url',
        title:        'Please enter a valid title',
        tweet:        'Please enter a valid tweet',
        empty:        'Please complete all fields',
        same:         'Please make emails equal',
        taken:        'Sorry, that email is taken',
        validate:     'Please contact <a class="d" href="mailto:foo@foo.com">support</a> to reset your password',
    };
    var Message = function (element) 
    {
        this.element = element;
    };
    Message.prototype.display = function( type ) 
    {
        this.element.innerHTML = messages[ type ];
    };
    return Message;
} () );

Effects

/**
 *Effects
 *    element - holds the element to fade
 *    direction - determines which way to fade the element
 *    max_time - length of the fade
 */

var Effects = ( function () 
{
    var Effects = function ( element )
    {
        this.element = element;
    };
    Effects.prototype.fade = function( direction, max_time ) 
    {
        var element = this.element;
        element.elapsed = 0;
        clearTimeout( element.timeout_id );
        function next()
        {
            element.elapsed += 10;
            if ( direction === 'up' )
            {
                element.style.opacity = element.elapsed / max_time;
            }
            else if ( direction === 'down' )
            {
                element.style.opacity = ( max_time - element.elapsed ) / max_time;
            }
            if ( element.elapsed <= max_time ) 
            {
                element.timeout_id = setTimeout( next, 10 );
            }
        }
        next();
    };
    return Effects;
} () );

Text

/**
 *Text
 *    form_elment - holds text to check
 */

var Text = ( function () 
{
    var Text = function ( form_element )
    {
        this.text_array = form_element.elements;
    };
    Text.prototype.patterns = 
    {
        prefix_url:     /^(http:)|(https:)\/\//,
        aml:            /<(.+)_([a-z]){1}>$/,
        url:            /^.{1,2048}$/,
        tweet:          /^.{1,40}$/, 
        title:          /^.{1,32}$/,
        name:           /^.{1,64}$/, 
        email:          /^.{1,64}@.{1,255}$/,
        pass:           /^.{6,20}$/
    };
    Text.prototype.checkPattern = function( type ) 
    {
        return this.patterns[ type ].exec( this.text_array[type].value );
    };
    Text.prototype.checkUrl = function( type ) 
    {
        return this.patterns[ type ].exec( this.text_array.url.value );
    };
    Text.prototype.checkSameEmail = function() 
    {
        return ( ( this.text_array.email.value ) === ( this.text_array.email1.value ) );
    };
    Text.prototype.checkEmpty = function() 
    {
        for ( var index = 0; index < this.text_array.length; ++index ) 
        {
            if ( this.text_array[ index ].value === '') 
            { 
                return 0; 
            }
        }
        return 1;
    };
    return Text;
} () );
  • It's far from the best because your using globals – Raynos Apr 12 '12 at 19:25
  • All these files are in global scope? `var Text = ...` – Raynos Apr 12 '12 at 19:27
  • I think he means you are not using a global object as in library.Tool, library.Effects so you create more than a single global scope object. – GillesC Apr 12 '12 at 19:30
  • @gillesc all globals are bad, even a single namespace – Raynos Apr 12 '12 at 19:31
  • @TheAllFoo then you just suffered from all the code in one file problem – Raynos Apr 12 '12 at 19:31
  • @TheAllFoo he is saying you should have one global variable, e.g. `var myLibrary = {};` and then all modules should be properties of that variable, e.g. `myLibrary.Text = (...)();` – jbabey Apr 12 '12 at 19:40
  • @jbabey no that's the exact opposite of what he is saying. – Daniel Ribeiro Apr 12 '12 at 19:41
  • @jbabey that is not what I'm saying at all. You should have no globals, no namespaces, your code shouldn't be in a single file – Raynos Apr 12 '12 at 20:02
  • 1
    @Raynos i hate to break it to you, but it's impossible to not have at least one global :) – jbabey Apr 12 '12 at 20:16
  • @jbabey your wrong, I know better. [globals are not needed](http://stackoverflow.com/a/10054250/419970) – Raynos Apr 12 '12 at 20:31
  • 2
    @Raynos even if you use a module loading system such as AMD or commonJS, those libraries are still exposing globals. it's not possible to execute javascript without a way of accessing it. – jbabey Apr 13 '12 at 13:07
  • @jbabey y u no read. zero globals, y u no read – Raynos Apr 13 '12 at 15:22
  • 2
    @Raynos taking require.js as an example, `require` and `define` are both global variables. i am having trouble understanding how you do not understand this concept. – jbabey Apr 13 '12 at 15:28
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/10058/discussion-between-raynos-and-jbabey) – Raynos Apr 13 '12 at 15:54

3 Answers3

2

The one thing I would suggest to change to make your code cleaner and reduce its footprint is to just set the prototype property at once, so that instead of doing

Object.prototype.method1 = function(){};
Object.prototype.method2 = function(){};

You do

Object.prototype = {
    method1: function(){},
    method2: function(){}
};

If you need to conserve the constructor reference, which is recommened, you should re-assign the constructor afterward. See this answer for more details.

Community
  • 1
  • 1
GillesC
  • 10,647
  • 3
  • 40
  • 55
1

I personally prefer using a modular code organization library like ncore

This encourages you to write your code as a set of modules (one module per file) and then hook them together using dependency injection and bootstrapping.

The code is slightly portable because modules are just objects on their own right, however if one doesn't use ncore advantages are lost.

The leaderboard app shows a detailed example of OO code organization

Raynos
  • 166,823
  • 56
  • 351
  • 396
1

A few suggestions... First would be to create a namespace object as scope for your libraries... jQuery uses "jQuery" and "$", underscore uses "_". I tend to use "CompanyName.SiteName"

if (typeof CompanyName == "undefined") var CompanyName = {};
CompanyName.SiteName = CompanyName.SiteName || {};

The first line explicitly checks against undefined, as you'll get errors in many browsers otherwise for root variables using the method on the SiteName property.

From there, I would make a couple adjustments... When you are calling an anonymous function inline, it's best to wrap the whole of the call inside the parens.

CompanyName.SiteName.ModuleName = (function(w){
    ...
    return moduleImplementation;
}(window || this)); //CompanyName.SiteName.ModuleName

This tends to avoid the confusion by having the parens wrap the whole, and by having a comment at the end of the module declaration.

Per the comment above, you may want to make the prototype declaration as a more singular statement. I would advise against this, as longer modules can make readability an issue.

myModule.prototype = {
    "method1": function(){
    }
    ...
    "methodN": function(){
       //by the time you get here, you may not see the top, and where you are nested in
    }
};

//with the dot-notation, or hash notation
myModule.prototype.methodN = ...
myModule.prototype["methodN"] = ...
//you can see where you are binding to at that function

You may also want to look into RequireJS and AMD

There's also the concept of dealing with simpler objects, and using functional binders. Treating your library as a set of functions (similar to C exports) that are passed and work with simpler objects/types. It really depends on your needs/usage and the specifics of your needs and use.

You may also want to look at javascript libraries like KnockoutJS, Underscore and Backbone for some examples.

Tracker1
  • 19,103
  • 12
  • 80
  • 106
  • namespaces are bad. Do not use them – Raynos Apr 12 '12 at 20:02
  • @Raynos Namespaces are fine.. they're good for code isolation. What's bad is when you make multiple deeply nested namespace calls... it's usually best to create a local variable alias to your namespace/function. But that is a separate issue for module development. Unless you have an environment that promotes encapsulation (such as node). – Tracker1 Apr 12 '12 at 20:17
  • @TheAllFoo I don't see Arc as a namespace, I see Arc, Tools and Message. – Tracker1 Apr 13 '12 at 22:56