EcmaScript 6, TypeScript and JSX

EcmaScript 6

Tabris.js 2 supports all ES5 and most ES6/ES7 (aka ES2015/ES2016) features without transpilers like Babel. This includes:

NOT supported (without transpiling) are:

TypeScript

Tabris.js 2 is optimized for use with TypeScript 2. TypeScript is a type-safe dialect of JavaScript/EcmaScript and also supports ES6 module syntax (import and export statements) and async/await. A complete guide to TypeScript can be found at typescriptlang.org. As an IDE we can recommend Visual Studio Code with the tslint extension, but there are many suitable TypeScript IDEs out there.

TypeScript files have to be “transpiled” to JavaScript before execution. The compiler is included when generating a new Tabris.js app using the tabris init command, so no additional installation step is required. Simply choose TypeScript App when given the option. After the app has been generated, type npm start to serve the TypeScript code to your Tabris Developer App as JavaScript. In Visual Studio Code you can also use the preconfigured start task instead.

As long as the task is still running, changes to your TypeScript code (any .ts file in src) will be detected by the TypeScript compiler automatically. No restart is needed.

Stay safe

In TypeScript not all APIs, not even all Tabris.js APIs, are perfectly type safe. It’s therefore recommended to follow these general guidelines:

Casting: Ideally, perform instanceof checks before casting. In TypeScript, castings can fail silently.

Implicit “any”: In TypeScript a value of the type any is essentially the same as a JavaScript value. The compiler will accept any actions on this value, including assigning it to typed variables. An implicit any may occur if you do not give a type for a variable, field or parameter, and none can be inferred by assignment. Always give the type of function parameters. Fields and variables are safe only if they are assigned a value on declaration.

Widget property access: Do not use widget.set(key, value) or widget.get(key). Instead access properties directly like this: widget.key = value. You can also safely use property objects: like widget.set({key: value}) and new Widget({key: value}).

Widget event handling: Do not use widget.on(event, handler). Instead, use widget.on({event: handler}).

Widget apply method: Use widget.apply only to set properties of the base Widget class, like layoutData.

Selector API and WidgetCollection: By default the widget methods find, children, sibling return a “mixed” WidgetCollection. This means you would have to do a type check and cast to safely retrieve a widget from the collection. However, you can also use widget classes (constructors) as selectors, which results in a collection that TypeScript “knows” to only have instances of that type. In that case no cast will be necessary. Example: widget.children(Button).first('.myButton') returns a button (or null), but nothing else. It should be noted that the set method of such a WidgetCollection is still not type-aware. You can use the forEach method instead to safely set properties for all widgets in the collection.

NPM modules: The tabris module is automatically type-safe, but the same is not true for all modules that can be installed via npm. You may have to manually install declaration files for each installed npm module.

JSX

JSX is an extension to the JavaScript/TypeScript syntax that allows mixing code with XML-like declarations. Tabris 2 supports type-safe JSX out of the box with any TypeScript based project. All you have to do is name your files .tsx instead of .ts. You can then use JSX expressions to create widgets. For example…

ui.contentView.append(
  <composite left={0} top={0} right={0} bottom={0}>
    <button centerX={0} top={100} text='Show Message'/>
    <textView centerX={0} top='prev() 50' font='24px'/>
  </composite>
);

…is the same as…

ui.contentView.append(
  new Composite({left: 0, top: 0, right: 0, bottom: 0}).append(
    new Button({centerX: 0, top: 100, text: 'Show Message'}),
    new TextView({centerX: 0, top: 'prev() 50', font: '24px'})
  )
);

JSX in Tabris.js TypeScript apps follows these specific rules:

  • Every JSX element is a constructor call. If nested directly in code, they need to be separated from each other (see below).
  • Attributes can be either strings (using single or double quotation marks) or JavaScript/TypeScript expressions (using curly braces).
  • Each elements may have any number of children, all of which are appended to their parent in the given order.
  • Element names starting lowercase are intrinsic elements. These include all instantiable widget build into Tabris.js, as well as WidgetCollection. The types of these elements don’t need to be explicitly imported.
  • Element names starting with uppercase are user defined elements, i.e. any class extending a Tabris.js widget. These do need to be imported.
  • To support attribute checks on user defined elements, add a field on your custom widget called jsxProperties. The type must match the type of the first constructor parameter. You do not have to assign anything to the field.
  • While the JSX expressions themselves are type-safe, their return type is not (it’s any), so follow the instructions for casting above. It can be considered safe to use unchecked JSX expressions within widget.append(), as all JSX elements are appendable types.

Note that this is not valid:

ui.contentView.append(
  <button centerX={0} top={100} text='Show Message'/>
  <textView centerX={0} top='prev() 50' font='24px'/>
);

JSX elements that are nested directly in code must be separated like any expression, in this case by a comma:

ui.contentView.append(
  <button centerX={0} top={100} text='Show Message'/>,
  <textView centerX={0} top='prev() 50' font='24px'/>
);

To avoid this, you may wrap your widgets in a WidgetCollection. This example has the same effect as the previous:

ui.contentView.append(
  <widgetCollection>
    <button centerX={0} top={100} text='Show Message'/>
    <textView centerX={0} top='prev() 50' font='24px'/>
  </widgetCollection>
);

JSX without TypeScript

If you want to use JSX without writing TypeScript, you can still use the TypeScript compiler to convert your .jsx files to .js. Simply generate a TypeScript app and add an entry "allowJs": true in the compilerOptions object of tsconfig.json. Then change the filenames in the include object from .ts and .tsx to .js and .jsx. You may also have to adjust your linter setup, if you use any.