Using Ember with Firebase

Firebase is a backend as a service provider. It can store data and authenticate your users with just a few lines of code. It integrates well with many different frameworks, including Ember.js.

In this recipe, we'll take a look at a few of the features of Firebase by creating a blogging application. This app will allow users to create, edit, or delete posts. Users will be able to log in with Twitter and be authenticated as well.

Getting ready

Before getting started, we must set up an account with Firebase at http://www.firebase.com. Google owns Firebase so this should be really easy. In fact, you should be able to log in directly with your Google credentials.

After logging in, you'll need to create a new application and set up a new provider. Follow these steps:

  1. Create a new application in the Firebase dashboard. It should look like the following image. Write down APP URL for later:
    Getting ready
  2. After creating the app, click on manage and then click on the left-hand side, where it says Login & Auth. Click on Twitter and enter your Twitter API Key and Twitter API Secret:
    Getting ready

    To get these keys, you'll need to create a new application through Twitter. To do this, go to apps.twitter.com and click on CREATE NEW APP. Follow the onscreen instructions. Make sure to set the callback URL to https://auth.firebase.com/auth/twitter/callback.

    This should be it. Make sure that you write down the name of the URL that Firebase created. You'll need it later when you set everything up in the environment.js file.

How to do it...

  1. In a new application, generate and install these files:
    $ ember install emberfire
    $ ember install torii
    $ ember install ember-bootstrap
    $ ember install ember-cli-showdown
    $ ember g route new
    $ ember g route posts
    $ ember g route application
    $ ember g controller new
    $ ember g controller posts
    $ ember g model post title:string body:string author:string titleURL:string
    $ ember g template index
    $ ember g util clean
    

    The files generated will be the skeleton of our application. The two install commands will install the necessary files for Firebase with authentication using an add-on called torii.

  2. Verify that the application adapter is set up for Firebase:
    // app/adapters/application.js
    import Ember from 'ember';
    import FirebaseAdapter from 'emberfire/adapters/firebase';
    
    const { inject } = Ember;
    
    export default FirebaseAdapter.extend({
      firebase: inject.service(),
    });

    This adapter is automatically generated for us when we install the emberfire add-on. It injects the Firebase service into our application and data store.

  3. Configure the emberfire adapter by editing the environment.js file:
    // config/environment.js
    …
        firebase: 'https://testemberfire.firebaseio.com/',
        torii: {
          sessionServiceName: 'session'
        },
    …

    To use Firebase, you must set the firebase property to your Firebase URL that you created earlier. Make sure that the torii property is also set so that we can use the session object in our application.

  4. Add a new folder called torii-adapters and add the application.js file:
    // app/tori-adapters/application.js
    import Ember from 'ember';
    import ToriiFirebaseAdapter from 'emberfire/torii-adapters/firebase';
    export default ToriiFirebaseAdapter.extend({
        firebase: Ember.inject.service()
    });

    Torii is a type of authentication abstraction for Ember.js. This will make it possible to use the session variable in our program.

  5. Update the clean.js file in the utils folder:
    // app/utils/clean.js
    export default function clean(title) {
        title = title.replace(/ /g, '_');
        return title.replace(/[^a-zA-Z0-9-_]/g, '');
    }

    This file is simply used to clean a URL and return it. In other words, it removes anything other than dashes and the characters, a-z. We'll be using this later for our URLs.

  6. Let's take a look at the model file and make sure that it looks OK:
    // app/models/post.js
    import DS from 'ember-data';
    
    export default DS.Model.extend({
        title: DS.attr('string'),
        body: DS.attr('string'),
        author: DS.attr('string'),
        titleURL: DS.attr('string')
    
    });

    The model contains all the data we'll be using for each post in our application. This should have been generated earlier.

  7. Update the router.js file using the titleURL as the path:
    // app/router.js
    import Ember from 'ember';
    import config from './config/environment';
    
    const Router = Ember.Router.extend({
        location: config.locationType
    });
    
    Router.map(function() {
        this.route('posts', {path: '/:titleURL'}, function() {
    });
        this.route('new');
    });
    
    export default Router;

    Some of this was generated for us when we created the posts and new route. However, we want to make sure that titleURL is set to the path of each individual post. We do this by passing the :titleURL dynamic segment to the path.

  8. Add the query for each individual post to the posts route:
    // app/routes/posts.js
    import Ember from 'ember';
    
    export default Ember.Route.extend({
        model(param) {
          return this.store.query('post', {
            orderBy: 'titleURL',
          equalTo: param.titleURL });
        }
    });

    When the user navigates to the /posts URL, the model will expect a parameter passed in. For example, if you navigate to /posts/my_post, the my_post segment will be passed as a parameter that can be accessed in the route. We'll use this parameter in the Firebase this.store.query method. The first argument is the name of the model. We can then use orderBy and equalTo to specify the exact post that we are looking for.

    Tip

    Uniqueness

    As you can imagine, when creating a new post, the title may or may not be unique. The this.store.query method will return all results as an array to the model. We could enforce uniqueness in Firebase by making the titleURL unique. Another possibility would be to check the uniqueness of the post title during creation. Either way, for this example, we'll assume that all titleURLs are unique.

  9. Edit the application route file and add the model and a few actions:
    // app/routes/application.js
    import Ember from 'ember';
    
    export default Ember.Route.extend({
        model(){
          return this.store.findAll('post');
        },
        actions:{
          login(){
            this.get('session').open('firebase', { provider: 'twitter'}).then((data)=> {
            });
          },
          logout(){
            this.get('session').close();
          }
        }
    });

    We want the main application to have access to the model route so that we can use the findAll method to retrieve all posts. This is basically the same as the Ember Data method we've used in previous recipes.

    There are two actions, login and logout. As we injected, using torii, our session into the program, we can access it from anywhere. By invoking this.get('session'), we can open or close a session. Firebase has several built-in authenticators, including Twitter and Facebook. The login action in this example will open a window to twitter so that the user can be authenticated.

    Tip

    Firebase security

    With any JavaScript browser application, security can be tricky. Firebase makes this a little easier for us. Firebase keeps track of users that are authenticated. In the Firebase dashboard, you can set rules that make it possible for only authenticated users to receive data. This is a little beyond the scope of this recipe. However, it is possible to secure your data with Firebase using a third-party authenticator such as Twitter or Facebook.

  10. Open the application template file. Add a navigation bar at the top and buttons to log in, log out, and add a new post:
    //app/templates/application.hbs
    <nav class="navbar navbar-inverse navbar-fixed-top">
        <div class="container-fluid">
        <div class="navbar-header" href="#">
        {{#link-to 'index' class='navbar-brand'}}My New Blog{{/link-to}}
        </div>
        <ul class="nav navbar-nav">
        {{#if session.isAuthenticated}}
        <li>{{#link-to 'new'}}Add New Post{{/link-to}}</li>
        {{/if}}
        </ul>
        <ul class="nav navbar-nav navbar-right">
        {{#unless session.isAuthenticated}}
        <li><a href="#" {{action 'login' }}>Login</a></li>
        {{else}}
        <li><a href="#" {{action 'logout' }}>Logout</a></li>
        {{/unless}}
        </ul>
        </div>
    </nav>
    <br>
    <br>
    <br>
    {{outlet}}

    As we have installed the ember-bootstrap add-on, we can create a really simple top navigation bar. The login and logout buttons are surrounded by the if helper. In every template, you have access to the session property. This property has a method called isAuthenticated that returns true if the user is logged in and false if the user is not logged in. We can use this to show the login button only if the user is NOT logged in. If the user is logged in, they'll see a logout button.

    We don't have an application controller, so these actions will bubble up to the application route where they'll be handled.

  11. Now update the index.hbs file with a link to each individual post:
    // app/templates/index.hbs
    
    <div class = 'row'>
        <div class='col-md-4'>
          <h1>Posts</h1>
            {{#each model as |post|}}
              <br>{{#link-to 'posts' post.titleURL}}{{post.title}}{{/link-to}}
            {{/each}}
        </div>
    
    </div>

    The model loops through each post and displays the title on the screen. Each titleURL is passed as a parameter to the posts route.

  12. In the new template, add some textboxes so that the user can add a post. Add a section to preview a post as well:
    // app/templates/new.hbs
    <br><br>
    <div class='col-md-4 border' >
        <h1>New Post</h1>
        <form {{action 'save' on="submit"}}>
          <dl>
            <dt>Title:<br> {{textarea value=title cols="40" |rows="1" placeholder='Title'}}</dt>
            <dt>Body:<br> {{textarea value=body cols="40" rows="6" placeholder='Body'}}</dt>
          </dl>
          <button type='submit' class='btn btn-primary'>Add</button>
        </form>
    </div>
    <div class='col-md-4 border'  >
        <h1>Preview</h1>
        <h3>{{title}}</h3>
        <h4>{{markdown-to-html markdown=body}}</h4>
    </div>

    The new template will be used to create a new post. The textarea helper creates two textboxes. The form has a save action that will be triggered when the form is submitted.

    When setting up the project, we installed a markdown add-on. This allows us to use markdown in the body of the post. Markdown is a text-to-HTML conversion tool. It makes it easier to write HTML in your text.

  13. In the posts template, display each post and show a way to edit the post as well:
    // app/templates/posts.js
    {{#each model as |model|}}
    <div class='row'>
        {{#if isEditing}}
          <div class='col-md-4 border'>
            <form {{action 'save' on='submit'}}>
              <dl>
                <dt>Title:<br> {{textarea value=model.title cols='40' rows='1'}}</dt>
                <dt>Body:<br> {{textarea value=model.body cols='40' rows='6'}}</dt>
              </dl>
          <div class = 'row'>
            <button type='submit' class = 'btn btn-primary'>Done</button>
          </div>
            </form>
          </div>
        {{/if}}
        <div class='col-md-4 border'>
          <h1>{{model.title}}</h1>
          <h3>{{markdown-to-html markdown=model.body}}</h3>
          <h4>-{{model.author}}</h4>
          {{#if session.isAuthenticated}}
            <form {{action 'edit' }}>
            <button type='submit' class='btn btn-primary'>Edit</button>
                <button type='delete' class= 'btn btn-primary' {{action 'delete'}}>Delete</button>
            </form>
          {{/if}}
        </div>
    </div>
    {{/each}}

    This displays each individual post. If the user is authenticated, they can either delete or edit the post.

    Once again, we use the textarea template helpers to display the textboxes. The form has an edit action attached that will set the isEditing property to true so that the post can be edited. The delete action deletes the post.

  14. Add the save action to the new controller:
    // app/controllers/new.js
    import Ember from 'ember';
    import cleanURI from '../utils/clean';
    
    export default Ember.Controller.extend({
        actions: {
          save(){
    
            const titleURL= cleanURI(this.get('title'));
            const post = this.store.createRecord('post',{
            title: this.get('title'),
            body: this.get('body'),
            author: 'test',
            titleURL: titleURL
            });
            post.save();
            this.set('title','');
            this.set('body','');
            this.transitionToRoute('index');
          }
        }
    });

    The save action is used to save the data to Firebase. First, it takes the title of the post and uses the utility, cleanURI, to remove all special characters and spaces. Firebase has a function called createRecord that is used to create new records. We then save the record to the store and set the values back to default. Finally, the application transitions back to the index.

  15. In the posts controller, add actions for edit, delete, and save:
    // app/controllers/posts.js
    import Ember from 'ember';
    import cleanURI from '../utils/clean';
    
    export default Ember.Controller.extend({
        actions: {
          edit(){
            this.set('isEditing', true);
          },
          delete(){
            this.get('model').forEach(model=>{
              model.deleteRecord();
            });
            this.get('model').save();
            this.set('isEditing', false);
            this.transitionToRoute('index');
          },
          save(){
            this.get('model').forEach(model=>{
              const titleURL = cleanURI(model.get('title'));
              model.set('titleURL', titleURL);
              model.save();
            });
            this.set('isEditing',false);
            this.transitionToRoute('index');
          }
        }
    });

    Let's break this down into more detail:

    …
        edit(){
          this.set('isEditing',true);
        },
    …

    The edit function sets the isEditing property to true. The posts template uses this property to show or not show the editing window:

    …
        delete(){
          this.get('model').forEach(model=>{
            model.deleteRecord();
          });
          this.get('model').save();
          this.set('isEditing',false);
          this.transitionToRoute('index');
        },
    …

    The delete action deletes the record. To do this, we must use the forEach method on our model. In the route, we used the query method, which returns an array. Therefore, we have to go through every record returned, and delete it. Once again, we'll assume that every title is unique and only has one record. Remember to always .save() so that the record is persisted in Firebase. After the record is deleted, we transition to the index route:

    …
        save(){
          this.get('model').forEach(model=>{
            const titleURL = cleanURI(model.get('title'));
            model.set('titleURL',titleURL);
            model.save();
          });
          this.set('isEditing',false);
          this.transitionToRoute('index');
        }
    …

    The save function gets the title, cleans it, sets it, and saves the model. In this example, we must use the forEach method to iterate over the array. Afterward, we set the isEditing property back to false and transition back to the index.

  16. Run the application and the following screen will be displayed:
    How to do it...

    This displays the top left corner of the screen. No posts are listed as we haven't added them yet:

    How to do it...
  17. The top right corner of the screen will show the Login button. We'll need to log in by pressing this button. This will bring a popup asking for credentials to our Twitter account. After logging in, the Add New Post text will be displayed:
    How to do it...
  18. Clicking on Add New Post will display the following screen:
    How to do it...
  19. You can see that the preview takes the markdown and converts it to HTML. After adding the new post, it will then be listed:
    How to do it...
  20. Clicking on the title will bring us to an edit screen. Here, we can Edit or Delete the post:
    How to do it...
  21. Clicking on the Edit button will bring up the edit screen:
    How to do it...

    From here, we can make any changes and save it back again. Each time a save occurs, the post is persisted in Firebase.

How it works...

Firebase talks to its backend service via the emberfire and torii add-ons. EmberFire is an official Ember Data adapter. It has many of the same features as other popular adapters. It can save, delete, edit, and query data fairly easily. One of its purposes is to make it really easy to persist and save data without having to set up your own backend.

Firebase also has authentication providers that it can hook into. Firebase handles all the authentication between the provider and application. All that this requires is that the provider is set up in Firebase.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset