What is it?
Neon (Ne) is a javascript Class system designed to wrap up object oriented logic around Javascript objects (which are not truly OO), and to establish a common base language for developers using such classes.
Usage
For browsers:
<script src="/vendor/neon.js"></script>
For node.js:
npm install neon
...
var Ne = require('neon');
//Namespace
UI = {};
// Create a simple Class
Class(UI, 'Widget')({
HTML : '<div></div>',
prototype : {
init : function() { ... },
render : function() { ... }
}
});
// Inject functionality with Modules
Module('Draggable')({
threshold : 10,
prototype : {
isDragging : false,
startDrag : function() { ... }
}
});
// Inherit and include to separate functionality
Class(UI, 'Overlay').inherits(UI.Widget).includes(Draggable)({
HTML : '<div class="overlay"></div>',
prototype : {
color : 'white',
init : function( config ) { this.setLabel( config.label ) ... },
setLabel : function( labelText ) { ... }
}
});
// Access Your Class variables
UI.Overlay.threshold // => 10
UI.Overlay.HTML // => '<div class="overlay"></div>'
// Catch params in the init method: init : function( config )
var overlay = new UI.Overlay({ color : 'black', label : 'This is my label!' });
// Use your instance
overlay.color; // => 'black'
overlay.color = 'red';
overlay.setLabel( 'Changing the label!' );
overlay.close();
Features
Convenient DSL for creating classes
The syntax for creating classes is very similar with that of other OO languages.
Class(NameSpace, 'ClassName')({
classProperty : 'value',
prototype : {
init : function( initParams ) { ... },
instanceMethod : function() { ... }
}
});
var myInstance = new ClassName( initParams );
Inheritance
Inheritance is pretty much the reason to use OO, so it provides a nice way of subclassing. In esence, it's about extending javascript objects with the properties of the base class.
Class(NameSpace, 'SubClass').inherits(NameSpace.SuperClass)({
prototype : {
init : function( initParams ) { ... } // overwrite SuperClass
}
});
Modules (composition)
You can include modules or mixins in your classes. You can extend multiple modules in a single class.
Module('MyModule')({
prototype : {
instanceProperty : false,
instanceMethod : function() { ... }
}
});
Class('MyClass').includes(MyModule)({
prototype : {
init : function( initParams ) { ... } // overwrite SuperClass
}
});
var myInstance = new ClassName( initParams );
myInstance.instanceMethod();
API
Class and Module instantiation
Class([namespace], [className]) // => Factory
Module([namespace], [className]) // => Factory
The optional namespace argument is a valid javascript object of which the created class will be a property, if no namespace is passed, it will be the global object.
The className argument is a string with the name of the class. It's possible to not pass a className, in which case the class will have an autogenerated name, this is common for creating anonymous classes. Note that you cannot have an anonymous class inside a namespace, the first argument will be interpreted as the className.
Class and Module definition
factory({
staticProperties...,
prototype : {
instanceProperties...
}
}); // => Class
The class factory is an executable property, which accepts the class definition as a parameter.
The objects' properties will be copied by reference as static properties to the class.
The prototype
property of the class definition will serve as the base
prototype for the created objects. In fact, this is the
standard prototype
of Javascript. The properties in it will be copied by reference to all created
objects.
Class initialization
init : function( ... )
When instantiating classes, they will call the init
method defined in them, if
overriden (It doesn't do anything by default).
To create an instance of an object, use the standard Javascript object
constructor new
. The parameters are passed to the init function
var x = new MyClass()
Class properties
MyClass.className // => Returns the name of the class
MyClass.superClass // => Returns the actual parent class
myInstance.constructor // => Returns a reference to the object's class
Caveats
Be aware that since javascript's prototype works by reference, all non primitive values will point to the parent's prototype. To avoid this, make sure you assign a new value to those properties after initialization
What it doesn't do
super
calls
The reasoning behind not including the super
functionality is that it requires
introspection. Even though it's possible, the library tries not to mess with the
Javascript way of doing things. If you need to call the same method of a parent
class, you can use the function property of the prototype and call it yourself,
e.g.
myMethod : function(params) {
ParentClass.prototype.myMethod.call(this, params);
}
private
scoping
When working in pure Javascript or using the Node module system, it's easy to hide functions by keeping them as a inner closures and not adding them to the prototype of the class. Neon does not have this functionality, since the classes are just wrapped objects and javascript does not have a way to hide properties However, the suggested convention is to name your private methods with an underscore as the first character (e.g. _myMethod) to indicate that the method is private.
Neon Standard Library
Web develpment has its own set of problems and the patterns they solve them, we decided to include these 3 main components that help a lot when developing for the browser and the server.
-
Event Support
.js -
Node Support
.js -
Widget
.js
Event Support & Custom Event Support
In javascript event-based programming is really important and one of the main patterns used to communicate components, it helps your code to stay decoupled while building complex programs.
Using EventSupport.js you can let your Neon objects become Event Emitters, so you can send and receive data.
Usage
First you need to add 2 scripts to you application after the Neon.js script.
<script type="text/javascript" src="CustomEvent.js"></script>
<script type="text/javascript" src="CustomEventSupport.js"></script>
This 2 scripts will load a class called CustomEvent and a module CustomEventSupport, this module can be added to any class on you existing Neonjs app.
To convert any Neon.js Class into a Event Emitter Class just add the module to you class using includes
.
Class('MyClass').includes(CustomEventSupport)({
prototype : {
init : function( initParams ) { ... }
}
});
This way your class and its instances will have new 3 new methods: bind
, unbind
and dispatch
Api
-
bind
... //let's bind to an Custom Event supported object myEmmiter.bind('my-event-name', function( ev ){ console.log('My data: ', ev.data); }); ... //Somewhere else: ... //now let's emmit some events myEmmiter.dispatch('my-event-name', {data: 'has been transmited!'}); ...
-
unbind
Regular unbinding
//Ok let's bind to an Custom Event supported object myEmmiter.bind('my-event-name', function( ev ){ console.log('My data: ', ev.data); }); //Ok I don't want to hear to this event anymore myEmmiter.unbind('my-event-name'); });
Selective unbinding
//create our callback var handler = function( ev ){ console.log('This will not be called after the selective unbind'); } //bind it myEmmiter.bind('my-event-name', handler); //Regular bind myEmmiter.bind('my-event-name', function(ev){ console.log('This will still work after the selective unbind'); }); //now let's just unbind this listener passing the binded handler myEmmiter.unbind('my-event-name', handler);
Warning: As you see, to selectively unbind listeners you need to to pass the handler funcion pointer that was binded, if
null
is passed, all bindings to that event will be removed. This complies to ECMA spec. -
dispatch
//Let's bind myEmmiter.bind('my-event-name', function(ev){ console.log('This will still work after the selective unbind'); }); //Let's emmit myEmmiter.dispatch('my-event-name', {data: 'has been transmited!'});
dispatch
will extend itself using the second parameter in this case{data: value}
we recommend using this kind of construct to create your events, always try to pass all your event information using adata
index in an object.
Example
In this example we changed the postion of a point, and bind other component to the change, this allows the binded component to trigger behaivor when the point emmits the changed
so then we can repaint the point in a map or calculate something based on the new information.
Class('Point').includes(CustomEventSupport)({
prototype : {
x : 0,
y : 0,
init : function(x, y) {
this.x = x;
this.y = y;
},
setPosition : function(x,y){
this.x = x;
this.y = y;
//emmit event
this.dispatch('changed', {data: {x: x, y: y} });
}
}
});
var point = new Point(1,4);
//bind to the event
point.bind('changed', function(ev){
console.log('point has changed with coordinates: ', 'x: '+ ev.data.x, 'y: '+ ev.data.y);
});
//set things in motion
point.setPosition(2, 4);
Custom Event Support also adds the events API to the class itself:
Class('MyClass').includes(CustomEventSupport)({...});
...
MyClass.dispatch(...);
For more information about Custom events in JavaScript check: Custom events in JavaScript. Neon events are build based on W3C spec for events, it can be check here:Document Object Model (DOM) Level 3 Events Specification
Node Support.js
Node support lets you create a logic tree in an API glance of your classes and instances.
The idea behind node support is to keep your logic connected but separated, using dot notation to navigate the tree:
//Create the parent class
var parentInstance = new ClassWithNodeSupport();
//Use Node Support to logically connect instances
parentInstance.appendChild( new ClassWithNodeSupport({
name : 'childNode'
})
);
//Access your nodes using dot notation :)
parentInstance.childNode.accessMethodsAndProperties();
Usage
First you need to add the NodeSupport script to you application after the Neon.js script.
<script type="text/javascript" src="NodeSupport.js"></script>
This script will load a class called NodeSupport, this module can be added to any class on you existing Neonjs app.
To add node support to your class just add the module to you class using includes
.
Class('MyClass').includes(NodeSupport)({
prototype : {
init : function( initParams ) { ... }
}
});
This way your class and its instances will have new 7 new methods: appendChild
, insertBefore
, removeChild
, setParent
, getDescendants
, getPreviousSibling
, getNextSibling
.
Api
-
appendChild([child])
Allows to append a node into another node.
Class('MyNodeSupportedClass').includes(NodeSupport)({ prototype : { init : function( initParams ) { ... } } }); var parentNode = new MyNodeSupportedClass(); parentNode.appendChild( new MyNodeSupportedClass() ); //The child node will be added to the children array parentNode.children[0];
-
insertBefore([child], [beforeChild])
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
Class('MyNodeSupportedClass').includes(NodeSupport)({ prototype : { init : function( initParams ) { ... } } }); var parentNode = new MyNodeSupportedClass(); parentNode.appendChild( new MyNodeSupportedClass() ); //The child node will be added to the children array parentNode.children[0];
-
removeChild([child])
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
Class('MyNodeSupportedClass').includes(NodeSupport)({ prototype : { init : function( initParams ) { ... } } }); var parentNode = new MyNodeSupportedClass(); parentNode.appendChild( new MyNodeSupportedClass() ); //The child node will be added to the children array parentNode.children[0];
-
setParent([parent])
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
Class('MyNodeSupportedClass').includes(NodeSupport)({ prototype : { init : function( initParams ) { ... } } }); var parentNode = new MyNodeSupportedClass(); parentNode.appendChild( new MyNodeSupportedClass() ); //The child node will be added to the children array parentNode.children[0];
-
getDescendants()
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
Class('MyNodeSupportedClass').includes(NodeSupport)({ prototype : { init : function( initParams ) { ... } } }); var parentNode = new MyNodeSupportedClass(); parentNode.appendChild( new MyNodeSupportedClass() ); //The child node will be added to the children array parentNode.children[0];
-
getPreviousSibling()
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
Class('MyNodeSupportedClass').includes(NodeSupport)({ prototype : { init : function( initParams ) { ... } } }); var parentNode = new MyNodeSupportedClass(); parentNode.appendChild( new MyNodeSupportedClass() ); //The child node will be added to the children array parentNode.children[0];
-
getNexSibling()
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
Class('MyNodeSupportedClass').includes(NodeSupport)({ prototype : { init : function( initParams ) { ... } } }); var parentNode = new MyNodeSupportedClass(); parentNode.appendChild( new MyNodeSupportedClass() ); //The child node will be added to the children array parentNode.children[0];
Piecing it all together
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Odio, maxime, accusamus, incidunt, officiis minima unde veritatis sunt a facere ipsam quasi dolorum magni fuga et optio. Alias vero deserunt commodi.
Final notes
So, why build another Class library system?
Neon has been around since 2010, a time where many class libraries were still being developed. Naturally, many of them were released simultaneousley (especially after the spike in node usage.) More to the point, Neon was created in order to make frontend coding more accesible to backend developers, who already knew OO languages such as Ruby or PHP, likewise, it had the objective creating a common language in which they could communicate. Neon does not try to emulate other languages and preserves the good parts of Javascript, with a little bit of syntax sugar.