Objectives

Import, rename and run a new starter project. Extend the project to include a Model. (Note: the video shows java + play revisions a little behind your current installation)

Creating a Play App

Create New Project

Create a new play project - this time using a different procedure from the first lab. Open a shell and enter the following:

git clone https://github.com/wit-hdip-comp-sci-2019/play-template-1

The shell should show something like this:

Cloning into 'play-template-1'...
remote: Counting objects: 66, done.
remote: Compressing objects: 100% (47/47), done.
remote: Total 66 (delta 9), reused 66 (delta 9), pack-reused 0
Unpacking objects: 100% (66/66), done.

Rename Project

The default name of the project should be changed now. This will require the following procedure:

  • Rename the folder play-template-1. Call the folder playlist instead.
  • Use Sublime Text to edit the file: playlist/conf/application.conf. The first three lines contains the following:

      # This is the main configuration file for the application.
      # ~~
      application.name=play-template-1
  • Change play-template-1 above to playlist, and save the file:

      # This is the main configuration file for the application.
      # ~~
      application.name=playlist

Import into Idea

Still in the shell, and in the project folder enter the following command:

play idealize

You should get this response:

~        _            _
~  _ __ | | __ _ _  _| |
~ | '_ \| |/ _' | || |_|
~ |  __/|_|\____|\__ (_)
~ |_|            |__/
~
~ play! 1.5.2, https://www.playframework.com
~
~ OK, the application is ready for Intellij Idea
~ Use File, Open Project... to open "playlist.ipr"
~

Now we can import the project into Idea. Launch Idea, and select File->Open. Select the Directory containing the project you have just created (play-template-1)

(Again - you may need to press the button on the extreme bottom left to get the view shown above)

Explore the controllers and views folders - examining each file.

Running a Play Application

Inside the project folder, enter the following command in the shell/command prompt:

play run

Which should respond with:

~        _            _
~  _ __ | | __ _ _  _| |
~ | '_ \| |/ _' | || |_|
~ |  __/|_|\____|\__ (_)
~ |_|            |__/
~
~ play! 1.5.2, https://www.playframework.com
~
~ Ctrl+C to stop
~
~ using java version "1.8.0_162"
Listening for transport dt_socket at address: 8000
17:50:30,555 INFO  ~ Starting /Users/edeleastar/dev/play-template-1
17:50:30,658 WARN  ~ You're running Play! in DEV mode
17:50:30,760 INFO  ~ Listening for HTTP on port 9000 (Waiting a first request to start) ...
~ Server is up and running

Navigate now to:

You should see view like these:

To stop the application, enter Ctrl+C in the shell:

This will stop the application. Try

... and try again, and verify that the app has now stopped.

Practice starting and stopping the app.

Playlist Branding

We are going to evolve this project into a simple playlist maker, enabling a user to create a playlist of songs.

First, we can make some simple changes to rebrand the app as Playlist instead of Template 1

Edit the following files and make the changes as shown:

views/tags/menu.html

...
  <header class="ui header item"> <a href="/"> Playlist </a></header>
...

views/about.html

...
<p>
  A Little Playlist Maker - Version 1
</p>
...

views/start.html

...
<h1 class="ui header">
  Welcome to Playlist 1
</h1>
<p>
  A small app to let you compose playlists.
</p>
...

Run the app (it may be still running). Check out the new 'branding'. Make sure the menu operates as expected.

Static Playlist

In this step we will start to display the playlist itself. First, introduce new partial called listsongs.html:

views/tags/listsongs.html

<table class="ui fixed table">
  <thead>
    <tr>
      <th>Song</th>
      <th>Artist</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>
      </td>
      <td>
      </td>
  </tbody>
</table>

Now change the dashboard view to invoke this partial:

views/dashboard.html

#{extends 'main.html' /}
#{set title:'Dashboard' /}

#{menu id:"dashboard"/}

<section class="ui segment">
  <h2 class="ui header">
    Playlist Title
  </h2>
  #{listsongs/}
</section>

Make sure the app runs successfully. The table will be empty.

To test out the UX, we might include some actual songs (replace the <tbody> of listsongs with the following):

views/tags/listsongs.html

<tbody>
  <tr>
    <td>
      Piano Sonata No. 3
    </td>
    <td>
      Beethoven
    </td>
  </tr>
  <tr>
    <td>
      Piano Sonata No. 7
    </td>
    <td>
      Beethoven
    </td>
  </tr>
  <tr>
    <td>
      Piano Sonata No. 10
    </td>
    <td>
      Beethoven
    </td>
  </tr>
</tbody>

... and also give the playlist a title in dashboard:

views/dashboard.html

....
  <h2 class="ui header">
    Beethoven Sonatas
  </h2>
...

Exercise the UX fully again. Make sure the playlist above is being displayed.

Keep an eye on the shell (console) used to launch the app:

Locate where in the controllers these messages are being generated:

...
    Logger.info("Rendering Dashboard");
...

These logging messages are a useful technique for figuring out what might be going wrong with an app. There are three variants of the logging statement: info, error and debug. Experiment now with some of these, and exercise the app to make sure the log messages appear.

...
    Logger.Error("some error has occurred");
...
...
    Logger.debug("some step has occurred...");
...

Introduce some of the above log statements, and verify that they appear as expected on the console.

Playlist Model

The app as we have it now could have been completely built as a static web site. However, the framework and tools we have used in this version enable us to evolve this static site into an interactive app

Model Classes

We can introduce 'model' classes to implement the playlist in our app. Introduce these two new classes into the models package:

app/models/Song.java

package models;

public class Song
{
  public String title;
  public String artist;

  public Song(String title, String artist)
  {
    this.title = title;
    this.artist = artist;
  }
}

app/models/Playlist.java

package models;

import java.util.ArrayList;
import java.util.List;

public class Playlist
{
  public String title;
  public List<Song> songs = new ArrayList<Song>();

  public Playlist(String title)
  {
    this.title = title;
  }
}

Controller

In the Dashboard controller, we can define a playlist object in Java, add some songs, and send to the view:

package controllers;

import models.Playlist;
import models.Song;
import play.Logger;
import play.mvc.Controller;

public class Dashboard extends Controller
{
  public static void index() {
    Logger.info("Rendering Dashboard");

    Song s1 = new Song("Piano Sonata No. 3", "Beethoven");
    Song s2 = new Song("Piano Sonata No. 7", "Beethoven");
    Song s3 = new Song("Piano Sonata No. 10", "Beethoven");
    Playlist playlist = new Playlist("Beethoven Sonatas");
    playlist.songs.add (s1);
    playlist.songs.add (s2);
    playlist.songs.add (s3);

    render ("dashboard.html", playlist);
  }
}

Views

The next step is to eliminate some of the static content from the listsongs partial. Here is a new version to replace the current version:

views/tags/listsongs.html

<table class="ui fixed table">
  <thead>
    <tr>
      <th>Song</th>
      <th>Artist</th>
    </tr>
  </thead>
  <tbody>
    #{list items:_playlist.songs, as:'song'}
      <tr>
        <td>
          ${song.title}
        </td>
        <td>
          ${song.artist}
        </td>
      </tr>
    #{/list}
  </tbody>
</table>

In the above, we are iterating over an array of songs inside a playlist object

Here is a new version of the dashboard, which will have to pass the playlist to the partial :

app/views/dashboard.html

#{extends 'main.html' /}
#{set title:'Dashboard' /}

#{menu id:"dashboard"/}

<section class="ui segment">
  <h2 class="ui header">
    Beethoven Sonatas
  </h2>
  #{listsongs playlist:playlist/}
</section>

This playlist object, in turn, must be passed to the dashboard controller for this to work. We already did this in the Dashboard conntroller.

Run the app now - and verify that you can see the playlists.

Multiple playlists

Here is a revised Dashboard index method:

app/controllers/dashboard.java

...
  public static void index() {
    Logger.info("Rendering Dashboard");

    Song s1 = new Song("Piano Sonata No. 3", "Beethoven");
    Song s2 = new Song("Piano Sonata No. 7", "Beethoven");
    Song s3 = new Song("Piano Sonata No. 10", "Beethoven");
    Playlist p1 = new Playlist("Beethoven Sonatas");
    p1.songs.add (s1);
    p1.songs.add (s2);
    p1.songs.add (s3);

    Song s4 = new Song("Piano Concerto No. 1", "Beethoven");
    Song s5 = new Song("Piano Concerto No. 12", "Beethoven");
    Song s6 = new Song("Piano Concerto No. 23", "Beethoven");
    Playlist p2 = new Playlist("Beethoven Concertos");
    p2.songs.add (s4);
    p2.songs.add (s5);
    p2.songs.add (s6);

    List<Playlist> playlists = new ArrayList<Playlist>();
    playlists.add(p1);
    playlists.add(p2);

    render ("dashboard.html", playlists);
  }
...

The above code will have errors - unless you import these libraries:

import java.util.ArrayList;
import java.util.List;

Before pasting in the above code - try to use the Eclipse autocorrect feature in insert them automatically.

Notice that we are now creating 2 playlists: p1 and p2. Each of these contains 3 songs. Read this code carefully and make sure you can understand the data structure.

Here is a new version of the dashboard view:

app/views/dashboard.html

#{extends 'main.html' /}
#{set title:'Dashboard' /}

#{menu id:"dashboard"/}

#{list items:playlists, as:'playlist'}
  <section class="ui segment">
    <h2 class="ui header">
      ${playlist.title}
    </h2>
    #{listsongs playlist:playlist/}
  </section>
#{/list}

Browse the dashboard app now - and you should see the two views:

See if you can follow the logic from the controller to the view:

...
    render ("dashboard.html", playlists);
...

Then in the view - a loop iterating over the playlists

#{list items:playlists, as:'playlist'}
  ...
  ...
#{/list}

... and finally in listsongs.html, iterating over each song in a single playlist:

    ...
    #{list items:_playlist.songs, as:'song'}
      ...
      ...
    #{/list}
    ...

This is a complex flow from java into one template... then into another.

Exercises

Exercise 1: Download and Rename Sample Solution

A complete version of the app as it should be at the end of this lab:

Download this, and unzip it. Then, from a shell inside the folder enter:

play idealize

... and then open the project. Run the project (using play run from the command line) and examine the running program by browsing to:

Exercise 2: Review Playlist Data Structure

Look again at the playlist as represented in:

    Song s1 = new Song("Piano Sonata No. 3", "Beethoven");
    Song s2 = new Song("Piano Sonata No. 7", "Beethoven");
    Song s3 = new Song("Piano Sonata No. 10", "Beethoven");
    Playlist p1 = new Playlist("Beethoven Sonatas");
    p1.songs.add (s1);
    p1.songs.add (s2);
    p1.songs.add (s3);

    Song s4 = new Song("Piano Concerto No. 1", "Beethoven");
    Song s5 = new Song("Piano Concerto No. 12", "Beethoven");
    Song s6 = new Song("Piano Concerto No. 23", "Beethoven");
    Playlist p2 = new Playlist("Beethoven Concertos");
    p2.songs.add (s4);
    p2.songs.add (s5);
    p2.songs.add (s6);

    List<Playlist> playlists = new ArrayList<Playlist>();
    playlists.add(p1);
    playlists.add(p2);

Review the structure carefully. Can you draw a picture of the structure of the data? Extend your version of the app to contain a new playlist - make up some songs. Verify that they are rendered as expected.

Exercise 3: New Fields

Extend the song model to also have a duration field. Change the constructor to initialise this field. Modify the dashboard view to display it. Also include a duration field in the Playlist.

Exercise 4: Extend Views

Extend the following views:

  • Start: Extend the start view to contain a more detailed outline of the purpose of the application
  • About: Extend the about view to provide contact details (may be fictitious) for the author/company that created the app.