Skip to content
This repository has been archived by the owner on Dec 3, 2020. It is now read-only.
4urbanoff edited this page Jan 29, 2015 · 10 revisions

Вид это объект, который делает из шаблона html-код (представление), наполняет его данными и помещает его в место назначения на странице приложения. Вид отвечает за своевременную отрисовку представления при изменении отображаемых в нем данных, а также несет в себе логику взаимодействия пользователя с представлением. Вид всегда принадлежит определенной модели.

Любой вид всегда имеет ссылку на свою модель в свойстве @model (для краткости имеется свойство @my также ссылающееся на модель), а в свойстве @element находится представление ввиде объекта jBone (или jQuery если приложение использует его в качестве библиотеки для манипуляций с DOM).

Создать вид можно через терминал с помощью встроенного генератора

$ nali view UserIndex

где User - это модель, для которой создается вид Index в результате мы получим следующие файлы

  • app/client/javascripts/views/user/index.js.coffee - сам вид
  • app/client/stylesheets/user/index.css.sass - его стили
  • app/client/templates/user/index.html - шаблон

открыв app/client/javascripts/views/user/index.js.coffee мы увидим

Nali.View.extend UserIndex:

  events:  []
  # тут описываются привязки событий DOM к методам вида

  helpers: {}
  # здесь методы-помощники для отрисовки представления
  
  onDraw: ->
  # этот колбек будет срабатывать каждый раз после перерисовки представления

  onShow:  ->
  # этот колбек сработает после вставки представления на страницу приложения

  onHide:  ->
  # этот колбек сработает после удаления представления со страницы приложения

файл app/client/stylesheets/user/index.css.sass содержит одну строку с именем класса представления

.UserIndex

а файл app/client/templates/user/index.html - пуст.

Дело в том, что вид автоматически оборачивает код шаблона в тег div c классом, имя которого равно имени вида, т.е. из пустого шаблона мы автоматически получим

<div class="UserIndex"></div>

из шаблона

<span class="name">Вася</div>

получим

<div class="UserIndex">
	<span class="name">Вася</div>
</div>

Обертывание произойдет только в том случае, если Вы сами не сделали этого в коде шаблона. Например если шаблон уже имеет вид

<div class="UserIndex">
	<span class="name">Вася</div>
</div>

то он таким и останется. Это позволяет переопределить тег обертывания и добавить какие-либо инструкции в атрибуты этого тега, например шаблон

<a href="user/{ @id }" class="UserIndex { @online? }">
	<span class="name">{ @name }</div>
</a>

также не будет подвергнут обертыванию, т.к. он уже обернут тегом a с классом UserIndex

Работа с DOM

По умолчанию для работы с DOM используется библиотека jBone, она имеет jQuery-подобный синтаксис и очень маленький вес, ввиду того, что в отличие от jQuery в ней реализован минимальный набор самых часто используемых функций. Она подключается автоматически, если в настройках приложения Вы не заменили её другой библиотекой, например той же jQuery. Внутри любого объекта клиентской части Вы можете обратиться к ней через нижнее подчеркивание, например

Nali.View.extend UserIndex:

  onShow: ->
    @_( 'div.toolbar' ) 
    # все теги `div` с классом `toolbar`, @_ тоже самое что и $

У вида всегда существует свойство element - это также объект jBone (либо jQuery), поэтому если Вам нужно найти какой-либо блок внутри представления вида, предпочтительнее действовать так

Nali.View.extend UserIndex:

  onShow: ->
    @element.find( 'div.toolbar' ) 
    # все теги `div` с классом `toolbar` внутри представления вида

Если вид является макетом, то у него всегда есть свойство yield, в котором содержится jBone объект блока, в который происходит вставка контента.

Инструкции шаблонов

Для вставки данных в представления Nali распознает следующие инструкции

  • { @propertyName } вставит значение свойства propertyName объекта как текст. Отсчет ведется от модели текущего вида, т.е. @ это модель. Можно использовать цепочки, например, мы создаем шаблон сообщения в чате, модель каждого сообщения связана с моделью юзера, его написавшего, через свойство user, каждый юзер имеет имя в свойстве name, поэтому чтобы в представление сообщения в нужном месте вставилось имя автора, в шаблоне необходимо написать следующую инструкцию - { @user.name }. Такая конструкция позволяет добраться до свойств практически любого объекта системы, например { @contacts.0.name } - так можно вставить свойство name первого пользователя списка контактов (contacts - массив или коллекция). Источником свойства может быть любой объект, а если он является наследником объекта Nali (например модель, коллекция и т.д.) то вид после отрисовки представления будет следить за событием update.property источника и производить перерисовку представления при возникновении этого события
  • { @propertyName? } вставит имя свойства если его значение истинно, например { @online? } вставит строку online, если свойство online модели, при приведении к булеву типу, равно true
  • { =helperName } вставит результат работы хелпера вида как текст (даже если он является html-кодом)
  • { +helperName } вставит результат работы хелпера вида как html, соответственно хелпер должен выдавать валидный html-код
  • { index of @messages } вставит вид index каждой модели коллекции messages. Инструкция должна быть обернута в тег, например
<div class="dialog">
	{ index of @messages }
</div>
<textarea name="text"></textarea>

а так не правильно

<div class="dialog">
	{ index of @messages }
	<textarea name="text"></textarea>
</div>
  • { yield } если шаблон является макетом, то помещаемый в него контент будет вставлен в месте, где указана эта инструкция. Данная инструкция также как и предыдущая должна быть обернута в тег, например
<div class="content">
	{ yield }
</div>

Кроме того внутри вида блок div.content будет доступен в свойстве yield как объект jBone (или jQuery в зависимости от того, что подключено).

Серверная прекомпиляция шаблонов

Nali позволяет обработать шаблон на сервере перед отдачей его на клиент. По умолчанию в качестве серверного шаблонизатора подключен ERB. Для его использования необходимо всего лишь изменить расширение шаблона, например index.html - обычный html-шаблон, index.html.erb - ERB-шаблон.

Подключение стороннего шаблонизатора

Для примера возьмем шаблонизатор Slim. Сначала добавим его в наш Gemfile

gem 'slim'

установим

bundle install

и подключим в настройках приложения

# config/application.rb

module Nali
  
  class Application
    
    client.register_engine '.slim', Slim::Template
    
  end
  
end

Теперь для использования Slim-шаблонов нужно всего лишь указывать расширение index.html.slim.

Хелперы

Это функции хранящиеся в объекте helpers вида и используемые для отрисовки представления, например

<div class="UserInfo">
	<span class="from">Место рождения: { =from }</span>
	<span class="from">Друзей: { =friendsCount }</span>
</div>
Nali.View.extend UserInfo:

	helpers:
		from: ->
			@getMy( 'city' ).capitalize() + ', ' + @getMy( 'country' ).capitalize()
		friendsCount: ->
			@getOf @my.friends, 'length' 

Метод getOf( source, property ) получает значение свойства из источника и подписывает вид на событие update.property. Метод getMy( property ) получает значение свойства из модели вида и также подписывает вид на событие update.property, технически внутри себя представляет вызов getOf( @model, property ).
Хелперы используются только если вставляемые данные необходимо как-то обработать перед выводом на страницу, в примере вывод количества друзей можно сделать без хелпера

<div class="UserInfo">
	<span class="from">Место рождения: { =from }</span>
	<span class="from">Друзей: { @friends.length }</span>
</div>

События представления

В свойстве events определяются привязки методов вида к DOM событиям элементов его представления, например

Nali.View.extend UserInfo:

	events: 'method1 on click at .block'
	# синтаксис записи следующий
	# methodName[, methodName ...] on event[, event ] at selector

	method1: -> console.log 'method1 run'

	method2: -> console.log 'method1 run'

Вид позволяет привязывать как один обработчик одному событию, так и несколько обработчиков на одно событие елемента или один обработчик на несколько событий или несколько обработчиков на несколько событий. Если у вас несколько независимых привязок, их необходимо указать ввиде массива:

Nali.View.extend UserInfo:

	events: [
		'method1 on click at .block1'
		'method2 on click at .block2'
		'method3, method4 on hover at .block3'
		'method5 on hover, focus at .block4'
		'method6, method7 on hover, focus, click at .block5'
	]

	method1: ( event ) -> console.log 'method1 run' + event
	method2: ( event ) -> console.log 'method2 run' + event
	method3: ( event ) -> console.log 'method3 run' + event
	method4: ( event ) -> console.log 'method4 run' + event
	method5: ( event ) -> console.log 'method5 run' + event
	method6: ( event ) -> console.log 'method6 run' + event
	method7: ( event ) -> console.log 'method7 run' + event

Обработчики устанавливаются на верхний родительский блок представления и работают делегированным способом.

Ссылки и формы

При нажатии на ссылку вид дает команду роутеру на переход по её адресу, но ссылки, адрес которых начинается с @..., вид обрабатывает сам, позволяя напрямую обращаться в методы объектов, например нажатие на ссылку

<a href="@someMethod">Ссылка</a>

приведет к вызову метода someMethod, отсчет ведется от текущего вида, т.е. @ это вид, поэтому произойдет вызов метода определенного в самом виде, можно использовать цепочки для доступа к методам других объектов, например

<a href="@model.someMethod">Ссылка</a>
<!-- или -->
<a href="@my.someMethod">Ссылка</a>

вызовет метод someMethod, который определен в модели, а

<a href="@my.contacts.0.destroy">Ссылка</a>

вызовет метод destroy у первого объекта из списка контактов, определенного в модели. Точку отсчета цепочек можно сменить с вида на модель с помощью двойного символа @@..., например

<a href="@@someMethod">Ссылка</a>
<!-- аналогично что и -->
<a href="@my.someMethod">Ссылка</a>
<!-- соответственно -->
<a href="@@contacts.0.destroy">Ссылка</a>
<!-- аналогично что и -->
<a href="@my.contacts.0.destroy">Ссылка</a>

В вызываемый метод можно сразу же передать параметры, для этого необходимо указать их через слеш, причем если в параметре имеется двоеточие, то он рассматривается как объект со свойством и значением, а несколько таких параметров подряд, как свойства и значения одного объекта. Если же двоеточия нет параметр расценивается как строка, например нажатие ссылки

<a href="@@updateUser/25/name:Valera/age:23">Ссылка</a>

соответствует вызову @model.updateUser( 25, {name:'Valera', age:23} )

<a href="@@updateUser/25/name:Valera/age:23/Hello World">Ссылка</a>

@model.updateUser( 25, {name:'Valera', age:23}, 'Hello World' )

<a href="@@updateUser/25/name:Valera/age:23/Hello World/ale:garazh">Ссылка</a>

@model.updateUser( 25, {name:'Valera', age:23}, 'Hello World', {ale: 'garazh'} )

С формами можно вытворять тоже самое, при этом первым параметром в вызываемый метод передается объект данных самой формы, затем параметры из адреса, например submit формы

<form action="@@updateUser/losung:Hello World">
	<input type="text" name="name" value="Valera" />
	<input type="text" name="age" value="23" />
	<input type="submit" value="Сохранить" />
</form>

приведет к вызову @model.updateUser( {name:'Valera', age:23}, {losung: 'Hello World'} ) если же адрес перехода формы (атрибут action) не начинается с @, то запустится роутер, найдет нужный маршрут и передаст данные формы в экшен котроллера

Связывание данных

Вид позволяет редактировать свойства модели с помощью элементов форм, для этого в теге элемента необходимо указать атрибут bind с именем свойства, например

Nali.Model.extend User:

	attributes:
		name:    null
		theme:  'dark'
		city_id: null
		invis:   null
		
	saveSettings: (params) ->
		# тут можно произвести еще какие-то действия с параметрами формы
		@save()
<div class="UserSettings">
	<form action="@@saveSettings">
		Ваше имя
		<input type="text" bind="name" name="name" />
		<!-- элемент будет связан со свойством name модели -->
		<!-- в элементе будет отображено текущее значение -->
		<!-- аналогично связывается элемент textarea -->
		Тема оформления
		<input type="radio" bind="theme" name="theme" value="dark" /> dark
		<input type="radio" bind="theme" name="theme" value="blue" /> blue
		<input type="radio" bind="theme" name="theme" value="red" /> red
		<!-- элементы будут связаны со свойством theme модели-->
		<!-- изначально будет выбран элемент с текущим значением -->
		Город
		<select name="city_id" bind="city_id">
			<option value="1">Moskow</option>
			<option value="2">Ivanovo</option>
		</select>
		<!-- элемент будет связан со свойством city_id модели -->
		<!-- изначально будет выбран элемент с текущим значением -->
		Видимость
		<input type="checkbox" bind="invis" name="invis" value="on" /> invisible
		<!-- элемент будет связан со свойством invis модели -->
		<!-- изначально будет включен, если model.invis == 'on' -->
		<!-- если выключить, присвоит свойству значение null -->
		<input type="submit" value="сохранить" />
	</form>
</div>

В данном примере при редактировании полей формы автоматически будут редактироваться связанные свойства модели вида, при этом изменения будут касаться только локальной модели. При нажатии на кнопку сохранить будет вызван метод saveSettings модели с параметрами формы, в нем произойдет сохранение модели на сервер. В видах Nali существует возможность редактирования свойств модели с прямым сохранением на сервер, для этого нужно использовать элементы форм не оборачивая их в тег <form>, тогда пример выше будет выглядеть следующим образом:

Nali.Model.extend User:

	attributes:
		name:    null
		theme:  'dark'
		city_id: null
		invis:   null
<div class="UserSettings">
	Ваше имя
	<input type="text" bind="name"  />
	Тема оформления
	<input type="radio" bind="theme" value="dark" /> dark
	<input type="radio" bind="theme" value="blue" /> blue
	<input type="radio" bind="theme" value="red" /> red
	Город
	<select bind="city_id">
		<option value="1">Moskow</option>
	<option value="2">Ivanovo</option>
	</select>
	Видимость
	<input type="checkbox" bind="invis" value="on" /> invisible
</div>

при этом атрибут name в тегах становится необязательным.

Показ и скрытие представлений

Определить видимость представления можно с помощью свойства visible вида, который равен true, если представление находится на странице или false в обратном случае. Для управления существуют два основных метода:

  • show(insertTo) вставляет html-код представления на страницу в конец блока insertTo, при этом передан может быть сам блок, его селектор или объект jBone (jQuery), после вставки вызывает колбек onShow вида.
Nali.View.extend UserInfo:

	events: []
	helpers: {}

user.view( 'info' ).show( '.container' )
# покажет представление info в конце блока .container

если представление всегда отображается в одном и том же месте можно определить метод insertTo в виде и вызывать show без аргументов

Nali.View.extend UserInfo:

	events: []
	helpers: {}
	insertTo: -> '.container'

user.view( 'info' ).show()
# покажет представление info в конце блока .container

если же необходимо вставить представление вида в представление-макет другого вида, необходимо определить метод layout возвращающий объект вида-макета

Nali.View.extend UserInfo:

	events: []
	helpers: {}
	layout: -> @my.view 'interface'

user.view( 'info' ).show()
# покажет представление-макет, а в нем представление info

при этом представление-макет должно иметь инструкцию { yield }

<div class="UserInterface">
	<div class="content">{ yield }</div>
</div>

При вызове метода show без агрументов сначала ищется метод insertTo, если его нет ищется layout, если и его нет, то представление вставится в html-элемент приложения, определенный при запуске (по умолчанию body)

  • hide(delay) Удаляет представление со страницы, вызывает колбек onHide. Опция delay позволяет совершить задержку (в миллисекундах) перед удалением представления, необходимую, например, для завершения каких-либо анимаций, определенных в методе onHide, вызов которого в любом случае происходит без задержек.
Nali.View.extend UserInfo:

	events: []
	helpers: {}

user.view( 'info' ).show().hide(5000)
# покажет представление info и удалит его со страницы через 5 секунд

Если задержка необходима всегда, её можно определить в свойстве hideDelay вида и вызывать метод hide без аргументов

Nali.View.extend UserInfo:

	events: []
	helpers: {}
	hideDelay: 5000

user.view( 'info' ).show().hide()
# также покажет представление info и удалит его со страницы через 5 секунд