In my previous article, "Teapot: Web Programming Made Easy," I showed you how to create a dynamic website using Pharo. Now, let's use Pharo on the front end...
PharoJS is a great way to do front-end web development. It offers two tremendous benefits:
- You get to avoid JavaScript, one of the rightfully most-despised programming languages in the world.
- You get to use Pharo, the Modern Smalltalk.
Using PharoJS, you can develop your front-end applications in half the time, perhaps even a third of the time, it would take using JavaScript and Angular or React or Ember or Meteor or Vue or whatever flavor-of-the-month JS web framework is being thrown at you.
This tutorial will help you get started quickly and easily. It assumes you have a working knowledge of Smalltalk. If you don’t, I suggest this Smalltalk tutorial.
Just to make things interesting, we’ll do a cross-platform mobile app, too, using Apache Cordova. We’ll do this by adapting our web app.
Installing PharoJS
First, install Pharo using this Pharo Quick Start guide.
Then, execute the following in the Playground:
<code>Gofer it
smalltalkhubUser: 'noury' project: 'PharoJS';
configurationOf: #PharoJS ;
loadStable.</code>
Save the image. Now, PharoJS is installed. We are ready to proceed with the web app.
A Counter App for the Web
We will create a web app whose sole purpose is to let you count the number of instances of something. Yes, this is a very boring app, but it will illustrate interactivity between the user and the program.
First, let’s get the UI out of the way. Create a working directory for our program. Call it PharoJS-Cordova-Tutorial. We will place the following files into this folder.
We need HTML and CSS for this. Being the brilliant web designers that we are, here are the HTML and CSS files:
<!-- index.html -->
<!DOCTYPE html>
<meta charset="utf-8" />
<head>
<title>PharoJS Counter</title>
<!--meta http-equiv="Content-Security-Policy"
content="default-src 'self' data: gap: https://ssl.gstatic.com 'unsafe-eval';
style-src 'self' 'unsafe-inline'; media-src *; img-src 'self' data: content:;">
<meta name="format-detection" content="telephone=no">
<meta name="msapplication-tap-highlight" content="no">
<meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1,
minimum-scale=1, width=device-width"-->
<link rel="stylesheet" type="text/css" href="css/index.css">
</head>
<body>
<div class="container">
<img src="img/pharoJsLogo.png" width="250">
<div id="countDisplay" class="countText">0</div>
<button id="resetButton" class="resetButton">Reset</button>
<button id="incrementButton" class="incrementButton">Increment</button>
<form id="form1">
First name:<br>
<input id="field1" type="text" name="firstname"><br>
Last name:<br>
<input id="field2" type="text" name="lastname"><br>
<button id="hello" class="hello" type="submit">OK</button>
</form>
</div>
<!--script type="text/javascript" src="cordova.js"></script-->
<script language="javascript" type="text/javascript" src="js/index.js"></script>
</body>
</html>
The bolded sections show commented-out HTML that will eventually be needed for Cordova. The italicized section is something I threw in just to illustrate how to handle input text fields.
.container{
text-align: center;
}
.countText{
font-size: 6em;
font-weight: bold;
}
.resetButton, .incrementButton{
font-size: 2em;
width: 200px;
border: none;
height: 50px;
border-radius: 10px;
}
.resetButton{
color: white;
background-color: #FF3333;
}
.incrementButton{
color: white;
background-color: #3399FF;
}
.hello{
color: white;
background-color: #3300FF;
}
The CSS file resides in the css subdirectory. As the HTML indicates, we need an image file called pharoJsLogo.png in the img subdirectory.
We need to create a class for our counter
object:
Object subclass: #PjxCounter
instanceVariableNames: 'count'
classVariableNames: ''
package: 'MyPharoJsProject'
This object does one thing and one thing only: keep track of the count. And here are the methods:
increment
self count: self count + 1
initialize
super initialize.
self reset
reset
self count: 0
Make sure you create the accessor methods for count
, too.
We need to create a class for the controller of our counter
object:
Object subclass: #PjxCounterController
instanceVariableNames: 'counter countDisplay'
classVariableNames: ''
package: 'MyPharoJsProject'
This object performs the task of keeping the browser screen updated. And here are the methods:
increment
self counter increment.
self updateDisplay
reset
self counter reset.
self updateDisplay
updateDisplay
self countDisplay innerHTML: self counter count
Make sure you create the accessor methods for counter
and countDisplay
.
Finally, we need to create a class for the actual web browser program:
PjFileBasedBrowserApp subclass: #PjxCounterCordovaApp
instanceVariableNames: 'counter controller'
classVariableNames: ''
package: 'MyPharoJsProject'
We need class-side methods to further define the program:
appClasses
<pharoJsSkip>
^super appClasses, {PjxCounter. PjxCounterController}
appJsSubFolder
^'js'
The #appJsSubFolder
method tells where to place the resultant JavaScript file (it’s in the js subdirectory). The #appClasses
method tells what other classes are required for this application. The directive <pharoJsSkip>
tells the system not to translate this method into JavaScript; you probably will never have to worry about it.
The program object deals with the DOM. Note the #addEventListener:
method which allows us to execute the appropriate code when something is clicked. And now the instance-side methods:
countDisplay
^ self domElementAt: 'countDisplay'
firstNameField
^ self domElementAt: 'field1'
helloForm
^ self domElementAt: 'form1'
incrementButton
^ self domElementAt: 'incrementButton'
initialize
super initialize.
counter := PjxCounter new.
controller := PjxCounterController new.
controller counter: counter.
controller countDisplay: self countDisplay.
self resetButton addEventListener: #click block: [ controller reset ].
self incrementButton addEventListener: #click block: [ controller increment ].
self helloForm addEventListener: #submit block: [ :e | self showHello. e preventDefault ]
resetButton
^ self domElementAt: 'resetButton'
showHello
window alert: 'Hello, ',self firstNameField value
(Tip: The #preventDefault
method short-circuits normal form submission behaviour so that the form doesn’t get sent to a server. In a front-end application, there may not be a server to send to.)
A note about HTML DOM methods: Things like #addEventListener:
and #value
are JavaScript methods/properties on the DOM. They translate directly into the Pharo-equivalent methods. Visit here and here and here for more examples of such methods.
So here’s our directory structure:
PharoJS-Cordova-Tutorial (containing index.html)
|
---- css (containing index.css)
|
---- img (containing pharoJsLogo.png)
|
---- js (containing index.js)
Test the program by doing this in the Playground
:
PjxCounterCordovaApp playground
The first time you run your program, you will be prompted for the location of your application’s files. Navigate to the folder PharoJS-Cordova-Tutorial created earlier.
You can also use the #setUpAppFolder
method later on to change the location of the application’s files. And you can use the #exportApp
method to generate the JavaScript output.
(Tip: You can back up your project source code by filing out. Just right-click on the package in the System Browser and choose File Out. It will create a file with a .st extension in your Pharo directory.)
And that’s our first PharoJS web app complete with user interactivity.
PharoJS for Mobile
Now, let’s try our hand at writing a mobile app (for either Android or iOS) by adapting our web app. There are several preliminary steps…
Installing Node.js
Visit https://nodejs.org/en/ and choose either the LTS (Long-Term Support) install or the Current install. This will give us the npm
package manager.
Installing Apache Cordova
Go to the command line or Terminal and type:
npm install -g cordova
For Android development, you will need to follow this platform guide (erratum: you will also need to create the ANDROID_SDK_ROOT
environment variable with the same value as ANDROID_HOME). For iOS development, you will need to follow this platform guide.
Here’s how we will structure the program directories:
We create a Cordova subdirectory in our main program directory. We also add platform support for both Android and iOS:
cordova create cordova
cd cordova
cordova platform add android ios
We copy the index.html, the css and img folders into the cordova/www directory.
In order to adapt our web app for Cordova, we need to modify the web app’s (not the Cordova app’s) HTML file script tag to point to ‘cordova/www/js/index.js’ and the #PjxCounterCordovaApp
class method #AppJsSubFolder
to point to folder ‘cordova/www/js’. In this manner, when you build the PharoJS app, both the web app and the Cordova app share the same JS output.
Remember the bolded sections in our HTML file? They need to be uncommented out for the Cordova app.
Now we have to generate the JavaScript code initially for Cordova. In the Playground, do:
PjxCounterCordovaApp exportApp
We can build and run the Cordova application thus:
cordova run android
# or
cordova run ios
(The smartphone emulators for Android and iOS are quite slow to start up. I find that the iOS simulator is especially sloooow, so I prefer working with Android.)
You should see something like this:
(Tip: Google engineers in their infinite wisdom disabled the Android emulator’s physical keyboard by default! You need to go into the AVD configuration to enable it. What a bunch of doofuses.)
Conclusion
PharoJS is pretty easy to use for writing a web browser application. It’s rather like writing a desktop application with none of the crapola you have to put up with in JavaScript. Angular and React are a whole basket of crazy you’d do best to avoid.
You should familiarize yourself with the classes in the package PharoJsApp
. The #PjDOMApplication
class, in particular, has a set of useful methods for dealing with the DOM.
I hope you give PharoJS a try for your next front-end, browser-based application. There is no need to endure JavaScript or Angular or React. Give yourself a break.