I am poptocat
So, my drama w/ UI stacks continues… Kind of a soap opera. We have been evaluating all kinds of stacks for our Vnext Portal. We landed on AngularJS. This was quite an adjustment (at least for me, personally). A full stack / client side MVC framework is pretty slick, but leaves a lot of questions open on integration w/ your back-end systems… as it turns out, it’s not really that big of an issue. Due to the contributors singular focus on releasing cool tech the documentation and examples of integrating more than one of these systems is just hard to find.
Anyhow… that’s what this post is about. We’re going to attempt to drive a fully working web UI from the experimental yeomEn (the E is for ExpressJS) branch of the Yeoman project.
Read up first:
If you have already been using Yeoman and Grunt projects in the past, you can probably breeze through this next section. For this posting, I cleared out my global NPM installations to make sure we touch on the prerequisites correctly.
Run the following commands (may require sudo depending on npm setup)
npm install -g yeoman
npm install -g grunt
This branch was made specifically to test using yeoman to manage and AngularJS / Express end-to-end stack. It’s still an experimental branch (at the time of this posting)
Open a terminal window somewhere you would typically store utilities (we’ll be referencing this location in our $PATH later)
git clone git://github.com/yeoman/yeoman.git
cd yeoman
git checkout express-stack
npm install
grunt install
The grunt install command will output a full path to your new binary (yeomen). Add that to your $PATH.
You should be able to run yeoman --version and yeomen --version from the terminal and get the same output. We have a few options for setting up a project. We’re going to create our Angular / Express app in a few steps, so we’ll skip using the generators for this series. After we build up from scratch, we’ll circle back and review what is provided for you in this preview version of yeoman.
Open a terminal and run the following series of commands: (Name your project)
mkdir [project name]
cd [project name]
yeomen init angularcrud
You should get output similar to the following:
Running "init:yeoman" (init) task
This task will create one or more files in the current directory, based on the
environment and the answers to a few questions. Note that answering "?" to any
question will show question-specific help and answering "none" to most questions
will leave its value blank.
"yeoman" template notes:
invoke angularcrud
Please answer the following:
Would you like to include Twitter Bootstrap? (Y/n) y
If so, would you like to use Twitter Bootstrap for Compass (as opposed to vanilla CSS)? (Y/n) n
Do you need to make any changes to the above before continuing? (y/N) n
Writing compiled Bootstrap
create app/styles/bootstrap.css
invoke angularcrud:common:angularcrud
create .gitattributes
create .gitignore
create app/.buildignore
create app/.htaccess
create app/404.html
create app/favicon.ico
create app/robots.txt
create app/scripts/vendor/angular.js
create app/scripts/vendor/angular.min.js
create app/scripts/vendor/es5-shim.min.js
create app/scripts/vendor/json3.min.js
create app/styles/main.css
create app/views/main.html
create Gruntfile.js
create package.json
create test/vendor/angular-mocks.js
invoke angularcrud:app:angularcrud
create app/scripts/app.js
create app/index.html
invoke angularcrud:controller:angularcrud
create app/scripts/controllers/main.js
create test/spec/controllers/main.js
invoke testacular:app:angularcrud
create testacular.conf.js
Our new app has been stubbed out w/ Angular so far. Let’s add Express.
yeomen init express
You should see output similar to the following:
Running "init:yeoman" (init) task
This task will create one or more files in the current directory, based on the
environment and the answers to a few questions. Note that answering "?" to any
question will show question-specific help and answering "none" to most questions
will leave its value blank.
"yeoman" template notes:
invoke express
create server/index.js
invoke express:crud:express
create server/Routes.js
We not have a fully integrated seed project for Express and Angular, and this was all built w/ yeoman (well, yeomEn actually). Most of the yeoman CLI will function w/ the new binary (yeomen). Although, keep in mind… this is still preview.
Currently, here’s where I plan to take this series… if you have suggestions on other posting topics in-line w/ this subject, please let me know.
Part 1: Exploring Scalatra
Github Project: kyleroche/scalatra-example
In Part 1 of this series we looked at setting up and configuring Scalatra. Instead of progressing immediately to complex configurations (CoffeeScript / Backbone.js / Jade / etc), I’m going to continue looking at configuration items for a bit. Mostly, we’ll be focusing on the ones I couldn’t find documentation for on the site (b/c they are busy building cool stuff).
In this posting, we’ll be looking at Internationalization. Coming from play! this was something we always took for granted. It’s super easy w/ play! and after 2.1 Scalatra handles it well also.
Like the old days of JSP, you’ll find resources, WEB-INF and some other familar folders in your project. For Internationalization, we need to focus on src/main/resources, which is where we’ll be putting our properties files.
Under resources create a directory called i18n. For reference, here are the supported nationality codes for language translation from ISO: Country Codes. For this posting, we’re going to select just two (US for United States and ES for Spain) of them.
For each (I really wanted to type foreach there… took a few tries to get the space) of the codes we need a corresponding properties file. Create these in the src/main/resources/i18n directory. Since US is my default, I’m going to leave that alone… if you wanted to specifically add any language you just use the format messages_[language code].properties. Scalatra will pick up the language if it’s in the i18n directory.
greeting=hi there
greeting=hola
This could be a 2.2 RC bug or a bug in general. But, after adding language files you’ll get a startup error like the following:
> container:start
[info] Updating {file:/Users/kyleroche/Documents/Development/workspace/example-app/}default-f56729...
[info] Resolving org.eclipse.jetty#jetty-io;8.1.7.v20120910 ...
[info] Done updating.
[info] Compiling 3 Scala sources to /Users/kyleroche/Documents/Development/workspace/example-app/target/scala-2.9.2/classes...
[trace] Stack trace suppressed: run last container:start for the full output.
[error] (container:start) java.lang.IllegalArgumentException: file:/Users/kyleroche/Documents/Development/workspace/example-app/target/scala-2.9.2/resource_managed/main/webapp is not an existing directory.
[error] Total time: 6 s, completed Dec 3, 2012 10:47:47 AM
This persists until something exists in the src/main/coffee directory. This is where coffeescript files are placed to get compiled into your application. We’ll talk about this in a later article, but for now add a file called example.coffee to this directory. The contents of the file can be any valid coffeescript. Something like alert "hello" will work just fine.
By default, our Scalatra project comes w/ a Scalate template and a Servlet called ExampleServlet.scala. (That might vary if you chose other settings on project creation). To use the i18n classes, we need to add those libraries to our Servlet. Add the following import statment to ExampleServlet.scala.
import org.scalatra.scalate.ScalateI18nSupport
Next, we have to extend our Servlet with some required traits for i18n. Change the class statement for the Servlet w/ the following.
class ExampleServlet extends ScalatraServlet with ScalateSupport with ScalateI18nSupport with CookieSupport{
We added ScalateI18nSupport and CookieSupport to the class. CookieSupport is a dependency of ScalateI18nSupport, so we need both.
In ExampleServlet we will find the following method already created for us.
get("/") {
<html>
<body>
<h1>Hello, world!</h1>
Say <a href="hello-scalate">hello to Scalate</a>.
</body>
</html>
}
We need to make one slight change to test out our language changes. Replace Hello, with {messages.get("greeting").getOrElse("salutations")} and reload the container. You should see hi there rendered in the place of Hello,. Change your browser’s default language to test out Spanish.
As long as a Request is in scope you can also use messages in your Scalate templates. For example, in a SCAML template you can add:
%h1= messages.get("greeting").getOrElse("not found")
Or, in Jade you could do something like:
p=messages.get("greeting").getOrElse("not found")
Next, I’ll be exploring the compiled assets functionality of Scalatra like CoffeeScript, LESS, etc.
We are huge fans of the play! framework. We are a Scala shop and play! runs our REST API and DAO layer. However, we build custom portals for most of our customers and also provide a common adminstrative portal solution for your m2m device needs / licensing / user provisioning, etc. Play! is pretty complex to setup as a portal solution so we started exploring other options. If we were going to lose the full stack advantage of something like play!, we wanted to get as barebones as possible.
Scalatra, although not totally barebones, is a great lightweight server (micro web-framework) for Scala applications. It borrows quite a bit from Sinatra and has a almost not learning curve to get going… Like Scalatra, the project’s documentation is also lightweight (grin). Hopefully, this guide will help some of you get going.
NOTE: I’m using 2.2 for this guide due to some FW features we required
We found installation to be pretty straight forward. It’s already documented on the 2.2. docs site. I didn’t actually follow this… we instead installed parts of the TypeSafe stack. If the standard installation doesn’t work for your system, you can use homebrew to install the requirements:
> brew install scala sbt maven giter8
Technically, this is a bit more than is required, but I’ll be using these components throughout this series.
Like every other developer, the command line makes me feel smarter and more hackeriffic. However, sooner or later, I need the project in some sort of IDE. If you’re coming from another Scala web framework you may already be setup. If not, you need to get the Scala-IDE for Eclipse Plugin so you can edit your project in Eclipse.
Scalatra has helper templates for getting started. We’ll use the g8 utility to pull down our first template. It’ll ask you a few standard questions to customize the template, then it’ll create the project in a directory off .
> g8 scalatra/scalatra-sbt
organization [com.example]: com.kyleroche
package [com.example.myapp]: com.kyleroche.example
name [My Scalatra Web App]: Example App
servlet_name [MyServlet]: ExampleServlet
version [0.1.0-SNAPSHOT]:
Applied scalatra/scalatra-sbt.g8 in example-app
Let’s just get this over with… edit project/plugins.sbt using VI or whatever command line editor you feel most comfortable using. Here’s my plugins.sbt file. I also added support for LESS and CoffeeScript, which we’ll get to in later posts.
libraryDependencies <+= sbtVersion(v => v match {
case "0.11.0" => "com.github.siasia" %% "xsbt-web-plugin" % "0.11.0-0.2.8"
case "0.11.1" => "com.github.siasia" %% "xsbt-web-plugin" % "0.11.1-0.2.10"
case "0.11.2" => "com.github.siasia" %% "xsbt-web-plugin" % "0.11.2-0.2.11"
case "0.11.3" => "com.github.siasia" %% "xsbt-web-plugin" % "0.11.3-0.2.11.1"
case x if (x.startsWith("0.12")) => "com.github.siasia" %% "xsbt-web-plugin" % "0.12.0-0.2.11.1"
})
addSbtPlugin("me.lessis" % "coffeescripted-sbt" % "0.2.3")
addSbtPlugin("me.lessis" % "less-sbt" % "0.1.10")
addSbtPlugin("com.typesafe.sbteclipse" % "sbteclipse-plugin" % "2.1.0")
Note: version numbers could differ over time.
To test, let’s open the sbt utility and execute the eclipse command, which preps our project for the IDE. You should see something similar to the following:
Kyles-MacBook-Pro:example-app kyleroche$ sbt
[info] Loading project definition from /Users/kyleroche/Documents/Development/workspace/example-app/project
[info] Updating {file:/Users/kyleroche/Documents/Development/workspace/example-app/project/}default-d10865...
[info] Resolving org.scala-sbt#precompiled-2_10_0-m7;0.12.1 ...
[info] Done updating.
[info] Set current project to Example App (in build file:/Users/kyleroche/Documents/Development/workspace/example-app/)
[info] Updating {file:/Users/kyleroche/Documents/Development/workspace/example-app/}default-f56729...
[info] Resolving org.eclipse.jetty#jetty-io;8.1.7.v20120910 ...
[info] Done updating.
> eclipse
[info] About to create Eclipse project files for your project(s).
[info] Successfully created Eclipse project files for project(s):
[info] Example App
>
The eclipse command would fail w/out the change to our plugins.sbt file.
Okay, let’s run the application from sbt and make sure everything looks okay in the browser. To do so, we’re going to run container:start from the sbt utility.
> container:start
[info] Compiling 3 Scala sources to /Users/kyleroche/Documents/Development/workspace/example-app/target/scala-2.9.2/classes...
[info] jetty-8.1.7.v20120910
[info] NO JSP Support for /, did not find org.apache.jasper.servlet.JspServlet
[info] started o.e.j.w.WebAppContext{/,[file:/Users/kyleroche/Documents/Development/workspace/example-app/src/main/webapp/]}
[info] started o.e.j.w.WebAppContext{/,[file:/Users/kyleroche/Documents/Development/workspace/example-app/src/main/webapp/]}
14:13:44.998 [pool-11-thread-5] INFO o.scalatra.servlet.ScalatraListener - Initializing life cycle class: Scalatra
[info] started o.e.j.w.WebAppContext{/,[file:/Users/kyleroche/Documents/Development/workspace/example-app/src/main/webapp/]}
14:13:45.131 [pool-11-thread-5] INFO o.f.s.servlet.ServletTemplateEngine - Scalate template engine using working directory: /var/folders/q2/y5m1j_q90mn5l_khl_s5ydth0000gn/T/scalate-9194221376497415326-workdir
[info] Started SelectChannelConnector@0.0.0.0:8080
[success] Total time: 3 s, completed Dec 1, 2012 2:13:45 PM
>
So, Scalatra is running in a dedicated Jetty container. You can access the application from http://localhost:8080.
Here’s how I swapped to the latest version. The features in the subsequent posts might depend on newer versions. This is optional if you just wanted to get started with Scalatra.
Open build.sbt (off project root) in Eclipse. Edit it as follows: (Don’t just cut/paste, verify your project specific settings)
organization := "com.kyleroche"
name := "Example App"
version := "0.1.0-SNAPSHOT"
scalaVersion := "2.9.2"
seq(webSettings :_*)
classpathTypes ~= (_ + "orbit")
libraryDependencies ++= Seq(
//"org.scalatra" % "scalatra" % "2.1.1",
//"org.scalatra" % "scalatra-scalate" % "2.1.1",
//"org.scalatra" % "scalatra-specs2" % "2.1.1" % "test",
"org.scalatra" % "scalatra" % "2.2.0-SNAPSHOT",
"org.scalatra" % "scalatra-scalate" % "2.2.0-SNAPSHOT",
"org.scalatra" % "scalatra-json" % "2.2.0-SNAPSHOT",
"org.scalatra" % "scalatra-data-binding" % "2.2.0-SNAPSHOT",
"com.typesafe.akka" % "akka" % "2.0.4",
"org.scalatra" % "scalatra-akka" % "2.2.0-SNAPSHOT",
"org.scalatra" % "scalatra-lift-json" % "2.2.0-SNAPSHOT",
"org.json4s" %% "json4s-jackson" % "3.0.0",
"org.scalatra" % "scalatra-specs2" % "2.2.0-SNAPSHOT" % "test",
"ch.qos.logback" % "logback-classic" % "1.0.6" % "runtime",
"org.eclipse.jetty" % "jetty-webapp" % "8.1.7.v20120910" % "container",
"org.eclipse.jetty" % "test-jetty-servlet" % "8.1.5.v20120716" % "test",
"org.eclipse.jetty.orbit" % "javax.servlet" % "3.0.0.v201112011016" % "container;provided;test" artifacts (Artifact("javax.servlet", "jar", "jar"))
)
resolvers ++= Seq(
"Sonatype Nexus Snapshots" at "https://oss.sonatype.org/content/repositories/snapshots",
"Typesafe Repository" at "http://repo.typesafe.com/typesafe/releases/"
)
seq(coffeeSettings: _*)
(resourceManaged in (Compile, CoffeeKeys.coffee)) <<= (resourceManaged in Compile)(_ / "webapp" / "js")
com.github.siasia.PluginKeys.webappResources in Compile <+= (resourceManaged in Compile)(_ / "webapp" )
That should do it. Run sbt clean update compile to make sure you don’t have any issues.
REFERENCES
Getting Started with Scalatra (4 part series) http://www.smartjava.org/content/tutorial-getting-started-scala-and-scalatra-part-i
Frank LoVecchio (my fellow m2m hacker extraordinaire) https://github.com/franklovecchio/scalatraback