Что такое панель? Это довольно простой компонент, разбивающий видимую область на 2-3 блока:
- Шапка. В шапку обычно выводится заголовок и какие-то (обычно навигационные) элементы правления.
- Тело. В тело панели выводится выводится произвольное содержимое. Часто этот блок делается скроллируемым, чтобы шапка не уходила из поля зрения.
- Подвал. Опциональный блок. Сюда выводят обычно общую для содержимого панели информацию и элементы управления.
Не смотря на кажущуюся простоту, реализации обычно не такие уж и простые. Связано это с тем, что вариантов его использования великое множество.
В шапке может быть, а может не быть:
- Заголовок. Дополнительно у него может быть подзаголовок.
- Хлебные крошки. Они могут быть частью заголовка, а могут — подзаголовка.
- Навигационные ссылки. Такие как "назад", "следующий" и тп.
- Кнопки. Такие как "открыть фильтры", "переключить флаг", "закрыть окно" и другие.
Короче говоря, в шапке может быть почти что угодно. В теле же, определённо должна быть возможность выводить любое содержимое. В подвале содержимое так же может быть произвольным.
Получается, что у панели должно быть минимум 3 параметра, которые принимают "сложное содержимое", то есть такое, которое не является плоским текстом, а содержит иерархию вложенных блоков.
Далее идёт обзор тех готовых решений, которые можно найти в гугле. Для каждого указан размер реализации в строках кода (CLOS). Плюс бонус в конце, для тех, кто доберётся ;-)
ReactJS
wmira/react-panel — 180 CLOS
В шапку выводятся:
- Опциональная иконка перед заголовком.
- Собственно заголовок.
- Опциональный набор кликабельных иконок в правой части шапки.
Размеры тела по умолчанию подстраиваются под содержимое. Полдвал не поддерживается.
Пример использования:
return (
<Panel
title={Привет, мир!}
titleIcon="icon-idea"
toolbox={[
{
className : "icon-close" ,
onclick : this.onClose.bind( this )
}
]}
>
<p>Ты прекрасен!</p>
</Panel>
)
Резюме: решение для очень частного случая, в коде бардак, документации нет, только один сомнительный пример использования в духе jQuery.
react-bootstrap — 235 CLOS
Шапка и подвал раcсчитанны на вывод простого текста, но есть возможность вывести туда что угодно. Тело подстраивается под размеры содержимого и может быть схлопнуто до нулевой высоты через флаг.
Пример использования:
return (
<Panel
header={
<div>
<span class="my-title">Привет, мир!</span>
<Button
bsStyle="danger"
onclick={ this.onClose.bind( this ) }
>
Закрыть
</Button>
</div>
}
footer={
<Button
bsStyle="success"
onclick={ this.onSuccess.bind( this ) }
>
О, да!
</Button>
}
>
<p>Ты прекрасен!</p>
</Panel>
)
Резюме: не смотря на костыли с оборачиванием списка компонент в div и запихиванием целого дерева в атрибут использовать можно почти для всего. Разве что "схлопываемость" зачастую будет просто лишним грузом, а когда потребуется что-то особенное, то этой куцей реализации скорее всего не хватит.
pivotal-cf/pivotal-ui — 173 CLOS
Шапка разделена на две секции: левую (header) и опциональную правую (actions), куда вы можете выводить любое содержимое. Подвал и тело имеют по одной секции. Для тела можно включить "скроллируемость", чтобы панель не вылезала за пределы области просмотра.
Пример использования:
return (
<Panel
className="bg-neutral-10"
header={
<h1>Привет, мир!</h1>
}
actions={
<DangerButton onclick={ this.onClose.bind( this ) } >
Закрыть
</DangerButton>
}
footer={
<PrimaryButton onclick={ this.onSuccess.bind( this ) }>
О, да!
</PrimaryButton>
}
>
<p>Ты прекрасен!</p>
</Panel>
)
Резюме: всё те же костыли, но реализация компактней, не содержит почти ничего лишнего и чуть удобней в использовании.
Велосипед — 44 CLOS
Как-то не удобно, что часть вёрстки задаётся в атрибутах, а часть в теле. Некоторые функции избыточны, а для реализации других приходится переписывать компонент. А раз всё равно рано или поздно переписывать, то давайте попробуем переписать так, чтобы и гибко получилось и без костылей.
function MyPanel({ className , ...props }) {
return (
<div
{ ...props }
className={ `my-panel-root ${ className || '' }` }
/>
)
}
function MyPanelTitle({ className , ...props }) {
return (
<h1
{ ...props }
className={ `my-panel-title ${ className || '' }` }
/>
)
}
function MyPanelHead({ className , ...props }) {
return (
<div
{ ...props }
className={ `my-panel-head ${ className || '' }` }
/>
)
}
function MyPanelBody({ className , ...props }) {
return (
<div
{ ...props }
className={ `my-panel-body ${ className || '' }` }
/>
)
}
function MyPanelFoot({ className , ...props }) {
return (
<div
{ ...props }
className={ `my-panel-foot ${ className || '' }` }
/>
)
}
Панель состоит из 3 опциональных блоков: шапка, тело подвал. Бонусом: можно добавить несколько шапок/тел/подвалов. Для блоков можно использовать как стандартные компоненты от панели, так и собственные, а внутрь них помещать что угодно.
Правда использование чуть более многословно, но зато обошлись без тэгов в атрибутах:
return (
<MyPanel className="my-panel-skin-pretty">
<MyPanelHead>
<MyPanelTitle>Привет, мир!</MyPanelTitle>
<button onclick={ this.onClose.bind( this ) } >Закрыть</button>
</MyPanelHead>
<MyPanelBody>
<p>Ты прекрасен!</p>
</MyPanelBody>
<MyPanelFoot>
<button onclick={ this.onSuccess.bind( this ) }>О, да!</button>
</MyPanelFoot>
</MyPanel>
)
Резюме: относительно компактное и весьма гибкое решение, имеет простой, понятный, правда несколько многословный (что в принципе свойственно XML) интерфейс.
$mol_pager — 11 CLOS
Шапка по умолчанию выводит заголовок. Содержимое любого блока, как и сами блоки могут быть заменены чем угодно. Тело скроллируется, а позиция скролла восстанавливается при перезагрузке.
Реализация настолько компактная, что её не страшно привести прямо тут:
$mol_pager $mol_viewer
childs /
< header $mol_viewer
childs < head /
< titler $mol_viewer
childs /
< title
< bodier $mol_scroller
childs < body /
< footer $mol_viewer
childs < foot /
Пример использования:
$my_app $mol_pager
title \Привет, мир!
head /
< titler
< closer $mol_clicker_danger
eventClick > eventClose null
childs / \Закрыть
body /
\Ты прекрасен!
foot /
< successor $mol_clicker_major
eventClick > eventSuccess null
childs / \О, да!
Резюме: на порядок компактнее реализация дающая тем не менее высокую степень гибкости, использование обходится без костылей, но применяется достаточно необычный синтаксис, требующий освоения. И, да, это не React, а $mol, где интерфейс тоже строится из компонент, которые агрегируют в себе другие компоненты, но компоненты не пересоздаются при каждом рендеринге, а кешируются. :-)
Выводы
В JSX можно сделать и так и сяк, но всё-равно будет что-то не то. Типичные проблемы:
- Жёсткий некастомизируемый код, вынуждающий велосипедить каждый раз когда нужно добавить пару перламутровых пуговиц.
- Лишние функции в общих компонентах. Следствие высокой жёсткости кода.
- Развесистый, непоследовательный интерфейс использования. Обычно содержимое тела передаётся способом отличным от содержимого остальных блоков.
- Все программисты на реакте программируют по разному. Кто как понял — тот так и фигачит. И скорее всего не так, как нужно вам.
- JSX похож на XML и JavaScript, однако не является ни над-, ни помножеством ни того, ни другого. Так что за кажущейся простотой скрывается необходимость разбираться в особенностях уникального синтаксиса.
- Даже простой компонент требует довольно много кода. И чем гибче вы его захотите сделать, тем запутанней получится код.
- Структура компонента, ровным слоем размазывается по его логике. Мимикрия под XML в этом случае становится бесполезной.
- Привлечение верстальщика возможно только после интенсивного курса по JS… после чего он увольняется и идёт работать программистом. :-)
С другой стороны, есть простой и последовательный синтаксис view.tree, оптимизированный для создания гибких компонент с минимумом исходного кода, и которому можно обучить любого верстальщика в считанные дни, после чего и его эффективность значительно повысится (ему не придётся копипастить огромные куски html или вручную накликивать нужные состояния компонентам) и эффективность программиста (ему не придётся "натягивать" вёрстку на логику каждый раз, когда верстальщик обновит макет).
Даже если вы разнорабочий full-stack программист, который умеет и в паттерны, и в семантику, и в стили — ваша эффективность всё-равно повысится за счёт уменьшения объёмов кода и лёгкого и непринуждённого создания компонент (чтобы создать простейшую компоненту достаточно создать файл с содержимым из одной короткой строки).
А как бы вы реализовали компонент "Панель" на вашем любимом фреймворке?
PS. Это не реклама $mol, это реклама языка view.tree. Если вы не готовы променять React на что-то другое, но заинтересовал синтаксис view.tree — можете реализовать его трансляцию в React. Идея view.tree — простая: полноценное управление компонентами не покидая крайне простого языка. А скрипты с логикой на JS/TS прикручиваются сбоку. В реализации $mol логика прикручивается посредством наследования и переопределения свойств, заданных во view.tree.
Комментарии (31)
jbubsk
09.11.2016 08:36+1>Что такое панель? Это довольно простой компонент, разбивающий видимую область на 2-3 блока
Почему 2-3? А если больше, то это уже не панель? А разве панель служит не для группировки блоков/элементов в себе как контейнере?vintage
09.11.2016 08:56Попробуете дать определение по лучше?
jbubsk
09.11.2016 09:16+1Уже дал, комментарий выше.
vintage
09.11.2016 10:03Оно на столько общее, что под него подпадает чуть ли не любой компонент.
jbubsk
09.11.2016 10:24Да, но это так и есть. А то что панель должна что-то разбивать, это вы уже сами придумали.
vintage
09.11.2016 10:42+1Ок, я сам это придумал, ввёл определение, и далее его использую. Можете заменить везде в статье слово "панель", на "вуферат", если под "панелью" вы привыкли понимать что-то другое. Суть ведь не в том, что называть панелью, а как реализовать описанный часто используемый компонент.
yroman
09.11.2016 09:12+3Вопрос конечно off-topic, но почему в вашем замечательном мегафреймворке вместо нормального слова children используется кривое неграмотное childs?
vintage
09.11.2016 10:07-4yroman
09.11.2016 10:30+4Странное единообразие. Children это устоявшееся название, используемое чуть ли не во всех фреймворках, любой программист без труда может понять что это. Зато коллегам носителям языка ваш стиль будет корежить взгляд. Имхо, вы слишком ударяетесь в крайности.
vintage
09.11.2016 10:37-3Носителей языка куда меньше, чем не носителей. А неносителям куда проще образовывать коллекции добавлением s в конце, чем ковыряться в словаре каждый раз. Можете считать это вариантом венгерской нотации. Да, а ещё эта запись короче, что важно, для часто используемого термина.
yroman
09.11.2016 10:45+2Да, но при этом у вашего фреймворка англоязычная документация. То есть неносителям вы таки предлагаете лезть в словарь, чтобы её прочесть?
vintage
09.11.2016 10:52В ней намеренно не используются хитрые обороты, чтобы автоматический переводчик мог перевести её наиболее адекватно.
bano-notit
10.11.2016 10:10Автоматический переводчик не может перевести что-либо адекватно, кроме односложных фраз, типа "Насколько хладен ваш чай?"
Вообще не понятна мне эта реклама вашего $mol. Вот честно. Если он так хорош, что даже лучше чем React, то наверное он сам найдёт свою аудиторию, зачем на себя наводить неприязнь людей, записывая статьи в профильных хаб?
vintage
10.11.2016 10:26Никто не говорит, что он переводит идеально. А вы что предлагаете? Делать вид, что проблемы понимания документации на не родном языке не существует?
Данная статья о JSX, его проблемах и путях их решения. Было бы странно не поместить её в профильный хаб.
Наиболее адекватное решение проблем JSX, на мой взгляд — использование языка view.tree. При желании, вы можете прикрутить его к React или любому другому компонентному фреймворку. Я же показал его преимущества на примере готовой реализации в $mol.
Давайте лучше поговорим о том, почему у вас вызывает неприязнь критика какого-то языка. Вы считаете JSX идеальным, а React непогрешимым? Вы не хотите, чтобы используемый вами инструмент улучшался, беря лучшие идеи у конкурентов? Вы боитесь, что через пару лет в моде будут совершенно другие решения и снова придётся переучиваться и переписывать свой код?
bano-notit
10.11.2016 22:49+1Мне не нравится, что вы слишком быстро приравняли React и $mol. Через 3 года уже будут совершенно другие технологии, и реакт, и анугляр, и ваш $mol загнуться. Но при этом надо не забывать, что React — библиотека, а ваша реализация больше похожа на что-то типа куска фреймворка (я это я про .tree и его реализацию).
На счёт неудобности записи JSX… Ну тут у каждого свои мозги, мне Emmet'а хватает. Пишу себеPanel>Header^Body^Footer
, нажимаю tab и получаю нормальный такой JSX.
Кстати, что за рвение такое смешивать верстальщиков с дизайнерами? Не понимаю. Если это на такие ситуации, когда дизайнер даёт слишком мудрёные шаблоны и верстальщик говорит: "Раз такой умный, сам и сделай!" Тогда это бредовая идея. Дизайнер должен знать, что выполнимо, а что нет. Иначе получается гибрид, которые не умеет делать ни то, ни другое. Обычно это какой-нибудь птушник, который закончил курс "дизайнеров одежды" и "оператор эвм" и тупо знает где в винде находится командная строка.
В общем, слишком рано вы начинаете свои восхваления.vintage
11.11.2016 09:10$mol — фреймворк.
$mol_viewer — библиотека, реализующая рендеринг компонент. Аналог React + TypeScript.
view.tree — декларативный DSL для описания компонент. Аналог TSX.
Даже если, через 3 года придёт понимание, что так рендерить нельзя, то появится какой-нибудь $mol_viewer2, реализующий новый подход, всё остальное останется на месте.
На счёт неудобности записи JSX…
Я говорил про неудобство чтения. Неудобство записи — следствие из неудобства чтения. И приведённные вами костыли (emmet), как раз подтверждают, что проблема есть и она беспокоит многих. Во view.tree такой проблемы нет изначально и подобные костыли не нужны.
Кстати, что за рвение такое смешивать верстальщиков с дизайнерами?
А это вы откуда взяли? Вы точно вкладку не перепутали?
saksmt
10.11.2016 10:30Венгерская нотация уже давно умерла (с появлением нормальных IDE), да и то, что вы тут описываете — не венгерская нотация.
justboris
10.11.2016 09:50+4Все указанные вами библиотеки обладают большей функциональностью, чем просто панель. Неудивительно, что они содержат больше кода.
Вот эквивалентное вашему решение (7 строк):
function Panel({head, body, footer}) { return <div className="panel"> <div className="panel-head">{head}</div> <div className="panel-body">{body}</div> <div className="panel-footer">{footer}</div> </div>; } //использование <Panel head={<div><h1>Привет, мир!</h1><button>Закрыть</button></div>} body="Ты прекрасен!" footer="О, да!" />
vintage
10.11.2016 10:09Боюсь вы невнимательно читали. Ваше решение:
- Не позволяет удалить футер.
- Не позволяет заменить целиком шапку (а не только её содержимое).
- Не позволяет дополнительно стилизовать панель в конкретном месте использования.
vintage
10.11.2016 10:45Впрочем, спасибо за подсказку, как можно упростить код "велосипеда". Изменил в статье на реализацию в 44 CLOS.
bano-notit
10.11.2016 22:51А смысл тогда панели то? Она же должна быть в одном стиле. Если она в другом стиле, то это по идее другой элемент. Короче, тут уже в реализации проблема, а не в JSX.
vintage
11.11.2016 09:01Должна быть в одном стиле, но при этом должна быть настраиваемой, а не высеченной в граните, из-за чего для малейшего изменения нужно заводить отдельный "высеченный в граните" компонент. К чему это приводит — можете глянуть на сайте Тинькоффа — 3мб в гзипе, чтобы показать одну страницу.
justboris
11.11.2016 12:49Имя класса для стилизации добавить не сложно. Как и удаляемые блоки. Кода сильно больше не стало:
function Panel({className, head, body, footer}) { return <div className={classNames('panel', className)}> {head && <div className="panel-head">{head}</div>} <div className="panel-body">{body}</div> {footer <div className="panel-footer">{footer}</div>} </div>; }
Пункт 2 про замену шапки я не понимаю. Если вам нужна панель с другой шапкой, возьмите и сделайте другой компонент. Зачем все пихать в единственный?
vintage
11.11.2016 13:56Отлично, теперь вам нужно добавить панели атрибут role, высоту подвала вам нужно задавать скриптом, а тело должно иметь кроссбраузерно стилизованный скроллбар. Отдельный компонент для каждого случая? Или один компонент, но с парой десятков параметров?
Затем, что нужен точно такой же компонент с точно такими же подкомпонентами, но один из подкомпонент должен быть немного другим.
justboris
11.11.2016 19:45Атрибут role выставляется внутри панели. Пробрасывать его снаружи только для некоторых панелей – странно.
Вычисляемая высота и кастомный скроллбар можно сделать и без js. Проблемы с высотой сейчас решаются flexbox, а скролббар можно кастомизировать как псевдоэлемент -webkit(-moz)-scrollbar
vintage
11.11.2016 21:44Пробрасывать его снаружи только для некоторых панелей – странно.
От чего же странно, если в определённом контексте у панели другая семантика?
Проблемы с высотой сейчас решаются flexbox
Я не говорил, что это некая "проблема". Это требование. Например, нам нужно, чтобы высоту можно было менять, двигая по подвалу пальцем.
скролббар можно кастомизировать как псевдоэлемент -webkit(-moz)-scrollbar
Это во первых не кроссбраузерно, а во вторых в вебките стилизованный таким образом скроллбар люто тормозит.
cool_grass
Я считаю, что в подвале можно использовать массив элементов, состоящий из названия ( иконки ) и ссылки ( функции ) и map'ил бы этот массив выводя все элементы в подвал, если количество элементов большое и не все могут уместиться в ширину подвала, последний элемент был бы выдающий список.
vintage
Но это же очень частный случай и далеко не для всех случаев подходит.