Skip to content

Latest commit

 

History

History
344 lines (303 loc) · 22 KB

6.the_template_forest.md

File metadata and controls

344 lines (303 loc) · 22 KB

6.1 Компоненты

6.2 Создание повторно используемых компонентов

Задание. Мы отображаем одинаковую информацию о продуктах в нескольких местах. Разделим product details на повторно используемые компоненты.
1) Создать шаблон component для продуктов и назвать его product-details. Помнить, что это компонент, поэтому его название будет отличаться от предыдущих шаблонов.
2) Заполнить новый шаблон начиная с контента, используемого ранее в products/deals. Пропустить охваченные элементы, но установить компонент для ожидания объекта product, который обладает необходимыми свойствами.
3) Внутри шаблона products/index использовать новый компонент product-details, передав ему product, который ожидает компонент. Также установить класс и обёртку, для соответствия текущим настройкам.
4) Обновить шаблон index для использования нашего нового компонента. Нужно иметь в виду, что планируется сохранить тег и класс текущего элемента.
5) Мы сможем отчистить немного больше места с помощью компонента. Одно из них - в шаблоне products/onsale. Опять таки, обновить его, используя наш компонент.
6) Напоследок, обновить шаблон products/deals, используя компонент. Нужно также иметь в виду, что планируется сохранить тег и класс текущего элемента.

Решение. На этот раз задание относится исключительно к HTML. Для начала создадим новый шаблон components/product-details, в который поместим часто повторяющиеся элементы для вывода продуктов без охватывающего их тега (li). А затем, в шаблонах index, products/index, products/onsale и products/deals заменим их на компоненты product-details, указав имя охватывающего тега и класса:

<!DOCTYPE html>
<html>
<head>
    ...
</head>
<body>
<script type='text/x-handlebars' data-template-name='application'>
    ...
</script>
<script type='text/x-handlebars' data-template-name='index'>
    ...
    <div class='row'>
        {{#each onSale}}
        {{product-details product=this tagName='div' classNames='col-sm-4'}}
        {{/each}}
    </div>
    ...
</script>
<script type='text/x-handlebars' data-template-name='contacts/index'>
    ...
</script>
<script type='text/x-handlebars' data-template-name='credits'>
    ...
</script>
<script type='text/x-handlebars' data-template-name='products'>
    ...
</script>
<script type='text/x-handlebars' data-template-name='product'>
    ...
</script>
<script type='text/x-handlebars' data-template-name='products/index'>
    ...
    <ul class='list-unstyled'>
        {{#each}}
        {{product-details product=this tagName='li' classNames='row'}}
        {{/each}}
    </ul>
</script>
<script type='text/x-handlebars' data-template-name='contacts'>
    ...
</script>
<script type='text/x-handlebars' data-template-name='contact'>
    ...
</script>
<script type='text/x-handlebars' data-template-name='products/onsale'>
    ...
    <ul class='list-unstyled'>
        {{#each}}
        {{product-details product=this tagName='li' classNames='row'}}
        {{/each}}
    </ul>
</script>
<script type='text/x-handlebars' data-template-name='products/deals'>
    ...
    <ul class='list-unstyled'>
        {{#each}}
        {{product-details product=this tagName='li' classNames='row'}}
        {{/each}}
    </ul>
</script>
<script type='text/x-handlebars' data-template-name='components/product-details'>
    <img {{bind-attr src='product.image'}} class='img-thumbnail col-sm-5' />
    <div class='col-sm-7'>
        <h2>{{product.title}}</h2>

        <p class='product-description'>{{product.description}}</p>

        <p>{{#link-to 'product' product class='btn btn-success'}}
            Buy for ${{product.price}}{{/link-to}}</p>
    </div>
</script>
</body>
</html>

Пояснение. Бывает так, что одни и те же части кода повторяются несколько раз. Чтобы эти повторения занимали меньше места, их заменяют на шаблон components. В этом шаблоне помещают все требуемые элементы; его имя может начинаться с "components/", а далее содержать дефис, как в нашем упражнении product-details. Следует также отметить, что все свойства, употребляемые в components получают предшествующее им через точку имя объекта (как "product.title"). В шаблонах же, в которых этот компонент употребляется, применяют {{имя-компонента имяОбъекта=this}}, куда, при необходимости помещают tagName и classNames для элемента-обёртки.

6.3 Компонент Contact

Задание. В предыдущем упражнении мы узнали как разбить разметку, чтобы сделать компоненты повторно используемыми. В этот раз используем наши знания для контактов.
1) Создать шаблон компонента для контактов, названный contact-details.
2) В этом компоненте отобразим avatar, name и about. Отчистим разметку для дальнейшего использования.
3) Внутри шаблона product, под reviews, использовать contact-details, передав создателя продукта.
4) Обернуть элемент для contact-details классом row.

Решение. Создадим шаблон components/contact-details и поместим в него изображение, имя и описание контакта. Затем поместим этот компонент в шаблон product, указав класс row:

<script type='text/x-handlebars' data-template-name='product'>
    ...
    <h3>Crafter of the product</h3>
    {{contact-details contact=crafter classNames='row'}}
</script>
<script type='text/x-handlebars' data-template-name='components/contact-details'>
    <div class='col-sm-5'>
        <img {{bind-attr src='contact.avatar' alt='contact.name'}} class='img-thumbnail img-rounded'/>
    </div>
    <div class='col-sm-7'>
        <h2>{{contact.name}}</h2>

        <p>{{contact.about}}</p>
    </div>
</script>

Пояснение. По умолчанию tegName подразумевает div, поэтому его не обязательно указывать при создании блока.

6.4 Объекты Component

6.5 Объект Product Component

Здание. Вернёмся назад к компоненту product-details с актуальным объектом component.
1) Создать объект ProductDetailsComponent точно так же, как controllers в app.js.
2) Внутри App.ProductDetailsComponent создать свойство reviewsCount, которое будет устанавливать количество отзывов в текущем продукте. Можно сделать это, используя computed alias.
3) Далее, создать свойство hasReviews, равное функции, возвращающей reviewsCount больше 0. Убедиться, что оно обновляется при изменении свойства reviewsCount.
4) В нашем шаблоне для компонента product-details отобразить количество отзывов на продукт. Сделать это только в том случае, если у продукта есть отзывы.

Решение. В app.js создадим расширение Component для ProductDetails и передадим в него два взаимосвязанных свойства: одно устанавливает количество отзывов, а другое возвращает первое, если оно больше 0, и постоянно обновляется. В HTML отобразим количество всех отзывов при условии, что они есть:
app.js:

App.ProductDetailsComponent = Ember.Component.extend({
    reviewsCount: Ember.computed.alias('product.reviews.length'),
    hasReviews: function () {
        return this.get('reviewsCount') > 0;
    }.property('reviewsCount')
});

index.html:

<script type='text/x-handlebars' data-template-name='components/product-details'>
    ...
    {{#if hasReviews}}
    <p class='text-muted'>Read all reviews ({{reviewsCount}}).</p>
    {{/if}}
</script>

Пояснение. Иногда в компонентах нужно передать дополнительные свойства. В таком случае не обойтись без расширения Component. Оно строится подобно контроллеру и пронимает в себя объект с требуемыми свойствами. Тогда шаблон будет заглядывать в компонент для свойств.

6.6 Объект Contact Component

Задание. Мы можем добавить немного больше функциональности для компонента contact-details так же поддерживая его объектом component.
1) Создать объект component для contact-details component точно так же, как для product-details.
2) В объекте компонента contact-details создать свойство productsCount, которое будет подсчитывать количество созданных контактом продуктов.
3) Создать новое свойство isProductive, которое будет возвращать true если количество продуктов, созданных контактом более 3. Убедиться, что оно обновляется при изменении productsCount.
4) В шаблоне contact-details выписать количество продуктов, созданных контактом, если они продуктивны.

Решение. Проделаем тоже самое, что в прошлом упражнении, только теперь для contact:
app.js:

App.ContactDetailsComponent = Ember.Component.extend({
    productsCount: Ember.computed.alias('contact.products.length'),
    isProductive: function () {
        return this.get('productsCount') > 3;
    }.property('productsCount')
});

index.html:

<script type='text/x-handlebars' data-template-name='components/contact-details'>
   ...
    {{#if isProductive}}
    <p class='text-muted'>Create {{productsCount}} products!</p>
    {{/if}}
</script>

Пояснение. Стоит отметить, что объекту Component можно также передавать атрибуты в качестве свойств.

6.7 Помощники визуализации Handlebars

6.8 Объекты View

Задание. Мы хотим дать всем нашим продуктам возможность отображать динамические классы, которые изменяются, основываясь на информации найденной в модели.
1) Создать объект Ember View в app.js, который будет использоваться для пути product.
2) Создать свойство isOnSale в product view и сослаться им на свойство isOnSale модели. Помнить, что view должно сообщать контроллеру проверить, если модель на распродаже.
3) Добавить класс is-on-sale к элементу шаблона product только если он на распродаже.

Решение. Создадим расширение View для Product, в котором сделаем возможность сообщать контроллеру есть ли модель на распродаже и, если есть, добавлять элементу шаблона новый класс:

App.ProductView = Ember.View.extend({
    classNames: ['row'],
    classNameBindings: ['isOnSale'],
    isOnSale: Ember.computed.alias('controller.isOnSale')
});

Пояснение. Что, если мы хотим сделать возможность гибко изменять стили путём добавления при необходимости новых классов? Это легко осуществить с помощью View. Это расширение несёт ответственность за инкапсуляцию HTML, регистрирует и реагирует на инициированные пользователем события, сообщает об изменениях контроллеру. В итоге, товары, выставленные на продажу примут дополнительный класс is-on-sale, преобразованный из isOnSale.

6.9 Частичность

6.10 Использование Partials

Задание. Шаблон product становится немного длинным. На чистом шаблонном уровне мы можем использовать частичность для разделения шаблона на меньшие части. Partials будут доступны для того же контроллера, из которого они вызваны, но могут помочь привести в порядок длинный шаблон.
1) Создать partial для reviews.
2) Внутри reviews partial скопировать функциональность из шаблона product, где в данный момент выводятся все отзывы.
3) Заменить код review в шаблоне product с помощью handlebar, указав на только что созданную partial.

Решение. В HTML создадим шаблон _reviews, в который переместим все элементы, относящиеся к отзывам из product, а на их месте расположим partial:

<script type='text/x-handlebars' data-template-name='product'>
    <div class='row'>
        <div class='col-sm-7'>
            ...

            {{partial 'reviews'}}
        </div>
        ...
    </div>
    ...
</script>
<script type='text/x-handlebars' data-template-name='_reviews'>
    <h3>Reviews</h3>
    <ul>
        {{#each reviews}}
        <li><p>{{text}}</p></li>
        {{else}}
        <li><p class='text-muted'><em>No reviews yet. Be the first to write one!</em></p></li>
        {{/each}}
    </ul>
</script>

Пояснение. Слишком длинный шаблон может запутать. Для того, чтобы его разбить на части используют Partials. Его принцип заключается в создании нового шаблона, имя которого начинается с нижнего подчёркивания (_reviews), а затем передачи этого шаблона в нужное место с помощью partial ({{partial 'reviews'}}).

6.11 Отображение

6.12 Использование Render

Задание. Что, если мы хотим отображать наиболее свежие отзывы первыми на стороне клиента? Мы можем поместить контент и передать опции с render handlebar helper. Обновим шаблон product для сортировки отзывов, используя render.
1) Вместо использования partial отобразить отзывы в шаблоне product используя вызов render, делающий тоже самое. Для этого переместить вызов partial и обновить имя шаблона так, чтобы больше не использовалось именная условность partial. При показе отзывов отрегулировать каждый цикл для итерации над переданной моделью.
2) Создать контроллер, который будет использоваться при отображении reviews.
3) В контроллере reviews отсортировать отзывы свойством reviewedAt в убывающем порядке.

Решение. Для начала в HTML изменим partial на render. Для этого изменим имя шаблона с отзывами, а также из цикла удалим reviews, а в product заменим partial на render для reviews. В app.js создадим новое расширение ArrayController для Reviews, в котором отсортируем отзывы по дате в обратном порядке:
app.js:

App.ReviewsController = Ember.ArrayController.extend({
    sortProperties: ['reviewedAt'],
    sortAscending: false
});

index.html:

<script type='text/x-handlebars' data-template-name='product'>
    <div class='row'>
        <div class='col-sm-7'>
            ...

            {{render 'reviews' reviews}}
        </div>
        ...
    </div>
    ...
</script>
<script type='text/x-handlebars' data-template-name='reviews'>
    <h3>Reviews</h3>
    <ul>
        {{#each}}
        <li><p>{{text}}</p></li>
        {{else}}
        <li><p class='text-muted'><em>No reviews yet. Be the first to write one!</em></p></li>
        {{/each}}
    </ul>
</script>

Пояснение. Иногда разделённые части требуется отсортировать на стороне клиента. Тогда {{partial}} становится не пригодным, а на его замену приходит {{render}}. Для этого нужно создать новый контроллер. Переданный в render объект при этом станет моделью.

6.13 Отображение товаров

Задание. Где ещё мы можем использовать render? Есть место в шаблоне contact, где мы отображаем все продукты, созданные контактом. Они не отсортированны (если не считать порядок их добавления). Добавим их сортировку.

  1. Нам нужно переместить часть существующего шаблона contact, которая проходит циклом по всем продуктам в новый шаблон. Назвать его contact/products так, чтобы он не конфликтовал с другими шаблонами.
  2. В шаблоне contact отобразим contact/products тем же способом, что и reviews.
  3. Создать ссылку на каждый продукт.
  4. Создать контроллер, для нового шаблона contact/products. Здесь будет добавлена сортировка.
  5. Отсортировать продукты в contact/products по заголовку.

Решение. Создадим шаблон contact/products, в который перенесём ссылки на продукты из contact, а на их месте расположим render. Кроме того создадим контроллер, который сортирует продукты в contact/products по заголовкам:
app.js:

App.ContactProductsController = Ember.ArrayController.extend({
    sortProperties: ['title']
});

index.html:

<script type='text/x-handlebars' data-template-name='contact'>
    <div class='row'>
        ...
        <div class='col-sm-7'>
            ...
            {{render 'contact/products' products}}
        </div>
    </div>
</script>
<script type='text/x-handlebars' data-template-name='contact/products'>
    <h3>Products</h3>
    <ul>
        {{#each}}
        <li>{{#link-to 'product' this}}{{title}}{{/link-to}}</li>
        {{/each}}
    </ul>
</script>

Пояснение. См. 6.10, 6.12.