Martin
Martin

Reputation: 490

Create namespace for CoffeeScript classes

How can I create namespace for my classes written in CoffeeScript?

For example, I have tree classes Aa, Bb and Cc. I want them insert into globaly assesible namespace - MyClasses, allow comunicate cross them and using them in jasmine-node.

class MyClasses.Aa
 @someProp: true

class MyClasses.Bb
 @someProp2: false

class MyClasses.Cc
 @doSomeStuff: -> MyClasses.Aa.someProp = false

I know, I can inject them into one file and compile, but I want to have one class = one file.

How can I do that please? Thank you!

EDIT: I tried this way, but I think it is not good, but it works in browser and jasmine-node

root = exports ? this
root.MyClasses = root.MyClasses ? {}

root.MyClasses.Aa = 

  class Aa

Upvotes: 3

Views: 2222

Answers (3)

Tarjei Huse
Tarjei Huse

Reputation: 1291

There is a suggested solution in the Coffescript wiki:

from https://github.com/jashkenas/coffee-script/wiki/FAQ :

# Code:
#
namespace = (target, name, block) ->
  [target, name, block] = [(if typeof exports isnt 'undefined' then exports else window), arguments...] if arguments.length < 3
  top    = target
  target = target[item] or= {} for item in name.split '.'
  block target, top

# Usage:
#
namespace 'Hello.World', (exports) ->
  # `exports` is where you attach namespace members
  exports.hi = -> console.log 'Hi World!'

namespace 'Say.Hello', (exports, top) ->
  # `top` is a reference to the main namespace
  exports.fn = -> top.Hello.World.hi()

Say.Hello.fn()  # prints 'Hi World!'

Upvotes: 1

Jonathan Tran
Jonathan Tran

Reputation: 15276

Use RequireJS.

In one file called "my-classes.coffee", define the namespace.

define [], ->
  # You need this if you want it to work in multiple environments.
  # Otherwise just use `window` to work in the browser.
  root = exports ? this

  root.MyClasses = {}

You can define your class in another file called "aa.coffee".

define ['my-classes'], (MyClasses) ->

  class MyClasses.Aa
    @someProp: true

Another file:

define ['my-classes'], (MyClasses) ->

  class MyClasses.Bb
    @someProp2: false

Now when you require, it should export MyClasses which includes MyClasses.Aa.

require ['my-classes', 'aa'], (MyClasses, _) ->
  console.log MyClasses.Aa.someProp

One issue with doing it this way is that you can't depend on just "my-classes" in the require statement. If you did that, MyClasses.Aa would be undefined. But you can't depend on just "aa" either, because "aa" doesn't export anything except by adding to MyClasses. In the above code snippet, MyClasses.Bb is undefined because I haven't explicitly depended on it. This is why many people either use one giant file or duplicate the boilerplate of re-exporting the namespace.

If anyone knows how to fix this, please let me know.

I, personally, find RequireJS to be complicated to use, and there are many different ways to set it up. One way I've used it with jasmine is by using a cake task to precompile my CoffeeScript down to JavaScript, and then have spec files like this.

requirejs = require('requirejs')
# Set the baseURL to your compiled JS dir.
requirejs.config { baseUrl: __dirname + '/../lib' }

requirejs ['my-classes', 'aa'], (MyClasses, _) ->

  describe "someProp", ->
    it "should be true", ->
      expect(MyClasses.Aa.someProp).toEqual true

This may not be the best way, but I was able to use it to run modules in the browser, on a Node server, and in jasmine-node tests. I've also seen some people use custom runners to avoid the boilerplate in their spec files.

If you'd rather not use RequireJS, you may find this question helpful. It works by using the namespace function defined on the CoffeeScript FAQs.

Upvotes: 2

jaime
jaime

Reputation: 42031

You could compile your CoffeeScript files with the -b flag which removes the safety wrapper. Or expose your classes to the global scope with something like you already have

a.coffee

root = exports ? window
MyClasses = root.MyClasses = root.MyClasses ? {}

class MyClasses.Aa
  @someProp: true

b.coffee

# same header as a.coffee
class MyClasses.Bb
  @someProp2: false

c.coffee

# same header as a.coffee
class MyClasses.Cc
  @doSomeStuffWith: (someClass)-> 
    # pass a class to the method instead of just 
    # modifying another class within the same scope
    someClass.someProp = false

Repeat for other classes. In the first line I find window much more explicit than this, also the second line allows you to just append classes to MyClasses without long sequences of namespaces.

In the browser your classes will be inside the global object MyClasses

In node.js you would use them like this (which is a little verbose IMHO):

var Aa = require("./a").MyClasses.Aa,
    Bb = require("./b").MyClasses.Bb,
    Cc = require("./c").MyClasses.Cc;

console.log(Aa.someProp)  // true
console.log(Bb.someProp2) // false

Cc.doSomeStuffWith(Aa)

console.log(Aa.someProp) // false

pd: I haven't checked it with node-jasmine.

Upvotes: 0

Related Questions