# Графические компоненты Первый и главный шаг в наведении порядка -- это оформить код в объекты, каждый из которых будет решать свою задачу. Здесь мы сосредоточимся на графических компонентах, которые также называют "виджетами". В браузерах есть встроенные виджеты, например `` и другие элементы, о которых мы даже и не думаем, как они работают. Просто работают: принимают значение, вызывают события... Наша задача -- сделать то же самое на уровне выше. Мы будем создавать объекты, которые генерируют меню, диалог или другие компоненты интерфейса, и дают возможность удобно работать с ними. ## Виджет Menu Мы начнём работу с виджета, который предусматривает уже готовую разметку. То есть, в нужном месте HTML находится DOM-структура для меню -- заголовок и список опций: ```html ``` Далее она может дополняться, изменяться, но в начале -- она такая. Обратим внимание на важные соглашения:
Вся разметка заключена в корневой элемент `
Это очень удобно: вынул этот элемент из DOM -- нет меню, вставил в другое место -- переместил меню. Кроме того, можно удобно искать подэлементы.
В разметке -- только классы.
Документ вполне может содержать много различных меню. Они не должны конфликтовать между собой, поэтому для разметки везде используются классы. Исключение -- корневой элемент. В данном случае мы предполагаем, что данное конкретное "меню сладостей" в документе только одно, поэтому даём ему `id`.
Для работы с разметкой будем создавать объект `new Menu` и передавать ему корневой элемент. В конструкторе он поставит необходимые обработчики. ```js function Menu(options) { var elem = options.elem; elem.on('mousedown selectstart', false); elem.on('click', '.title', function() { elem.toggleClass('open'); }); } // использование var menu = new Menu({ elem: $('#sweets-menu') }); ``` Меню: [example src="menu-1"] Это, конечно, только первый шаг, но уже здесь видны некоторые важные соглашения в коде.
У конструктора только один аргумент -- объект `options`.
Это удобно, так как у графических компонентов обычно много настроек, большинство из которых имеют разумные значения "по умолчанию". Если передавать аргументы через запятую -- их будет слишком много.
Обработчики назначаются через делегирование.
Вместо того, чтобы найти элемент и поставить обработчик на него: ```js var titleElem = elem.find('.title'); titleElem.on('click', function() { elem.toggleClass('open'); } ``` ...Мы пишем так: ```js elem.on('click', '.title', function() { elem.toggleClass('open'); }); ``` Это ускоряет инициализацию, так как не надо искать элементы, и даёт возможность в любой момент менять DOM внутри, в том числе через `innerHTML`, без необходимости переставлять обработчика.
## Публичные методы Уважающий себя компонент обычно имеет публичные методы, которые позволяют управлять им снаружи. Рассмотрим повнимательнее этот фрагмент: ```js elem.on('click', '.title', function() { elem.toggleClass('open'); }); ``` Здесь в обработчике события сразу код работы с элементами. Пока одна строка -- всё понятно, но если их будет много, то при чтении понадобится долго и упорно вникать: "А что же, всё-таки, такое делается при клике?" Для улучшения читаемости выделим обработчик в отдельную функцию `toggle`, которая к тому же станет полезным публичным методом: ```js function Menu(options) { var elem = options.elem; elem.on('mousedown selectstart', false); *!* elem.on('click', '.title', onTitleClick); function onTitleClick(e) { toggle(); } function toggle() { elem.toggleClass('open'); }; */!* this.toggle = toggle; } ``` Здесь и сам обработчик события тоже вынесен в отдельную функцию `onTitleClick`. Наши бонусы:
  1. Во-первых, стало проще найти и расширить обработчик события в коде -- имя `onTitleClick` найти и запомнить.
  2. Во-вторых, код стал лучше читаться.
  3. Во-третьих, `toggle` теперь -- отдельная функция, доступная извне.
Пример использования публичного метода: ```js var menu = new Menu(...); menu.toggle(); ``` ## Генерация DOM-дерева До этого момента меню "оживляло" уже существующий HTML. Но в более сложном интерфейсе нужно уметь сгенерировать меню "на лету", по данным. Для этого добавим меню три метода: Функция генерации корневого элемента с заголовком `render` отделена от генерации списка `renderItems`. Почему -- будет видно чуть далее. Новый способ использования меню: ```js *!* // создать объект меню с данным заголовком и опциями */!* var menu = new Menu({ title: "Сладости", items: [ "Торт", "Пончик", "Пирожное", "Шоколадка", "Мороженое" ] }); *!* // получить DOM-элемент меню */!* var elem = menu.getElem(); *!* // вставить меню в нужное место страницы */!* $('#sweets-menu-holder').append( elem ); ``` Код `Menu` с новыми методами: ```js function Menu(options) { var elem; function getElem() { if (!elem) render(); return elem; } function render() { elem = $(''); elem.append( $('', { class: "title", text: options.title })) elem.on('mousedown selectstart', false); elem.on('click', '.title', onTitleClick); } function renderItems() { var items = options.items || []; var list = $('