# Working with DOM

Одним из основных отличий, которое вносит **RAD.js**, в отличии от чистого **Backbone.js**, это отсутствие необходимости прямой работы с DOM структурой вашего приложения.

Именно *отсутствие необходимости*, то есть по факту вы и дальше можете на прямую работать с DOM, но в этом нет прямой необходимости.

В качестве альтернативы у Вас есть следующие возможности:

* использование [**properties**](#properties) вашей `view`
* переопределение метода [**getTemplateData**](#getTemplateData) в вашей `view` для подготовки данных которые будут отображены в вашем шаблоне
* и, наконец, использование [**references**](#references) в логике вашей `view`

## <a name="properties"></a>properties

Одной из возможностей напрямую не работать с DOM являеться использование *properties* Вашей `view`.

По факту объект *properties* Вашей `view` являеться просто *Backbone.Model* , без каких либо дополнительных свойств и изменений. На изменение атрибутов которой подписана ваша `view`. 

Это значит что при лобом изменении свойств вашей `view` будет вызван ee метод [`render`](../API.md#view_render). Более подробно об этом можно почитать в [API](../API.md#view_props).

### template.ejs

Мы же попытаемся использовать данный метод на практике, для этого мы создадим файл `template.ejs` следующего вида:

```ejs
<div>
    <button>Clicks:<%= this.props.get('count') %></button>
</div>
```

**На что следует обратить внимание:**
	
* синтаксис шаблона ничем не отличаеться от синтаксиса `underscore.js` шаблона
* отличие состоит в том что контекстом выполнения вашей шаблонной функции являеться ваша `view`, свойство **this.props** которой мы используем для получения атрибута, как у обычной `Backbone.Model` (чем собственно и являються свойства вашей `view`)

> На самом деле, то что контекстом выполнения шаблонной функции являеться Ваша `view` дает просто огромные возможности, так как вы можете непосредственно из шаблона вызывать любые ее методы или использовать любые ее свойства в шаблоне.

### index.js

Теперь нам необходимо внести изменения в `index.js`, для загрузки данного шаблона как шаблона `view`, забиндить события клика на кнопке и описать логику, для этого внесем следующие изменения:

```javascript
import {View, publish} from 'RAD.js';

class WelcomePage extends View {

    // view template
    template = require('./template.ejs');

    // events binding
    events = {
        'click button': 'onClick'
    };

    // first type of working with DOM via props changing
    onClick() {
        var count = this.props.get('count');
        this.props.set('count', count + 1);
    }
}

// place view to DOM container via navigator plugin
publish('navigation.show', {
    container: '#screen',
    content: WelcomePage,
    options: {
        count: 0
    }
});
```

**На что следует обратить внимание:**
	
* для загрузки файла нашего шаблона, его компиляции и установки в качестве шаблонной функции вашей `view` понадобилась всего одна строчка, за нас все сделал webpack `itemplate-loader`
* собятия описыватся и биндяться точно также как и в **Backbone.js**
* в функции которая вызываеться по клику кнопки, происходит просто изменение значения одного из атрибутов свойств вашей `view`. Никакого дополнительного вызова `render` не происходит
* стартовые значения свойств `view` передаються как `options` в сообщении для вашего плагина навигатор.

> На самом деле одной из основных целей была полная совместимость **RAD.js** c **Backbone.js**, поэтому на вещах которые делаються точно так же как и в **Backbone.js** мы не будем останавливаться в дальнейшем. Единственная специфика может быть связана с использованием **Backbone.js** с *ES6*.
>
> По факту *properties* вашей `view` создаються из *options* переданных в ее конструктор. Поэтому если бы вы выбрали стандартный способ размещения вашей `view` в DOM, как мы рассматривали [в прошлом разделе](Quickstart.md#standart), то вы бы просто указали `count: 0` как одну из опций конструктора.

Теперь если вы собирете ваш пример так же как и в предидущем случае, вы увидите кнопку которая считает количество нажатий.

*Основополагающей идеей, данного подхода являеться наличие у `view` объекта properties при изменении свойств/атрибутов которого происходит рендеринг.*

## <a name="getTemplateData"></a>getTemplateData

А что делать если свойства вашей `view` это не совсем то, а ингда совсем не то что необходимо отобразить при рендеринге?

Например очень часто на практике возникает ситуация, когда данные полученные из какого-то источника требуют адаптирования для отображения, причем как правило это не простое форматирование. Но использовать возможность встраивания *javascript* непосредственно в шаблон - значило бы размазывать логику представления между шаблоном и js, что являеться не совсем правильным.

Для этой цели у `view` существует метод [`getTemplateData`](../API.md#view_get-template-data).

### template.js

Добавим всего одну строчку в наш шаблон

```ejs
<h1><%= data.string %></h1>
```

### index.js

А в наш класс всего один метод:

```javascript
// second type of working with DOM via template data
getTemplateData() {
    return {
        string: 'Hello, World!'
    }
}
``` 

**На что следует обратить внимание:**
	
* По сути переопределив `getTemplateData` у `view` вы определяете адаптер данных для отображения.
*  результат метода `getTemplateData` доступен при рендеренге прямо в шаблоне в качестве объекта `data`.

Собрав проект вы увидите и надпись *"Hello, World!"* и кнопку со счечиком.

*Основополагающей идеей, данного подхода являеться возможности у `view` переопределить метод для подготовки данных, которые будут отображены в шаблоне.*

## <a name="references"></a>references

Как и пердидущий метод, возможность использования **references** внутри `view` продиктованна практической необходимостью.

В качестве небольшого примера использования прямых сылок внутри `view` мы свяжем поле ввода с надписью на заголовке.

### template.ejs

Для этого внесем в наш файл шаблона очередные изменения, после которых он будет выглядеть следующим образом:

```ejs
<%
/* ES5 javascript section */
var name = this.props.get('name');
%>
<div>
    <h1><%= data.string + name + '!' %></h1>
    <div>
        <input ref="my_input" type="text" value="<%= name %>"/>
    </div>
    <button>Clicks:<%= this.props.get('count') %></button>
</div>
```

Атрибут `ref` тега `input` являеться служебным и позволяет получить ссылку непосредственно в `view` без использования методов DOM.

**На что следует обратить внимание:**

* в шаблонах мы используем только *ES5* синтаксис, это ограничение связано с тем что они не компилируються **babel**. То есть если вы будете использовать приложение в браузере который поддерживает *ES6*, никто не мешает Вам использовать новый синтаксис в шаблонах. 
* в `<%= ... %>` шаблонах можно получать значения простейших js выражений.
* область видимости *javascript* переменных определенных в шаблоне являеться шаблонная функция
* однострочные коментарии `//` в *javascript* области шаблона не допустимы, это связано со спецификой компиляции шаблона, необходимо использовать `/* ... */`
* последняя строка *javascript* области шаблона должна заканчиваться на `;`, это связано со спецификой компиляции шаблона
* самозакрывающиеся html теги, как в данном примере `input` рекомется закрывать. 

### index.js

Мы приведем полный листинг `index.js` с изменениями необходимыми для демонстрации третьего подхода:

```javascript
import {View, publish} from 'RAD.js';

class WelcomePage extends View {

    // view template
    template = require('./template.ejs');

    // events binding
    events = {
        'click button': 'onClick',
        'input input': 'onInput'
    };

    // first type of working with DOM via props changing
    onClick() {
        let count = this.props.get('count');
        this.props.set('count', count + 1);
    }

    // second type of working with DOM via template data
    getTemplateData() {
        return {
            string: 'Hello, '
        }
    }

    // third type of working with DOM via references
    onInput() {
        let name = this.refs.my_input.value;
        this.props.set('name', name);
    }
}

// place view to DOM container via navigator plugin
publish('navigation.show', {
    container: '#screen',
    content: WelcomePage,
    options: {
        count: 0,
        name: 'World'
    }
});
```

Как вы можете заметить, в методе `onInput` мы использовали автоматически сгенерированную ссылку на DOM элемент с установленным атрибутом `ref` через  свойство `view` **this.refs**, для получения его значения.

Более подробно о `references` можно почитать в [документации](../API.md#view_refs).

> Как вы наверное заметили и объект **refs** у `view`, и, собственно, непосредственная работа с сылками аналогична работе с сылками в **React.js**
> 
> И будьте внимательны, дело в том что объект **refs** может быть изменен после рендеринга `view` поэтому не сохраняйте прямые ссылки на элементы DOM, они могут стать не валидными. Пользуйтесь `this.refs['...']`

*Основополагающей идеей, данного подхода являеться необходимость работы с DOM элементами `view` без выполнения запросов по поиску этих элементов.*

## Source

Исходники нашего проекта можно получить по следующей [ссылке](source/2.zip).

## Альтернативное решение

Более классическое решение:

```javascript
import {View, publish, template} from 'RAD.js'

class WelcomePage extends View {
    template = template(`<div>
	    <h1><%= 'Hello ' + this.name + '!' %></h1>
	    <div>
	        <input type="text" value="<%= this.name %>"/>
	    </div>
	    <button>Clicks:<%= this.counter %></button>
	</div>`);

    events() {
        return {
            'click button': 'onClick',
            'input input': 'onInput'
        };
    }

    initialize(options) {
        this.counter = options.counter;
        this.name = options.name;
    }

    onClick() {
        this.counter += 1;
        this.render();
    }

    onInput(e) {
        this.name = e.target.value;
        this.render();
    }
}

new WelcomePage({
    el: '#screen',
    counter: 0,
    name: 'World'
}).render();
```

В данном решении мы показали возможность ручного рендеринга `view` по событиям, шаблон как строку для `view`, передачу данных в конструктор и работу с событиями в DOM.

> Обратите внимание на то что `events` определены функцией, это вызвано тем что мы изпользуем бабель плагин(об этом мы писали в предидущем разделе), который не совсем корректно оборачивает свойства экземпляра класса, плагин навигатор умеет с этим работать но в данном случае мы его не использовали: `el: '#screen'`. [Исходники](source/2_alt.zip) альтернативного решения.

## Что дальше?

Далее мы рассмотрим [второе отличие](Injection.md) от **Backbone.js**.
