Профессиональные макеты за несколько шагов с WebSharper

Всем нравится, когда их продукт выглядит профессионально, верно? Но я думаю, что есть что-то, что нравится людям еще больше: профессиональный продукт, который легко производить! Что ж, это именно то, что вы получаете, когда используете расширение Golden Layout для WebSharper!

Эта статья может немного растянуться, но к ее концу вы не только будете иметь представление о расширении Golden Layout (будь то на C#, F# или JavaScript), но и сделаете довольно крутое веб-приложение. Подумайте об этом: если чтение этой статьи займет 15 минут, в следующий раз, когда вы захотите написать что-то подобное, это может быть сделано до того, как вы перечитаете статью! Удачи!

Расширение

Давайте начнем нашу небольшую демонстрацию с краткого обзора!

WebSharper — это фреймворк для веб-разработки поверх .NET, который поддерживает F# и C# и имеет такие функции, как реактивные переменные, Doc шаблоны для обеспечения быстрой итерации (вы можете прочитать немного больше об этом здесь), безопасность типов а также простое удаленное взаимодействие и компилятор F#/C# в JavaScript. Последнее позволяет нам использовать любую библиотеку JavaScript из F#, если для нее есть привязка, а теперь есть привязка для Golden Layout!

Golden Layout — это многоэкранный менеджер макетов для веб-приложений, который позволяет создавать профессионально выглядящие макеты для вашего веб-сайта или приложения с помощью нескольких строк кода. Сегодня я хотел бы показать вам, как получить доступ к возможностям этой замечательной библиотеки от WebSharper.

Наш образец

Я хотел показать вам проблему, которая выигрывает как от макетов, так и от реактивности, поэтому я решил провести вас через этапы создания редактора Markdown с окном предварительного просмотра, где в одном окне вы можете редактировать ваш код уценки, мгновенно видя результат в другом окне. Вы также можете свободно менять расположение окон. С помощью Golden Layout и других расширений WebSharper мы можем воплотить эту идею в жизнь менее чем в 120 строк кода!

Нулевой шаг — приобретите WebSharper!

Страница Загрузка WebSharper предоставляет средства для использования WebSharper с Visual Studio и Xamarin Studio. Я рекомендую вам загрузить бета-версию 4.x (кодовое название Zafir), так как в ней есть несколько интересных новых функций по сравнению с предыдущей версией. Если вы используете Visual Studio, закройте все его запущенные экземпляры, запустите загруженный файл .vsix, и WebSharper должен быть готов для вас при следующем открытии Visual Studio.

Если вам не хочется устанавливать WebSharper на свой компьютер, у вас все еще есть try.websharper.com, где вы можете попробовать WebSharper и любое из его бесплатных расширений. Почти все, что мы делаем в этой статье, будет одинаково работать в Visual Studio и Try WebSharper.

Создание проекта

После установки WebSharper вы должны увидеть пару новых шаблонов проектов. В этом руководстве мы будем работать с приложением под названием Одностраничное приложение WebSharper 4. Я называю свой проект «MDEditor». Чтобы начать с чистого листа, давайте удалим пример кода и сохраним минимум перед тем, как приступить к написанию нашего приложения. После очистки ваши файлы Client.fs и index.html должны выглядеть так:

Попробуйте запустить проект прямо сейчас! Пустая белая страница — хороший знак.

Возможно, вы получите ошибку выполнения при попытке запустить приложение. Не паникуйте! Наиболее вероятной причиной этого является то, что проект ссылается на версию FSharp.Core, которая не установлена ​​на вашем компьютере. Мы можем решить эту проблему, выполнив два простых шага:
1. Щелкните проект правой кнопкой мыши, выберите Свойства и выберите подходящую Целевую среду выполнения F#. Сохраните настройки проекта.
2. Откройте файл Web.config вашего проекта. Там будет тег bindingRedirect. Обновите его до haveoldVersion="0.0.0.0-4.4.1.0" и установите newVersion на версию FSharp.Core, которую вы выбрали на первом шаге.
Если это по-прежнему не решит проблему, не стесняйтесь спрашивать на Форумах WebSharper или StackOverflow.

Добавление расширений

Расширения WebSharper можно загрузить с помощью Диспетчера пакетов NuGet. Щелкните проект правой кнопкой мыши, выберите Управление пакетами NuGet…, выберите страницу Обзор и установите флажок Включить предварительную версию. Мы собираемся использовать эти три расширения: Zafir.GoldenLayout, Zafir.Remarkable и Zafir.Google.CodePrettify. Последние два необходимы для работы предварительного просмотра уценки и подсветки кода. Не буду подробно останавливаться на их использовании, подробнее о них можно прочитать здесь. Теперь мы можем добавить эти строки в начало файла Client.fs:

open WebSharper.GoldenLayout
open WebSharper.Google.CodePrettify
open WebSharper.Remarkable

Использование золотого макета

Инициализация Golden Layout сама по себе требует нескольких шагов.

Во-первых, мы должны описать дерево элементов контента. Эти элементы контента можно разделить на две большие роли: те, которые имеют фактическое содержание (Component и ReactComponent), и те, которые объединяют другие элементы (Row , Column, Stack).

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

Последний важный, но простой шаг — инициализация менеджера компоновки. Это занимает ровно одну строку кода.

Что насчет CSS?

Хороший вопрос! Как и многие другие библиотеки JavaScript, Golden Layout поставляется с собственными файлами CSS, чтобы он выглядел красиво. Нет, мы не собираемся включать их в наш index.html. Разработчик WebSharper знает лучше. В нашем распоряжении есть атрибут Require , который служит для включения ресурсов, поставляемых с расширениями.

В случае с Golden Layout мы должны включить базовый CSS, затем либо светлую тему, либо темную тему. Все это можно найти в пространстве имен WebSharper.GoldenLayout.Resources. Имейте в виду, что порядок атрибутов Require действительно учитывается, поэтому базовый CSS также должен идти первым в нашем коде. Вот код:

Сделанный! Таблицы стилей Golden Layout теперь будут автоматически добавляться на наш сайт.

Компоненты

Как я уже говорил ранее, ComponentName компонента больше похоже на тип компонента, поэтому давайте посмотрим, с каким типом компонентов мы хотим работать:

Нам понадобятся два типа компонентов: один, который содержит ввод текста, и один, который показывает предварительный просмотр этого ввода. Давайте поместим следующий код внутри нашего module Client = перед функцией Main ():

Теперь, если вы случайно ознакомились с оригинальной документацией Golden Layout (или уже знакомы с ней), ваше ощущение, что в этом фрагменте что-то не так, верно. Что делает этот ItemFactory.CreateComponent?

<technical rant>
В исходной библиотеке JavaScript к объекту конфигурации Content Item's добавлялись различные специальные поля в зависимости от типа элемента. Поскольку это не очень хорошо работает в среде со статическими типами, расширение WebSharper использует несколько иной подход:

  1. У вас есть структура Item со всеми общими свойствами Content Items.
  2. У вас есть разные структуры конфигурации для каждого типа со своими особыми свойствами.
  3. Вы комбинируете их с методом класса ItemFactory, чтобы получить полную конфигурацию Content Item.

Результат будет иметь тип с именем GeneralItemConfig.

Обратите внимание, что GeneralItemConfig не предназначен для создания объектов конфигурации. Он используется только в том случае, если исходная библиотека возвращает или передает конфигурацию Content Item в качестве параметра. Подробнее об этом читайте в официальной документации расширения.
</technical rant>

Хорошо, отбросим технические вопросы, давайте немного взглянем на наш код! Как я и обещал, мы дали нашим компонентам их имена, которые мы будем использовать для добавления к ним содержимого на одном из следующих нескольких шагов. Мы также установили их Title и отключили их кнопку закрытия. Закрытие окна ввода или предварительного просмотра было бы несколько контрпродуктивным, поскольку мы не реализуем никакого способа их повторного открытия, поэтому лучшее, что мы можем сделать, это по крайней мере не дать пользователю выстрелить себе в ногу. (В любом случае мы не работаем над новым стандартом C++.)

Но где мы размещаем компоненты?

Опять же, хороший вопрос! Я вижу, что вы внимательно следите за статьей. Вещь, в которую мы собираемся поместить компоненты, называется "диспетчер компоновки" и является самым важным членом семейства классов Golden Layout. Если вы уже знакомы с API исходной библиотеки JavaScript, у нас для вас хорошие новости: эта работает точно так же!

Класс называется GoldenLayout и ему требуется объект конфигурации Layout и необязательный параметр Dom.Element или JQuery для создания макета. Поведение по умолчанию заключается в том, что ваш макет будет добавлен к body HTML-страницы, что совершенно нормально в нашем случае. Кстати.

Давайте поместим этот код в нашу функцию Main ():

Здесь у нас есть класс GoldenLayout (наш менеджер компоновки), получающий класс конфигурации Layout, у которого установлено свойство Content. Это «дерево элементов контента», о котором я рассказывал вам парой абзацев ранее. Это свойство Content является корнем этого дерева и ожидает массив элементов контента. Но как это сделает дерево? Что ж, как вы можете видеть в этом фрагменте, элементы контента также имеют подобное свойство, поэтому элементы контента также могут иметь другие элементы контента как детей, который превращает это в дерево. Если подумать, что это за CreateRow?

Строка — это еще один тип элемента контента, предназначенный для агрегирования других. С помощью «строки» мы указываем Golden Layout отображать содержимое вертикально, бок о бок. Конечно, когда макет виден пользователю, он может свободно выравнивать эти макеты. Мы делаем этот пункт невозможным для закрытия тоже по той же причине, что и раньше.

Регистрация компонентов

Ранее я говорил, что регистрация компонентов означает «присоединение функции-создателя к каждому значению ComponentName». Но что это вообще значит?

У нас есть эта функция:

GoldenLayout.RegisterComponent(
    componentName: string,
    componentCreator: (Container * obj -> Unit)
): unit

И подпись componentCreator должна выглядеть примерно так:

componentCreator((container: Container) * (state: obj)): unit

Класс Container описан в оригинальной документации и предназначен для того, чтобы вы могли получить HTML-элемент-оболочку, который будет содержать содержимое вашего компонента. state предназначен для переноса произвольного состояния, назначенного компоненту. Это необязательный параметр структур Component и ReactComponentconfig, поэтому вы не видели его раньше, потому что мы его не назначали, а мы не назначали, потому что в данном случае он нам не нужен. Если бы наш редактор уценки обрабатывал несколько пар окон редактора и предварительного просмотра одновременно, состояние, вероятно, содержало бы уникальное имя окна редактора, а состояние предварительного просмотра знало бы, какому редактору принадлежит окно предварительного просмотра.

Хорошо, теперь, когда концепция API определена, давайте добавим следующий код к нашей функции Main ():

Скорее всего, вы получите какие-то ошибки, но не беспокойтесь, просто open WebSharper.UI.Next.Html, вот где такие функции, как spanAttr live.

Теперь, для быстрого обзора фрагмента: мы используем функцию RegisterComponent, описанную чуть выше, мы передаем componentName и componentCreator. Как я уже говорил ранее, мы используем только параметр container, поэтому оставляем привязку для state пустой. Давайте пройдемся по двум функциям изнутри.

Две функции создателя почти идентичны. Сначала мы определяем HTML span с уникальным идентификатором и без дочерних элементов, затем мы получаем его Dom через свойство и делаем из него объект JQuery. container.GetElement() возвращает оболочку компонента в виде объекта JQuery, который мы очищаем, а затем добавляем к нему наш свежий span. Поскольку функция должна возвращать значение unit, мы завершаем цепочку свойством JQuery.Ignore.

WebSharper имеет привязку для JQuery из коробки. Как удобно!

Как видите, эти две цепочки вычислений не дают большого содержания нашим компонентам. Но что будет потом?

Побочный квест

Прежде чем мы сможем приступить к выполнению нашей миссии по созданию лучших профессионально выглядящих макетов, есть еще несколько вещей, которые мы должны убрать с нашего пути. А именно настроить Remarkable. Как я уже сказал, я не буду объяснять использование этого расширения, но есть пост в блоге и, вы знаете, документация. Просто вставьте этот код в наши конфигурации Golden Layout перед let Main () =, и все будет готово.

Еще одна задача, не имеющая прямого отношения к работе с макетами, — придание стиля нашим скоро создаваемым элементам ввода и вывода.

Я не очень хочу распространяться об этом, так как полагаю, что любой, кто читает эту статью, имеет хотя бы базовое представление о CSS. Просто добавьте этот код к тегу <header> в нашем файле index.html.

Последний штрих

И на этом мы подошли к последнему шагу в процессе настройки Golden Layout: фактической инициализации, которая (как я уже сказал) представляет собой ровно одну строку кода:

lm.Init()

Поместите это в конец функции Main (), и все готово!
…С этим?

С макетами!

У нас макеты выложены, но они пока ничего не делают. Приготовьтесь удивиться количеству кода, который нам понадобится, чтобы придать поведение нашему приложению!

Получение реакции

Реактивное программирование в F# требует реактивных переменных. В этом случае наша реактивная переменная должна содержать текст, который вводит пользователь, поэтому мы создаем его, добавляя
let codeVar = Var.Create "# Hello world"
line перед нашей функцией Main ().

Начальное значение string заставит codeVar иметь тип Var<string>, что нам и нужно.

Нам также нужно что-то для ввода пользователем, поэтому давайте создадим ввод textarea и поместим в него наш элемент #inarea-wrapper.

Doc.InputArea [attr.id "inarea"] codeVar
|> Doc.RunById "inarea-wrapper"

InputArea нужны атрибуты и Var<string>, который у нас есть (совпадение?), а затем Doc.RunById заменяет содержимое данного элемента на Doc, которое идет в последнем параметре. Вот и все!

А теперь код для отображения превью:

Здесь мы видим div без содержимого (изначально) и с двумя атрибутами: классический идентификатор и… событие viewUpdate?

Что такое View? View похоже на текущее значение реактивной переменной в любой момент времени. Событие viewUpdate принимает View и обратный вызов, который будет вызываться каждый раз при изменении значения представления, и принимает два параметра: исходный элемент и новое значение представления, которое является кодом, пользователь вводит в этом случае.

Тело обратного вызова берет этот code, конвертирует уценку в HTML с remarkable.Render, затем помещает этот HTML-код внутрь этого самого div, затем мы получаем обязательный JQuery.Ignore, и, наконец, идет вызов PR.PrettyPrint(), который делает подсветку синтаксиса в предварительный просмотр, так как Remarkable отвечает только за синтаксический анализ уценки.

Вы удивлены? Количество дополнительных строк кода, необходимых для работы редактора и предварительного просмотра, составило поразительное 16. Или было?

…Сделанный?

Ну типа. Но вы могли заметить, что я не сказал вам, куда положить последние два куска кода, и это именно потому, что в данный момент их некуда положить. Вы можете видеть, что мы используем идентификаторы, которые находятся в зарегистрированных компонентах. Так почему же мы не поместили этот код в registerComponent? Честно говоря, преобразование inputarea в элемент DOM каким-то образом разрывает связь с нашей реактивной переменной. Тогда как насчет того, чтобы поместить его после lm.Init()? Кажется, что инициализация менеджера компоновки занимает «довольно много» времени, поэтому она не будет завершена к тому времени, когда мы захотим добавить к ней элементы. Тогда когда мы должны вызывать этот код? Именно когда макет инициализируется!

Сделанный!

Golden Layout, поскольку это хорошая библиотека, имеет все виды событий для большинства своих классов. Одним из них является событие "initialised" класса GoldenLayout, которое является нашим менеджером компоновки. Бьюсь об заклад, большинство из вас привыкли подписываться на события JavaScript с помощью strings, но расширение WebSharper пошло еще дальше! У нас есть enum для этих особых событий! Ого! И, конечно же, функции обработки подписки на события для всех них перегружены. Все, что нам нужно сделать сейчас, это поместить наш предыдущий код в обработчик событий. Вставьте этот код прямо перед нашей строкой lm.Init():

Быстрая молитва, Ctrl+F5 в Visual Studio, и мы должны увидеть наш потрясающий реактивный редактор разметки с изменяемыми размерами и перемещаемыми макетами!

Дополнительные примеры

Если вы хотите увидеть больше примеров работы с Golden Layout от WebSharper, вы можете попробовать этот фрагмент онлайн:

Редактор Markdown с предварительным просмотром (да, тот, который мы только что сделали)
Создавать вкладки динамически
Создавать вкладки статически

Документацию расширения можно найти здесь.

Послесловие

Надеюсь, вам понравилась эта статья о расширении Golden Layout от WebSharper! Если это так, не стесняйтесь рекомендовать, чирикать или делать татуировку, чтобы вы могли показать статью большему количеству людей! Спасибо, что прочитали и поделились!

Увидимся!