25
Components

Components are objects that hold the properties of controllers and views in Ember. The concept behind components is to have reusable DOM elements that have their own context or scope. Components accept attributes to customize the content rendered inside their templates. Also, components can allow actions to be assigned to properties from a parent controller or parent route (Figure 25.1).

Figure 25.1  Component property

Component property

As you assess the architecture of your application, you will start to see elements grouped together with minor differences throughout many routes and templates. If a grouping of elements can be removed from the template and described with variables depicting the elements’ state, it is a prime candidate to become a component.

This chapter will show you relatively simple examples of wrapping DOM elements in JavaScript objects to be used across multiple routes. Much like the helpers you saw in Chapter 23, components take arguments in the form of attributes to produce HTML. They also have the added feature of having their own actions and scoped properties to update themselves upon user interaction.

This chapter will only show you the tip of the iceberg of scalable application development. Ember applications in production rely heavily on components to create consistent user interfaces and maintainable code bases that sometimes span hundreds of routes. However, the examples you see here will provide a foundation of understanding for future projects where you want to turn your route templates into reusable pages or single elements.

Iterator Items as Components

An example of grouped elements that can often become a component is elements rendered in {{#each}} iterators. There may be a <div> container element with a particular className based on the iterator object, a title, an image path, a button with an action, and/or styles based on the object’s property states. To make these easily reusable, you can wrap all the DOM code in a component template and create a component JavaScript file to handle the decorators and actions that a controller would normally handle.

Your sightings list is built with an {{#each}} iterator, and you are going to create your first component to represent a sighting list item. Start by generating a component via Ember CLI in the terminal:

ember g component listing-item

This creates three files: app/components/listing-item.js, app/templates/components/listing-item.hbs, and the test file tests/integration/components/listing-item-test.js.

Now that the component is created, you need to find the code you want the component to replace. Open app/templates/sightings/index.hbs and locate this block of code. You will be moving part of this code to the component in a moment. For now, just take a look at it.

<div class="row">
  {{#each model as |sighting|}}
    <div class="col-xs-12 col-sm-3 text-center">
      <div class="media well">
        <img class="media-object thumbnail" src="{{if sighting.cryptid.profileImg
          sighting.cryptid.profileImg 'assets/images/cryptids/blank_th.png'}}"
          alt="{{sighting.cryptid.name}}" width="100%" height="100%">
        <div class="caption">
          <h3>{{sighting.cryptid.name}}</h3>
          {{#if sighting.location}}
            <h3>{{sighting.location}}</h3>
            <p>{{moment-from sighting.sightedAt}}</p>
          {{else}}
            <h3 class="text-danger">Bogus Sighting</h3>
          {{/if}}
        </div>
        {{#link-to 'sighting.edit' sighting.id tagName="button"
          class="btn btn-success btn-block"}}
          Edit
        {{/link-to}}
      </div>
    </div>
  {{/each}}
</div>

The container <div> with the col-xs-12 class (that is, the shaded <div>) needs to be on this specific page. It is a visual container for the layout, specifying the size of its content. If you were to add this container to your sightings list component, the container size would be fixed for every instance of the component across the site.

However, the <div class="media well"> container and its contents can be moved to a component. This component can then be added to any size of container while holding the characteristics of a single item in a list. The component will contain the major elements of the item being listed – mainly the media container and the image, title, and Edit button.

Open app/templates/components/listing-item.hbs and begin by adding the image and cryptid name:

<img class="media-object thumbnail" src="{{imagePath}}" alt="{{name}}"
  width="100%" height="100%">
<div class="caption">
  <h3>{{name}}</h3>
  {{yield}}
</div>

A component represents a single DOM element. Everything in the component template, then, is a child of that DOM element. By default, Ember’s HTMLBars uses JavaScript to create <div> elements and renders the component template inside them.

The code you just wrote adds an image and a caption with a name to the <div> element created by a {{#listing-item}} component. Just like other templates, the component has dynamic portions that are variables you will pass in. (We will discuss the {{yield}} in a moment.)

When you add this component to the route template, it will render this:

<div>
  <img class="media-object thumbnail" src="[cryptid's imagePath string]"
    alt="[cryptid's name string]" width="100%" height="100%">
  <div class="caption">
    <h3>[cryptid's name string]</h3>
    {{yield}}
  </div>
</div>

The dynamic portions of the template, {{name}} and {{imagePath}}, will be attributes provided to the component. The {{yield}} is where you can pass child elements to the component to render in a specific location of the DOM node structure. The component template file acts a layout or master set of elements, and the {{yield}} is for elements you need to add with each instance of the component. You will use it later in the chapter.

While this is not exactly the markup you are replacing in app/templates/sightings/index.hbs, it is a good starting point.

Replace your existing code in app/templates/sightings/index.hbs with the component.

<div class="row">
  {{#each model as |sighting|}}
    <div class="col-xs-12 col-sm-3 text-center">
      <div class="media well">
        <img class="media-object thumbnail" src="{{if sighting.cryptid.profileImg
          sighting.cryptid.profileImg 'assets/images/cryptids/blank_th.png'}}"
          alt="{{sighting.cryptid.name}}" width="100%" height="100%">
        <div class="caption">
          <h3>{{sighting.cryptid.name}}</h3>
          {{#if sighting.location}}
            <h3>{{sighting.location}}</h3>
            <p>{{moment-from sighting.sightedAt}}</p>
          {{else}}
            <h3 class="text-danger">Bogus Sighting</h3>
          {{/if}}
        </div>
        {{#link-to 'sighting.edit' sighting.id tagName="button"
          class="btn btn-success btn-block"}}
          Edit
        {{/link-to}}
      </div>
      {{#listing-item imagePath=sighting.cryptid.profileImg
        name=sighting.cryptid.name}}
      {{/listing-item}}
    </div>
  {{/each}}
</div>

You have replaced a lot of code with a single line. Some functionality was removed in the process, but you will fix that soon. The next step is adding the container styles back on the component element. It is missing the "media" and "well" Bootstrap styles.

Open app/components/listing-item.js and add a classNames property to the listing-item component:

import Ember from 'ember';

export default Ember.Component.extend({
  classNames: ["media", "well"]
});

The property classNames passes its value to the classNames attribute of the <div> element created by the component.

Now the rendered component looks like this:

<div class="media well">
  <img class="media-object thumbnail" src="[imagePath string]" alt="[name string]"
    width="100%" height="100%">
  <div class="caption">
    <h3>[name string]</h3>
    {{yield}}
  </div>
</div>

Next, you will add elements to the {{yield}} section of the component template to make the implementation of this component instance unique to the sightings route. First, in app/templates/sightings/index.hbs, add the component’s contextual content that will render in the {{yield}}:

...
      {{#listing-item imagePath=sighting.cryptid.profileImg
        name=sighting.cryptid.name}}
        {{#if sighting.location}}
          <h3>{{sighting.location}}</h3>
            <p>{{moment-from sighting.sightedAt}}</p>
        {{else}}
          <h3 class="text-danger">Bogus Sighting</h3>
        {{/if}}
        {{#link-to 'sighting.edit' sighting.id tagName="button"
          class="btn btn-success btn-block"}}
          Edit
        {{/link-to}}
      {{/listing-item}}

This code, which should look familiar, reinstates the sighting location and time as well as the Edit button.

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

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