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:
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!
Frequently Asked Questions about Single Page App with Laravel and Ember.js
How does Ember.js work with Laravel for single page applications?
Ember.js is a JavaScript framework that is designed to help developers build scalable single-page web applications. It does this by providing a complete solution that includes data management and an application flow. Laravel, on the other hand, is a PHP framework used for web application development following the MVC architectural pattern. When used together, Laravel handles the backend operations such as data manipulation, while Ember.js takes care of the frontend, providing a seamless user experience. The two frameworks communicate through APIs, with Laravel providing the API endpoints that Ember.js consumes.
What are the benefits of using Ember.js with Laravel?
Using Ember.js with Laravel offers several benefits. Firstly, it allows for the separation of concerns, with Laravel handling server-side operations and Ember.js managing client-side operations. This separation makes the application more maintainable and scalable. Secondly, Ember.js provides a convention-over-configuration paradigm, which means less time spent on setup and configuration, and more time on actual development. Lastly, both Laravel and Ember.js have strong community support, which means you can find solutions and help easily if you encounter any issues.
How do I set up Laravel and Ember.js for a single page application?
Setting up Laravel and Ember.js for a single page application involves several steps. First, you need to install Laravel and set up a new Laravel project. Then, you need to install Ember.js and create a new Ember.js application. After that, you need to configure Laravel to provide API endpoints that the Ember.js application can consume. This involves setting up routes, controllers, and models in Laravel. Finally, you need to configure the Ember.js application to consume the API endpoints provided by Laravel.
How do I handle data in a Laravel and Ember.js single page application?
In a Laravel and Ember.js single page application, data is typically handled through API endpoints. Laravel provides the API endpoints, which return data in a format that Ember.js can consume, typically JSON. On the Ember.js side, you use Ember Data, a library for robustly managing model data in Ember.js applications. Ember Data provides a consistent API for loading, creating, updating, and deleting records, regardless of the underlying data source.
How do I handle routing in a Laravel and Ember.js single page application?
In a Laravel and Ember.js single page application, routing is handled on the Ember.js side. Ember.js provides a powerful routing system that allows you to design your application’s URL structure, handle application state, and load data. When a user navigates to a particular URL, Ember.js maps that URL to a route handler, which loads the appropriate data and renders the appropriate template.
How do I handle authentication in a Laravel and Ember.js single page application?
Authentication in a Laravel and Ember.js single page application can be handled using Laravel’s built-in authentication system on the server side, and Ember Simple Auth on the client side. Ember Simple Auth is a lightweight library for implementing authentication and authorization in Ember.js applications. It provides a set of conventions and services that make it easy to add authentication to your application, and it integrates well with Laravel’s authentication system.
How do I handle errors in a Laravel and Ember.js single page application?
Error handling in a Laravel and Ember.js single page application can be done using Laravel’s built-in error handling capabilities on the server side, and Ember.js’s error handling capabilities on the client side. Laravel provides several ways to handle errors, including custom error pages, logging, and exception handling. On the Ember.js side, you can handle errors at the route level, the model level, or the application level, depending on the nature of the error.
How do I test a Laravel and Ember.js single page application?
Testing a Laravel and Ember.js single page application involves testing both the server-side code and the client-side code. Laravel provides several testing tools, including PHPUnit for unit testing and Laravel Dusk for browser testing. On the Ember.js side, you can use QUnit for unit testing, integration testing, and acceptance testing. Ember.js also provides a test runner that makes it easy to run your tests in different environments.
How do I deploy a Laravel and Ember.js single page application?
Deploying a Laravel and Ember.js single page application involves several steps. First, you need to build your Ember.js application for production. This involves minifying your JavaScript and CSS files, and optimizing your images. Then, you need to upload your Laravel and Ember.js code to your server. After that, you need to configure your server to serve your Laravel and Ember.js application. This involves setting up your web server, database, and environment variables. Finally, you need to run any necessary database migrations and seed your database.
What are some best practices for developing a Laravel and Ember.js single page application?
Some best practices for developing a Laravel and Ember.js single page application include following the convention-over-configuration paradigm, keeping your code DRY (Don’t Repeat Yourself), writing tests for your code, using version control, and following the principles of responsive web design. It’s also important to keep up to date with the latest versions of Laravel and Ember.js, as they often include important bug fixes and performance improvements.
Aleksander is young developer who loves to play with the newest web technologies. In his free time, he reads about PHP, Firefox OS or experiments with a new language. Currently, his main interests are PHP design patterns, laravel, dart and cloud.