<commit_before>﻿@{
	ViewBag.Title = "UI (пользовательский интерфейс) для плагинов";
}

<div class="row">
	<div class="col-md-12">
		<h1>UI (пользовательский интерфейс) для плагинов</h1>
		<p>
			Как мы видели в разделе о разработке плагинов, стандартный плагин ListenerPlugin дает возможность
			обрабатывать HTTP запросы (возвращать на клиент файлы из ресурсов или данные в JSON).
			Еще один стандартный плагин WebUIPlugin реализует веб-интерфейс системы.
		</p>
		<p>
			Веб-интерфейс представляет собой что-то вроде панели управления вашим домом. Он состоит из разделов,
			причем каждый плагин может содержать собственные разделы и они автоматически будут отображаться
			в интерфейсе. Все разделы делятся на 2 группы: <em>системные</em> (различные настройки, например, раздел
			сценариев) и <em>пользовательские</em> (разделы, с которыми пользователи работают в повседневной
			жизни, например, "прогноз погоды" и "будильники").
		</p>
		<div class="row">
			<div class="col-md-6">
				<img class="ths-image img-responsive" src="~/Content/images/common-pages.png"
					 alt="Список пользовательских разделов" title="Список пользовательских разделов" />
			</div>
			<div class="col-md-6">
				<img class="ths-image img-responsive" src="~/Content/images/system-pages.png"
					 alt="Список системных разделов" title="Список системных разделов" />
			</div>
		</div>
		<p>
			Кроме этих "рабочих" разделов имеется еще один специальный раздел — "рабочий стол"
			(он же "стартовая страничка"). Там находятся "элементы стартовой страницы" — небольшие панели,
			которые отображают полезную информацию. При клике на них происходит переход в какой-либо раздел
			или выполняется действие. Стартовая страница (как и весь остальной интерфейс) корректно отображается
			как на больших экранах, так и на небольших экранах мобильных устройств.
		</p>
		<div class="row">
			<div class="col-md-3">
				<img class="ths-image img-responsive" src="~/Content/images/start-page-small.png"
					 alt="Стартовая страница на маленьком экране" title="Стартовая страница на маленьком экране" />
			</div>
			<div class="col-md-9">
				<img class="ths-image img-responsive" src="~/Content/images/start-page-large.png"
					 alt="Стартовая страница на большом экране" title="Стартовая страница на большом экране" />
			</div>
		</div>
		<p>
			Технически, веб-интерфейс представляет собой модульное одностраничное приложение на основе
			<a href="http://marionettejs.com" target="_blank">marionette.js</a>. Загрузка модулей
			происходит по мере необходимости с помощью <a href="http://requirejs.org" target="_blank">require.js</a>.
			Для визуальной части используется <a href="http://getbootstrap.com" target="_blank">Twitter Bootstrap</a>.
		</p>
		<h2>Добавление нового раздела</h2>
		<p>
			Любой раздел системы - это небольшая программа на языке JavaScript. Она описывает, что именно
			должен видеть пользователь на экране и какие действия должны быть выполнены, когда пользователь
			взаимодействует с элементами интерфейса.
		</p>
		<p>
			Для добавления нового раздела в систему необходимо в проекте вашего плагина создать js файл,
			который будет содержать js-код раздела. Веб-интерфейс загружает разделы через
			<a href="http://requirejs.org" target="_blank">require.js</a>,
			поэтому для определения раздела используйте функцию <code>define</code> (т.е. каждый раздел -
			это отдельный модуль в терминах require.js).
		</p>
		<pre>
define(
	['app', 'marionette'],
	function (application, marionette) {
		var module = {
		<strong>start</strong>: function () {
				alert("module started!");
			}
		};
		return module;
	});
</pre>
		<p>
			Как видите, функция <code>define</code> принимает два параметра:
		</p>
		<ol>
			<li>Массив зависимостей - содержит имена модулей, от котрых зависит ваш модуль.</li>
			<li>
				Функция инициализации - будет вызвана один раз, при
				загрузке вашего модуля. Она должна возвращать объект, представляющий
				ваш модуль.
			</li>
		</ol>
		<p>
			Каждый модуль из списка зависимостей будет передан как входной параметр в функцию
			инициализации. В примере указано две зависимости: 'app' - это предопределенное имя
			основного модуля приложения (например, с его помощью можно отображать информацию
			на экране), 'marionette' - это предопределенное имя модуля библиотеки
			<a href="http://marionettejs.com" target="_blank">marionette.js</a>
			(там содержатся средства, облегчающие разработку пользовательского интерфейса).
		</p>
		<p>
			Для того, чтобы модуль мог быть использован веб-интерфейсом, необходимо, чтобы он
			содержал метод с названием <strong>start</strong>. По сути, когда пользователь
			нажимает на ссылку перехода в какой-либо раздел, система загружает js-файл раздела
			при помощи require.js, далее вызывается функция инициализации (если это не было
			сделано ранее) и у объекта, полученного из функции инициализации, вызывается метод
			<strong>start</strong>.
		</p>
		<p>
			В приведенном выше примере модуль ничего не отображает на странице. Он просто
			выдает пользователю alert с текстом "module started!".
		</p>

		<p>
			Далее необходимо настроить загрузку js-файла раздела в браузер. Для этого в свойствах файла
			укажите для параметра <em>Build action</em> значение <em>EmbeddedResource</em> и задайте
			URL для файла.
		</p>
		<p>
			Для того, чтобы указать URL, пометьте свой плагин атрибутом <code>ThinkingHome.Plugins.WebUI.Attributes.AppSection</code>
			из сборки <code>ThinkingHome.Plugins.WebUI</code> (если вы еще не добавили ее к проекту, самый простой способ сделать это - набрать в консоли
			Nuget Package Manager <kbd>Install-Package ThinkingHome.Plugins.WebUI</kbd>)
		</p>
		<pre>
[Plugin]
[AppSection(<strong>"My plugin"</strong>,		// название рездела
	<strong>SectionType.Common</strong>,		// тип раздела
	<strong>"/my-plugin/my-file.js"</strong>,	// URL
	<strong>"ThinkingHome.Plugins.MyPlugin.Resources.my-file.js"</strong>)]
public class MyPlugin : PluginBase
{
	...
}
</pre>
		<p>
			Кроме URL и пути к файлу ресурса, в параметрах атрибута <code>[AppSection]</code>
			необходимо указать название раздела и его тип ("пользовательский" или "системный"). После этого ссылка на раздел автоматически
			добавится в нужный список разделов.
		</p>
		<p>
			<img class="img-responsive ths-image" src="~/Content/images/section-in-list.png" />
		</p>
		<h2>Отображение данных на странице</h2>
		<p>
		</p>
		<h2>События</h2>
		<p>
		</p>
		<h2>Запросы к серверу</h2>
		<p>
		</p>
		<h2>Элементы рабочего стола</h2>
		<p><commit_msg>- описание отображения данных на странице;<commit_after>﻿@{
	ViewBag.Title = "UI (пользовательский интерфейс) для плагинов";
}

<div class="row">
	<div class="col-md-12">
		<h1>UI (пользовательский интерфейс) для плагинов</h1>
		<p>
			Как мы видели в разделе о разработке плагинов, стандартный плагин ListenerPlugin дает возможность
			обрабатывать HTTP запросы (возвращать на клиент файлы из ресурсов или данные в JSON).
			Еще один стандартный плагин WebUIPlugin реализует веб-интерфейс системы.
		</p>
		<p>
			Веб-интерфейс представляет собой что-то вроде панели управления вашим домом. Он состоит из разделов,
			причем каждый плагин может содержать собственные разделы и они автоматически будут отображаться
			в интерфейсе. Все разделы делятся на 2 группы: <em>системные</em> (различные настройки, например, раздел
			сценариев) и <em>пользовательские</em> (разделы, с которыми пользователи работают в повседневной
			жизни, например, "прогноз погоды" и "будильники").
		</p>
		<div class="row">
			<div class="col-md-6">
				<img class="ths-image img-responsive" src="~/Content/images/common-pages.png"
					 alt="Список пользовательских разделов" title="Список пользовательских разделов" />
			</div>
			<div class="col-md-6">
				<img class="ths-image img-responsive" src="~/Content/images/system-pages.png"
					 alt="Список системных разделов" title="Список системных разделов" />
			</div>
		</div>
		<p>
			Кроме этих "рабочих" разделов имеется еще один специальный раздел — "рабочий стол"
			(он же "стартовая страничка"). Там находятся "элементы стартовой страницы" — небольшие панели,
			которые отображают полезную информацию. При клике на них происходит переход в какой-либо раздел
			или выполняется действие. Стартовая страница (как и весь остальной интерфейс) корректно отображается
			как на больших экранах, так и на небольших экранах мобильных устройств.
		</p>
		<div class="row">
			<div class="col-md-3">
				<img class="ths-image img-responsive" src="~/Content/images/start-page-small.png"
					 alt="Стартовая страница на маленьком экране" title="Стартовая страница на маленьком экране" />
			</div>
			<div class="col-md-9">
				<img class="ths-image img-responsive" src="~/Content/images/start-page-large.png"
					 alt="Стартовая страница на большом экране" title="Стартовая страница на большом экране" />
			</div>
		</div>
		<p>
			Технически, веб-интерфейс представляет собой модульное одностраничное приложение на основе
			<a href="http://marionettejs.com" target="_blank">marionette.js</a>. Загрузка модулей
			происходит по мере необходимости с помощью <a href="http://requirejs.org" target="_blank">require.js</a>.
			Для визуальной части используется <a href="http://getbootstrap.com" target="_blank">Twitter Bootstrap</a>.
		</p>
		<h2>Добавление нового раздела</h2>
		<p>
			Любой раздел системы - это небольшая программа на языке JavaScript. Она описывает, что именно
			должен видеть пользователь на экране и какие действия должны быть выполнены, когда пользователь
			взаимодействует с элементами интерфейса.
		</p>
		<p>
			Для добавления нового раздела в систему необходимо в проекте вашего плагина создать js файл,
			который будет содержать js-код раздела. Веб-интерфейс загружает разделы через
			<a href="http://requirejs.org" target="_blank">require.js</a>,
			поэтому для определения раздела используйте функцию <code>define</code> (т.е. каждый раздел -
			это отдельный модуль в терминах require.js).
		</p>
		<pre>
define(
	['app', 'marionette', 'backbone', 'underscore'],
	function (application, marionette, backbone, _) {
		var module = {
		<strong>start</strong>: function () {
				alert("module started!");
			}
		};
		return module;
	});
</pre>
		<p>
			Как видите, функция <code>define</code> принимает два параметра:
		</p>
		<ol>
			<li>Массив зависимостей - содержит имена модулей, от котрых зависит ваш модуль.</li>
			<li>
				Функция инициализации - будет вызвана один раз, при
				загрузке вашего модуля. Она должна возвращать объект, представляющий
				ваш модуль.
			</li>
		</ol>
		<p>
			Каждый модуль из списка зависимостей будет передан как входной параметр в функцию
			инициализации. В примере указано две зависимости: 'app' - это предопределенное имя
			основного модуля приложения (например, с его помощью можно отображать информацию
			на экране), 'marionette' - это предопределенное имя модуля библиотеки
			<a href="http://marionettejs.com" target="_blank">marionette.js</a>
			(там содержатся средства, облегчающие разработку пользовательского интерфейса).
			'backbone' - предопределенное имя модуля библиотеки
			<a href="http://backbonejs.org/" target="_blank">backbone.js</a>.
		</p>
		<p>
			Для того, чтобы модуль мог быть использован веб-интерфейсом, необходимо, чтобы он
			содержал метод с названием <strong>start</strong>. По сути, когда пользователь
			нажимает на ссылку перехода в какой-либо раздел, система загружает js-файл раздела
			при помощи require.js, далее вызывается функция инициализации (если это не было
			сделано ранее) и у объекта, полученного из функции инициализации, вызывается метод
			<strong>start</strong>.
		</p>
		<p>
			В приведенном выше примере модуль ничего не отображает на странице. Он просто
			выдает пользователю alert с текстом "module started!".
		</p>

		<p>
			Далее необходимо настроить загрузку js-файла раздела в браузер. Для этого в свойствах файла
			укажите для параметра <em>Build action</em> значение <em>EmbeddedResource</em> и задайте
			URL для файла.
		</p>
		<p>
			Для того, чтобы указать URL, пометьте свой плагин атрибутом <code>ThinkingHome.Plugins.WebUI.Attributes.AppSection</code>
			из сборки <code>ThinkingHome.Plugins.WebUI</code> (если вы еще не добавили ее к проекту, самый простой способ сделать это - набрать в консоли
			Nuget Package Manager <kbd>Install-Package ThinkingHome.Plugins.WebUI</kbd>)
		</p>
		<pre>
[Plugin]
[AppSection(<strong>"My plugin"</strong>,		// название рездела
		<strong>SectionType.Common</strong>,		// тип раздела
		<strong>"/my-plugin/my-file.js"</strong>,	// URL
		<strong>"ThinkingHome.Plugins.MyPlugin.Resources.my-file.js"</strong>)]
public class MyPlugin : PluginBase
{
	...
}
</pre>
		<p>
			Кроме URL и пути к файлу ресурса, в параметрах атрибута <code>[AppSection]</code>
			необходимо указать название раздела и его тип ("пользовательский" или "системный"). После этого ссылка на раздел автоматически
			добавится в нужный список разделов.
		</p>
		<p>
			<img class="img-responsive ths-image" src="~/Content/images/section-in-list.png" />
		</p>
		<h2>Отображение данных на странице</h2>
		<p>
			Данные появляются в модуле из источника данных (например они могут быть получены с сервера или введены пользователем).
			Будем считать, что изначально данные существуют в виде объектов JavaScript. Для примера создадим объекты внутри приложения.
		</p>
		<pre>
start: function () {
	...
	var items = [
		{ id: 1, name: '<NAME>' },
		{ id: 2, name: '<NAME>' },
		{ id: 3, name: '<NAME>' },
		{ id: 4, name: '<NAME>' }
	];
	
	var model = new backbone.Collection(items);
	...
}
</pre>
		<p>
			В приведенном примере мы создали массив объектов, каждый из которых имеет два поля: id (номер объекта) и name (текстовое поле).
			Также мы создали специальный объект - модель для нашего нашего массива (с помощью библиотеки Backbone.js). Благодаря Backbone.js
			мы имеем средства для удобной работы с нашими объектами. Например, мы легко можем задать порядок сортировки для коллекции
			и при обращении к ее элементам они будут выдаваться в нужном порядке.
		</p>
		<pre>
		...
		var model = new backbone.Collection(items);
		model.comparator = 'name';
		
		model.sort();
		model.each(function(obj) {
			console.log(obj.get('name'));
		});
		
		/*
			будет выведено:
				<NAME>
				<NAME>
				<NAME>
				<NAME>
		*/
</pre>
		<p>
			Обратите внимание, объект <code>backbone</code>, к которому мы обращались при создании модели - это входной параметр нашей функции инициализации
			модуля. Это третий по списку параметр, а значит, в качестве его значения будет передан объект, соответствующий третьему пункту нашего списка
			зависимостей (т.е. туда будет передан объект Backbone из библиотеки backbone.js).
		</p>
		<p>
			Для отображения данных в системе используются представления (views) из marionette.js. Представления - это специальные объекты,
			которые знают, как отобразить данные на странице и какие события сгенерировать при взаимодействии пользователя
			с элементами интерфейса.
		</p>
		<p>
			Сначала необходимо описать параметры представления и его поведения. Далее нужно создать его объект - экземпляр представления
			и передать туда модель с данными. После этого нужно вызвать команду "отобразить представление на странице".
		</p>
		<pre>

	<strong>// описываем параметры представлений для коллекции и для ее отдельного элемента</strong>
	var peopleView = marionette.ItemView.extend({
			template: _.template('<%= name %> (<%= id %>)')			<strong>// шаблон отображения</strong>
		});
		
	var peoplesView = marionette.CompositeView.extend({
			template: _.template('&lt;h1>Peoples&lt;/h1>&lt;div id="list">&lt;/div>'),	<strong>// шаблон отображения</strong>
			itemView: peopleView,			<strong>// представление для элемента коллекции</strong>
			itemViewContainer: '#list'		<strong>// контейнер, куда будут добавляться элементы</strong>
		});
		
	<strong>// создаем экземпляр представления и передаем туда данные (модель Backbone.Collection)</strong>
	var view = new peoplesView({ collection: model });
	
	<strong>// отображаем представление на странице</strong>
	application.setContentView(view);
</pre>
		<p>
			Обратите внимание, объекты marionette и application - это тоже входные параметры функции инициализации.
		</p>
		<h3>Размещение шаблонов в отдельных файлах</h3>
		<h2>События</h2>
		<p>
		</p>
		<h2>Запросы к серверу</h2>
		<p>
		</p>
		<h2>Элементы рабочего стола</h2>
		<p>
{'fraction_non_alphanumeric': 0.10983187134502924, 'fraction_numerical': 0.0028021442495126704, 'mean_word_length': 4.106376360808709, 'pattern_counts': {'":': 0, '<': 289, '<?xml version=': 0, '>': 293, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1362033', 'n_tokens_mistral': 7142, 'n_tokens_neox': 7475, 'n_words': 1703}