Abstract. Development of Web-applications is labor-consuming task. Difficulty is partially dependent on restrictions of scripting language JavaScript which used to program behaviour of Web-pages. The reasons of it are clear: with language complication become difficult to reveal all thin places in architecture of VM engine that can lead to user problems, in the form of trojans, viruses and other infections that use these security holes. On another hand, JavaScript obstruct a developer from writing sophisticated app with ten of KLOC as he knows at that point difficulty of maintaining increased dramatically due to lacks of type control and unhandled errors. JooScript aims to make this curve smoother by providing at start basic constructive components, metaprogramming, common errors handling and strict types, all within JavaScript bounds.
We would say that Joo is
a new language for creating web documents with almost any sort of content, from simple formatted to text to complex interactive applications,
but honestly Joo is just JavaScript on steroids. What JavaScript do, JooScript can do as well in more robustness manner.
Joo pronounced as [dʒuː], but it will no mistake saying Yoo.
JooScript currently on its own halfway. Core components and concept are mostly ready but it required disciplined work toward fixing bugs and writing documentation. We also listen to feedbacks to choose right way of futher development.
We would recommend to use Joo for complex task because it speed up creative process a lot. That is no need to worry about accidental errors, just write down your ideas.
It also has stable basis which is not going to be changed any time soon.
As for us, we used Joo for our main project so that we well tested it and carefully designed.
Int, Uint and Float.DummyXML, a more safer and widely available alternative to E4X.Signal class (message queue).Dictionary class (similar to HashMap in Java or dict in Python or arrays in Perl).Download jooscript.js and add it into your web-page:
<script type="text/javascript" src="jooscript.js"></script>
Additionaly JooScript parameters can be changed in this way:
joo_settings({
compat_mode: true,
runtime_typing: false
})
Had jooscript.js used in parallel with MooTools (known for its use global object Type as well) or other JS-framework, compatibility mode required to avoid problems. In compat mode all internal classes available only within module context. By default compat mode is on.
By default runtime typing is on and while it is useful during development cycle (you will be warned of common programmer mistakes), it is required that it will be switched off in production.
One of distant goals was ability to load third-party code within sandbox mode. To achieve this we have to keep compatibility with even outdated platforms such as old Internet Explorers. So that unless don't used fancy new property descriptors, code will be fully compliant, otherwise only Firefox will properly support all features of JavaScript 1.8 (test it).
JooScript is introducing new architecture capabilities and metaprogramming for JS developers. This approach was inspired by a few mature languages (Python one of them).
With no extra words lets look at generic example of a module skeleton:
// Create first Module
$Unit(function(unit, root, glob, None){
// Importing required classes
unit.ImportFrom("acme", ["stars.*", "beings.*", "stuff.*"])
// Create package
this.Package("acme.milkyway.solarsystem", function(solarsystem){
// Create our first class and bind it to the group namespace
solarsystem.Earth = unit.buildClass(
// class name
"Earth",
// list of ancestors
[unit.Rivers, unit.Birds, unit.Animals, unit.Human],
// Class role (specification), where are declared all class props and its configuration.
// Idea of role syntax come from ECMAScript 5'th property descriptors and mostly related
// to it as opposite values. For example "internal" descriptor = true is a shortcut
// for enumerable, writable and configurable descriptors = false, hidden = true is
// enumerable = false and so on ... By default "public" descriptor is true.
// It is also required that type of a property is defined.
// Role record can be string, number or object.
{
"name" : "String",
"earth_init": {
type: "Function",
constructor: true,
argumentType: ["String", "Number", "Number", "Number"],
defaultArguments: [1800, 70, 20],
restArgument: true,
docs: "Earth constructor"
}
},
// class body, within it this context is class prototype
function(__class__){
// initialize properties with default value (it is required)
this.name = None
// constructor
this.earth_init = function(self, name, rivers, birds, animals, args) {
// initialize ancestors
self.rivers_init(rivers)
self.birds_init(birds)
self.animals_init(animals)
self.name = name
trace(__class__.className, "was created. Rest arguments", args)
}
},
// Last but not least set a parent of the Earth
unit.Sun
);
},{
// Export list
Earth : "Class"
})
// Finally, test our example
unit.Import(["acme.milkyway.solarsystem.Earth"])
// Create a new instance of the Sun
var star = unit.Sun()
// Create a new instance of the Earth
var earth = unit.Earth("Terra", 5300, 940, 520, 80, 15)
var man = earth.createHuman()
trace("His name is " + man.name)
setTimeout(function(){
// do nothing
}, 60*60*24*7)
})
(run it)
Although the code above is an intermediate layer between JooScript and JavaScript, jooscript.js can be used as standalone library without compiler. And in Joo syntax my skeleton will looks like this:
from acme import stars.*, beings.*, stuff.*
package acme.milkyway.solarsystem
{
export Earth
class Earth(Rivers, Birds, Animals, Human) childof Sun {
role Earth {
public prop name typeof String: "",
public constructor earth_init( name typeof String,
rivers typeof Number,
birds typeof Number,
animals typeof Number ):
"Earth constructor"
}
name = None
function earth_init(self, name, rivers=1800, birds=70, animals=20, *args){
self.rivers_init(rivers)
self.birds_init(birds)
self.animals_init(animals)
self.name = name
trace("Earth was created. Rest arguments", args)
}
}
}
// Finally, test our example
import acme.milkyway.solarsystem.Earth
// Create a new instance of the Sun
let star = Sun()
// Create a new instance of the Earth
let earth = Earth("Terra", 5300, 940, 520, 80, 15)
let man = earth.createHuman()
trace("His name is " + man.name)
setTimeout(function(){
// do nothing
}, 60*60*24*7)
(run it) - sadly, compiler is not ready yet.
Types in JooScript are the result of several years of strong effort and research. We believe that this work will be extensively used by many developers.
Types in JooScript are feature of language design, that is not a hack. Types has static nature, that is once defined, it cannot be substituted.
Since the purpose of this work were fully pragmatic, there is no academic paper which would describe internal mechanic. For technical details please refer to source code.
Here is a simple example of "typed properties":
$Unit(function(unit, _, _, None)
{
// debug helper
function raises(f){
try{
f()
} catch(e) {
trace(e)
}
}
// i will create new class with virtual props "name" and "age",
// note that getter and setter have to be name of internal function
unit.buildClass("Person", [],
{
person_init: {
type: "function",
constructor: true
},
name: {
type: "string",
get: "get_name",
set: "set_name"
},
age: {
// i used uint type for age to catch arithmetic errors,
// e.g. function returned unexpected NaN (which has Number type by the way)
type: "uint",
get: "get_age",
set: "set_age"
}
},
function()
{
// private
var roster = []
// public
this.person_init = function(self, name)
{
self.id = roster.length
roster.push({name: None, yearOfBirth: None})
self.name = name
}
this.get_name = function(self)
{
return roster[self.id].name
}
this.set_name = function(self, name)
{
roster[self.id].name = name
}
this.get_age = function(self)
{
var age = (new Date).getFullYear() - roster[self.id].yearOfBirth
// test for age is not NaN
return age && Uint(age)
}
this.set_age = function(self, age)
{
roster[self.id].yearOfBirth = (new Date).getFullYear() - age
}
})
var bob
// Person constructor required at least one passed argument,
// so that next call will raise an error
raises(function(){
bob = unit.Person()
})
bob = unit.Person("Bob")
// if we are trying to get Bob's age before setting his
// date of birth, we got only type exception due to calculation error
raises(function(){
bob.age
})
// in the same time assigning value with inappropriate type will fail
raises(function(){
bob.age = 56
})
// age property required integer number to assign
bob.age = Uint(56)
// check results
trace(bob.name, bob.age)
})
(run it)
Any object in JooScript can be a first-class type. For example, to cast anonymous function to the class type, can be used following code:
// class Anonymous
function Anonymous(context, args){
// to check whether function is called from class constructor
// (it is required to keep parenting chain intact)
if( context === Anonymous ) {
this.abc = args[0]
}
}
// substitute function constructor with class constructor
Anonymous.new = Class.new
// register class to change object type
Class.registerClasses([Anonymous])
// finally static method 'new()' is a class constructor, and Anonymous is a new unique type
var anon = Anonymous.new("abc")
trace( Type.is_a(Anonymous, Class) ) // => true
trace( Type.is_a(anon, Anonymous) ) // => true
(run it)
In JooScript has been implemented appropriate messages queue as implementation of this idea:
when "signalname" do something
once "signalname" in thread do somewhat
raise "signalname"
Compiler will translate Joo syntax into JavaScript ...
Signal.when("signalname", something)
Signal.once.call(thread, "signalname", somewhat)
Signal.raise("signalname")
Where something and somewhat is a signal processors and thread is a class instance.
A cute demonstration of using Signal class as ancestor:
$Unit(function(unit) {
// She is Alice
unit.buildClass("Alice", [unit.Signal],
{
"alice_init": {
type: "Function",
constructor: true
}
},
function() {
this.alice_init = function(self){
self.signal_init()
self.addSignalListener("sayhello", function(msg){
if( Type.is_a( msg.from, unit.Bob ) ) {
trace("Alice: Hello!")
msg.from.raise("hello")
}
})
}
})
// He is Bob
unit.buildClass("Bob", [unit.Signal],
{
"bob_init": {
type: "Function",
constructor: true
}
},
function() {
this.bob_init = function(self, she){
self.signal_init()
self.addSignalListener("hello", function(msg){
trace("Bob: That's all she said.")
})
she.raise({type:"sayhello", from:self})
}
})
// They have a short conversation
unit.Bob( unit.Alice() )
})
(run it)
You can use also global events contentloaded and initialize:
Signal.once("contentloaded", function(){
// fired once after DOMContentLoaded event
})
Signal.once("initialize", function(){
// fired once after DOMContentLoaded event and after all
// internal initialization routines
})
Note: if application running on Node these events will be triggered only internally. You need to dispatch them at the bottom of script. As for browser, module will be called after contentloaded and package after initialize signal.
One more thing is that signals are fired immediately, that is they are not postponed.
In sum, using Signals are good way to avoid regression while internal logic changing frequently. Just open new channel and send messages between different persons of your program. If they were not respond, it's not a big problem.
Dictionary is a new underlying structure which combined features both of array and object. Similar type can be found in PHP, Perl and Python. Now in JavaScript.
$Unit(function(unit)
{
// Create dict from pairs [key, value]
var lib = unit.Dictionary([
["0345453743", {
title: "The Ultimate Hitchhiker's Guide to the Galaxy",
author: "douglasadams"}],
["0545139708", {
title: "Harry Potter and the Deathly Hallows",
author: "jkrowling"}],
["014028334X", {
title: "One Flew Over the Cuckoo's Nest",
author: "kenkesey"}],
])
// Create from object
var authors = unit.Dictionary({
"douglasadams": {
"fullname": "Douglas Adams"
},
"jkrowling": {
"fullname": "J.K. Rowling"
}
})
function searchBook(queryISBN)
{
var result
for( var i=0; i<lib.length; i++)
{
var bookISBN = lib.keyAtIndex(i)
var book = lib.valueAtIndex(i)
// Important difference from array is that dict returns exception if such
// entry were not found
var author
try {
author = authors.__get__(book.author).fullname
} catch(e) {
if( unit.Type.is_a(e, unit.KeyError) ){
author = "Unknown"
} else {
throw e
}
}
if( bookISBN == queryISBN )
{
result = {
author: author,
title: book.title
}
}
}
return result
}
var book = searchBook("014028334X")
trace("Author:", book.author)
trace("Title:", book.title)
})
(run it)
DummyXML allow construct XML from objects and read XML streams easily than ever.
$Unit(function(unit)
{
unit.Import(["dummyxml.*"])
XML.prettyPrinting = false
var contacts = unit.Dictionary({
"Milagros Pokorny": "poko1004@wp.pl",
"Guy Rippe": "rippy@foolkick.com",
"Erik Leja": "leja@lazyelephant.org"
})
unit.contacts_count = contacts.length
var xml = unit.DummyXML("<contacts total={contacts_count}/>")
contacts.forEachPair(function(pair){
var name = pair[0]
var email = pair[1]
var person = xml.append("<person/>")
person.append("<name/>").append(name)
person.append("<email/>").append(email)
})
xml = unit.DummyXML(xml.toXMLString())
trace("Total contacts:", xml.attr("total"))
xml.forEachElm(function(person){
trace("Name:", person.elm("name").text())
trace("Email:", person.elm("email").text())
})
})
(run it)
Although we plan to extend it with some great ideas from Curl[2], DummyXML now usable for basic things like producing and reading HTML and XML documents.
To use DummyXML, load dummyxml.js package into your Web-page.
Because Joo is written in JavaScript it can be used to perform both client side and server side processing. For example it can be used for serving HTML documents on Node.js instance.
We could cite the Curl authors[2]:
Producing an interactive program/document is much easier when the appropriate level of abstraction can be used for all components without having to cross language and representation barriers.
This is about Joo: we are trying to incorporate language and representation and solve conflicts between them in a natural and unobtrusive way.
Plus W3 shim for Internet Explorer. Call w3c(node) to be consistent.
For quick start just load jooscript_debug.php into web-page.
For compiling you need basic GNU tools (it`s available in any Linux distro by default) and UglifyJS. Then type in command line:
git clone git://github.com/buzzilo/jooscript-basics.git
cd jooscript-basics
make
make tests
You are ready!
JooScript released under the MIT License.
Wanna to know more? Read the sources! I'm seriously, source code are worth to reading if you want to figure out how it works. I do my best but unfortunately I cannot spend half of my time on documenting all of features.
MiniChangeLog 7cc1cbe Evgeny 2011-09-02 Updated tests; clean up and release 0.5 alpha 6 9d1c4bc Evgeny 2011-09-02 Added Function::bind shim f8b678a Evgeny 2011-09-01 Implemented XML flags ignoreComments and prettyPrinting d4fc5f6 Evgeny 2011-09-01 Updated initialization routine 70f61d0 Evgeny 2011-09-01 Group w3c renamed to org.w3 598dd9d Evgeny 2011-08-31 Store ancestors in Dict 348c5ae Evgeny 2011-08-31 Fixed Float repr; added virtual object property (with getter and setter) 7fb5ac3 Evgeny 2011-08-04 Added XML.ignoreWhitespace instruction 02dceda Evgeny 2011-08-03 Removed debug hints 2e4c8e0 Evgeny 2011-08-03 Joo and Node became friends