Hello Node: How to transpile ActionScript 3 for Node.js
Note: This tutorial was originally published in Josh Tynjala's NextGen ActionScript website, but is now donated to Apache Royale. The tutorial has been adapted to correct the things that changed in Apache Royale since it was published.
Over the years, many developers have dreamed of using ActionScript on both the client and the server. Today, the Apache Royale™ SDK finally makes it possible.
Let's learn to use asnodec to write ActionScript code that runs in the popular server-side JavaScript environment, Node.js.
With asnodec, we'll get full access to all Node.js APIs, and it's even possible to require npm modules in ActionScript. We'll start with a simple example.
Requirements
For this tutorial, you should install Node.js. The newest Long Term Support (LTS) release is recommended.
Additionally, you will need Apache Royale 0.9.4 or newer. Use the downloads page, or download it from Node Package Manager with
npm install -g @apache-royale/royale-js
Create a new project
- Create a new, empty folder for your project, and name it HelloNode.
- Inside the new project, create a new folder named src. This is where our ActionScript classes will go.
- Inside the src folder, create a file named HelloNode.as, and add the following code:
package
{
public class HelloNode
{
public function HelloNode()
{
console.log("Hello", process.release.name, process.version);
dns.lookup("localhost", null, dnsLookupCallback);
}
private function dnsLookupCallback(error:Object, address:String):void
{
console.log("The address of localhost is:", address);
}
}
}
In this class, we're doing two things. First, we're printing the version of Node to the console. Then, we're using Node's built-in dns module to look up an IP address.
It is not necessary to call
require()
for built-in Node modules in ActionScript. The compiler will detect when a module is used, and it will generate the appropriate call torequire()
automatically when generating the final JavaScript. (require()
is necessary for custom modules)
Compile the project on the command line
Inside the Apache Royale SDK, the js/bin folder contains several different exeuctables used to transpile ActionScript to JavaScript.
What do each of those executables in js/bin do?
- asjsc compiles pure ActionScript to JavaScript with access to web browser APIs like the HTML DOM.
- asnodec compiles pure ActionScript to JavaScript with access to Node.js APIs to create server-side or command line projects. We'll use this one.
- mxmlc compiles applications that use the Apache Royale framework components.
Use the asnodec executable to transpile the HelloNode
ActionScript class that you created above for Node.js.
asnodec src/HelloNode.as
This will produce a folder named bin containing js-debug and js-release folders. The js-debug folder contains JavaScript that is easy to read, and each class is loaded at runtime from a separate file. The js-release folder contains JavaScript that has been concatenated and minified for production.
The project should now contain the following files and folders:
Finally, let's try running our code with Node.js.
Run the project
Inside the js-debug folder, a file named index.js will be created as the entry point for your Node.js project. You can run this script using the node
executable:
node bin/js-debug/index.js
You should see the following output in your console:
Hello node v6.11.0
The address of localhost is: 127.0.0.1
(The Node version number might be different, obviously!)
What's Next?
This is just a simple example, but it gives you a glimpse of how developers can bring ActionScript server-side using Apache Royale and Node.js. By using an established ecosystem like Node.js, ActionScript developers can take advantage of all of the libraries published to NPM and join a large, vibrant community.
Loading external data through HTTPService
This example shows you how to use HTTPService to access external data to use in your Apache Royale application.
You can use HTTPService to retrieve data in XML, JSON, or other formats. We'll use Github API services to get JSON formatted GitHub data so we can load info about the code of this example, which is hosted in GitHub.
It uses the new Jewel UI set that supports themes and is available in the 0.9.4 release or later.
<?xml version="1.0" encoding="UTF-8"?>
<j:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:j="library://ns.apache.org/royale/jewel"
xmlns:js="library://ns.apache.org/royale/basic"
xmlns:html="library://ns.apache.org/royale/html"
xmlns:services="services.*">
<fx:Script>
<![CDATA[
import org.apache.royale.events.Event;
import org.apache.royale.events.MouseEvent;
import org.apache.royale.jewel.Alert;
public function getGithubContent(event:MouseEvent):void
{
service.getContent();
}
public function dataReadyHandler(event:Event):void
{
Alert.show(service.jsonToString, "JSON data retrieved");
jsonData.html = "Some JSON Data: <br><strong> - service.json.name:</strong> " + service.json.name +
"<br><strong> - service.json.sha:</strong> " + service.json.sha +
"<br><strong> - service.json._links.html</strong> " + service.json._links.html;
sourceCodeMXMLText.text = service.sourceCode;
}
]]>
</fx:Script>
<fx:Declarations>
<services:GitHubService id="service"
sourceCodeUrl="https://api.github.com/repos/apache/royale-asjs/contents/examples/blog/BE0011_Loading_external_data_through_HTTPService/src/main/royale/BE0011_Loading_external_data_through_HTTPService.mxml"
dataReady="dataReadyHandler(event)"/>
</fx:Declarations>
<j:initialView>
<j:View>
<j:beads>
<j:HorizontalCenteredLayout/>
</j:beads>
<j:Card percentWidth="90">
<html:H3 text="Loading Github external data through HTTPService"/>
<j:Label text="This example loads its source code in the text code panel:"/>
<html:Pre height="300" percentWidth="100" style="background-color: white">
<html:beads>
<j:ScrollingViewport/>
</html:beads>
<html:Code id="sourceCodeMXMLText"/>
</html:Pre>
<j:Label id="jsonData" multiline="true" html="This label shows JSON data when loaded."/>
<j:Button text="Retrieve source code from Github" emphasis="primary" click="getGithubContent(event)"/>
</j:Card>
</j:View>
</j:initialView>
</j:Application>
We create an Apache Royale interface that shows a text code panel to load the source code of this example in it. Github doesn't let us load a page from its domain in an iFrame, so this is the only way to embed GitHub content in your application.
The text code panel is made of pre and code html tags with some custom style to make the background white. We provide a Label to show accessing Json data directly with dot notation. Finally we provide a button to trigger the HTTPService.
Tip: We use the ScrollingViewport bead to add scrolling behavior to our text code panel.
The most important piece in this example is the custom GitHubService class that wraps the HTTPService object. We declare it in MXML in our application to pass the Github URL to request and declare an event handler to show the data once it is loaded.
This is the GitHubService class:
package services
{
import org.apache.royale.events.Event;
import org.apache.royale.events.EventDispatcher;
import org.apache.royale.net.HTTPConstants;
import org.apache.royale.net.HTTPService;
import org.apache.royale.utils.string.Base64;
[Event(name="dataReady", type="org.apache.royale.events.Event")]
/**
* GitHubService is in charge of getting the source code of some example
* so we can show the code in a TabBarContentPanel along with the working example
*/
public class GitHubService extends EventDispatcher
{
/**
* constructor
*/
public function GitHubService():void
{
service = new HTTPService();
service.addEventListener(HTTPConstants.COMPLETE, completeHandler);
}
/**
* the service that performs the request to Github
*/
private var service:HTTPService;
/**
* we dispatch an event once we have the source code from github
*/
private function completeHandler(event:Event):void
{
dispatchEvent(new Event("dataReady"));
}
private var _sourceCodeUrl:String = null;
/**
* The source code url we want to retrieve
*/
public function get sourceCodeUrl():String
{
return _sourceCodeUrl;
}
public function set sourceCodeUrl(value:String):void
{
_sourceCodeUrl = value;
service.url = sourceCodeUrl;
}
/**
* json returns the retrieved GitHub JSON Object
*/
public function get json():Object
{
return service.json;
}
/**
* jsonToString returns the retrieved GitHub JSON Object as String
*/
public function get jsonToString():String
{
return service.data;
}
/**
* decode and return the base 64 content (real source code)
*/
public function get sourceCode():String
{
return Base64.decode(service.json.content);
}
/**
* trigger the HTTPService to retrieve the GitHub data
*/
public function getContent():void
{
service.send();
}
}
}
We instantiate the HTTPService in the constructor, and declare an event listener for the HTTPConstants.COMPLETE event, so we perform actions when the data finishes loading. The action we do from this class is throw a new event "dataReady" to consume in our application.
sourceCodeUrl will pass the GitHub url to be called by our service class, and fills HTTPService.url so HTTPService knows what url to target.
As we get the data loaded, we can manage it with HTTPService.data, and we have a convenient HTTPService.json getter to access the JSON Object that HTTPService already parses for us. We exposed this data in our json class as jsonToString and json getters respectively.
Finally, the source code is in the json.content variable, but comes encoded in base64, so we can use Apache Royale's decode function in the Base64 class to get the decoded xml string to use in our example App. We exposed this in a convenient getter function in our service called sourceCode.
Where to go from here
- Apache Royale documentation page about loading external data
- Jewel Label Royale Docs page
- Jewel Button Royale Docs page
- Jewel Card Royale Docs page
The result of this code snippet is the following:
(We're using an iframe to host the actual results of this example compilation. To see the example in a separate window click this link.)
Full project with source code can be found here:
Customization through the Royale API
This example shows you how to use the powerful Royale API to get access to the internal workings of components and customize them to suit your needs. As you can see, although Royale does a lot for you to simplify development, you always have full control of your code.
It uses the new Jewel UI set that supports themes and is available in the 0.9.4 release or later.
<?xml version="1.0" encoding="UTF-8"?>
<j:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:j="library://ns.apache.org/royale/jewel"
xmlns:js="library://ns.apache.org/royale/basic"
xmlns:html="library://ns.apache.org/royale/html"
xmlns:models="models.*">
<fx:Script>
<![CDATA[
import org.apache.royale.core.IBeadLayout;
import org.apache.royale.core.IBeadView;
import org.apache.royale.events.CloseEvent;
import org.apache.royale.events.Event;
import org.apache.royale.events.MouseEvent;
import org.apache.royale.jewel.Alert;
import org.apache.royale.jewel.CheckBox;
import org.apache.royale.jewel.beads.layouts.HorizontalLayout;
import org.apache.royale.jewel.beads.layouts.VerticalLayout;
import org.apache.royale.jewel.beads.views.AlertView;
private var alert:Alert;
private var check:CheckBox;
// Adding content to the Alert component and changing the ControlBar's Buttons Layout
private function clickHandler(event:MouseEvent):void {
alert = Alert.show("This example shows access to AlertView and ControlBar to add a CheckBox to the Alert's content area, expand the Button layout and change its defaults. The height of the alert is changed to 300px, too.", "Customized Alert Example", 3);
alert.addEventListener(CloseEvent.CLOSE, alertClickHandler);
alert.height = 300;
check = new CheckBox();
check.selected = true;
check.text = "Buttons must fill the ControlBar's available space";
check.addEventListener(Event.CHANGE, expandButtons);
expandButtons();
}
private function expandButtons(event:Event = null):void {
var alertView:AlertView = alert.getBeadByType(IBeadView) as AlertView;
if(event == null)
{
var verticalLayout:VerticalLayout = new VerticalLayout();
verticalLayout.gap = 9;
alertView.content.addBead(verticalLayout);
alertView.content.addElement(check);
}
var layout:HorizontalLayout = alertView.controlBar.getBeadByType(IBeadLayout) as HorizontalLayout;
layout.itemsExpand = check.selected;
}
// Event handler function for displaying the selected Alert button.
private function alertClickHandler(event:CloseEvent):void {
alert.removeEventListener(CloseEvent.CLOSE, alertClickHandler);
if (event.detail == Alert.YES)
status.text="You answered Yes";
else
status.text="You answered No";
}
]]>
</fx:Script>
<j:initialView>
<j:View>
<j:beads>
<j:HorizontalCenteredLayout/>
</j:beads>
<j:Card width="350">
<html:H3 text="Customization through Royale API"/>
<j:Label text="This is a complex example that adds and retrieves beads at runtime. Click the button below to display an Alert window that adds content and makes changes in some parts of the default layout."
multiline="true"/>
<j:Button text="Click Me" click="clickHandler(event)"/>
<j:Label id="status"/>
</j:Card>
</j:View>
</j:initialView>
</j:Application>
This example takes the Using the Jewel Alert Control example and uses the Royale API to add content and customize some parts of the Alert.
The code is more complex than in some of our other examples, for teaching purposes:
- In the clickHandler method, we create an Alert control and create a CheckBox to add to the Alert's content zone. Then we call the expandButtons method to end initial customization.
- The expandButtons method is where the heavy work of the Royale API happens:
- Since all components in Royale are "composed" through the Strand/Bead API, we want to access the view part of the Alert; in this case we're talking about AlertView.
- We can access AlertView with getBeadByType that retrieves a bead by its type.
- Then, for learning purposes, we create a VerticalLayout bead and add it to the Alert's content using the addBead method (you can investigate other methods in the API like removeBead as well).
- We add the CheckBox created in the previous method to the Alert's content. Since this involves a call-back method and an initialization method, we only want to add the checkbox at initialization time and not each time the user clicks the CheckBox.
- Finally, we retrieve the ControlBar's default HorizontalLayout and customize it to expand its items (the buttons) to fill all the available space in the control bar. We use getBeadByType again to reference the layout, and then use a method available in most Jewel Layouts called itemsExpand that expect a Boolean. When this method is set to "true" all items in the layout expand to use all available space.
Where to go from here
- Jewel Alert Royale Docs page
- Jewel Button Royale Docs page
- Jewel CheckBox Royale Docs page
- Jewel Label Royale Docs page
- Jewel Card Royale Docs page
The result of this code snippet is the following:
(We're using an iframe to host the actual results of this example compilation. To see the example in a separate window click this link.)
Full project with source code can be found here: