JavaScript, Dart/Chrome and TIScript/Sciter comparison

JavaScript Dart TIScript

Code embedding

<script src='program.js'></script>
// Note: This will only work in Dartium (a build of
// Chromium with Dart VM)
<script type='application/dart' src='program.dart'>
</script>

// Also, you'll need this to kickstart the Dart engine.
<script type='text/javascript'>
  if (navigator.webkitStartDart) {
    navigator.webkitStartDart();
</script>
<script type='text/tiscript' src='program.js' />

Printing to the console

console.log('Level completed.');
print('Level completed.');
stdout.println("Level completed.");
stdout.printf("Level %d completed.\n", levelNo);
// use of stringizer
stdout.$n(Level {levelNo} completed.");

Code modularity

Define a library

// No native implementation.
// Consider require.js and AMD
// declares the following is in the animals library
#library('animals');

class Dog {
  noise() => 'BARK!';
}
// declares the following in the animals namespace, 
// file animals.tis:
namespace animals {

  class Dog {
    function noise() { return "BARK!"; }
  }

}

Use a library

// No native implementation.
// Consider require.js and AMD
#import('animals');
var fido = new Dog();

// prefixes are supported to avoid namespace collisions
#import('animals', prefix: 'pets');
var fido = new pets.Dog();
include("animals.tis");
var fido = new animals.Dog();

// to avoid namespace collisions include the file in
// other namespace
namespace Zoo {
  include("animals.tis");
}
var fido = new Zoo.animals.Dog();

Variables

Create + assign variable

var myName = 'Aaron';
// Dart variables can be typed...
String myName = 'Aaron';

// but they don't need to be
var myOtherName = 'Aaron';
var myName = 'Aaron';

Hoisting

// JavaScript "hoists" variables to the top of
// their scope.  So the following function:
function printName() {
  console.log('Hello, ' + name);
  var name = 'Bob';
}

// is equivalent to this function:
function printName() {
  var name;
  console.log('Hello, ' + name);
  name = 'Bob';
}

printName();
// Hello, undefined
// Dart does not hoist variables.  The following
// method will issue a compilation error 
// of "cannot resolve name"
printName() {
  print('Hello, $name'); // compilation error here
  var name = 'Bob';
}
// TIScript does not hoist variables.  The following
// method will issue a runtime error 
// of "Variable 'name' not found"
function printName() {
  stdout.printf("Hello, %s\n",name); // error here
  var name = "Bob";
}

Final variables

// no support
final name = 'Bob';

// you can combine types and final
final String name = 'Bob';

// Trying to reassign a final variable raises an error
name = 'Alice';
// ERROR: cannot assign value to final variable
const name = "Bob";

// Trying to reassign a const raises an error
name = "Alice";
// ERROR: cannot assign to read-only variable

Collections

Arrays / Lists

var a = new Array();
var a = [];
var a = [1, 2, 3];
var a = new List();
var a = [];
var a = [1, 2, 3];
// same as in JavaScript.
// No need for new entities here.
var a = new Array();
var a = [];
var a = [1, 2, 3];
var a = ["apple", "banana", "cherry"];

// returns the new length of the array
a.push("donut");
// == 4

a.length;
// == 4

a.pop();
// == "donut"
var a = ['apple', 'banana', 'cherry'];

// returns null
a.add('donut');
// == null

a.length
// == 4

a.removeLast()
// == 'donut'
// same as in JavaScript.
var a = ["apple", "banana", "cherry"];
// returns the new length of the array
a.push("donut");
// == 4
a.length;
// == 4
a.pop();
// == "donut"

Custom sort

var numbers = [42, 2.1, 5, 0.1, 391];
numbers.sort(function(a, b) {
  return a - b;
});
// == [0.1, 2.1, 5, 42, 391]
var numbers = [42, 2.1, 5, 0.1, 391];
numbers.sort((a, b) => a - b);
// == [0.1, 2.1, 5, 42, 391];
// use of short anonymous function notation:
var numbers = [42, 2.1, 5, 0.1, 391];
numbers.sort( :a, b: a - b );
// == [0.1, 2.1, 5, 42, 391]

Key-value pairs

// An empty key-value pair can be declared
// in two different ways in JavaScript
var periodic = new Object();
var periodic = {};
// Dart has a Map interface (interface?) 
// for key-value pairs
var periodic = {};
var periodic = new Map();
// Same as in JavaScript
var periodic = new Object();
var periodic = {};

Appropriate keys

var periodic1 = {
  gold: 'AU',
  silver: 'AG'
};

var periodic2 = {
  'gold': 'AU',
  'silver': 'AG'
};
// Keys in Dart must be string literals
var periodic = {
  'gold' : 'AU',
  'silver' : 'AG'
};
// keys - symbols
var periodic1 = {
  gold: "AU",
  silver: "AG"
};
// keys - strings
var periodic2 = {
  "gold": "AU",
  "silver": "AG"
};
// symbol and string are two distinct types.

Accessing values

periodic1.gold // == 'AU'
periodic1['gold'] // == 'AU'

periodic2.gold = 'Glitter';
periodic2['gold'] = 'Glitter';
// Values can only be 'get' or 'set' by using 
// the square bracket notation.  
// Dot notation does not work

periodic['gold'] // == 'AU'

periodic['gold'] = 'Glitter';
periodic1.gold // == "AU"
periodic1["gold"] // == undefined
periodic2.gold // == undefined
periodic2["gold"] // == "AU"

// replaces old value
periodic1.gold = "Glitter";
// creates new entry under "gold" key
periodic1["gold"] = "Glitter";

Sets (collections of unique items)

// no native JavaScript equivalent
var fruits = new Set();
fruits.add('oranges');
fruits.add('apples');
fruits.length // == 2
// duplicates existing item:
fruits.add('oranges');
fruits.length // == 2
var fruits = {};
fruits["oranges"] = true;
fruits["apples"] = true;
fruits.length // == 2
// duplicates existing item:
fruits["oranges"] = true;
fruits.length // == 2

Queues (FIFO)

var queue = [];
queue.push('event:32342');
queue.push('event:49309');

console.log(queue.length); // 2

var eventId = queue.shift();

console.log(eventId === 'event:32342');
console.log(queue.length); // 1
// Queues are optimized for removing items
// from the head
var queue = new Queue();
queue.add('event:32342');
queue.add('event:49309');

print(queue.length);  // 2

var eventId = queue.removeFirst();

print(eventId == 'event:32342'); // true
print(queue.length); // 1
var queue = [];
queue.push("event:32342");
queue.push("event:49309");

stdout.println(queue.length); // 2

var eventId = queue.shift();

stdout.println(eventId === "event:32342");
stdout.println(queue.length); // 1

Strings

Raw strings

// JavaScript does not have 'raw' strings.
// All escaping must be done manually.
var escapedString = 'A tab looks like \\t';
var rawString
    = @'The following is not expanded to a tab \t';
var escapedString
    = 'The following is not expanded to a tab \\t';

rawString == escapedString // == true
// TIScript does not have 'raw' strings.
// All escaping must be done manually.
var escapedString = 'A tab looks like \\t';

Interpolation

var name = 'Aaron';
var greeting = 'My name is ' + name;

var greetingPolish
    = 'My Polish name would be ' + name + 'ski';

element.style.top = (top + 20) + 'px';
var name = 'Aaron';
var greeting = 'My name is $name.';

var greetingPolish
    = 'My Polish name would be ${name}ski.';

// calculations can be performed 
// in string interpolation
element.style.top = '${top + 20}px';
// TIScript has "stringizer" functions -
// string literal combined with function call:

var name = 'Aaron';
var greeting = String.$(My name is {name}.);

var greetingPolish
    = String.$(My Polish name would be {name}ski.);

// calculations can be performed 
// in string interpolation
element.style.top = String.$({top + 20}px);

Substring

'doghouses'.substring(3, 8) // == 'houses'
'doghouses'.substr(3, 5) // == 'houses'
'doghouses'.substring(3, 8); // == 'houses'
// Good old JS:
"doghouses".substring(3, 8) // == "houses"
"doghouses".substr(3, 5) // == "houses"

Replace all occurences

'doghouses'.replace(/s/g, 'z') // == 'doghouzez'
'doghouses'.replaceAll('s','z'); // == 'doghouzez'
"doghouses".replace(/s/g, "z") // == 'doghouzez'
"doghouses".replace("s", "z") // == 'doghouzez'

Replace one occurence

'racecar'.replace(/r/, 'sp') // == 'spacecar'
'racecar'.replaceFirst('r', 'sp'); // == 'spacecar'
"racecar".replace(/r/, "sp") // == "spacecar"

Multi-line strings

var string =
    'This is a string that spans\n' +
    'many lines.\n';
// Dart ignores the first new-line 
// (if it is directly after the quotes), 
// but not the last.
var string = '''
This is a string that spans
many lines.
''';
// consequent string literals are collapsed
// into single literal:
var string1 =
    "This is a string that spans\n"
    "many lines.\n";
// string literals can span multiple lines:
var string2 =
"This is a string that spans
many lines.
";

Split into array

var animals = 'dogs, cats, gophers, zebras';
var individualAnimals = animals.split(', ');
// == ['dogs', 'cats', 'gophers', 'zebras'];
var animals = 'dogs, cats, gophers, zebras';
var individualAnimals = animals.split(', ');
// == ['dogs', 'cats', 'gophers', 'zebras'];
var animals = "dogs, cats, gophers, zebras";
var individualAnimals = animals.split(", ");
// == ['dogs', 'cats', 'gophers', 'zebras'];

Test whether a string starts with a substring

// JavaScript has no built-in startsWith function
String.prototype.startsWith = function(beginning)
{
  var head = this.substr(0, beginning.length);
  return head == beginning;
}

'racecar'.startsWith('race') // == true
'racecar'.startsWith('pace') // == false
// Dart string objects have a 
// built-in startsWith method
'racecar'.startsWith('race'); // == true
'racecar'.startsWith('pace'); // == false
"racecar".indexOf("race") == 0; // == true
"racecar".indexOf("pace") == 0; // == false

// If someone wants exactly startsWith method then
// String class can be extended by such method:

function String.startsWith(str) {
  return this.indexOf(str) == 0;
}

Booleans

If statement

var bugNumbers = [3234,4542,944,124];
if (bugNumbers.length > 0) {
  console.log('Not ready for release');
}
var bugNumbers = [3234,4542,944,124];
if (bugNumbers.length > 0) {
  print('Not ready for release');
}
var bugNumbers = [3234,4542,944,124];
if (bugNumbers.length > 0) {
  stderr.println("Not ready for release");
}
// or just
if (bugNumbers)
  stderr.$n(Not ready for release);

Ternary statements

var bugNumbers = [3234,4542,944,124];
var status = bugNumbers.length > 0 
    ? 'RED' : 'GREEN';

console.log('The build is ' + status);
var bugNumbers = [3234,4542,944,124];
var status = bugNumbers.length > 0
    ? 'RED' : 'GREEN';

print('The build is $status');
var bugNumbers = [3234,4542,944,124];
var status = bugNumbers ? "RED" : "GREEN";

stdout.$n(The build is {status});

Checking for empty string

var emptyString = '';

if (!emptyString) {
console.log('empty strings are' +
            ' treated as false');
}
var emptyString = '';

if (emptyString.isEmpty()) {
  print('use isEmpty()');
}
var emptyString = "";

if (!emptyString) {
  stdout.$n(empty strings are treated as false);
}

Checking for zero

var zero = 0;

if (!zero) {
  console.log('0 is treated as false');
}
var zero = 0;

if (zero == 0) {
  print('use == 0 to check zero');
}
var zero = 0;

if (!zero) {
  stdout.$(0 is treated as false);
}

Checking for null

var myNull = null;

if (!myNull) {
  console.log('null is treated as false');
}
var myNull = null;

if (myNull == null) {
  print('use == null to check null');
}
var myNull = null;

if (!myNull)
  stdout.$n(null is treated as false);

Checking for NaN

var myNaN = NaN;

if (!myNaN) {
  console.log('NaN is treated as false');
}
var myNan = 0/0;

if (myNaN.isNaN()) {
  print('use isNaN to check if a number is NaN');
}
var myNaN = 0.0/0.0;

if (myNaN.isNaN())
  stdout.println("use Float.isNaN()");

Checking for undefined

var isUndefined;

if (!isUndefined) {
  console.log('undefined is treated as false');
}
// Dart does not have a concept of undefined
var isUndefined;

if (!isUndefined)
  stdout.println("undefined is treated as false");
if (isUndefined === undefined)
  stdout.println("test for exactly undefined");

Value and identity equality

var letterA = 'A';
var charA = String.fromCharCode(65);

// JavaScript converts both values to the
// same type before checking their value
// with 'double equals'.
letterA == charA // == true

// Similarly...
var number5 = 5;
var char5 = '5';

// This comparison triggers type conversion
number1 == char1 // == true

// 'triple equals' checks type and value
letterA === charA // == true
number5 === char5 // == false
// NOTE: the follow is BRAND NEW and implementations
// do not yet match the below semantics. The below is
// how it will work.

/*
 * In Dart, == will check the following, in order:
 * 1) if x === y then return true (=== is identity).
 *    Otherwise:
 * 2) if either x or y is null, return false. Otherwise
 * 3) return the result of x.equals(y) 
 *    (pending: might become eq())
 */

/*
 * This means:
 * a) use x == null instead of x === null
 * b) there's almost no reason to every use === or !==
 * c) when implementing equals() you don't have to worry
 *    about manually checking for null arg
 * d) you can't define your own type that considers itself
 *    equal to null
 * e) null == null
 */

// therefore, the following code will work:

var letterA = 'A';
var charA = new String.fromCharCodes([65]);

// String defines equals() as
// 'same character codes in same order'
letterA == charA // == true

// However, the following is different than JavaScript

var number5 = 5;
var char5 = '5';

number1 != char1 // == true, because of different types
var charcodeA = 'A'; // integer equal to UNICODE
                    // codepoint of 'A'
var letterA = "A";
var charA = String.fromCharCode(65);

// letterA and charA are strings
// but unicodeA is an integer:
letterA == charA // == true
charcodeA == charA // == false, different types

// Similarly...
var number5 = 5;
var char5 = "5"; // note, string

// This comparison does not trigger
// type conversion:
number1 == char1; // == false

Functions

Function definition

function fn() { return 'Hello'; }
fn(); // == 'Hello'

(function(){})() // == returns undefined
// Like JavaScript, use the 'return' keyword in a function
// definition to return a value.
fn() { return 'Hello'; }
fn(); // == 'Hello'

// A function with no return value returns null.
((){})(); // == returns null

// if the body of the function is returning
// a single expression,
// this is the short form
fn() => true;
function fn() { return "Hello"; }
fn(); // == 'Hello'

(function(){})() // == returns undefined

Assign a function to a variable, anonymous functions

var loudify = function(msg) {
  return msg.toUpperCase();
}

loudify('not gonna take it anymore');
// == NOT GONNA TAKE IT ANYMORE
var loudify = (msg) => msg.toUpperCase();

loudify('not gonna take it anymore');
// == NOT GONNA TAKE IT ANYMORE
// TIScript has three forms of
// defining anonymous functions,
// 1. Single expression function:
var loudify1 = : msg : msg.toUpperCase();

// 2. Block of expressions function:
var loudify2 = : msg { return msg.toUpperCase(); }

// 3. Statndard JS way:
var loudify3 = function(msg)
               { return msg.toUpperCase(); }

loudifyN("not gonna take it anymore");
// == NOT GONNA TAKE IT ANYMORE

Optional parameters

function fn(a, b, c) { return c; };

fn(1) // == undefined
fn(1, 2, 3) // == 3
fn(a, b, c) => c;

fn(1); // ERROR: NoSuchMethodException
fn(1, 2, 3); // == 3

// Dart specifies optional parameters with square braces
fn(a, [b, c]) => c;
fn('a'); // == null
// Same as in JS
function fn(a, b, c) { return c; };

fn(1) // == undefined
fn(1, 2, 3) // == 3

Default parameters

function send(msg, rate) {
  rate = rate || 'First Class';
  return msg + " was sent via " + rate;
}

send('hello')
// == 'hello was sent via First Class'
send("I'm cheap", '4th class')
// == "I'm cheap was sent via 4th class
send(msg, [rate='First Class']) {
  return '${msg} was sent via ${rate}';
}

send('hello');
// == 'hello was sent via First Class'
send("I'm cheap", '4th class');
// == "I'm cheap was sent via 4th class"
// JavaScript version will work as it is.
// As also this one:

function send(msg, rate = "First Class") {
  return String.$({msg} was sent via {rate});
}

send("hello")
// == 'hello was sent via First Class'
send("I'm cheap", "4th class")
// == "I'm cheap was sent via 4th class

Named parameters

// JavaScript does not have
// native support for named parameters
send(msg, [rate='First Class']) {
  return '${msg} was sent via ${rate}';
}

// you can use named parameters
// if the argument is optional
send("I'm cheap", rate:'4th class');
// == "I'm cheap was sent via 4th class"
// TIScript has special notation for
// calling functions that accept single
// object parameter:

function send(params) {
  var rate = params.rate || "First Class";
  return String.$({params.msg} sent {rate});
}

// Short form - call-with-object:
send {msg:"I'm cheap",rate:"4th class"};

Variable number of arguments

function superHeroes() {
  for (var i = 0; i < arguments.length; i++) {
    console.log("There's no stopping "
                + arguments[i]);
  }
}

superHeroes('UberMan',
           'Exceptional Woman',
           'The Hunk');
// Dart does not support 
// variable numbers of arguments
// If last parameter is marked by '..'
// then it will take array of values passed: 

function superHeroes(msg, heroes..) {
  for (var hero in heroes)
    stdout.println(msg, hero);
}

superHeroes(
    "There's no stopping",
    "UberMan",
    "Exceptional Woman", 
    "The Hunk");

Iterators

For loops for lists

var colors = ['red', 'orange', 'green'];

for (var i = 0; i < colors.length; i++) {
  console.log(colors[i]);
}
var colors = ['red', 'orange', 'green'];

for (var i = 0; i < colors.length; i++) {
  print(colors[i]);
}
var colors = ["red", "orange", "green"];

for (var i = 0; i < colors.length; i++)
  stdout.println(colors[i]);

For-in loops

var fruits = ['orange', 'apple', 'banana'];

// 'in' notation in JavaScript returns the
// indices of the array, not the values

for (var i in fruits) {
  console.log(fruits[i]);
}
var fruits = ['orange', 'apple', 'banana'];

// 'in' notation in Dart returns the element
// of the list, not the index

for (var fruit in fruits) {
  print(fruit);
}
var fruits = ['orange', 'apple', 'banana'];

// 'in' notation in TIScript returns the
// element of the list, not the index

for (var fruit in fruits)
  stdout.println(fruit);

// If indexes are needed then use this form
for (var (i,fruit) in fruits)
  stdout.$({i}. {fruit});

For-in loops for objects/maps

var data = { … };

for (var key in data) {
  console.log('key', key);
  console.log('value', data[key]);
}
var data = { … };

for (var key in data.getKeys()) {
  print('$key, ${data[key]}');
}

// Alternatively, the forEach loop
// is a method on a Map in Dart.

data.forEach((key, value){
  print('${key}, ${value}');
});
//JavaScript code will work as it is in TIscript

//And to enumerate keys and values
//at the same time use this form
for (var (key,val) in data) {
  stdout.print("key", key);
  stdout.println("value", val);
}

Closures and counters in loop

var callbacks = [];

// A closure must be used to preserve the 
// return for each function at each step of 
// the loop. Otherwise every entry in callbacks 
// will return 2;
for (var i = 0; i < 2; i++) {
  (function(_i) {
    callbacks.push(function() {
      return _i;
    });
  })(i);
}

// Without the internal closure, the result is 2
callbacks[0]() // == 0

// ECMAScript 6 can support this with the use
// of blocks
let callbacks = [];
for (let i = 0; i < 10; i++) {
  let j = i;
  callbacks.push(function() { print(j) });
}
// Dart doesn't reuse and close over the same
// loop variable in each iteration
var callbacks = [];
for (var i = 0; i < 2; i++) {
  callbacks.add(() => print(i));
}

callbacks[0]() // == 0
var callbacks = [];

// A closure must be used to preserve the return for
// each function at each step of the loop.  Otherwise
// every entry in callbacks will return 2;

for (var i = 0; i < 2; i++)
  (:_i:callbacks.push(::_i))(i);

// Without the internal closure, the result is 2
callbacks[0]() // == 0

Classes

Define

function Person() {
  this.name = null;
};

Person.prototype.greet = function() {
  return 'Hello, ' + this.name;
}
class Person {
  var name;

  greet() => 'Hello, $name';
}
class Person {
  function this() // constructor
    { this.name = null; }

  function greet()
    { return String.$(Hello,{this.name});
}

Constructor with parameter

function Person(name) {
  this.name = name;
};
class Person {
  var name;

  Person(name) {
    this.name = name;
  }
}

// shorter alternative

class Person {
  var name;

  // parameters prefixed by 'this.' will
  // assign to instance variables automatically
  Person(this.name);
}
class Person {
   function this(name)
     { this.name = name; }
}

Instantiate

var person = new Person();
var person = new Person();
var person = new Person();

Reflection

var name = 'Bob';
typeof name // == 'String'
// There currently is no way to get the class of an
// object. Reflection support coming soon.
var name = "Bob";
typeof name; // == #string
name.prototype === String; // true
name instanceof String; // true

Check the type

var name = 'Bob';

name instanceof String // == true

(!(name instanceof Number)) // == true
var name = 'Bob';

name is String // == true

name is! int // == true
var name = "Bob";

name instanceof String; // == true

name !instanceof Integer; // == true

Subclass

function Person(name) {
  this.name = name;
}

Person.prototype.greet = function() {
  return 'Hello, ' + this.name;
}

function Employee(name, salary) {
  Person.call(this, name);
  this.salary = salary;
}

Employee.prototype = new Person();
Employee.prototype.constructor = Employee;

Employee.prototype.grantRaise = function(percent){
  this.salary = (this.salary * percent).toInt();
}
class Person {
  var name;

  Person(this.name);

  greet() => 'Hello, $name';
}

class Employee extends Person {
  var salary;

  Employee(name, this.salary) : super(name);

  grantRaise(percent) {
    salary = (salary * percent).toInt();
  }
}
class Person {

  function this(name) { this.name = name; }

  function greet() {
    return String.$(Hello, {name});
}

class Employee: Person {

  function this(name, salary) {
    super(name); // call of ctor of super class
    this.salary = salary;
  }

  function grantRaise(percent) {
    this.salary
      = (this.salary * percent).toInteger();
  }
}

Runtime program manipulation

eval

eval('alert("hello from eval")');
// Dart doesn't support eval(). This is not a bug.
var script = "alert(\"hello from eval\")";
// general case:
eval(script);

// controlled eval:
eval(script,
   {
     alert: function() {/*nothing!*/},
     self: null,
     document: null,
     window: null
   });
// the object passed serves role of global
// namespace inside the script.

Adding method to class or namespace

String.prototype.startsWith = function(beginning){
  var head = this.substr(0, beginning.length);
  return head == beginning;
}
// Dart doesn't support changing a class after
// the program has been compiled
// adding method startsWith to all strings:

function String.startsWith(head)
{
  return this.indexOf(head) == 0;
}
function String.endsWith(tail)
{
  return this.indexOf(tail) ==
      this.length - tail.length;
}

Operator overloading

// not supported
class Point {
  var x, y;
  Point(this.x, this.y);

  operator + (Point p) => new Point(x + p.x, y + p.y);

  toString() => 'x:$x, y:$y';
}

main() {
  var p1 = new Point(1, 1);
  var p2 = new Point(2, 2);
  var p3 = p1 + p2;
  print(p3);
}
// not supported yet

Finding elements in DOM

document.getElementById('main')

document.querySelector('#main')
document.query('#main')
self.select("#main")

self.$(#main) // stringizer form of the select

Find one element by class

document.getElementsByClassName('visible')[0]

document.querySelector('.visible')
document.query('.visible')
self.select(".visible")

self.$(.visible)

Find all elements with class

document.getElementsByClassName('visible')

document.querySelectorAll('.visible')
document.queryAll('.visible')
// Methods return array of element references:

self.selectAll(".visible");

self.$$(.visible);

Find elements by tag, name and class

document
  .getElementById('main')
  .getElementsByTagName('div')[0]
  .getElementsByClassName('visible')

document.querySelectorAll
   ('#main div:first-of-type .visible')
document.queryAll
    ('#main div:first-of-type .visible')
self.selectAll
     ("#main div:first-of-type .visible")

self.$$(#main div:first-of-type .visible)

Get sub-element by selector

// no such feature, 
// consider use jQuery find() method.
// no such feature
elem.selectAll("li");
elem.$$(li);
// in select's the :root selector
// designates "lookup root" so to
// select immediate childern of the element
// use one of these:
elem.selectAll(":root > li");
elem.$$(:root > li);

Iterate over list of elements

for (var i = 0, el; el = els[i]; i++) {
  doSomethingWithEl(el);
}
for (var el in els) {
  doSomethingWithEl(el);
}
for (var el in els) {
  doSomethingWithEl(el);
}

Access the first child

elem.firstChild()
elem.nodes[0]
// Element is an array of its children, so:

elem[0]

Test if an element has children elements

elem.hasChildNodes() // not exactly actually
!elem.nodes.isEmpty()
// Element is an array of its children, so:

elem.length > 0;

Manipulating DOM

Create an element

var element = document.createElement('div');
#import('dart:html');

var element = new Element.tag('div');
var element = new Element("div");

Set element content

var element = document.createElement('p');
element.innerHTML
  = 'brown <em>fox</em>';
var element =
  new Element.html('<p>brown <em>fox</em></p>');
var element = new Element("p");
parent.append(element);

element.html = "<p>brown <em>fox</em></p>"; // or
element.$content(<p>brown <em>fox</em></p>);

Add an element to a parent

element.appendChild(newElement);
element.nodes.add(newElement);
element.append(newElement);
element.prepend(newElement);
element.insert(12,newElement);

element.$append(<p>...</p>);
element.$prepend(<p>...</p>);

// add the markup after the element
element.$after(<p>...</p>);
// insert the markup before the element
element.$before(<p>...</p>);

Remove an element

element.parentNode.removeChild(element);
element.remove();
element.remove();

Regular expressions

var email = 'test@example.com';
email.match(/@/)
// == ['@']
var email = 'test@example.com';
(new RegExp(@'o')).firstMatch(email)
// == Match Object
var email = 'test@example.com';
email.match(/@/)
// == ['@']
var invalidEmail = 'f@il@example.com';
invalidEmail.match(/@/g)
// == ['@', '@']
var invalidEmail = 'f@il@example.com';
(new RegExp(@'o')).allMatches(invalidEmail)
// == Iterable Match Object
var invalidEmail = 'f@il@example.com';
invalidEmail.match(/@/g)
// == ['@', '@']

Exceptions

Throw an exception

throw new Error("Intruder Alert!!");

// or...

throw "Intruder Alert!!";
throw new Exception("Intruder Alert!!");
throw "Intruder Alert!!";

Catch an exception

try {
  undefinedFunction();
} catch(e) {
  if (e instanceof ReferenceError) {
   console.log(
    'You called a function that does not exist');
  }
} finally {
  console.log(
   'This runs even if an exception is thrown');
}
try {
  Math.parseInt("three");
} catch(BadNumberFormatException bnfe) {
  print("Ouch! Detected: $bnfe");
} catch(var e) {
  print("If some other type of exception");
} finally {
  print("This runs even if an exception is thrown");
}
try {
  undefinedFunction();
} catch(e) {
  stderr.println(e);
} finally {
  stdout.println(
   "This runs even if an exception is thrown");
}

Event handling

Attach an event handler

element.addEventListener(
  'click', handleOnClick, false);
element.on.click.add(handleOnClick);
element.onClick = handleOnClick;

// or

element.subscribe(
    handleOnClick,
    Event.BEHAVIOR_EVENT,
    Event.BUTTON_CLICK);

// or by using decorators:

@click @on "button#some" :
{
  // do some on button#some click
}

Remove an event handler

element.removeEventListener(
  'click', handleOnClick, false);
element.on.click.remove(handleOnClick);
element.onClick = undefined;
// or
element.unsubscribe(handleOnClick);

Timing

Schedule a future event

setTimeout(function() { … }, 500);
window.setTimeout(() { … }, 500);
element.timer(500, function() {...} );
//the function will be called with 'this'
//set to the element.

Measure the execution time of a function

function measure(fn) {
  var start = Date.now();
  fn();
  return Date.now() - start;
}
measure(fn) {
  Stopwatch watch = new Stopwatch.start();
  fn();
  return watch.elapsedInMs();
}
function measure(fn) {
  // Date.ticks() - milliseconds
  //          since system start
  var start = Date.ticks();
  fn();
  return Date.ticks() - start;
}

HTML attributes

Get HTML attribute

element.getAttribute('href')
element.attributes['href']
element.attributes["href"]
// or
element.@["href"]
element.setAttribute('playable', true);
element.attributes['playable'] = true;
element.attributes["playable"] = true;
//or
element.@["playable"] = true;

Remove HTML attribute

element.removeAttribute('playable');
element.attributes.remove('playable');
element.attributes["playable"] = undefined;

Check for HTML attribute existence

element.hasAttribute('href')
element.attributes.contains('href')
element.attributes["playable"] !== undefined;

CSS classes

Add CSS class

element.className += ' new-class';

element.classList.add('new-class');
element.classes.add('new-class');
element.attributes.addClass("new-class");

Remove CSS class

element.className
   = element.className.replace(/ new-class/, '');

element.classList.remove('new-class');
element.classes.remove('new-class');
element.attributes.removeClass("new-class");

AJAX

Request data

var client = new XMLHttpRequest;

client.onreadystatechange = function() {
  if (this.readyState == 4) {
    processData(this);
  }
}
client.open('GET', 'data.json');
client.send();

function processData(request) {
  console.log('data received: '
           + request.responseText);
}
// The getTEMPNAME method name will be changed.
var xhr = new XMLHttpRequest.getTEMPNAME(
  "/data.json", (req) {
  print("data received: ${req.responseText}");
});
function onDataArrived(data) {
   // data is an object - already parsed JSON

   this.html = String.printf("Got data %V", data);

   // String.printf("%V", data) is
   // exactly JSON.stringize()
}

// sends HTTP GET request to the server,
// parses response and calls onDataArrived
// when ready:

element.request(
   onDataArrived, #get, "/data.json" );

// While the request is executing the element
// gets :busy CSS state flag. So you can define
// that spinning thing on the element.

jQuery features

React to document finishing loading

$(document).ready(function(){
  console.log('Content is loaded');
});
window.on.contentLoaded.add(
  (e) => print('Content is loaded');
);

// However, main() will normally run
// after DOMContentLoaded in Dartium,
// and soon Frog.
function self.ready()
{
   stdout.println("Content is loaded");
}

// in Sciter script code is executed only
// when DOM is complete.

// self.ready() is called after
// DOM elements will get their styles and
// all behaviors are attached.

Element lookup

var els = jQuery('div');
List els = document.query('div');
var el = $("div"); // first
var els = $$("div"); // all

Nearest parent lookup

var nearestUL = els.parent('ul');
// no such feature
var nearestUL = el.selectParent("ul"); // or
var nearestUL = el.$p(ul); 

Test if an element matches a selector

// true if element is a <li> inside 
// an unordered list
el.is('ul > li')
// no such feature
// true if element is a <li> inside 
// an unordered list
el.$is(ul > li)

Node creation

var pic = $('<img/>');

pic.addClass('avatar');
pic.toggleClass('main');

pic.attr('src', 'myPic.jpg');

$('body').append(pic);
var pic = new Element.tag('img');

pic.classes.add('avatar');
pic.classes.toggle('main');

pic.attributes['src'] = 'myPic.jpg';

document.body.nodes.add(pic);
$(body).$append(<img src='mypic.jpg'
                     class='avatar main' >);

Event handling

$('a.person').click(function(e){
  console.log('Person clicked');
})
document.queryAll('a.person').forEach((el) {
  el.on.click.add((e) => print('Person clicked'));
});

// coming soon!!
document.queryAll('a.person')
     .on.click.add((e) => print('Person clicked'));
function handler()
  { stdout.println("Person clicked"); }

for(var el in $$(a.person))
  el.onClick = handler;

Relative nodes

var myNode = $('div:first');

var parent = myNode.parent();

var next = myNode.next();
var myNode = document.query('div');

var parent = myNode.parent;

var next = myNode.nextNode;
var myNode = $(div);

var parent = myNode.parent;

var next = myNode.next;

Children

var myNode = $('div:first');

// If there are children, remove them
if (!myNode.is(':empty')) {
  myNode.empty();
}
var myNode = document.query('div');

if (!myNode.nodes.isEmpty()) {
  myNode.nodes.clear();
}
var myNode = $(div);

// If there are children, remove them
if (!myNode.$is(:empty))
  myNode.clear();

// or
if (myNode.length)
  myNode.clear();

Clone

var clonedElement = $('#about').clone();
var clonedElement 
    = document.query('#about').clone(true);
var clonedElement = $(#about).clone();

Scripting behaviors

Declaring Behavior class

// No direct equivalent, 
// consider use of XUL in FF.
// No such feature at all?
class ButtonWithPopup : Behavior
{
  // on mouse down show contained
  // <menu> element as a popup window below (2)
  // this element:
  function onMouse(evt) {
    if(evt.type == Event.MOUSE_DOWN) {
      this.popup( this.$(menu), 2);
      return true; // event handled, done
    }
  }
}

Assigning behavior to all matched DOM elements

In CSS using becss:
button.popup-menu
{
  binding:url(button-with-popup.xul);
}
// in script
button.style.binding = "button-with-popup.xul";
// No such feature at all?
// In CSS:
button.popup-menu
{
  prototype: ButtonWithPopup url(button-with-popup.tis);
}
// In script (using runtime subclassing)
for(var btn in $$(button.popup-menu))
  btn.prototype = ButtonWithPopup;