Single Page App with Laravel and EmberJS

Aleksander Koko
Share

In this part, we will see how Ember works, how to use Ember Data and how to build something simple with it. Router, Route, Model, Template and Store are some of the concepts of Ember. I’m not going to explain every one of those, so if you feel stuck, use the documentation. As usual, you can download the code for this part here.

Let’s code

Note that while developing with Ember, it’s a good idea to download the Ember Inspector. They released Ember with a Chrome Extension and now that extension is also on Firefox.

For this example, we are going to put every line of JS inside /public/static/app.js. In a real project, this is not a good idea. This simplifies our example but ask yourself – have you ever done some serious work with MVC architecture in just one big file? We saw how Laravel works: controllers are in one folder, each of them in one file, the configuration is in its own folder, the models too. I suggest you do the same thing with Ember when you dive into a proper project.

The first thing you ever do when starting Ember is create the application. It is a global namespace for everything that you code with Ember. An Application can be created like this:

App = Ember.Application.create();

I suggest activating a bit of debugging just by adding a line of code when you create the application.

App = Ember.Application.create({
        LOG_TRANSITIONS: true
    });

It doesn’t do much more than output your movement through the URLs and templates in the console. Also, we are going to use Ember Data which is a separate module of Ember and provides a nice integration with REST, translating everything from Store Object to request on the server. By default, Ember Data uses the Rest Adapter. You can also use the Fixture Adapter for testing and local development. Basically, Ember Data is a bridge between servers (Rest API) and local storage with the Store Object.

As we saw earlier, our API uses a namespace. Ember’s Data comes with a Rest Adapter which accepts a namespace, a prefix like we saw on Laravel Route groups. Lets pass in our namespace as an argument.

App.ApplicationAdapter = DS.RESTAdapter.extend({
        namespace: 'api/v1'
    });

The adapter now requests all the data via example.com/api/v1/.

Link the App Store with the Adapter and you are ready to start developing.

App.Store = DS.Store.extend({
        adapter: 'App.ApplicationAdapter'
    });

One of the main concepts of Ember is URL. Everything is built around that idea. The Router keeps the URLs and the templates synchronized. Inside the Router, you can define a resource and map that resource to a specific URL. In this example, we will work only with the photo resource and the user resource. Feel free to add the category resource and make some one to many relations with Ember. Don’t forget that earlier we created some relations (one-to-many and belongs-to) with Laravel, but we didn’t use them. Using one-to-many relations in Laravel is easy enough, but I don’t want to overwhelm you. If enough interest in generated in the comments, we’ll add this to our app in a followup post, along with pagination.

The Router is the place where all the routes should be defined. Here, we defined two resources with their URLs. The URL is optional here. :photo_id is an argument. Let’s say that we navigate to example.com/photo/2. What would happen? We have a resource that passes our request to the model or controller, and there we grab some data from the Store. If the Store doesn’t find it, it looks on the server. :photo_id can be used to retrieve this data. In this case it looks for example.com/api/v1/photos/2. You see that photo is plural. Ember by itself looks for the plural of the resource.

App.Router.map(function() {
        this.resource('photo', {path: "/photo/:photo_id"});
        this.resource('user', {path: "/user/:user_id"});
    });

A route begins with the first letter of the Resource capitalized and should be in the App namespace. Also, add the word “Route” after the resource’s name. So for the photo resource the route should be like this: App.PhotoRoute

It should also extend the Route object.

App.PhotoRoute = Ember.Route.extend({});

The Route Object can have different hooks for different things. Two of those hooks are used for defining the Controller name for that resource and defining the Model. Let’s stick with the model.

App.PhotoRoute = Ember.Route.extend({
        model: function(params){
            return this.store.find('photo', params.photo_id);
        }
    });

Inside, we have specified the model hook and passed a parameter. Where does this parameter go? The photo resource has a url with a parameter: /photo/:photo_id. photo_id is stored in params and can be used inside the function. Don’t forget that every resource and every route has access to the Store. The Store object saves all the info inside it and uses Local Storage for better performance. That way, it cuts down the number of requests on the server. That’s why developing with Ember speeds up your application – in the end, the users are happier.

By using store.find('resource') you can retrieve all the data for this resource from the store object. You can also retrieve only one row. For example, if you want to receive only a photo with a given id, use the store object and find the photo resource with the given id as a second parameter.

return this.store.find('photo', params.photo_id);

Ember searches for the data in example.com/api/v1/photo_id . By default, Ember works with the data by looking for ids. If you have inserted some relations for this resource, then you can also retrieve the data associated with it. That’s all the code for the routes, very similar for each case and straightforward:

App.IndexRoute = Ember.Route.extend({
        model: function(){
            return this.store.find('photo');
        }
    });
    
    App.PhotoRoute = Ember.Route.extend({
        model: function(params){
            return this.store.find('photo', params.photo_id);
        }
    });
    
    App.UserRoute = Ember.Route.extend({
        model: function(params){
            return this.store.find('user', params.user_id);
        }
    });

A quick note: the IndexRoute is a default Route, linked with the root URL. And by root I mean the example.com/ URL. There are other default Routes, like ApplicationRoute that executes as the application starts.

The Model Object

Inside Ember’s Model Object, you specify the data and its type of resource. A nice feature of Ember is that when the value of a resource is changed and another value depends on the changed value, it automatically gets updated via some observer magic. A model should start with a capitalized letter and should extend the Model Object.

App.Photo = DS.Model.extend({});

Inside that Object you should specify all the fields and other values that depend on those core values. You can also add Relations inside the model.

The photo model should look something like this:

var attr = DS.attr;             // This cuts my writting. Inside the model i use attr instead of DS.attr
    
    App.Photo = DS.Model.extend({
        user_id: attr("number"),    // The expected value is a number
        url: attr("string"),        // The expected value is a string
        title: attr("string"),
        description: attr("string"),
        category: attr("number"),
    
        fullUrl: function(){        // Another value that depends on core values.
            return "/files/" + this.get("url");
        }.property('url'),
    
        backgroundImage: function(){// This depends on another value but not on core ones
            return 'background: url("' + this.get("fullUrl") + '") no-repeat; ';
        }.property('fullUrl')
    
    });

With attr (DS.attr) you specify how you want this data to arrive. For example, we want the user_id value to be a number. This way, we are secured from outside data.

The User Model is similar. Remember, Ember Data will look for it in /api/v1/users. The naming convention is a bit tricky. For example, if you request a resource named user, Ember Data will look for example.com/prefix/users, and if you request a particular resource then it requests example.com/prefix/users/user_id. Knowing how Laravel exposes the data and how Ember wants its data can save you from headaches.

App.User = DS.Model.extend({
        name: attr("string"),
        lastname: attr("string"),
        username: attr("string"),
    
        fullname: function(){
            return this.get('name') + " " + this.get('lastname');
        }.property("name", "lastname")
    });

Views

Before jumping into templates, I suggest using the Ember Inspector to view the state of your application. There you can find the Routes, Views and Controllers. You can also find the relations between the Controllers and Routes. Take some time to look around with the Inspector, it’ll be of great help later on when you develop your own Ember apps.

Do you remember the first template we wrote in the third part? That’s the application template. That template will be rendered when example.com is accessed in the browser.

You can’t develop the application further if you don’t make a modification inside that template. Replace <!-- The content will be here --> comment with: {{outlet}}.

Why? All our resources are nested inside the application route. But if I look at my code I see no Index on the Router. Why is that?

By default the example.com/ url is assigned to IndexRoute unless you’ve assigned that URL to another route. Ember puts the application onto the top level by default and everything is nested inside it. If you request a URL inside that application route, then by using {{outlet}} as a placeholder, Ember takes that route’s template and puts it inside that placeholder.

Lets make another template and use it for the IndexRoute. This will be the first page. The first template is the app template. The index template will be rendered inside the application’s {{outlet}}.

data-template-name is the name of the template. All the code inside that script tag will be placed inside the {{outlet}}.

<script type="text/x-handlebars" data-template-name="index">
        <ul class="small-block-grid-1 medium-block-grid-2 large-block-grid-3 custom-grid-ul">
            {{#each}}

                <li {{bind-attr style="backgroundImage"}}>
                    <div class="custom-grid">
                        {{#link-to 'photo' this}}<h5 class="custom-header">{{title}}</h5>{{/link-to}}
                        <span>Author: {{user_id}}</span>
                    </div>
                </li>

            {{/each}}
        </ul>
    </script>

{{#each}} is something like a loop. If the model of the template has an array and we want to query for all the data, then we use this special tag. This loop starts with {{#each}} and ends with {{/each}}. Inside this loop, we use all the values that are returned from the loop. Remember that inside the model we returned the resource photo. The model retrieves the data from the Store and returns it to the template. Look at the Photo model. We specified some fields there and those fields are being used inside the template, inside the {{#each}} loop.

Another special tag is the {{#link-to}} tag. This tag generates a link to the photo route and passes a parameter. The this parameter is the id of that object. In this case the photo id. Again, the {{#link-to}} tag ends with {{/link-to}}. {{title}} isn’t a special tag, it merely retrieves the title value for that object.

Lets add the photo template. This template is the template for the Photo Route. Again, I suggest to see the naming conventions for how this is mapped and how the naming is done.

<script type="text/x-handlebars" data-template-name="photo">
        <div style="text-align: center;">
            <h4>{{title}}</h4><br>
            <img {{bind-attr src="fullUrl" alt="title"}}><br>
            <span>Author: {{#link-to 'user' user_id}}{{author.name}}{{/link-to}}</span>
        </div>
    </script>

By using the {{attribute-here}} tag, the selected attributes will be generated inside this tag. We have used it inside an <img> tag. Using {{title}} inside a tag as an attribute causes problems. Handlebars and Ember generate some extra objects inside the DOM. To solve this problem, we use {{bind-attr}} instead. When we make a link to the user route, we pass a parameter: the user_id. By clicking the link, the URL will be updated with example.com/user/the_id. But we don’t have a user template yet. Let’s create one.

<script type="text/x-handlebars" data-template-name="user">
        <h2>Hello: {{fullname}} </h2>
    </script>

This displays only the full name. fullname is a property of our App.User that extends DS.Model.

Before wrapping it all up, I made a gif of how it looks:

enter image description here

Wrapping up

As you can see, this is not a completed project yet. A lot of work is still needed; go ahead and experiment with it, learn from it and change it. The full project will be hosted on my Github account and will be updated frequently. Any contribution is welcome, I’d love to work together.

In this series we learned a lot – I learned a lot too. We saw how to work with the cloud, learned about its good sides and bad sides. We saw how we could develop an application in both environments and how to configure Laravel for different environments. We saw how to build a REST API with Laravel by staying on the same page of an application with Ember. I hope you all had as much fun as I have.

What do you think? Do you want to see more on Heroku, Laravel or Ember? Leave a comment below, it’s always good to hear feedback from the readers!