Tutorial #13 – Responsive Search with AngularJS + JSON + Scala Play Framework

Here I’ll be showing how to write a responsive search form using Angular and Scala Play. As you type text into the search form the results will come from the server as JSON and get shown by angular.

The search is a simple search of filenames in the current directory. An exercise for the reader is to change the search to be recursive through sub-directories.

We need to do the following:
1. Create an empty project
2. Add AngularJS to the project.
3. Create a search form and Angular and Play Controllers

1. Create the empty project.

I outline that here: https://scalaplayschool.wordpress.com/2014/08/10/hello-world-using-scala-play/

I download activator (that runs the Play framework), unzip it, put it on my Path and use these commands:

$ cd
$ activator new lesson13 play-scala
$ cd lesson13
$ activator eclipse
$ activator run

This will create a new project in a folder called lesson13, change into that folder, create the files required by eclipse and start the Play server to compile and run our application as we write it.

2. Add AngularJS to the Project
Download a copy of angular.min.js and place it in the public/javascripts folder. I downloaded mine from here: http://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js

We need to reference it in our main template by adding a line inside the tag. We will also add the AngularJS data-ng-app declaration and initialize the Angular app. Here is the listing for app/views/main.scala.html :

 

@(title: String)(content: Html)

<!DOCTYPE html>
<html>
  <head>
    <title>@title</title>
    	<link rel="stylesheet" media="screen" href="@routes.Assets.at("stylesheets/main.css")">
    	<link rel="shortcut icon" type="image/png" href="@routes.Assets.at("images/favicon.png")">
    <script src="@routes.Assets.at("javascripts/angular.min.js")"         type="text/javascript"></script>
    <script src="@routes.Assets.at("javascripts/hello.js")"         type="text/javascript"></script>
  </head>
  <body>
<div data-ng-app="myApp">
      <script type="text/javascript">
      var app = angular.module('myApp', []);
      </script>
      @content</div>
</body>
</html>


Of course, you could reference the one at ajax.googleapis.com but as I’m writing this on a train that won’t work for me ☺

We’ll test we have Angular working by updating our app/views/index.scala.html with a simple test:

 

@(message: String)

@main("Welcome to Play") {
  <input data-ng-model="input" autofocus>

  <input data-ng-model="input">
}


With this test, as we type in either textbox the other will show the same value as we type it:

3. Create a search form and Angular and Play Controllers

Now we have AngularJS working we will just update the current front page to show a search form and a list of Search Results. Here is our new app/views/index.scala.html template:

 

@(message: String)

@main("Welcome to Play") {
<div data-ng-controller="SearchController">
		<input data-ng-model="input" data-ng-change="doSearch(input)" autofocus>
<h2>There are {{ results.length }} results</h2>
<ul>
	<li data-ng-repeat="result in results">
				{{ result }}</li>
</ul>
</div>
}


We’ve added some Angular attributes to HTML elements, specifically the attributes beginning with ‘data-ng’. Angular will handle binding values and functions to these elements. The Angular code for this we’ll put in public/javascripts/hello.js which was created when we specified the play-scala template at project creation time.

The main things to notice are that the doSearch function which in javascript is $scope.doSearch() and in the data-ng-onchange attribute is just “doSearch()”, and also the url for the search would be better as a parameter then we can have Play generate it. I used this technique in Tutorial #13.

This is the new public/javascripts/hello.js :

 

function SearchController($scope, $http) {
	$scope.results = [];
	$scope.input = "";
	$scope.doSearch = function() {
		var httpRequest = $http({
			method : 'GET',
			url : "/search/" + $scope.input,
		}).success(function(data, status) {
			$scope.results = data;
		}).error(function(arg) {
			alert("error ");
		});

	};
	// run the search when the page loads.
	$scope.doSearch();
}


And here is app/models/Result.scala – it has a case class (an immutable data holder) and a companion class of the same name. This case class / companion pattern occurs a lot in Scala.

 

package models

import java.io.File

case class Result(text: String)

// Finds files in the current dir. matching the given search term
object Result {

  // Simple list of files in the current directory
  def all = new File(".").listFiles().map(file => file.getName())

  // Simple case-sensitive filter
  def find(term: String) = Result.all.filter(_.contains(term))
}


We also need to create app/controllers/Search.scala to perform the search and return the results.

 

package controllers

import models.Result
import play.api._
import play.api.mvc._
import play.api.libs.json._
import play.api.data._
import play.api.data.Forms._
import scala.collection.mutable.ListBuffer
import java.io.File

object Search extends Controller {

  // Simple action - return search results as Json
  def perform(term:String) = Action {
    val m = Result.find(term)
    Ok(Json.toJson(m))
  }
}


To wire it all up we just need to a couple of routes into conf/routes as shown:

 

# Routes
# This file defines all application routes (Higher priority routes first)
# ~~~~
GET  /               controllers.Application.index
GET  /search/:term   controllers.Search.perform(term:String)
GET  /search/        controllers.Search.perform(term:String="")
GET  /assets/*file   controllers.Assets.at(path="/public", file)

 

Ok, let’s test it. Here is the page once it has loaded:

It’s only a simple list with no styling but we could add bootstrap as in previous tutorials for a better look.

Next let’s type into the textbox. As we type the results are filtered – as the value of the textbox changes the ‘data-ng-onchange’ gets called and the search is performed again:

lesson13-or

Advertisements