Helpers

Handlebars templates are strings interpolated by JavaScript functions. When the function comes across double curlies, it tries to resolve the instance of the delimiter with an object property or invoke a nested function to return a string. These nested functions are called helpers and are created in the application. There are a few helpers built into the Handlebars library, and Ember adds some of its own.

Helpers can take two forms. The first are inline helpers, which use the syntax {{[helper name] [arguments]}}. The arguments can include a hash of options, such as:

{{input type="text" value=firstName disabled=entryNotAllowed size="50"}}

More complex helpers use a block syntax:

{{#[helper name] [arguments]}}
  [block content]
{{/[helper name]}}

For example, if you wanted to present a sign-in link only to users who were not already logged in, you could use something like the block below:

{{#if notSignedIn}}
  <a href="/">Sign In</a>
{{/if}}

For block helpers, content can be passed to the block to augment the output with dynamic segments. Handlebars’ built-in conditionals, described in the next section, are block helpers.

You are going to use helpers to render sections of your templates for sightings and cryptids as well as the NavBar.

Conditionals

Conditional statements let you introduce basic control flow into Handlebars templates. Their syntax looks like this:

{{#if argument}}
  [render block content]
{{else}}
  [render other content]
{{/if}}

Or, alternatively, like this:

{{#unless argument}}
  [render block content]
{{/unless}}

Conditional statements take a single argument that resolves to a truthy or falsy value. (Those are not typos. A truthy value is one that evaluates to true in a Boolean context. All values are truthy except those defined as falsy: the values false, 0, "", null, undefined, and NaN.)

Time to get to work. Open app/templates/sightings/index.hbs and add a conditional statement so that sighting entries display either the location or, if there is no location data, a polite warning about the missing data.

<div class="panel panel-default">
  <ul class="list-group">
    {{#each model as |sighting|}}
      <li class="list-group-item">
        {{sighting.location}} - {{sighting.sightedAt}}
      </li>
    {{/each}}
  </ul>
</div>
<div class="row">
  {{#each model as |sighting|}}
    <div class="col-xs-12 col-sm-3 text-center">
      <div class="media well">
        <div class="caption">
          {{#if sighting.location}}
            <h3>{{sighting.location}} - {{sighting.sightedAt}}</h3>
          {{else}}
            <h3 class="text-danger">Bogus Sighting</h3>
          {{/if}}
        </div>
      </div>
    </div>
  {{/each}}
</div>

You changed the DOM structure of your sightings template to start outputting sighting information in the form of styled elements using Bootstrap’s wells style. The use of {{#if}} and {{else}} allows you to render different HTML when the location of the sighting has not been added.

Now you need to change the data sent from the route to the template to see the results of your new conditional. In your sightings route model in app/routes/sightings.js, set the location property of one sighting to the empty string.

...
  model(){
    ...
    let record3 = this.store.createRecord('sighting', {
      location: 'Asilomar',
      sightedAt: new Date('2016-03-21')
    });

    return [record1, record2, record3];
  }
});

Start your server and point your browser to http://​localhost:4200/​sightings to see the new list of sightings (Figure 23.2).

Figure 23.2  Bogus sighting

Bogus sighting

The conditional statement evaluated the truthy value of the empty string for the last sighting instance. Thus, the template rendered the block content with the text “Bogus Sighting.”

Loops with {{#each}}

You have already used the {{#each}} block helper in the index template. This helper renders each object in the array as an instance of the content in the block. The argument for {{#each}} is the array as an |instance| contained in the block argument. The block will only render if the argument passed in to {{#each}} is an array with at least one element.

Like the {{#if}} block helper, this helper supports an {{else}} block that will render when the array argument is empty.

Use {{#each}} {{else}} {{/each}} to create a listing of all recorded cryptids – or, if there are none, the text “No Creatures” – in app/templates/cryptids.hbs:

{{outlet}}
<div class="row">
  {{#each model as |cryptid|}}
    <div class="col-xs-12 col-sm-3 text-center">
      <div class="media well">
        <div class="caption">
          <h3>{{cryptid.name}}</h3>
        </div>
      </div>
    </div>
  {{else}}
    <div class="jumbotron">
      <h1>No Creatures</h1>
    </div>
  {{/each}}
</div>

Similar to sightings, you are listing the cryptids in styled wells. The {{else}} block allows you to include a condition in your template when there are no items in the array you are listing. Your app can render a different element for this conditional of an empty model array.

Navigate to http://​localhost:4200/​cryptids to see your new cryptid listing (Figure 23.3).

Figure 23.3  Cryptids listing

Cryptids listing

Now, in app/routes/cryptids.js, remove the model callback by commenting out the return statement to exercise the else of your conditional.

...
  model(){
    // return this.store.findAll('cryptid');
  }
});

Reload http://​localhost:4200/​cryptids. Your cryptid listing is now blank (Figure 23.4).

Figure 23.4  Empty cryptids listing

Empty cryptids listing

With {{each}} {{else}} {{/each}}, you can have conditional views based on the presence of data and very little conditional logic. Now that you have seen (and tested) the wonders of conditional iterators, return app/routes/cryptids.js to its previous state:

...
  model(){
    // return this.store.findAll('cryptid');
  }
});

Binding element attributes

Element attribute values can be rendered from controller properties just as element content is rendered between DOM element tags. In earlier versions of Ember, there was a helper to bind attributes called {{bind-attr}}. Now, thanks to HTMLBars, you can you just use {{}} to bind a property to the attribute.

Attribute binding is common with element properties like class and src. Your cryptids have an image path in their data, so you can dynamically bind their src property to a model attribute.

Add an image to the cryptid listing in app/templates/cryptids.hbs:

<div class="row">
  {{#each model as |cryptid|}}
    <div class="col-xs-12 col-sm-3 text-center">
      <div class="media well">
        <img class="media-object thumbnail" src="{{cryptid.profileImg}}"
          alt="{{cryptid.name}}" width="100%" height="100%">
        <div class="caption">
          <h3>{{cryptid.name}}</h3>
        </div>
      </div>
    </div>
  {{else}}
    <div class="jumbotron">
      <h1>No Creatures</h1>
    </div>
  {{/each}}
</div>

You will need to add the cryptid images from your course assets to the directory tracker/public/assets/image/cryptids. When the Ember server is running, the public directory is the root for assets. For a production application you may need to configure these paths, but for development, public/assets is a good place to work. These files are copied to the dist directory where your application is compiled and served.

When deploying an application to a server and adding images to persisted data you need to be conscious of the path to the actual image file. In our example, we are serving the images from the same directory as our application, and the database stores the relative path of the image to our application.

Before HTMLBars, the {{bind-attr}} helper could be used as an inline ternary operation for assigning properties based on a Boolean property. Now, you can use the inline {{if}} helper. This is common when your UI has styles that represent true and false states of a specific property.

Use the ternary form in app/templates/cryptids.hbs to handle missing images:

<div class="row">
  {{#each model as |cryptid|}}
    <div class="col-xs-12 col-sm-3 text-center">
      <div class="media well">
        <img class="media-object thumbnail" src="{{cryptid.profileImg}}"
          src="{{if cryptid.profileImg cryptid.profileImg
          'assets/images/cryptids/blank_th.png'}}"
          alt="{{cryptid.name}}" width="100%" height="100%">
        <div class="caption">
        ...

Unlike the block {{#if}} helper, the inline {{if}} helper does not yield block content. The inline helper evaluates the first argument as a Boolean and outputs either the second or third argument.

Here, you are evaluating the truthiness of {{cryptid.profileImg}} for the first argument. If it is truthy, the output is the cryptid’s image path. Otherwise, a placeholder image is specified.

You can use a dynamic value as an argument to all inline helpers. You can also pass any JavaScript primitive as an argument, such as a string, number, or Boolean.

Before you look at the results of your conditional, create a cryptid without an image path in the beforeModel hook in app/routes/cryptids.js:

import Ember from 'ember';

export default Ember.Route.extend({
  beforeModel(){
    this.store.createRecord('cryptid', {
      "name": "Charlie",
      "cryptidType": "unicorn"
    });
  },
  model(){
    return this.store.findAll('cryptid');
  }
});

Now reload http://​localhost:4200/​cryptids and check out your new images (Figure 23.5).

Figure 23.5  Cryptids listing with images

Cryptids listing with images

Links

As discussed in Chapter 20, routing is unique to browser-based applications. Ember listens to a couple of event hooks to manage routing in your application. For this reason, you should create links with {{#link-to}} block helpers. This helper takes the route (represented by a string) as the first argument to create an anchor element. For example, {{#link-to 'index'}}Home{{/link-to}} creates a link to the root index page.

To see how this works, you are going to update your main navigation with links using {{#link-to}} helpers.

Begin in app/templates/application.hbs. Replace the NavBar’s test links with links to your sightings, cryptids, and witnesses:

...
      <div class="collapse navbar-collapse" id="top-navbar-collapse">
        <ul class="nav navbar-nav">
          <li>
            <a href="#">Test Link</a>
          </li>
          <li>
            <a href="#">Test Link</a>
          </li>
          <li>
            {{#link-to 'sightings'}}Sightings{{/link-to}}
          </li>
          <li>
            {{#link-to 'cryptids'}}Cryptids{{/link-to}}
          </li>
          <li>
            {{#link-to 'witnesses'}}Witnesses{{/link-to}}
          </li>
        </ul>
      </div><!-- /.navbar-collapse -->

Now that you have links to your listing pages, reload your app and test them. Click around. Hit the back button. You have a working web app! Take a moment to celebrate.

Your next task is to make the images on the cryptids page link to an individual page for each creature. To do this, you will take advantage of the fact that the {{#link-to}} helper can take multiple arguments to customize the link. In app/templates/cryptids.hbs, wrap the <img> tag with a link to a specific cryptid using cryptid.id as the second argument:

...
    <div class="media well">
      {{#link-to 'cryptid' cryptid.id}}
        <img class="media-object thumbnail"
        src="{{if cryptid.profileImg cryptid.profileImg
        'assets/images/cryptids/blank_th.png'}}"
        alt="{{cryptid.name}}" width="100%" height="100%">
      {{/link-to}}
      <div class="caption">
        <h3>{{cryptid.name}}</h3>
      </div>
    </div>
...

Now that you have links to the cryptid route and the anchor has a path to cryptids/​[cryptid_id], you will need to edit router.js so the CryptidRoute knows to expect a dynamic value.

...
Router.map(function() {
  this.route('sightings', function() {
    this.route('new');
  });
  this.route('sighting', function() {
    this.route('edit');
  });
  this.route('cryptids');
  this.route('cryptid', {path: 'cryptids/:cryptid_id'});
  this.route('witnesses');
  this.route('witness');
});
...

Try it out. You should see a blank page after clicking one of the cryptid images. This is good. Your app is routing you to the CryptidRoute, which is rendering the app/templates/cryptid.hbs, singular. That file is currently blank.

If you clicked on Charlie the unicorn’s image, you probably got an error. Recall that his record was created in the beforeModel hook and does not have an id. That means that the value it tries to pass to the {{#link-to}} helper is null.

This is a good time to remove that beforeModel hook. Creation should be reserved for pages creating new cryptids, which we will cover in Chapter 24.

Remove the hook from app/routes/cryptids.js.

...
  beforeModel(){
    this.store.createRecord('cryptid', {
      "name": "Charlie",
      "cryptidType": "unicorn"
    });
  },
  model(){
    return this.store.findAll('cryptid');
  }
...

Next, add the request for cryptid data in the app/routes/cryptid.js (singular).

import Ember from 'ember';

export default Ember.Route.extend({
  model(params){
    return this.store.findRecord('cryptid', params.cryptid_id);
  }
});

The cryptid_id dynamic route parameter is passed to the route’s model hook as an argument. You use this parameter to call the store’s findRecord method.

Now, edit the template for an individual cryptid, app/templates/cryptid.hbs, to show the cryptid’s image and name.

{{outlet}}
<div class="container text-center">
  <img class="img-rounded" src="{{model.profileImg}}" alt="{{model.type}}">
  <h3>{{model.name}}</h3>
</div>

The model passed to this template is a single object, not an array of objects to iterate over. The this.store.findRecord method returns a single cryptid instance. In the template, the model is this instance and the properties are retrieved using {{model.[property-name]}}.

In the browser, use your NavBar to navigate to Cryptids and then click one of the cryptid images to view its detail page (Figure 23.6).

Figure 23.6  Cryptid detail page

Cryptid detail page

You will explore {{#link-to}} more in future chapters. Remember, helpers are functions that are invoked when the template is rendered. Ember comes with its own, but you are not limited to the built-in helpers.

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

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