Newer posts are loading.
You are at the newest post.
Click here to check if anything new just came in.

January 10 2014

21:22

Building a Customer Management App Using AngularJS and Laravel

When creating a single-page app we should use some kind of framework to do some of the job for us, so we can focus on the actual functionality. AngularJS fits here perfectly, because features like dynamic dependency injection and bi-directional data binding are just great. Sometimes we also require some kind of server. If you’ve chosen PHP then Laravel may be your best option, as it’s easy to work with and pretty powerful.


Introduction

In this tutorial you will create a simple customer/transaction management system with the ability to add and remove both transactions and customers. This is probably not the kind of thing you make very often, but it shows how to use features of both frameworks.

Before we start you should setup a MySQL database which we will use (Laravel supports many more of them, but this is still the most popular one). You don’t need any web server since we will be using PHP’s built-in one (but please keep in mind, that this solution is only for the development and should never be used in production – it lacks many features that are required for your app to work properly in public). For that, we will need at least PHP version 5.4.0.


Preparation

The first thing we have to do is to install Laravel. The full process is described on Laravel’s website. After that, you should have your project directory created with all of Laravel’s files in there. Navigate to that directory in your command line and run this command there:

php artisan serve

If all goes OK, you should see that the local development server was started on locahost:8000. Open your browser and navigate there, you should see Laravel’s welcome page:

first_run

Now we can proceed to the actual application.


Migrations and Models

Models in Laravel are just like in any other MVC framework. It’s using the Eloquent ORM to ease the work for you – you will probably never need to write an SQL query again (unless you’ll want something that Eloquent does not support). Using migrations you can modify the database structure with the ability to rollback changes if something goes wrong. You can read more about migrations in the documentation.

In our app we will use two models:

  • Customer – will hold the customer data
  • Transaction – will hold the information about a transaction

Let’s start by creating migrations for them. If you have not done so already, shut down the server we started earlier (Ctrl + C).

Customers

First, invoke this command:

php artisan migrate:make create_customers_table

This will create a migration file with a basic structure for you. Now navigate to app/database/migrations. There should be a file with its name starting with a timestamp and ending with “create_customers_table“. Laravel automatically created this basic structure for you. The up() method is called when the migration is applied, and down() when it’s rolled back.

First call the Schema::create() method. It takes two arguments – the schema’s name and a callback function:

Schema::create('customers', function ($table) {

The callback is executed when the table is created. The table object is passed as the $table variable and we manipulate the table’s structure using it. Let’s add an auto-incrementing id field:

	$table->increments('id');

Next there will be three string fields for the customer’s first name, surname and email:

	$table->string('first_name');
	$table->string('last_name');
	$table->string('email')->unique();

We make the email field unique by calling the unique() method on it.

The last method is for the timestamps:

	$table->timestamps();
});

This will create two date fields in the schema: created_at and updated_at. These will be used by Eloquent to store the time when the item was created and updated.

Finally, the code should look like this:

public function up() {
	Schema::create('customers', function ($table) {
		$table->increments('id');
		$table->string('first_name');
		$table->string('last_name');
		$table->string('email')->unique();
		$table->timestamps();
	});
}

The down() method is much simpler – it just deletes the schema:

public function down() {
	Schema::drop('customers');
}

Transactions

The code here will be similar to the customers’ one. First invoke this command:

php artisan migrate:make create_transactions_table

Now locate the appropriate file in the app/database/migrations and open it. Like earlier, start by creating the schema:

Schema::create('transactions', function ($table) {

Now add the fields for the id, transaction’s name, its cost and the id of the customer that it belongs to:

	$table->increments('id');
	$table->string('name');
	$table->float('amount');
	$table->integer('customer_id');

And of course the timestamps:

	$table->timestamps();
});

The final code should look like this:

public function up() {
	Schema::create('transactions', function ($table) {
		$table->increments('id');
		$table->string('name');
		$table->float('amount');
		$table->integer('customer_id');
		$table->timestamps();
	});
}

And now the down() method:

public function down() {
	Schema::drop('transactions');
}

Database Configuration

Now before you apply the migrations you’ll have to configure the connection to your database. Open the app/config/database.php file and go to line 55. Here is the configuration data for MySQL (there are few others in there, for example you could use SQLite or Postgres):

'mysql' => array(
	'driver'    => 'mysql',                 // database driver, don't touch
	'host'      => 'localhost',             // host of the database, usually localhost unless you have your db on some server
	'database'  => 'database',              // name of the database you will be using, it must be created earlier
	'username'  => 'root',                  // username that the script will use to connect, I strongly advice against using root user for this
	'password'  => '',                      // password for the user above, it's better not to use a blank one
	'charset'   => 'utf8',                  // encoding of the db
	'collation' => 'utf8_unicode_ci',       // db's collation setting
	'prefix'    => '',                      // prefix of the database tables, useful if you have multiple scripts using the same database
),

After you have filled that in, you are good to go. Make sure you saved the file and invoke this command from your app’s main directory (the one with the artisan file in it):

php artisan migrate

And thats it. If there were no errors, that means that the tables were created successfully. You can connect to your db using, for example, phpMyAdmin to check manually if you want.

Models

In Laravel, creating a model after you’ve configured your database using migrations is really quick. Navigate to app/models and delete the example User.php file that is there. Now create two files named Customer.php and Transaction.php.

Let’s start with Customer.php. Every model in Laravel has to extend the Eloquent class:

class Customer extends Eloquent {

Now we will define a relationship between the customer and their transactions. This is done by defining a public method in the model with the name of the property we would like to have in it (in this case transactions):

	public function transactions() {

Now in the body of the function there will be only one line:

		return $this->hasMany('Transaction');
	}
}

This tells Eloquent that it should provide all transactions with customer_id of the customer under a property named transactions.

Now we will do pretty much the same for the transactions, but we will reverse the relationship to make the transaction’s owner accessible via the customer property:

class Transaction extends Eloquent {
	public function customer() {
		return $this->belongsTo('Customer');
	}
}

This is done using the $this->belongsTo() method of the model.


Controllers

Now to actually use the models we have to create controllers for them. Head to the app/controllers directory, delete the HomeController.php only – BaseController.php is important as our controllers will extend it. Now create two files: CustomerController.php and TransactionController.php.

CustomerController

This controller will handle everything related to the customers – adding, removing and showing a list of them. Start by defining the class:

class CustomerController extends BaseController {

We will be using Laravel’s feature named RESTful controllers. It makes creating routes easier because we only have to define the base URI and Laravel will handle everything for us. This requires you to start your function names with the appropriate HTTP verb and then continue with the subroute name (using camelCase). So for example, if we would have a method named getNames and the base URI would be /customers, then the method will be accessible at /customers/names.

The getIndex(), postIndex(), deleteIndex() etc. methods will be mapped to the default route (in this case /customers).

Now let’s define our first route – getting the customer by their id:

	public function getIndex() {

Let’s get the id from the query parameters (Laravel provides a nice Input class to deal with that, so you don’t have to use $_GET, $_POST and $_FILES):

		$id = Input::get('id');

And search for the user in the database using that id:

		return Customer::find($id);
	}

Every method of the controller has to return a value that is a string or has a __toString() method. In this case the Customer model that is returned will be converted to JSON before sending.

Now lets return a list of all users (this will be accessible under /customers/all):

	public function getAll() {
		return Customer::all();
	}

As you can see, we can get all customers using the model’s all() method.

Now the longest part, adding a new customer:

	public function postIndex() {

First let’s check if all information needed was provided. We can do this using the Input::has() method:

		if (Input::has('first_name', 'last_name', 'email')) {

Let’s put all of the input fields in the $input variable to avoid calling Input::get() over and over. This can be done using Input::all():

			$input = Input::all();

Next we will check if any of the inputs are empty. If so, we will return a HTTP 400 Bad Request error with a more verbose message:

			if ($input['first_name'] == '' || $input['last_name'] == '' || $input['email'] == '') {
				return Response::make('You need to fill all of the input fields', 400);
			}

Since we wanted to return a status code other than 200 instead of just returning the message as a string, we used Response::make(), which takes the data to send as the first parameter and the status code as the second. Take a look at the docs if you want to know more about responses.

Now we finally create a new Customer model and feed it with the data provided:

			$customer = new Customer;
			$customer->first_name = $input['first_name'];
			$customer->last_name = $input['last_name'];
			$customer->email = $input['email'];

After that we can save the newly created model and respond to the request with it:

			$customer->save();
			
			return $customer;

Here we handle the case if not all of the inputs were provided:

		} else {
			return Response::make('You need to fill all of the input fields', 400);
		}
	}

Finally, we also need the ability to remove the customers. This one is really short:

	public function deleteIndex() {

We start by getting the id of the customer to delete:

		$id = Input::get('id');

Next, we search for and delete the customer:

		$customer = Customer::find($id);
		$customer->delete();

After that, we respond to the request with the id provided:

		
		return $id;
	}
}

Now before the routes can be accessed, we have to hook them. Open the app/routes.php file, delete everything but the comment and add this line at the end of the file:

Route::controller('/customers', 'CustomerController');

This will tell Laravel to route all requests at /customers to our CustomerController. Now you can use CURL to play with it. First start the server with php artisan serve and then you can, for example, create a customer:

curl -X POST -d "first_name=Jane&last_name=Doe&email=jdoe@gmail.com" http://localhost:8000/customers

Then you can get the list of all customers:

curl http://localhost:8000/customers/all

TransactionController

This, like the model is very similar to the CustomerController. First create the class:

class TransactionController extends BaseController {

Then let’s define the method to get all transactions for a user:

	public function getIndex() {
		$id = Input::get('id');
		return User::find($id)->transactions;
	}

As you can see we are using the relationship defined earlier to get the transactions (now recall the query you had to write to achieve the same thing using plain PHP and SQL).

The next thing will be the creation of transactions:

	public function postIndex() {

Like earlier, we are checking if all of the required information is provided:

		if (Input::has('name', 'amount')) {

If so, assign it to an $input variable:

			$input = Input::all();

Check if any of the values provided are empty and if so return an error:

			if ($input['name'] == '' || $input['amount'] == '') {
				return Response::make('You need to fill all of the input fields', 400);
			}

Now create the transaction and supply it with all of the info provided:

			$transaction = new Transaction;
			$transaction->name = $input['name'];
			$transaction->amount = $input['amount'];

Now we need to add it to the appropriate customer. Let’s find them by the id provided and add the $transaction to their transactions list:

			$id = $input['customer_id'];
			User::find($id)->transactions->save($transaction);

This is done using the transactions->save() method provided by Laravel. Now we can respond with the transaction created:

			return $transaction;

And handle the case where none or not all of the data was provided:

		} else {
			return Response::make('You need to fill all of the input fields', 400);
		}
	}

After that there is also a method to delete the transaction in the same way that we deleted the customer:

	public function deleteIndex() {
		$id = Input::get('id');
		$transaction = Transaction::find($id);
		$transaction->delete();
		
		return $id;
	}
}

Now just add the route and you can test the controller using CURL:

Route::controller('/transactions', 'TransactionController');

Conclusion

Alright, this is the end of the first part – in the second part of this tutorial, we will create the front-end using AngularJS. Feel free to add more features to your app (like editing customers or sorting), in case you did not find the information you were looking for, take a look at Laravel’s documentation.

October 02 2013

14:00

Resources to Get You Up to Speed in AngularJS

Some of you may have read my Ember.js series of articles and realized I'm pretty smitten with the framework. But as a professional developer, it's important that I consider other tools and take the time to vet them properly. One of the frameworks that everyone keeps telling me to look at is AngularJS. It aims to make building complex web apps easier but from experience, I know that most frameworks have a learning curve. It's never just dive in and code.

As such, I wanted to complile a list of resources which I've found that I'll be leveraging as I go down the path of learning AngularJS and which I thought might be useful to Tuts+ readers. I've broken them down by categories and included both commercial and freely available resources.

And to be clear, this isn't the end-all, be-all of resources. Invariably, someone will ask why I didn't include a specific resource and that's okay. Just realize this list isn't all-inclusive and solely meant to kickstart my AngularJS learning effort.


AngularJS Official Resources

The AngularJS Project Site

Google has done an excellent job of compiling solid documentation and support resources to help developers work with AngularJS. When you visit the site, the thing that stands out for me is how it immediately drives you to understanding how to begin using Angular. That's important since, as with any MVC-type framework, Angular has a learning curve to overcome and it seems Google are doing their best to help ease any pain associated with it.

Also, being an open-source effort affords you the opportunity to peak under the hood as the code is hosted on GitHub.

AngularJS YouTube Channel

The Angular YouTube channel has a wealth of information available to you to understand specifics about the framework. In looking through the videos, I would consider this less of a beginner's resource as a hub for those who want to dig deeper into specific areas of AngularJS like data-binding. There are some videos that will walk you through building an app but it's not typical courseware that you'd expect from sites like Tuts+ or PluralSight. Nonetheless, the videos are well-done and considering the amount of content, certainly well-worth investigating especially since they're free.

Google Group Support Forum

Support is always the bane of every open source project, mainly because they're typically volunteer-driven so support is basically a labor of love. While there are a couple of ways to get support for your AngularJS questions, this is an official Google medium and it is VERY active.

IRC #angularjs

Want more direct and immediate support? Then jump on into #angularjs on Freenode for some real-time help. When I jumped into the channel, it was jam packed with developers so you should have no problems striking up a chat with someone about Angular.


Online AngularJS Courses

Sometimes you need a jumpstart. Something to guide you through the basics of a technology and get you over the initial hump. Thankfully, there's a nice blend of free and commercial courseware at your disposal.

Tuts+ Premium Angular Course

Obviously a little biased but we're pretty pleased with our AngularJS course by Tom Ashworth which walks you through the important concepts of AngularJS

Egghead.io

John Lindquist has produced 49 videos of sheer AngularJS awesomeness and the amazing part is that he's provided them for free. To quote him, "I've never been satisfied with video tutorial sites, so I'm doing something about it."

What I like about the course is that each video is short and concise (average of three to five minutes per video). I loathe overly long videos and prefer quick hits that drill down into a topic to get you the info you need to start hacking.

Thinkster.io

The team at Thinkster put an interesting twist on their course. They took the excellent courseware created at Egghead.io and combined it with walk-throughs that break down what the video taught you and expand on the topic. This is actually very clever. They even give credit where it's deserved:

We’ve found that the egghead.io videos are the best starting resource available, so every chapter will lead off with them.

This will be my second resource for learning AngularJS (after Tuts+ of course).

Code School

Code School has an excellent reputation for building online courses. While I haven't done this specific course, in looking at the comments, it seems like it's a solid introduction to the framework.

Pluralsight

Another great company that has steadily amassed a ton of great courseware on a variety of topics, Pluralsight also offers a course on AngularJS which from the syllabus appears to be one of the most comprehensive courses available. I especially like that it includes a big section on testing AngularJS apps, something that seems to be an afterthought in most cases.


AngularJS Community Resources

Jeff Cunningham's AngularJS-Learning Repo

If there's one community resource you need to know about, it's the AngularJS-Learning Github page created by Jeff Cunningham. It is one the most comprehensive lists of AngularJS resources I've seen and includes links to blog posts, articles, videos and more. It's also been translated into multiple languages which is icing on the cake.

This truly is a resource to bookmark regardless your level of experience with the framework.

DailyJS

I've been a fan of Alex Young for some time because his site, DailyJS, posts some of the best information about JavaScript on the Internet. I especially love his tutorials where he creates a multi-part series that walks you through using frameworks to actually build something.

In this tutorial series, he walks you through creating a feed reader leveraging several important technologies including Yeoman, BootStrap, Grunt and Bower. So not only do you get to learn about AngularJS but also how it ties into some of the most widely used tools and technologies in use today.

Stack Overflow

The old reliable for support questions, Stack Overflow seems to always be one of the important places to reference when you have issues. Sure, the answers may not always be what you're looking for but at 11k+ AngularJS questions already asked, it's highly likely you'll find the answer you're looking for.

One Hungry Mind

Lukas Ruebbelke's passion for AngularJS resonates in his posts about the framework. With posts dating back to April, 2012, there's certainly plenty of information available to budding AngularJS devs. There's even a post on using AngularJS to build Windows 8 apps!

TodoMVC AngularJS Demo App

Ever wanted to jump into a new technology and wish you had some solid code to use as a reference to learn by? Yeah, same here. That's what the TodoMVC project aims to provide. The project provides a simple but well-written todo app that leverages major libraries including Backbone.js, Dojo and more. Want to learn YUI? There's a todo app for that. Want to learn Kendo UI? Same thing.

Of course, AngularJS is represented here and the framework page provides you not only with source code but a live demo that you can tinker with. Good stuff.


AngularJS Books

Being so new, books on AngularJS aren't common but there are some notables to mention and several that are in production as you read this. The two standouts at the moment are:

Manning is also working on AngularJS in Action. It's currently in Manning's Early Access Program (MEAP) so you can pre-order it and receive the chapter updates as the authors make them available.


Way More Stuff Available

My list is by no means the end all. If you just look at Jeff Cunningham's list you'll find a TON more links to look over. What I wanted to do was narrow down the options to what I felt were good resources to start off with so that I don't get overwhelmed with the volume of information available.

Hopefully this list helps you kickstart your AngularJS learning efforts and allows you to build something really exciting.

June 27 2013

21:01

Building a Web App From Scratch in AngularJS

In a previous AngularJS tutorial I covered all the basics of how to get up and running with Angular in around 30 minutes. This tutorial will expand on what was covered there by creating a simple real world web application.

This simple web application will allow its users to view, search and filter TV Show Premieres for the next 30 days. As a keen series viewer, I am always looking for something new to watch when my favorite shows are off air, so I thought I would create an app to help me find what I am looking for.

Before we get started, you may want to take a look at the demo from above, to see what we will be creating in this tutorial.


Getting Started

To begin, we need a skeleton AngularJS application which already has all the required JavaScript and CSS to create the TV Show Premieres app. Go ahead and download this skeleton from the “download source files” button above.

Once you have downloaded the files you should have a directory structure as shown below:

figure1-skeleton-directory-structure

Looking at the directory structure and the included files you will see that we will be using Twitter Bootstrap to make our web app a little prettier, but this tutorial will not look at Twitter Bootstrap in any detail (learn more about Twitter Bootstrap). Additionally, this tutorial will not be covering how to setup a new AngularJS application as the aforementioned AngularJS tutorial already covers this in detail.

Upon opening index.html, with your browser of choice, you should see a very simple web page with just a title and some basic formatting as seen below:

figure2-basic-web-page

Loading In Our Data

The first thing we are going to need to create our TV Show app, is information about TV shows. We are going to use an API provided by Trakt.tv. Before we can get started you are going to need an API key, you can register for one on their website.

Why use this API? Do I really have to register? We are using this API so our app will use real data and will actually provide some use once completed. Also, by using this API we do not need to go over any server side implementations within this tutorial and can focus completely on AngularJS. An extra couple of minutes to register for the API will be well worth it.

Now that you have your own API key, we can utilize the Trakt API to get some information on TV shows. We are going to use one of the available API calls for this tutorial, more information on this is available in the api docs. This API call will provide us with all the TV Show Premieres within a specified time frame.

Open mainController.js and modify it to match the below code:

    app.controller("mainController", function($scope, $http){

        $scope.apiKey = "[YOUR API KEY HERE]";
        $scope.init = function() {
            //API requires a start date
            var today = new Date();
            //Create the date string and ensure leading zeros if required
            var apiDate = today.getFullYear() + ("0" + (today.getMonth() + 1)).slice(-2) + "" + ("0" + today.getDate()).slice(-2);
            $http.jsonp('http://api.trakt.tv/calendar/premieres.json/' + $scope.apiKey + '/' + apiDate + '/' + 30 + '/?callback=JSON_CALLBACK').success(function(data) {
                console.log(data);
            }).error(function(error) {

            });
        };

    });

If you look within the index.html file, for the following line:

    <div class="container main-frame" ng-app="TVPremieresApp" ng-controller="mainController" ng-init="init()">

You will see that the ng-init method is calling the init function, this means that the init() function within our mainController will be called after the page has been loaded.

If you read the API documentation for the calendar/premieres method you will have seen that it takes three parameters, your API key, the start date (e.g. 20130616) and the number of days.

To provide all three parameters, we first need to get today’s date using JavaScripts Date() method and format it to the API specified date format to create the apiDate string. Now that we have everything we need, we can create an $http.jsonp call to the API method. This will allow our web app to call a URL that is not within our own domain and receive some JSON data. Ensure that ?callback=JSON_CALLBACK is prepended onto the request URI so that our attached .success callback function is called on response.

Within our .success function we then simply output the received data to the console. Open index.html within your browser and open the JavaScript console, you should see something like the following:

figure3-javascript-console

This demonstrates that we are successfully performing a call to the Trakt API, authenticating with our API key and receiving some JSON data. Now that we have our TV show data, we can move on to the step.


Displaying Our Data

Processing the JSON Objects

Before we can display our data, we need to process and store it. As the API returns the premiere episodes grouped by date, we want to remove this grouping and just create a single array with all the premiere episodes and their associated data. Modify mainController.js to be as follows:

    app.controller("mainController", function($scope, $http){
        $scope.apiKey = "[YOUR API KEY]";
        $scope.results = [];
        $scope.init = function() {
            //API requires a start date
            var today = new Date();
            //Create the date string and ensure leading zeros if required
            var apiDate = today.getFullYear() + ("0" + (today.getMonth() + 1)).slice(-2) + "" + ("0" + today.getDate()).slice(-2);
            $http.jsonp('http://api.trakt.tv/calendar/premieres.json/' + $scope.apiKey + '/' + apiDate + '/' + 30 + '/?callback=JSON_CALLBACK').success(function(data) {
                //As we are getting our data from an external source, we need to format the data so we can use it to our desired effect
                //For each day, get all the episodes
                angular.forEach(data, function(value, index){
                    //The API stores the full date separately from each episode. Save it so we can use it later
                    var date = value.date;
                    //For each episodes, add it to the results array
                    angular.forEach(value.episodes, function(tvshow, index){
                        //Create a date string from the timestamp so we can filter on it based on user text input
                        tvshow.date = date; //Attach the full date to each episode
                        $scope.results.push(tvshow);
                    });
                });
            }).error(function(error) {

            });
        };
    });

The above code is well commented and should be easy to follow, lets take a look at these changes. First, we declare a scope variable $scope.results as an array which will hold our processed results. We then use angular.forEach (which is similar to jQuery’s $.each method for those who know it) to loop through each date group and store the date in a local date variable.

We then create another loop which loops through each of the TV shows within that date group, adds the locally stored date to the tvshow object and then finally adds each tvshow object to the $scope.results array. With all of this done, our $scope.results array will look like the following:

figure4-formatted-tvshow-json-objects

Creating the List HTML

We now have some data we wish to display within a list, on our page. We can create some HTML with ng-repeat to dynamically create the list elements based on the data within $scope.results. Add the following HTML code within the unordered list that has the episode-list class in index.html:

    <li ng-repeat="tvshow in results">
        <div class="row-fluid">
            <div class="span3">
                <img src="{{tvshow.episode.images.screen}}" />
                <div class="ratings"><strong>Ratings:</strong> <span class="label"><i class="icon-thumbs-up"></i> {{tvshow.episode.ratings.loved}}</span> <span class="label"><i class="icon-thumbs-down"></i> {{tvshow.episode.ratings.hated}}</span> <span class="label label-important" ng-class="{'label-success': tvshow.episode.ratings.percentage >= 50}"><strong>%</strong> {{tvshow.episode.ratings.percentage}}</div>
            </div>
            <div class="span6">
                <h3>{{tvshow.show.title}}: {{tvshow.episode.title}}</h3>
                <p>{{tvshow.episode.overview}}</p>
            </div>
            <div class="span3">
                <div class="fulldate pull-right label label-info">{{tvshow.date}}</div>
                <ul class="show-info">
                    <li><strong>On Air:</strong> {{tvshow.show.air_day}} {{tvshow.show.air_time}}</li>
                    <li><strong>Network:</strong> {{tvshow.show.network}}</li>
                    <li><strong>Season #:</strong> {{tvshow.episode.season}}</li>
                    <li><strong>Genres:</strong> <span class="label label-inverse genre" ng-repeat="genre in tvshow.show.genres">{{genre}}</span></li>
                </ul>
            </div>
        </div>
    </li>

This HTML is simply creating a single list element with ng-repeat. ng-repeat="tvshow in results" is telling angular to repeat this list element for each object within the $scope.results array. Remember that we do not need to include the $scope, as we are within an element with a specified controller (refer to the previous tutorial for more on this).

Inside the li element we can then reference tvshow as a variable which will hold all of the objects data for each of the TV shows within $scope.results. Below is an example of one of the objects within $scope.results so you can easily see how to reference each slice of data:

    {
    "show":{
        "title":"Agatha Christie's Marple",
        "year":2004,
        "url":"http://trakt.tv/show/agatha-christies-marple",
        "first_aired":1102838400,
        "country":"United Kingdom",
        "overview":"Miss Marple is an elderly spinster who lives in the village of St. Mary Mead and acts as an amateur detective. Due to her long and eventful life crimes often remind her of other incidents. Although Miss Marple looks sweet, frail, and old, she fears nothing; either dead or living.",
        "runtime":120,
        "network":"ITV",
        "air_day":"Monday",
        "air_time":"9:00pm",
        "certification":"TV-14",
        "imdb_id":"tt1734537",
        "tvdb_id":"78895",
        "tvrage_id":"2515",
        "images":{
            "poster":"http://slurm.trakt.us/images/posters/606.jpg",
            "fanart":"http://slurm.trakt.us/images/fanart/606.jpg",
            "banner":"http://slurm.trakt.us/images/banners/606.jpg"
        },
        "ratings":{
            "percentage":91,
            "votes":18,
            "loved":18,
            "hated":0
        },
        "genres":[
            "Drama",
            "Crime",
            "Adventure"
        ]
    },
    "episode":{
        "season":6,
        "number":1,
        "title":"A Caribbean Mystery",
        "overview":""Would you like to see a picture of a murderer?", Jane Marple is asked by Major Palgrave whilst on a luxurious holiday in the West Indies. When she replies that she would like to hear the story, he explains. There once was a man who had a wife who tried to hang herself, but failed. Then she tried again later, and succeeded in killing herself. The man remarried to a woman who then tried to gas herself to death. She failed, but then tried again later and succeeded. Just as Major Palgrave is about to show the picture to her, he looks over her shoulder, appears startled, and changes the subject. The next morning, a servant, Victoria Johnson, finds him dead in his room. Doctor Graham concludes that the man died of heart failure; he showed all the symptoms, and had a bottle of serenite (a drug for high blood pressure) on his table.",
        "url":"http://trakt.tv/show/agatha-christies-marple/season/6/episode/1",
        "first_aired":1371366000,
        "images":{
            "screen":"http://slurm.trakt.us/images/fanart/606-940.jpg"
        },
        "ratings":{
            "percentage":0,
            "votes":0,
            "loved":0,
            "hated":0
        }
    },
    "date":"2013-06-16"
    }

As an example, within the li element, we can get the show title by referencing tvshow.show.title and wrapping it in double curly brackets:{{ }}. With this understanding, it should be easy to see what information will be displayed for each list element. Thanks to the CSS bundled with the skeleton structure, if you save these changes and open index.html within your browser, you should see a nicely formatted list of TV shows with the associated information and images. This is shown in the figure below:

figure5-formatted-show-list

Conditional Classes

You may or may not have noticed:

ng-class="{'label-success': tvshow.episode.ratings.percentage >= 50}"

…which is attached to one of the span elements, within the ratings section, in the above HTML. ng-class allows us to conditionally apply classes to HTML elements. This is particularly useful here, as we can then apply a different style to the percentage span element depending on whether the TV show rating percentage is high or not.

In the above HTML example, we want to apply the class label-success, which is a Twitter Bootstrap class, which will style the span to have a green background and white text. We only want to apply this class to the element if the rating percentage is greater than or equal to 50. We can do this as simply as tvshow.episode.ratings.percentage >= 50. Take a look down the list of formatted TV shows in your browser, if any of the percentages meet this condition, they should be displayed green.


Creating a Search Filter

We now have a list of upcoming TV show premieres, which is great, but it doesn’t offer much in the way of functionality. We are now going to add a simple text search which will filter all of the objects within the results array.

Binding HTML Elements to Scope Variables

Firstly we need to declare a $scope.filterText variable within mainController.js as follows:

    app.controller("mainController", function($scope, $http){
        $scope.apiKey = "[YOUR API KEY]";
        $scope.results = [];
        $scope.filterText = null;
        $scope.init = function() {
            //API requires a start date
            var today = new Date();
            //Create the date string and ensure leading zeros if required
            var apiDate = today.getFullYear() + ("0" + (today.getMonth() + 1)).slice(-2) + "" + ("0" + today.getDate()).slice(-2);
            $http.jsonp('http://api.trakt.tv/calendar/premieres.json/' + $scope.apiKey + '/' + apiDate + '/' + 30 + '/?callback=JSON_CALLBACK').success(function(data) {
                //As we are getting our data from an external source, we need to format the data so we can use it to our desired affect
                //For each day get all the episodes
                angular.forEach(data, function(value, index){
                    //The API stores the full date separately from each episode. Save it so we can use it later
                    var date = value.date;
                    //For each episodes add it to the results array
                    angular.forEach(value.episodes, function(tvshow, index){
                        //Create a date string from the timestamp so we can filter on it based on user text input
                        tvshow.date = date; //Attach the full date to each episode
                        $scope.results.push(tvshow);
                    });
                });
            }).error(function(error) {

            });
        };
    });

Now we need to add a text input so that the user can actually input a search term. We then need to bind this input to the newly declared variable. Add the following HTML within the div which has the search-box class in index.html.

    <label>Filter: </label>
    <input type="text" ng-model="filterText"/>

Here we have used ng-model to bind this input to the $scope.filterText variable we declared within our scope. Now this variable will always equal what is inputted into this search input.

Enforcing Filtering On ng-repeat Output

Now that we have the text to filter on, we need to add the filtering functionality to ng-repeat. Thanks to the built-in filter functionality of AngularJS, we do not need to write any JavaScript to do this, just modify your ng-repeat as follows:

    <li ng-repeat="tvshow in results | filter: filterText">

It’s as simple as that! We are telling AngularJS – before we output the data using ng-repeat, we need to apply the filter based on the filterText variable. Open index.html in a browser and perform a search. Assuming you searched for something that exists, you should see a selection of the results.


Creating a Genre Custom Filter

So, our users can now search for whatever they are wanting to watch, which is better than just a static list of TV shows. But we can take our filter functionality a little further and create a custom filter that will allow the user to select a specific genre. Once a specific genre has been selected, the ng-repeat should only display TV shows with the chosen genre attached.

First of all, add the following HTML under the filterText input in index.html that we added previously.

    <label>Genre: </label>
    <select ng-model="genreFilter" ng-options="label for label in availableGenres">
        <option value="">All</option>
    </select>

You can see from the above HTML that we have created a select input bound to a model variable called genreFilter. Using ng-options we are able to dynamically populate this select input using an array called availableGenres.

First of all, we need to declare these scope variables. Update your mainController.js file to be as follows:

    app.controller("mainController", function($scope, $http){
        $scope.apiKey = "[YOUR API KEY HERE]";
        $scope.results = [];
        $scope.filterText = null;
        $scope.availableGenres = [];
        $scope.genreFilter = null;
        $scope.init = function() {
            //API requires a start date
            var today = new Date();
            //Create the date string and ensure leading zeros if required
            var apiDate = today.getFullYear() + ("0" + (today.getMonth() + 1)).slice(-2) + "" + ("0" + today.getDate()).slice(-2);
            $http.jsonp('http://api.trakt.tv/calendar/premieres.json/' + $scope.apiKey + '/' + apiDate + '/' + 30 + '/?callback=JSON_CALLBACK').success(function(data) {
                //As we are getting our data from an external source, we need to format the data so we can use it to our desired affect
                //For each day get all the episodes
                angular.forEach(data, function(value, index){
                    //The API stores the full date separately from each episode. Save it so we can use it later
                    var date = value.date;
                    //For each episodes add it to the results array
                    angular.forEach(value.episodes, function(tvshow, index){
                        //Create a date string from the timestamp so we can filter on it based on user text input
                        tvshow.date = date; //Attach the full date to each episode
                        $scope.results.push(tvshow);
                        //Loop through each genre for this episode
                        angular.forEach(tvshow.show.genres, function(genre, index){
                            //Only add to the availableGenres array if it doesn't already exist
                            var exists = false;
                            angular.forEach($scope.availableGenres, function(avGenre, index){
                                if (avGenre == genre) {
                                    exists = true;
                                }
                            });
                            if (exists === false) {
                                $scope.availableGenres.push(genre);
                            }
                        });
                    });
                });
            }).error(function(error) {

            });
        };
    });

It is obvious that we have now declared both genreFilter and availableGenres which we saw referenced within our HTML. We have also added some JavaScript which will populate our availableGenres array. Within the init() function, while we are processing the JSON data returned from the API, we are now doing some additional processing and adding any genres that are not already within the availableGenres array to this array. This will then populate the select input with any available genres.

If you open index.html within your browser, you should see the genre select drop down populate as illustrated below:

figure6-genre-select-drop-down

When the user chooses a genre, the $scope.genreFilter variable will be updated to equal the selected value.

Creating the Custom Filter

As we are wanting to filter on a specific part of the TV show objects, we are going to create a custom filter function and apply it alongside the AngularJS filter within the ng-repeat.

At the very bottom of mainController.js, after all of the other code, add the following JavaScript:

    app.filter('isGenre', function() {
        return function(input, genre) {
            if (typeof genre == 'undefined' || genre == null) {
                return input;
            } else {
                var out = [];
                for (var a = 0; a < input.length; a++){
                    for (var b = 0; b < input[a].show.genres.length; b++){
                        if(input[a].show.genres[b] == genre) {
                            out.push(input[a]);
                        }
                    }
                }
                return out;
            }
        };
    });

The above JavaScript declares a custom filter to our app called isGenre. The function within the filter takes two parameters, input and genre. input is provided by default (which we will see in a moment) and is all the data that the ng-repeat is processing. genre is a value we need to pass in. All this filter does, is take the specified genre and checks to see if each of the TV show objects within input have the specified genre attached to them. If an object has the specified genre, it adds it to the out array, which will then be returned to the ng-repeat. If this doesn’t quite make sense, don’t worry! It should shortly.

Applying the Custom Filter

Now that we have our customer filter available, we can add this additional filter to our ng-repeat. Modify your ng-repeat in index.html as follows:

    <li ng-repeat="tvshow in results | filter: filterText | isGenre:genreFilter">

This simply chains another filter onto the ng-repeat output. Now the output will be processed by both filters before it is displayed on the screen. As you can see we have specified our custom filter as isGenre: and then we are passing the scope variable genreFilter as a parameter, which is how we provide our customer filter with the genre variable we talked about earlier. Remember that AngularJS is also providing our filter with the data that the ng-repeat is processing as the input variable.

OK, our custom genre filter is complete. Open index.html in a browser and test out the new functionality. With this filter in place, a user can easily filter out genres they are not interested in.


Calling Scope Functions

You may have noticed that each TV show listing also shows the genre itself. For some additional functionality, we are going to allow the user to click these genres, which will then automatically apply the genre filter for the genre they have clicked on. First of all, we need to create a scope function that the ng-click can call. Add the following code within the mainController on mainController.js:

    $scope.setGenreFilter = function(genre) {
        $scope.genreFilter = genre;
    }

In the above code, this function takes a genre value and then sets the $scope.genreFilter to the specified value. When this happens, the genre filter select box’s value will update and the filter will be applied to the ng-repeat output. To trigger this function when the genre span elements are clicked, add an ng-click to the genre span elements within index.html as follows:

    <span class="label label-inverse genre" ng-repeat="genre in tvshow.show.genres" ng-click="setGenreFilter(genre)">{{genre}}</span>

The ng-click calls our previously created setGenreFilter function and specifies a genre. Open index.html and try it out!


Custom Ordering With AngularJS

Our TV show premiere app is looking pretty good, users can easily refine the results displayed using a series of intuitive filters. To enhance this experience we are going to add some custom ordering functionality so our users will be able to choose a range of ordering options.

Add the following HTML under the genre select drop down:

    <label>Order by: </label>
    <select ng-model="orderField" ng-options="label for label in orderFields" class="input-medium"></select>
    <select ng-model="orderReverse"class="input-medium">
        <option value="true">Descending</option>
        <option value="false">Ascending</option>
    </select>

With this code added, we have two more drop downs. One to select how to order the data and another to choose the direction in which to order the data. We now need to create a function within our controller to make the order comparison. Add the following JavaScript under our setGenreFilter function:

    $scope.customOrder = function(tvshow) {
        switch ($scope.orderField) {
            case "Air Date":
                return tvshow.episode.first_aired;
                break;
            case "Rating":
                return tvshow.episode.ratings.percentage;
                break;
        }
    };

We also need to declare some additional scope variables:

    $scope.orderFields = ["Air Date", "Rating"];
    $scope.orderDirections = ["Descending", "Ascending"];
    $scope.orderField = "Air Date"; //Default order field
    $scope.orderReverse = false;

If you now open index.html within your browser, you should see the added drop downs populated with Air Date already selected as the default order field. This is shown in the figure below:

figure7-order-drop-downs

Finally, as we have done with our other filters, we are going to need to append this to our ng-repeat, update this as follows:

    <li ng-repeat="tvshow in results | filter: filterText | isGenre:genreFilter | orderBy:customOrder:orderReverse">

We are now applying an order-by-filter on our data in addition to the other filters. We are telling the order by to use our customOrder function and we are passing our orderReverse scope variable through as well. Open index.html in a browser and see the ordering in action.


Conclusion

AngularJS has allowed us to quickly create a detailed and functional web application with minimum effort. Utilizing AngularJS’s built-in filter functions, alongside some of our own custom code, our web application allows our users to easily filter and search through the TV show premieres.

After reading this tutorial you should now be able to understand and use the following principles:

  • Using ng-repeat to display information on screen.
  • Binding to inputs, allowing users to search and filter ng-repeat output.
  • Chaining filters on ng-repeat to perform multiple filtering functions.
  • Custom ordering of data.
  • Using events such as ng-click to respond to user interaction.
  • Using ng-class to conditionally apply styling to page elements.

So in conclusion, the topics covered in this tutorial should give you a strong foundation and understanding of what you can achieve when creating rich web applications in AngularJS.

Older posts are this way If this message doesn't go away, click anywhere on the page to continue loading posts.
Could not load more posts
Maybe Soup is currently being updated? I'll try again automatically in a few seconds...
Just a second, loading more posts...
You've reached the end.
(PRO)
No Soup for you

Don't be the product, buy the product!

close
YES, I want to SOUP ●UP for ...