Skip to content

Instantly share code, notes, and snippets.

@michaelsbradleyjr
Created July 28, 2011 04:42
Show Gist options
  • Save michaelsbradleyjr/1110978 to your computer and use it in GitHub Desktop.
Save michaelsbradleyjr/1110978 to your computer and use it in GitHub Desktop.
Next-gen Joose ideas

Next-gen Joose

My goal is to outline some of my thoughts on how a next-gen Joose (e.g. Joose4) might be structured around AMD style modules and avoid use of global namespaces, for Joose itself and Classes, Roles, etc. that are defined with Joose.Class(), Joose.Role(), etc.

This is an outline only, and not an attempt to rigorously solve all of the technical problems faced in such a big shift in the design of the Joose object system.

AMD require, define are assumed to be implemented

As hinted at above, this outline will assume that runtime environment fully supports the AMD spec. For browsers, that means using an implementation like RequireJS. For node.js, that means creating some sort of "shim" so that modules defined with asynchronous loading in mind will work without modification and alongside synchronously loaded modules. RequireJS, for example, provides an r.js adapter for use with node.js, but at present it's a little clunky.

For now, assume that we can come up with a clever work-around or shim for the node.js environment. Hint: it might mean embedding a cross-environment event-emitter in the meta layer so that any Joose-based Class / Role / Module can signal that it is ready after the initialize method has returned. Then in a top-level script that's requiring something Joose-based, the developer can do something like:

var myJooseThing = require('myJooseThing')

var typicalNodeMod = require('normalNodeMod')

myJooseThing.on('ready', function callback() {...})

In the above example, require is not overloaded -- it's just the normal node.js require(). Inside myJooseThing, a shim has probably been invoked at the top-level which will, within the module context, provide AMD style require and define.

Another possibility would be to bundle an AMD implementation with Joose which in a node.js context uses a purely synchronous mechanism while respecting dependencies supplied in define statement/s. Currently, node.js (v0.5.x) will not trip over define, but it's left to the developer to make sure all dependences were properly loaded, as those listed in the define statement will be ignored.

Point 1: require and use should not overlap

require should always behave as the AMD spec defines it; though again, perhaps in a node.js context our implementation can operate synchronously while resulting in identical behavior when it comes to dependency loading, the return values of define, etc.

use (as a method -or- builder) will imply that the module to be loaded is written with Joose's (next-gen) API in mind. use should never be utilized as a loader for "non-Joose modules". This will be explained in the next point.

If a Joose module needs to load some non-Joose script, the developer should use require or list it in the dependencies expression of a define statement.

For example:

define(['jquery', 'joose'], function (jquery, joose) {
    
    ...
    
    return joose.Class(...)
    
})

-or-

define(['joose'], function (joose) {
    
    var thisModule
    
    require('jquery', function callback(...) {
       
       ...
       thisModule = joose.Role(...)
       
    }
    
    return thisModule
    
})

Point 2: use expects modules which "target" the next-gen Joose API

The examples that immediately follow (and the "local namespacing" scheme they suppose) will need more explanation. But it's probably easier to give some sample code and then justify how I imagine it can work:

Example 1

/home/michael/project/lib/MyClass.js

var myjuice = require('joose')

var MyClass = myjuice.Class('MyClass', {

    does : 'MyClass.MyRole',

    has : {...},

    methods : {...}

})

lib/MyClass/MyRole.js

define({

    has : {...},

    methods : {...}

})

Example 2 (an alternate implementation of Ex 1)

/home/michael/project/lib/MyClass.js

var myjuice = require('joose')

var MyClass = myjuice.Class('MyClass', {

    use : 'MyClass.MyRole',
    
    has : {...},

    methods : {...}

})

MyClass.on('ready', function callback() {

    MyClass.meta.extend({

        does : MyClass.MyRole
    
})})

lib/MyClass/MyRole.js

define({

    target : 'Role',

    has : {...},

    methods : {...}

})

So how does it work?

This will be noted again below, but one key idea is that Class(), Role(), use() as defined in Joose will be aliased in the meta layer. So: MyClass.meta.makes.Class(...) would return the same thing as myjuice.Class(...), or something along those lines. Makes sense? I hope that's actually possible. The idea is to avoid a "namespace dependency" on "Joose", literally.

In Example 1, because a dependency is being loaded in the context of a does builder, it's implicit that the module's "target" is meta.makes.Role().

In Example 2, as the module is loaded through the use builder, the "target" property (in the object returned by AMD style require+define behind the scenes) tells use() that meta.makes.Role() should be called.

In both cases the Role should be stored in the "local namespace", i.e. MyClass.MyRole, which is implicit according to the dot-notation -> filename of the dependency spec'd in use / does.

The string-name 'MyClass' in the definition var MyClass = myjuice.Class('MyClass', {...}) does not involve populating the global namespace with MyClass. Rather, it serves as an "anchor" for use(). Without it, in both examples the Role would have stored in MyClass.MyClass.MyRole rather than MyClass.MyRole.

Of course if a does or isa builder, etc. is supplied with an object reference rather than a string value, then the use/require logic would not come into effect (i.e. the definition would need to have been made higher up in the script).

Point 3: No automatic global namespaces and "top-level" definitions should be accompanied with a local declaration

var myjuice = require('joose')

var MyClass = myjuice.Class('MyClass', {...})

But then this should be okay:

myjuice.Class('MyClass.AnotherClass', {...})

As noted in Point 2, the string-name in the "top-level" definition of MyClass can serve as an "anchor" for use based dependency loading, whether implicit or explicit.

But also, such a top-level def effectively sets up a "local namespace", which can be referenced by string-name in subsequent defs. The following should be equivalent:

(no "anchors" below)

var myjuice = require('joose')

var MyClass = myjuice.Class({...})

MyClass.AnotherClass = myjuice.Class({...})

(implicit top-level anchor in second definition below, owing to meta.makes providing a reference to MyClass, even though it wasn't defined with a string-name)

var myjuice = require('joose')

var MyClass = myjuice.Class({...})

MyClass.meta.makes.Class('AnotherClass', {...})

(explicit anchors below)

var myjuice = require('joose')

var MyClass = myjuice.Class('MyClass', {...})

myjuice.Class('MyClass.AnotherClass', {...})

Of course, since global namespaces aren't setup by way of the string-names (per my proposal), then if the developer doesn't make the proper declarations or mixes and matches the styles in an incorrect manner, the script is likely to throw ReferenceError. That's not great, but it may be the price of abandoning global namespacing.

Point 4: Class(), Role(), use(), etc. should be available through meta to avoid global "Joose" namespace-dependency

It seems important to drop the notion than Joose as a module should be always be referenced as "Joose".

var myjuice = require('joose')

var superJoose = require('joose')

The basic Class(), Role(), etc. definition-facilities should then be exposed in some clever way through the meta layer, so that use based module loading can be implicitly tied back to the Joose-module-instance used to define a class at the top-level. See the examples above, where I've assumed such an arrangement is do-able.

Joose itself should be defined AMD style (no dependencies):

define(function () {

    var Joose =  {}
  
    ... // implementation elided
  
    return Joose

})

Point 5: use method and builder is more disciplined with respect to local namespaces

In Joose3, JX.N.D allows a great deal of freedom, as we're all aware. I could have a module named Baz.js which defines Roles Foo and Bar. By way of auto namespace creation in the global context I can then do:

require('joose')

Joose.Class('MyClass', {

  use : [ 'Baz', 'SomeRole' ],
  
  does : [ 'Foo', 'Bar', 'SomeRole' ],
  
  ...

})

In next-gen Joose we would favor something like:

define(['joose', 'Foo', 'Bar'], function (joose, Foo, Bar) {

    var MyClass = joose.Class({

        does : [ Foo, Bar, 'SomeRole' ],
    
        ...
  
})})

Modules Foo and Bar would be defined in separate AMD style modules, likely each having 'joose' listed as a dependency and define returning something like return joose.Role('Foo', {...}).

SomeRole would be defined in the manner of Examples 1 and 2 in Point 2 above, with a target : 'Role' property.

In short, use logic will be more narrowly scoped in next-gen Joose than in Joose3. For one, per AMD spec, each module loaded with use should contain a single define statement.

Also, rather than specifying the namespacing within a module, the local namespace of instantiated Classes, Roles, etc. is always relative to the context in which use is invoked (that gives the localized "top level"), and according to the dot notation -> filename specified in the builders or the argument/s of use().

Also note that use() itself should not be injected in the global namespace. It should always be called as a method of joose, for example:

var myjoose = require('joose')

myjoose.use(...)

-or-

var myjoose = require('joose')

var MyClass = myjoose.Class(...)

MyClass.meta.dependencies.use(...)

Or something along those lines.

Point 6: use() can also be used for bootstrapping core Joose

It should be possible with the help of use() to "bootstrap" joose, i.e. with something like Joose3's JooseX.Attribute:

var thisJoose = require('joose')

thisJoose.use(require('joosex-attribute'), function callback() {

    var MyClass = thisJoose.Class({
   
        has : {
     
            myProp : {
       
                lazy : true,
                is : 'rw',
                init : function () {...}
       
}}})})

My idea is that the joosex-attribute module should contain an AMD style define statement which returns an object with a target : 'Extend' property, or something like that. That would tell use that rather than instantiating a Class or Role, it should invoke meta.extend logic relative to the context that called use -- in this scenario that's thisJoose.

If use is available through the meta layer, then it might be possible to use a bootstrapping module in the context of a Class or Role:

var thisJoose = require('joose')

var MyClass = thisJoose.Class('MyClass', {...})

MyClass.meta.dependencies.use(require('joosex-thing'), function callback() {

    ...

})

I'm uncertain as to if and how precisely that would work, though.

Again, it's assumed that joosex-thing will return (via define) an object with a target : 'Extend' property.

Point 7: module loading (e.g. use) should be baked into core Joose

Finally, it seems to me that module loading is so fundamental to post-modern JS app design (browsers and servers) that next-gen Joose's AMD module system and use facilities should be baked into the core of Joose.

And, as suggested above, event-driven asynchronous programming is so closely related to modules and post-modern JS apps in general, it seems like Joose's meta model should supply a node.js style event emitter implementation and semantics in the core distribution (usable in browsers and node.js):

.on('event', callback)

This should be avaialble for both classes and instances, if possible.

Author

Michael Bradley, Jr. [email protected]

Copyright and License

This text is Copyright (c) 2011 by Michael Bradley, Jr.

The concepts and proposals offered here are free software, licensed under:

The MIT License

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.


JavaScript Reference
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment