Что такое панель? Это довольно простой компонент, разбивающий видимую область на 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)


  1. cool_grass
    09.11.2016 08:24

    Я считаю, что в подвале можно использовать массив элементов, состоящий из названия ( иконки ) и ссылки ( функции ) и map'ил бы этот массив выводя все элементы в подвал, если количество элементов большое и не все могут уместиться в ширину подвала, последний элемент был бы выдающий список.


    1. vintage
      09.11.2016 08:51

      Но это же очень частный случай и далеко не для всех случаев подходит.


  1. jbubsk
    09.11.2016 08:36
    +1

    >Что такое панель? Это довольно простой компонент, разбивающий видимую область на 2-3 блока

    Почему 2-3? А если больше, то это уже не панель? А разве панель служит не для группировки блоков/элементов в себе как контейнере?


    1. vintage
      09.11.2016 08:56

      Попробуете дать определение по лучше?


      1. jbubsk
        09.11.2016 09:16
        +1

        Уже дал, комментарий выше.


        1. vintage
          09.11.2016 10:03

          Оно на столько общее, что под него подпадает чуть ли не любой компонент.


          1. jbubsk
            09.11.2016 10:24

            Да, но это так и есть. А то что панель должна что-то разбивать, это вы уже сами придумали.


            1. vintage
              09.11.2016 10:42
              +1

              Ок, я сам это придумал, ввёл определение, и далее его использую. Можете заменить везде в статье слово "панель", на "вуферат", если под "панелью" вы привыкли понимать что-то другое. Суть ведь не в том, что называть панелью, а как реализовать описанный часто используемый компонент.


  1. yroman
    09.11.2016 09:12
    +3

    Вопрос конечно off-topic, но почему в вашем замечательном мегафреймворке вместо нормального слова children используется кривое неграмотное childs?


    1. vintage
      09.11.2016 10:07
      -4

      1. yroman
        09.11.2016 10:30
        +4

        Странное единообразие. Children это устоявшееся название, используемое чуть ли не во всех фреймворках, любой программист без труда может понять что это. Зато коллегам носителям языка ваш стиль будет корежить взгляд. Имхо, вы слишком ударяетесь в крайности.


        1. vintage
          09.11.2016 10:37
          -3

          Носителей языка куда меньше, чем не носителей. А неносителям куда проще образовывать коллекции добавлением s в конце, чем ковыряться в словаре каждый раз. Можете считать это вариантом венгерской нотации. Да, а ещё эта запись короче, что важно, для часто используемого термина.


          1. yroman
            09.11.2016 10:45
            +2

            Да, но при этом у вашего фреймворка англоязычная документация. То есть неносителям вы таки предлагаете лезть в словарь, чтобы её прочесть?


            1. vintage
              09.11.2016 10:52

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


              1. bano-notit
                10.11.2016 10:10

                Автоматический переводчик не может перевести что-либо адекватно, кроме односложных фраз, типа "Насколько хладен ваш чай?"


                Вообще не понятна мне эта реклама вашего $mol. Вот честно. Если он так хорош, что даже лучше чем React, то наверное он сам найдёт свою аудиторию, зачем на себя наводить неприязнь людей, записывая статьи в профильных хаб?


                1. vintage
                  10.11.2016 10:26

                  Никто не говорит, что он переводит идеально. А вы что предлагаете? Делать вид, что проблемы понимания документации на не родном языке не существует?


                  Данная статья о JSX, его проблемах и путях их решения. Было бы странно не поместить её в профильный хаб.


                  Наиболее адекватное решение проблем JSX, на мой взгляд — использование языка view.tree. При желании, вы можете прикрутить его к React или любому другому компонентному фреймворку. Я же показал его преимущества на примере готовой реализации в $mol.


                  Давайте лучше поговорим о том, почему у вас вызывает неприязнь критика какого-то языка. Вы считаете JSX идеальным, а React непогрешимым? Вы не хотите, чтобы используемый вами инструмент улучшался, беря лучшие идеи у конкурентов? Вы боитесь, что через пару лет в моде будут совершенно другие решения и снова придётся переучиваться и переписывать свой код?


                  1. bano-notit
                    10.11.2016 22:49
                    +1

                    Мне не нравится, что вы слишком быстро приравняли React и $mol. Через 3 года уже будут совершенно другие технологии, и реакт, и анугляр, и ваш $mol загнуться. Но при этом надо не забывать, что React — библиотека, а ваша реализация больше похожа на что-то типа куска фреймворка (я это я про .tree и его реализацию).
                    На счёт неудобности записи JSX… Ну тут у каждого свои мозги, мне Emmet'а хватает. Пишу себе Panel>Header^Body^Footer, нажимаю tab и получаю нормальный такой JSX.
                    Кстати, что за рвение такое смешивать верстальщиков с дизайнерами? Не понимаю. Если это на такие ситуации, когда дизайнер даёт слишком мудрёные шаблоны и верстальщик говорит: "Раз такой умный, сам и сделай!" Тогда это бредовая идея. Дизайнер должен знать, что выполнимо, а что нет. Иначе получается гибрид, которые не умеет делать ни то, ни другое. Обычно это какой-нибудь птушник, который закончил курс "дизайнеров одежды" и "оператор эвм" и тупо знает где в винде находится командная строка.
                    В общем, слишком рано вы начинаете свои восхваления.


                    1. vintage
                      11.11.2016 09:10

                      $mol — фреймворк.
                      $mol_viewer — библиотека, реализующая рендеринг компонент. Аналог React + TypeScript.
                      view.tree — декларативный DSL для описания компонент. Аналог TSX.


                      Даже если, через 3 года придёт понимание, что так рендерить нельзя, то появится какой-нибудь $mol_viewer2, реализующий новый подход, всё остальное останется на месте.


                      На счёт неудобности записи JSX…

                      Я говорил про неудобство чтения. Неудобство записи — следствие из неудобства чтения. И приведённные вами костыли (emmet), как раз подтверждают, что проблема есть и она беспокоит многих. Во view.tree такой проблемы нет изначально и подобные костыли не нужны.


                      Кстати, что за рвение такое смешивать верстальщиков с дизайнерами?

                      А это вы откуда взяли? Вы точно вкладку не перепутали?


          1. saksmt
            10.11.2016 10:30

            Венгерская нотация уже давно умерла (с появлением нормальных IDE), да и то, что вы тут описываете — не венгерская нотация.


            1. vintage
              10.11.2016 10:44

              Спасибо, капитан. Поможете нам расшифровать выражение "можете считать вариантом"?


              1. saksmt
                10.11.2016 11:07

                Не тот случай, это даже "вариантом" назвать нельзя.


                1. vintage
                  10.11.2016 11:38

                  Зато можно назвать "вариацией".


  1. 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="О, да!"
    />


    1. vintage
      10.11.2016 10:09

      Боюсь вы невнимательно читали. Ваше решение:


      1. Не позволяет удалить футер.
      2. Не позволяет заменить целиком шапку (а не только её содержимое).
      3. Не позволяет дополнительно стилизовать панель в конкретном месте использования.


      1. vintage
        10.11.2016 10:45

        Впрочем, спасибо за подсказку, как можно упростить код "велосипеда". Изменил в статье на реализацию в 44 CLOS.


      1. bano-notit
        10.11.2016 22:51

        А смысл тогда панели то? Она же должна быть в одном стиле. Если она в другом стиле, то это по идее другой элемент. Короче, тут уже в реализации проблема, а не в JSX.


        1. vintage
          11.11.2016 09:01

          Должна быть в одном стиле, но при этом должна быть настраиваемой, а не высеченной в граните, из-за чего для малейшего изменения нужно заводить отдельный "высеченный в граните" компонент. К чему это приводит — можете глянуть на сайте Тинькоффа — 3мб в гзипе, чтобы показать одну страницу.


      1. 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 про замену шапки я не понимаю. Если вам нужна панель с другой шапкой, возьмите и сделайте другой компонент. Зачем все пихать в единственный?


        1. vintage
          11.11.2016 13:56

          Отлично, теперь вам нужно добавить панели атрибут role, высоту подвала вам нужно задавать скриптом, а тело должно иметь кроссбраузерно стилизованный скроллбар. Отдельный компонент для каждого случая? Или один компонент, но с парой десятков параметров?


          Затем, что нужен точно такой же компонент с точно такими же подкомпонентами, но один из подкомпонент должен быть немного другим.


          1. justboris
            11.11.2016 19:45

            Атрибут role выставляется внутри панели. Пробрасывать его снаружи только для некоторых панелей – странно.


            Вычисляемая высота и кастомный скроллбар можно сделать и без js. Проблемы с высотой сейчас решаются flexbox, а скролббар можно кастомизировать как псевдоэлемент -webkit(-moz)-scrollbar


            1. vintage
              11.11.2016 21:44

              Пробрасывать его снаружи только для некоторых панелей – странно.

              От чего же странно, если в определённом контексте у панели другая семантика?


              Проблемы с высотой сейчас решаются flexbox

              Я не говорил, что это некая "проблема". Это требование. Например, нам нужно, чтобы высоту можно было менять, двигая по подвалу пальцем.


              скролббар можно кастомизировать как псевдоэлемент -webkit(-moz)-scrollbar

              Это во первых не кроссбраузерно, а во вторых в вебките стилизованный таким образом скроллбар люто тормозит.