You are now an Ambersmith

What’s Next For You?


Having completed the first installment of the Amber tutorial, you are now a bona fide member of the Amber community — an Ambersmith! In this second installment, we shall look at using an alternative to the Web package; it’s called Silk. And we shall try our hand at vector graphics using the Athens graphics framework. (As a bonus, we’ll look at the JavaScript 3D graphics library called Three.js.)

JavaScript is the “assembly language” of the Web. Why would you want to program in assembly language?

Amber is a Web developer’s best friend

Follow the Silk Road

First, create another Amber project. Start up your application in the browser and open the Helios IDE. You will see that the Silk package is already installed. (I suggest you look at the documentation for the Silk and DOMite classes by checking off ‘Doc’.)

Let’s consider a simple little form:

<form id="myForm1">
<table>
<tr><td>Username:</td><td><input name="name"
required></td></tr>
<tr><td>Password:</td><td><input name="password"
type="password" required></td></tr>
<tr><td>Country:</td><td><select name="country">
<option value="United States">Imperialists</option>
<option value="Canada">Canuck</option>
<option value="China">Chung Kuo</option></select>
</td></tr>
<tr><td><input type="submit" value="Okay"
onclick="return doSubmit()"></td></tr>
</table>
</form>

With the Web package, you’d generate the HTML in a Widget object thus:

html form id: 'myForm1'; with: [
html table with: [
html tr with: [
html td with: [
html label with: 'Username:'].
html td with: [
html input name: 'name'; type: 'text';
at: 'required' put: 'required';
yourself]].
html tr with: [
html td with: [
html label with: 'Password:'].
html td with: [
html input name: 'password';
type: 'password';
at: 'required' put: 'required';
yourself]].
html tr with: [
html td with: [
html label with: 'Country:'].
html td with: [
html select name: 'country';
with: [self countryOptions: html];
yourself]].
html tr with: [
html td with: [
html input type: 'submit';
value: 'Okay';
onClick: [self doSubmit]]]]]

As Amber maintainer and Silk designer, Herbert Vojčík, wrote in his essay, “Silk is just too flexible,” Silk is a stream-based, DOM API-driven package that remembers where you are in the DOM node tree (i.e., cursor-based). It allows for a more elegant and easy approach to DOM manipulation.

There are many different ways to use Silk to implement the above form, as Herby discusses. My own personal preference is the following:

'#client-main' asSilk
FORM: {
'id' -> 'myForm1'.
Silk TABLE: {
Silk TR: {
Silk TD: 'Username:'.
Silk TD: {Silk INPUT: {'name'->'name'.
'required'->'required'}}}.
Silk TR: {
Silk TD: 'Password:'.
Silk TD: {Silk INPUT: {'name'->'password'.
'type'->'password'.
'required'->'required'}}}.
Silk TR: {
Silk TD: 'Country:'.
Silk TD: {Silk SELECT: {'name'->'country'.
self countryOptions}}}.
Silk TR: {
Silk TD: {(Silk INPUT: {'type'->'submit'. 'value'->'Okay'})
on: #click
bind: [:e | self doSubmit: e] }}}}.

Where:

countryOptions
^{Silk OPTION: {‘Imperialists’. ‘value’->’United States’}.
Silk OPTION: {‘Canuck’. ‘value’->’Canada’}.
Silk OPTION: {‘Chung Kuo’. ‘value’->’China’}}

It’s the cleanest and most flexible. You can extract parts of the Silk ‘stream’ into separate methods and invoke them smoothly back into the stream. For example:

'#client-main' asSilk
FORM: {
'id' -> 'myForm1'.
Silk TABLE: {
self usernameRow.
self passwordRow.
self countryRow.
self submitRow}}.
usernameRow
^Silk TR: {
Silk TD: 'Username:'.
Silk TD: {Silk INPUT: {'name'->'name'.
'required'->'required'}}}
passwordRow
^Silk TR: {
Silk TD: 'Password:'.
Silk TD: {Silk INPUT: {'name'->'password'.
'type'->'password'.
'required'->'required'}}}
countryRow
^Silk TR: {
Silk TD: 'Country:'.
Silk TD: {Silk SELECT: {'name'->'country'.
self countryOptions}}}.
submitRow
^Silk TR: {
Silk TD: {(Silk INPUT: {'type'->'submit'. 'value'->'Okay'})
on: #click
bind: [:e | self doSubmit: e] }}

Note that #doSubmit: takes an event parameter called ‘e’. This is needed so that we can circumvent normal propagation behaviour in the form by doing ‘preventDefault’ . (In the first installment, we used ‘^false’, but that only works with JQuery.) For example:

doSubmit: event
(('#myForm1' asSilk element) checkValidity) ifTrue: [
"collect the input values"
('#myForm1' asSilk allAt: 'input') do: [:each |
(each propAt: 'type') = 'submit' ifFalse: [
'#output-list' asSilk LI: each asJQuery val]].
('#myForm1' asSilk allAt: 'select') do: [:each |
'#output-list' asSilk LI: each asJQuery val].
event preventDefault]

Silk is still young. We can expect more evolution from it. But it is quite usable now and it may help make your task easier. Try it.

Mighty Athens

Athens is a modern, object-oriented, vector graphics framework for Smalltalk. It is primarily based on the Cairo graphics library, although an OpenGL backend is expected sometime in the future (hint, hint).

The first thing to do is install the Athens library with bower:

bower install "athens=git://github.com/matthias-springer/amber-athens-library.git" --save

It has a local.amd.json file, so we can proceed directly to ‘grunt devel’ and importing the appropriate packages [1]:

local.amd.json is...
{
"paths": {
"amber-athens": "src"
}
}
Package imports: {
'amber-athens/Athens-Core-Matrices'.
'amber-athens/Athens-Core-Paints'.
'amber-athens/Athens-Core-Paths'.
'amber-athens/Athens-Core-PathsGeometry'.
'amber-athens/Athens-HTML'.
'amber-athens/Athens-HTML-Matrices'.
'amber-athens/Athens-HTML-Paints'.
'amber-athens/Athens-HTML-Paths'.
'amber-athens/Athens-HTML-Tutorial'. "not needed;
only used in the tutorial"
"and the rest of your imports"}

For our tutorial, edit your index.html to include this:

<div id="canvas-container">
</div>

Start your application and Helios, and insert the following code somewhere, perhaps in a method bound to a button…

|container|
container := '#canvas-container' asJQuery.
"Clear previous canvases"
container empty.
surface := AthensHTMLSurface extent: 500@400.
surface appendToJQuery: container.
"Render border around canvas."
surface canvasTag asJQuery
css: 'border' with: '1px #aaa solid'.

When you press the button, you should see an empty canvas, a bordered box. This shows that Athens has been successfully installed and imported. At this point, you can follow through an extensive tutorial to try various Athens APIs.

But let’s jump straight to the cool part. Append the following two lines to the above code:

AthensVGTigerDemo instance surface: surface.
AthensVGTigerDemo instance toggle.

Now, when you press the button, you should see a spectacular image…

“Tyger! Tyger! burning bright
In the forests of the night,
What immortal hand or eye
Dare frame thy fearful symmetry?”

If you’re ambitious, take the time to explore the Tiger Demo code. I’m sure you’ll learn a lot.

Actual screen capture of 3D graphics created using THREE

Three.js — WebGL Simplified

WebGL is a JavaScript API for rendering interactive 3D graphics in the browser. It is based on OpenGL ES 2.0. Three.js is a popular library that makes WebGL much easier to use. Thankfully, it has a bower install:

bower install threejs --save

Once installed, create a threejs.amd.json file in the root of your project [2]:

{
"paths": {
"threejs": "build/three"
},
"shim": {
"threejs": {
"exports": "THREE"
}
}
}

Run ‘grunt devel’. Then start up your application to import ‘threejs’ [3]:

imports: { 'three'->'threejs'. 'amber/jquery/Wrappers-JQuery'. 'amber/web/Web'. 'silk/Silk' }

Now, we can use the THREE API. From their Getting Started exercise called “Creating a scene”, we can do this:

scene := three Scene new.
camera := three PerspectiveCamera
newWithValues: {75. (window innerWidth)/(window innerHeight).
0.1. 1000}.
renderer := three WebGLRenderer new.
renderer setSize: (window innerWidth) height: (window innerHeight).
Silk new << renderer domElement.
geometry := three BoxGeometry newValue: 1 value: 1 value: 1.
material := three MeshBasicMaterial
newValue: #{'color'->16r00FF00}.
cube := three Mesh newValue: geometry value: material.
scene add: cube.
camera position z: 5.
self render.

The ‘render’ method uses a callback function:

render
|rend|
rend := [|rot|
requestAnimationFrame value: rend.
rot := cube rotation.
rot x: rot x + 0.1.
rot y: rot y + 0.1.
renderer render: scene with: camera].
rend value

When you run this, you should see a gyrating green cube on a black background.

It may be some time before you learn how to render the Batmobile, but at least the journey will be fun!


Check out my last Amber tutorial. Learn how to write cross-platform mobile apps!

[1] Here, we are going to import several packages. “src” is the location of these packages and “amber-athens” will be used to identify the packages.

[2] The library doesn’t have a ‘define’ call, so a shim is needed. See: http://requirejs.org/docs/api.html#config-shim

[3] ‘three’->’threejs’. This association allows you to avoid directly using the global THREE variable in the library.