Subscriber Management

Collecting Subscribers

Before the main application launches, you may want to spread the world and ask your potential customers to sign up for the future marketing campaign.

The home page, which we build earlier in the Homepage Design section, includes the Enter your email text box and the Sign Up button. The idea is that when a subscriber enters the email address and clicks the Sign Up button several this happen:

  • Subscriber’s email address and IP address are captured.
  • Captured data is stored in the database.
  • Administrator is notified that a new subscriber has signed up.

Subscriber Model

In order to work with the subscriber data, let’s add a Subscriber model.

Ctrl-click models and select New - Scala Class.

Enter Subscriber in the name field and double-click Object.

If prompted, click Add to add the file to version control.

Add the following import statements below the package declaration:

Delete the current Subscriber class definition and replace it with two case classes:

Subscriber case class contains the subscriber data: email, IP address, and the timestamp. SubscriberForm case class is used to hold the data captured by the HTML view.

Add SubscriberOperations object extending Logging:

Inside the object, add two private values:

Collection defines database collection users, where the model data is stored and log defines an instance of the Logger.

Add a new method.

Create method accepts an instance of Subscriber case class. From the class instance, a MongoDB document is composed.

Using the Database utility the document is stored in the subscribers collection.

Super administrator’s email is read from the database.

New subscriber notification email is composed.

Notification email is sent with SendGrid utility.

Example: LineDrop Play/Scala Web Application - Subscriber Model

Index View

Open index.scala.html in the views directory and review the file contents. Elements with the following ids will react to the subscription event:

  • get_notified
  • thank_you
  • subscriber_email
  • button_sign_up

When the user taps the button, we want to validate the contents of subscriber_email and send the data to a controller.

Once data is sent, we want to hide get_notified, subscriber_email, button_sign_up, and display thank you.

Index jQuery

Open index.js in public/javascripts directory.

Add an event handler to the button_sign_up button.

Once the view has loaded, the button_sign_up click event will invoke the validate function.

Add an event handler to the Enter key press.

Add the validate function.

The function checks for the “@” symbol in the subscriber_email content. If present, the subscriber function is called; otherwise the field is focused.

Add the subscribe function.

The subscribe function creates a structured AJAX request of POST type to a URL /subscribe/json. The payload of the request is a JSON-formatted string with the “email” and the contents of subscriber_email as a key/value pair.

CSRF token, stored on in a hidden field, is added to the header of the request.

On a successful server response, the message included in the response object is printed. The response is expected to be in JSON format.

For usability’s sake, we display thank_you by removing a Bootstrap “d-none” class and hide get_notified, subscriber_email, and button_sign_up by adding a Bootstrap “d-none” class.

Example: LineDrop Play/Scala Web Application - Index jQuery

Home Controller

Open HomeController and add the following import statements:

Add an instance of the Logger to the Home Controller class, above the index method definition.

Add the subscriber JSON Reads converter to the class below the log definition.

Add a new method to handle the AJAX request.

Subscriber_json tries to parse the body of the request as JSON and, utilizing JSON Reads converter, assigns the request contents directly into _form variable as an instance of SubscriberForm.

Depending on the server environment, the IP address of the request is extracted either from remote address or the header. Later, when setting up the production environment, a special header will be defined in the proxy server configuration; otherwise, all IP addresses will register as localhost.

If an error occurs, it is logged.

Regardless, of the result, a success message is returned.

Example: LineDrop Play/Scala Web Application - Home Controller

Routing

Finally, let’s route the URL to the controller method.

Open routes and add an entry for a POST request to /subscribe/json.

Test

Before testing, please make sure that the database is running.

Application Response

Open Chrome and browse to http://localhost:9000.

Enter your email address and click Sign Up.

You should see the Thank you message.

Log

Logger will have printed the debug message in the Terminal window. The log has also been written to /logs/application.log in the project’s directory.

Database Records

Launch MongoDB Compass.

When prompted, enter mongodb://localhost as the connection string and click Connect.

Click sample database.

You should see the new subscribers collections.

Click on subscribers in the left panel to view the new document.

Email Notification

Check your email. You should have received a New subscriber email.

Review

Let’s review what happened when you subscribed.

...

Once the Sign Up button is tapped, email syntax is validated and the subscribe function is invoked. Data is composed into JSON format and sent as a POST request to /subscribe/json with AJAX.

The router matches URL syntax to route definition and invokes HomeController.subscribe_json.

Home controller’s subscribe_json method reads the POST request and maps the JSON payload into a SubscriberForm class using a JSON Reads converter. Depending on the server environment, the IP address is read either from the request or the hearder. Subscriber Operations’ create method is invoked passing an instance of Subscriber object.

Subscriber Operations’ create method composes a document containing the subscriber data and invokes Database utility to insert the document into the database. Administrator’s email address is read for the configuration, and the notification email message is composed.

SendGrid.send is invoked to send the notification email to the administrator.

Commit and Push Changes

Select VCS - Commit from the top menu.

Review the files and directories and enter the commit message.

Click the dropdown arrow on the Commit button and select Commit and Push.

Click Push to confirm.

Subscribers Dashboard

Although, subscriber data can be pulled directly for the database, it is more convenient to interact with the subscriber data from a view.

Let’s add support to display subscriber data to the application. Once the application can present subscriber data, we will secure it to allow only authenticated users to access the information.

Dashboard Template

Dashboard template is used by the Subscribers view and the Users view, both of which we will add shortly. The template is based on the Bootstrap’s Dashboard example. The template and the views accessible only to the authenticated users will live in the views’ secure package.

The template includes a search input used by both the Subscribers and the Users views and is supported by search.js.

Ctrl-click views and select New - Package.

Type in views.secure and press Enter to create a new package.

Ctrl-click secure and select New - HTML File.

Enter dashboard_template.scala.html in the name field.

Double-click HTML 5 file.

If prompted, click Add to add the file to version control.

Browse to the project repository to view the file:

LineDrop Play/Scala Web Application - Dashboard Template

Copy the contents of the file and paste them into dashboard_template.scala.html replacing the original code.

Reference

Bootstrap - Dashboard Template

Search jQuery

Search.js contains the filter search function.

Expand the public directory and ctrl-click javascripts and select New - File.

Type in search.js in the name field and press Enter key.

If prompted, click Add to add the file to version control.

LineDrop Play/Scala Web Application - Search jQuery

Copy the contents of the file and paste them into search.js.

The function reads the value of the input element with id search, which is located in the Dashboard template.

For each list item with a searchable class inside searchable_list , the function checks if the item contains the search value. If so, the element is hidden with jQuery’s toggle function. All hidden elements are then filtered with the jQuery’s filter function.

Reference

https://api.jquery.com/toggle/

https://api.jquery.com/filter/

Subscribers View

Let’s update the Subscribers view to present the data in a convenient, visual, and searchable way.

Expand views/secure and open subscribers.scala.html.

Browse to the project repository to view the file:

LineDrop Play/Scala Web Application - Subscribers View

Copy the contents of the file and paste them into subscribers.scala.html replacing the original code.

Subscribers view utilizes Scala code injections to access and work with compound objects.

At the very top of the file, two import statements are prefixed with the “@” symbol to use Scala code directly in the view. Please note, Scala code will be compiled first, followed by jQuery, and then finally HTML.

Next, values passed to the view by the controller are defined.

Subscribers is a map which holds all subscribers, count_by_date is a list of total subscribers for each date, and role is the user type.

The view is divided into a navigation panel designated by the nav tag and the main content area designated by the main tag.

Inside the navigation, Scala if statement is used to conditionally display a link to the /users. The link is only visible to administrators.

The main area holds the chart, populated by script at the bottom of the file, and the subscriber list.

Each element in the subscriber list is created using Scala’s for loop.

A map of subscribers is passed to the view by the controller. The map is converted to list and sorted by the created timestamp.

Notation _.2 is a shorthand device for getting the element’s value; in a similar manner, _.1 would be the element’s key.

Because there is no default iterator for Joda’s DateTime, converting the value to Int by using getMillis allows us to use the sortBy method. Each subscriber element is an instance of the Subscriber class.

The file includes a CDN link to Chart.min.js, which provides support for the graph, and a reference to search.js, which provides support for the filter search implemented on the page.

The final block of code is a script which sets the chart’s options and data. Labels are keys of the count_by_date map and data is defined by values of the count_by_date map. Comma-separated arrays for labels and data are populated by the Scala’s for loop.

Note on If and For Syntax

Scala’s if and for declarations in a view cannot have any space between the keyword and the opening parenthesis. For example:

Correct use: @for((date,count) <- count_by_date { …
Incorrect use: @for ((date,count) <- count_by_date { …

Reference

Bootstrap - Dashboard Template

Subscriber Model

Add a new method to the Subscriber model to read all subscriber data from the Database.

The method uses the Database utility’s find_all method to get all documents in the subscribers collection. Data is returned as a sequence; each sequence element is converted to the needed type to create a key/value pair for the map. Key is the MongoDB’s ObjectId of the document, and the value is a Subscriber object. Each key/value pair is added to a mutable, meaning changeable, map. The resulting map is converted to an immutable map, since the map will no longer need to change.

Add count_by_date method to aggregate and group subscriber data. This method introduces the power and convenience of Scala and showcases one of the reasons why it is LineDrop’s language of choice.

Method accepts a map of subscriber data and checks that the map is not empty, otherwise an empty List is returned.

Values in the subscriber map are reduced to the left, meaning that when comparison function is executed, only the value on to the left of the equal sign remains. In this case, the function compares two adjacent objects by taking milliseconds of the timestamp of object creation.

Once the values are reduced, producing a subscriber object with the oldest timestamp with type Joda DateTime, object’s created property is formatted in UTC, day of the year extracted, and rounded to the start of the day.

Today’s day is calculated by extracting the day of the year of the current timestamp in UTC and rounding it to the end of the day.

The span of data in days is calculated.

By looping through each day from start to now, only the subscribers created on that day are considered and a key/value pair is added to the aggregate map with the date as the key and the subscriber count as the value. On each iteration, the day is incremented.

The final map is converted to a List and sorted in ascending order by timestamp.

Example: LineDrop Play/Scala Web Application - Session Model

Dashboard Controller

Update the Dashboard Controller's subscribers method. Inside the case Some(user) replace the Ok action with the new code.

Subscribers is a map containing all subscriber data and count_by_date is a list of tuples with date as a key and its corresponding total count of subscribers as a value. The map and the list are passed to the view.

Example: LineDrop Play/Scala Web Application - Dashboard Controller

Test

Application Response

Browse to http://localhost:9000/subscribers.

A graph of subscriber count by date should be visible on the page as well as the list of each subscription event below. Test the filter search by typing in an email address, IP address, or some part of the date.

Add more subscribers by entering email addresses on the home page. The same address may be used multiple times.

Log

Logger will have printed debug messages in the Terminal window. The log has also been written to /logs/application.log in the project’s directory.

Review

...

Once the user is authenticated, the controller uses Subscriber Operations to get all subscribers and the aggregate count by date. Subscriber view is rendered.

Commit and Push Changes

Select VCS - Commit from the top menu.

Review the files and directories and enter the commit message.

Click the dropdown arrow on the Commit button and select Commit and Push.

Click Push to confirm.


Next: User Management