Отображение блоков в зависимости от текущей локации

в 18:08, , рубрики: backbone.js, javascript, Веб-разработка, метки: ,

Думаю будет интересно только тем, кто только начал работать с Backbone.js. Хотя…

Для чего это нужно?

Представьте что у вас есть приложение, но котором надо организовать систему уведомлений (новое письмо, лайк, активность друга и т.д.). Отображение подобного рода уведомлений будет не везде, а в определенных участках приложения. Например:

  • на главной странице нужно отображать все уведомления
  • на странице твиттера нужны только уведомления которые касаются только его
  • на «другой» странице нужны все уведомления кроме твиттера

Ссылки

Демо
Исходники

Структура

- app
   - modules
         anotherOne.js
         main.js
         navigation.js
         notification.js
         twitter.js
    app.js
    init.js
- assets
   - css
         index.css
   - js
       - libs
             backbone.js
             jquery-1.7.2.js
             underscore.js 
index.html

Ближе к делу

Первым делом запускаем локальный HTTP-сервер:

python -m SimpleHTTPServer
Serving HTTP on 0.0.0.0 port 8000 ...

Который будет доступен по адресу http://localhost:8000
Создаем index.html, подключаем необходимые библиотеки, стиль, запускаем и смотрим в папку app.

В файле init.js создаем переменную app, которая может делегировать событиями, менять роут, получать и компилировать шаблон (с кешированием)

var app = _.extend({
	templates : {},
	template : function(_id, _data) {
		var id = _id.slice(1);
		if (!this.templates[id]) {
			this.templates[id] = _.template($(_id).html());
		}
		return this.templates[id](_data);
	}
}, Backbone.Events)

Файл app.js отвечает за инициализацию и запуск приложения. Создадим 4 роута — три обычные и один волшебный, который будет перехватывать битые роуты (или их отсутствие, например, при первой загрузке)

routes : {
	'main' : 'main',
	'twitter' : 'twitter',
	'anotherOne' : 'anotherOne',
	'*action' : 'otherRoute'
}

Если сработает этот волшебный роут пользователь будет перенаправлен на роут main.

otherRoute : function () {
	this.navigate('main', {trigger:true});
}

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

Отображение блоков в зависимости от текущей локации

Рассмотрим структуру модуля main (twitter, anotherOne — идентичны ему).
Котроллер инициализирует и отрисовывает MainView

app.Main = function() {
	var view = new this.MainView(); // инициализируем
	view.render(); // отрисовываем
}

app.Main.prototype.MainView = Backbone.View.extend({

	el : '#main', // id елемента-родителя
	template : '#template-main', // id шаблона

	initialize : function () {
		_.bindAll(this);
	},

	render : function() {
		// компилируем шаблон и заменяем им HTML родителя
		this.$el.html(app.template(this.template));
	}
})

Очень часто встречаю такую запись

$(this.el)

Так делать не рекомендуется. Backbone кеширует el и позволяет обратиться к нему через

this.$el

Модуль navigation

Подписываемся на обновления роута:
app.on('navigation_changed', view.changeNavigation);

В случае, если роут изменится мы попадем в функцию changeNavigation:

changeNavigation : function (location) {
	$(this.$location).hide(); // прячем все страницы
	$(this.$notification).hide(); // прячем все уведомления
	$('#' + location).show(); // показываем текущую страницу
	$(this.$button).removeClass('active'); // сбрасываем стиль кнопки-индикатора 
	// делаем активной нужную кнопку
	$(this.$button + '[data-id="' + location + '"]').addClass('active'); 
	// показываем уведомления, которые должны отображаться везде
	$(this.$notification + '[data-include-in="*"]').show(); 
	// показываем уведомления которые должны отображаться на текущей странице
	$(this.$notification + '[data-include-in*="' + location + '"]').show(); 
	// показываем все уведомления типа exclude
	$(this.$notification + '[data-exclude-from]').show();  
	// и прячем те, которые не должны попасть на текущую страницу
	$(this.$notification + '[data-exclude-from*="' + location + '"]').hide(); 
}

Модуль notification

Инициализируем NotificationView и рендерим блок с настройками.

app.Notification = function() {
	var view = new this.NotificationView();
	view.renderSettings();
}

Вешаем события onclick на кнопки закрытия и создания уведомления соответственно.

initialize : function () {
	_.bindAll(this);

	this.$el.on('click', this.$closeButton, this.closeNotificationHandler);
	$(this.$settings).on('click', this.$addButton, this.addNotificationHandler)
}

После клика по кнопке закрытия ищем ближайшего родителя этой кнопки, анимируем его свертывание и удаляем из DOM.

closeNotificationHandler : function (e) {
	var $notification = $(e.currentTarget).parents(this.$notification);
	$notification.slideUp(100, function() {
		$notification.remove();
	});
}

Сюда мы попадаем после клика по кнопке "Показать уведомление".

addNotificationHandler : function () {
	var $location = $(this.$locationCheckbox + ':checked'), // получаем выбранные локации
		type = $(this.$typeRadio + ':checked').attr('value'), // получаем тип уведомления
		index,
		settings = {
			location : [],
			type : type,
			show : false, // по умолчанию уведомление скрыто
			message : null
		};

	// если установлен тип exclude-from и ни одна локация не выделана - можно 
	// просто проигнорировать это уведомление
	if (!type || !$location.length && type !== 'include-in') return false;
	if ($location.length) { // выбрана какая-то локация(и)
		$.each($location, function(key, element) {
			// наполняем массив локациями 
			settings.location.push(element.value); 
		});
		// проверяем есть ли текущая локация в списке 
		index = settings.location.indexOf(Backbone.history.fragment); 
		// склеиваем все локации в строку с пробелом в роли разделителя. 
		settings.location = settings.location.join(' '); 
		// показываем уведомление если: 
		// 1) установлен тип include-in и мы находимся в одной из выбранных локаций.
		// 2) установлен тип exclude-from и мы не находимся в одной из выбранных локаций. 
		settings.show = type === 'include-in' ? index !== -1 : index === -1;  
	}
	else {
		// ни одна локация не выбрана - показываем уведомление везде
		settings.show = true;
		settings.location = '*';
	}
	settings.message = type + ': ' + settings.location;

	this.addNotification(settings);
}

include-in — data-аттрибут в котором хранится список роутов (через пробел, или * — для всех роутов), где должны показываться уведомление
exclude-from — data-аттрибут в котором хранится список роутов (через пробел), где не должны показываться уведомление

Автор: vermilion1

* - обязательные к заполнению поля