brut 0.0.20 → 0.0.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitignore +24 -3
- data/.nvim.lua +1 -0
- data/Dockerfile.dx +12 -3
- data/Gemfile.lock +9 -7
- data/README.md +0 -7
- data/Rakefile +6 -4
- data/bin/dev +20 -0
- data/bin/docs +27 -0
- data/bin/setup +47 -1
- data/brut-css/.nvim.lua +1 -0
- data/brut-css/README.md +28 -0
- data/brut-css/bin/build +31 -0
- data/brut-css/bin/dev +1 -0
- data/brut-css/bin/docs +15 -0
- data/brut-css/bin/setup +5 -0
- data/brut-css/config/media-queries-all.css +15 -0
- data/brut-css/config/media-queries-minimal.css +5 -0
- data/brut-css/config/postcss.config.cjs +7 -0
- data/brut-css/config/pseudo-classes-all.css +9 -0
- data/brut-css/dx +1 -0
- data/brut-css/package-lock.json +3217 -0
- data/brut-css/package.json +36 -0
- data/brut-css/src/css/appearance.css +145 -0
- data/brut-css/src/css/border.css +522 -0
- data/brut-css/src/css/colors.css +3502 -0
- data/brut-css/src/css/dimensions.css +548 -0
- data/brut-css/src/css/flex.css +179 -0
- data/brut-css/src/css/index.css +13 -0
- data/brut-css/src/css/layout.css +120 -0
- data/brut-css/src/css/list.css +41 -0
- data/brut-css/src/css/positioning.css +354 -0
- data/brut-css/src/css/properties/colors.css +455 -0
- data/brut-css/src/css/properties/index.css +3 -0
- data/brut-css/src/css/properties/spacing.css +140 -0
- data/brut-css/src/css/properties/typography.css +224 -0
- data/brut-css/src/css/reset.css +107 -0
- data/brut-css/src/css/spacing.css +585 -0
- data/brut-css/src/css/typography.css +519 -0
- data/brut-css/src/css/utils.css +104 -0
- data/brut-css/src/docs/1_getting-started/1_overview.md +46 -0
- data/brut-css/src/docs/1_getting-started/2_installation.md +25 -0
- data/brut-css/src/docs/1_getting-started/3_core-concepts.md +75 -0
- data/brut-css/src/docs/1_getting-started/4_simple-example.md +132 -0
- data/brut-css/src/docs/1_getting-started/page.html.ejs +10 -0
- data/brut-css/src/docs/2_properties/page.html.ejs +71 -0
- data/brut-css/src/docs/3_classes/color-demo.html.ejs +31 -0
- data/brut-css/src/docs/3_classes/page.html.ejs +87 -0
- data/brut-css/src/docs/4_customization/1_design-system.md +36 -0
- data/brut-css/src/docs/4_customization/2_breakpoints.md +75 -0
- data/brut-css/src/docs/4_customization/3_pseudo-classes.md +74 -0
- data/brut-css/src/docs/4_customization/4_advanced-configuration.md +40 -0
- data/brut-css/src/docs/4_customization/page.html.ejs +10 -0
- data/brut-css/src/docs/docs.css +98 -0
- data/brut-css/src/docs/includes/body-and-header.html.ejs +30 -0
- data/brut-css/src/docs/includes/footer-and-rest.html.ejs +9 -0
- data/brut-css/src/docs/includes/head.html.ejs +5 -0
- data/brut-css/src/docs/includes/nav.html.ejs +10 -0
- data/brut-css/src/docs/index.html.ejs +32 -0
- data/brut-css/src/docs/prism-twilight.min.css +1 -0
- data/brut-css/src/js/Logger.js +71 -0
- data/brut-css/src/js/build.js +111 -0
- data/brut-css/src/js/cli/CLIArgError.js +7 -0
- data/brut-css/src/js/cli/Debug.js +27 -0
- data/brut-css/src/js/cli/DocsDir.js +16 -0
- data/brut-css/src/js/cli/DocsTemplateSourceDir.js +16 -0
- data/brut-css/src/js/cli/InputFile.js +31 -0
- data/brut-css/src/js/cli/MediaQueryConfigFile.js +10 -0
- data/brut-css/src/js/cli/OutputFile.js +22 -0
- data/brut-css/src/js/cli/ParsedArg.js +17 -0
- data/brut-css/src/js/cli/PathToBrutCSSRoot.js +19 -0
- data/brut-css/src/js/cli/PseudoClassConfigFile.js +11 -0
- data/brut-css/src/js/cli.js +108 -0
- data/brut-css/src/js/docGenerator.js +467 -0
- data/brut-css/src/js/mediaQueryConfigParser.js +98 -0
- data/brut-css/src/js/post-css-plugins/addMediaQueriesPlugin.js +49 -0
- data/brut-css/src/js/post-css-plugins/addPseudoClassesPlugin.js +42 -0
- data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin/Category.js +9 -0
- data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin/DocState.js +185 -0
- data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin/Documentable.js +8 -0
- data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin/Group.js +7 -0
- data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin/ParsedComment.js +73 -0
- data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin/Property.js +9 -0
- data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin/PropertyCategory.js +4 -0
- data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin/PropertyGroup.js +8 -0
- data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin/Rule.js +12 -0
- data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin/RuleCategory.js +4 -0
- data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin/RuleGroup.js +8 -0
- data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin/SeeRef.js +5 -0
- data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin/SeeURL.js +9 -0
- data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin.js +49 -0
- data/brut-css/src/js/post-css-plugins/generateRootCustomPropertiesPlugin.js +45 -0
- data/brut-css/src/js/pseudoClassConfigParser.js +145 -0
- data/brut-js/.projections.json +10 -0
- data/brut-js/README.md +118 -0
- data/brut-js/bin/build +10 -0
- data/brut-js/bin/ci +5 -0
- data/brut-js/bin/setup +5 -0
- data/brut-js/docs/README.md +8 -0
- data/brut-js/docs/jsdoc-plugins/customElementTag.js +8 -0
- data/brut-js/docs/jsdoc-theme/publish.js +692 -0
- data/brut-js/docs/jsdoc-theme/static/scripts/linenumber.js +25 -0
- data/brut-js/docs/jsdoc-theme/static/scripts/prettify/Apache-License-2.0.txt +202 -0
- data/brut-js/docs/jsdoc-theme/static/scripts/prettify/lang-css.js +2 -0
- data/brut-js/docs/jsdoc-theme/static/scripts/prettify/prettify.js +28 -0
- data/brut-js/docs/jsdoc-theme/static/styles/jsdoc-default.css +327 -0
- data/brut-js/docs/jsdoc-theme/static/styles/prettify-jsdoc.css +111 -0
- data/brut-js/docs/jsdoc-theme/static/styles/prettify-tomorrow.css +132 -0
- data/brut-js/docs/jsdoc-theme/tmpl/augments.tmpl +10 -0
- data/brut-js/docs/jsdoc-theme/tmpl/container.tmpl +199 -0
- data/brut-js/docs/jsdoc-theme/tmpl/details.tmpl +143 -0
- data/brut-js/docs/jsdoc-theme/tmpl/example.tmpl +2 -0
- data/brut-js/docs/jsdoc-theme/tmpl/examples.tmpl +13 -0
- data/brut-js/docs/jsdoc-theme/tmpl/exceptions.tmpl +32 -0
- data/brut-js/docs/jsdoc-theme/tmpl/layout.tmpl +38 -0
- data/brut-js/docs/jsdoc-theme/tmpl/mainpage.tmpl +14 -0
- data/brut-js/docs/jsdoc-theme/tmpl/members.tmpl +38 -0
- data/brut-js/docs/jsdoc-theme/tmpl/method.tmpl +131 -0
- data/brut-js/docs/jsdoc-theme/tmpl/modifies.tmpl +14 -0
- data/brut-js/docs/jsdoc-theme/tmpl/params.tmpl +131 -0
- data/brut-js/docs/jsdoc-theme/tmpl/properties.tmpl +108 -0
- data/brut-js/docs/jsdoc-theme/tmpl/returns.tmpl +19 -0
- data/brut-js/docs/jsdoc-theme/tmpl/source.tmpl +8 -0
- data/brut-js/docs/jsdoc-theme/tmpl/tutorial.tmpl +19 -0
- data/brut-js/docs/jsdoc-theme/tmpl/type.tmpl +7 -0
- data/brut-js/docs/jsdoc.config.json +23 -0
- data/brut-js/docs/package-lock.json +343 -0
- data/brut-js/docs/package.json +7 -0
- data/brut-js/package-lock.json +2171 -0
- data/brut-js/package.json +32 -0
- data/brut-js/specs/AjaxSubmit.spec.js +256 -0
- data/brut-js/specs/Autosubmit.spec.js +127 -0
- data/brut-js/specs/ConfirmSubmit.spec.js +193 -0
- data/brut-js/specs/ConstraintViolationMessage.spec.js +33 -0
- data/brut-js/specs/ConstraintViolationMessages.spec.js +29 -0
- data/brut-js/specs/CopyToClipboard.spec.js +35 -0
- data/brut-js/specs/Form.spec.js +181 -0
- data/brut-js/specs/I18nTranslation.spec.js +19 -0
- data/brut-js/specs/LocaleDetection.spec.js +22 -0
- data/brut-js/specs/Message.spec.js +15 -0
- data/brut-js/specs/SpecHelper.js +23 -0
- data/brut-js/specs/Tabs.spec.js +41 -0
- data/brut-js/specs/config/asset_metadata.json +7 -0
- data/brut-js/src/AjaxSubmit.js +384 -0
- data/brut-js/src/Autosubmit.js +63 -0
- data/brut-js/src/BaseCustomElement.js +261 -0
- data/brut-js/src/ConfirmSubmit.js +116 -0
- data/brut-js/src/ConfirmationDialog.js +143 -0
- data/brut-js/src/ConstraintViolationMessage.js +125 -0
- data/brut-js/src/ConstraintViolationMessages.js +98 -0
- data/brut-js/src/CopyToClipboard.js +96 -0
- data/brut-js/src/Form.js +151 -0
- data/brut-js/src/I18nTranslation.js +61 -0
- data/brut-js/src/LocaleDetection.js +117 -0
- data/brut-js/src/Logger.js +90 -0
- data/brut-js/src/Message.js +56 -0
- data/brut-js/src/RichString.js +113 -0
- data/brut-js/src/Tabs.js +168 -0
- data/brut-js/src/Tracing.js +247 -0
- data/brut-js/src/appForTestingOnly.js +15 -0
- data/brut-js/src/index.js +130 -0
- data/brut-js/src/testing/AssetMetadata.js +35 -0
- data/brut-js/src/testing/AssetMetadataLoader.js +25 -0
- data/brut-js/src/testing/CustomElementTest.js +235 -0
- data/brut-js/src/testing/DOMCreator.js +45 -0
- data/brut-js/src/testing/index.js +48 -0
- data/brutrb.com/.vitepress/config.mjs +106 -0
- data/brutrb.com/.vitepress/plugins/jsdocLinker.js +34 -0
- data/brutrb.com/.vitepress/plugins/rdocLinker.js +18 -0
- data/brutrb.com/.vitepress/theme/custom.css +7 -0
- data/brutrb.com/.vitepress/theme/index.js +18 -0
- data/brutrb.com/.vitepress/theme/style.css +149 -0
- data/brutrb.com/ai.md +68 -0
- data/brutrb.com/assets.md +138 -0
- data/brutrb.com/bin/build +5 -0
- data/brutrb.com/bin/deploy +7 -0
- data/brutrb.com/bin/dev +5 -0
- data/brutrb.com/bin/setup +5 -0
- data/brutrb.com/brut-js.md +117 -0
- data/brutrb.com/business-logic.md +55 -0
- data/brutrb.com/cli.md +278 -0
- data/brutrb.com/components.md +243 -0
- data/brutrb.com/configuration.md +257 -0
- data/brutrb.com/css.md +103 -0
- data/brutrb.com/custom-element-tests.md +149 -0
- data/brutrb.com/database-access.md +201 -0
- data/brutrb.com/database-schema.md +312 -0
- data/brutrb.com/deployment.md +66 -0
- data/brutrb.com/dev-environment.md +179 -0
- data/brutrb.com/doc-conventions.md +39 -0
- data/brutrb.com/end-to-end-tests.md +174 -0
- data/brutrb.com/flash-and-session.md +224 -0
- data/brutrb.com/forms.md +866 -0
- data/brutrb.com/getting-started.md +66 -0
- data/brutrb.com/handlers.md +153 -0
- data/brutrb.com/hooks.md +178 -0
- data/brutrb.com/i18n.md +188 -0
- data/brutrb.com/images/Makefile +10 -0
- data/brutrb.com/images/dev-env-overview.dot +54 -0
- data/brutrb.com/images/dev-env-overview.png +0 -0
- data/brutrb.com/images/dev-env-protocol.dot +37 -0
- data/brutrb.com/images/dev-env-protocol.png +0 -0
- data/brutrb.com/images/logo-300.png +0 -0
- data/brutrb.com/images/logo.png +0 -0
- data/brutrb.com/images/overview.graffle +0 -0
- data/brutrb.com/images/overview.png +0 -0
- data/brutrb.com/images/spa.dot +19 -0
- data/brutrb.com/images/spa.png +0 -0
- data/brutrb.com/images/workspace-protocol.dot +44 -0
- data/brutrb.com/images/workspace-protocol.png +0 -0
- data/brutrb.com/index.md +36 -0
- data/brutrb.com/instrumentation.md +183 -0
- data/brutrb.com/javascript.md +122 -0
- data/brutrb.com/jobs.md +14 -0
- data/{doc-src → brutrb.com}/keyword-injection.md +122 -68
- data/brutrb.com/markdown-examples.md +85 -0
- data/brutrb.com/middleware.md +80 -0
- data/brutrb.com/not-released.md +5 -0
- data/brutrb.com/overview.md +404 -0
- data/brutrb.com/package-lock.json +2404 -0
- data/brutrb.com/package.json +11 -0
- data/brutrb.com/pages.md +378 -0
- data/brutrb.com/public/images/logo-300.png +0 -0
- data/brutrb.com/public/images/logo.png +0 -0
- data/brutrb.com/routes.md +215 -0
- data/brutrb.com/security.md +105 -0
- data/brutrb.com/seed-data.md +63 -0
- data/brutrb.com/space-time-continuum.md +85 -0
- data/brutrb.com/tutorial.md +3 -0
- data/brutrb.com/unit-tests.md +148 -0
- data/docker-compose.dx.yml +6 -3
- data/docs/404.html +21 -0
- data/docs/CNAME +1 -0
- data/docs/ai.html +24 -0
- data/docs/api/Brut/BackEnd/SeedData.html +493 -0
- data/docs/api/Brut/BackEnd/Sidekiq/Middlewares/Server/FlushSpans.html +214 -0
- data/docs/api/Brut/BackEnd/Sidekiq/Middlewares/Server.html +125 -0
- data/docs/api/Brut/BackEnd/Sidekiq/Middlewares.html +125 -0
- data/docs/api/Brut/BackEnd/Sidekiq.html +125 -0
- data/docs/api/Brut/BackEnd/Validators/FormValidator.html +414 -0
- data/docs/api/Brut/BackEnd/Validators.html +128 -0
- data/docs/api/Brut/BackEnd.html +132 -0
- data/docs/api/Brut/CLI/App.html +1576 -0
- data/docs/api/Brut/CLI/AppRunner.html +491 -0
- data/docs/api/Brut/CLI/Apps/BuildAssets/All.html +264 -0
- data/docs/api/Brut/CLI/Apps/BuildAssets/CSS.html +306 -0
- data/docs/api/Brut/CLI/Apps/BuildAssets/Images.html +262 -0
- data/docs/api/Brut/CLI/Apps/BuildAssets/JS.html +314 -0
- data/docs/api/Brut/CLI/Apps/BuildAssets.html +183 -0
- data/docs/api/Brut/CLI/Apps/DB/Create.html +365 -0
- data/docs/api/Brut/CLI/Apps/DB/Drop.html +357 -0
- data/docs/api/Brut/CLI/Apps/DB/Migrate.html +383 -0
- data/docs/api/Brut/CLI/Apps/DB/NewMigration.html +335 -0
- data/docs/api/Brut/CLI/Apps/DB/Rebuild.html +329 -0
- data/docs/api/Brut/CLI/Apps/DB/Seed.html +347 -0
- data/docs/api/Brut/CLI/Apps/DB/Status.html +383 -0
- data/docs/api/Brut/CLI/Apps/DB.html +183 -0
- data/docs/api/Brut/CLI/Apps/Scaffold/Action/Route.html +303 -0
- data/docs/api/Brut/CLI/Apps/Scaffold/Action.html +512 -0
- data/docs/api/Brut/CLI/Apps/Scaffold/Component.html +398 -0
- data/docs/api/Brut/CLI/Apps/Scaffold/CustomElementTest.html +374 -0
- data/docs/api/Brut/CLI/Apps/Scaffold/E2ETest.html +410 -0
- data/docs/api/Brut/CLI/Apps/Scaffold/Form.html +262 -0
- data/docs/api/Brut/CLI/Apps/Scaffold/Page/Route.html +303 -0
- data/docs/api/Brut/CLI/Apps/Scaffold/Page.html +480 -0
- data/docs/api/Brut/CLI/Apps/Scaffold/RoutesEditor.html +450 -0
- data/docs/api/Brut/CLI/Apps/Scaffold/Test.html +380 -0
- data/docs/api/Brut/CLI/Apps/Scaffold.html +253 -0
- data/docs/api/Brut/CLI/Apps/Test/Audit.html +464 -0
- data/docs/api/Brut/CLI/Apps/Test/E2e.html +407 -0
- data/docs/api/Brut/CLI/Apps/Test/JS.html +262 -0
- data/docs/api/Brut/CLI/Apps/Test/Run.html +578 -0
- data/docs/api/Brut/CLI/Apps/Test.html +253 -0
- data/docs/api/Brut/CLI/Apps.html +125 -0
- data/docs/api/Brut/CLI/Command.html +2342 -0
- data/docs/api/Brut/CLI/Error.html +139 -0
- data/docs/api/Brut/CLI/ExecutionResults/Result.html +664 -0
- data/docs/api/Brut/CLI/ExecutionResults.html +675 -0
- data/docs/api/Brut/CLI/Executor.html +430 -0
- data/docs/api/Brut/CLI/InvalidOption.html +245 -0
- data/docs/api/Brut/CLI/Options.html +753 -0
- data/docs/api/Brut/CLI/Output.html +699 -0
- data/docs/api/Brut/CLI/SystemExecError.html +451 -0
- data/docs/api/Brut/CLI.html +263 -0
- data/docs/api/Brut/FactoryBot.html +225 -0
- data/docs/api/Brut/Framework/App.html +1097 -0
- data/docs/api/Brut/Framework/Config.html +1045 -0
- data/docs/api/Brut/Framework/Container.html +1379 -0
- data/docs/api/Brut/Framework/Error.html +140 -0
- data/docs/api/Brut/Framework/Errors/AbstractMethod.html +144 -0
- data/docs/api/Brut/Framework/Errors/Bug.html +234 -0
- data/docs/api/Brut/Framework/Errors/MissingConfiguration.html +257 -0
- data/docs/api/Brut/Framework/Errors/MissingParameter.html +273 -0
- data/docs/api/Brut/Framework/Errors/NoClassForPath.html +471 -0
- data/docs/api/Brut/Framework/Errors/NotFound.html +308 -0
- data/docs/api/Brut/Framework/Errors/NotImplemented.html +234 -0
- data/docs/api/Brut/Framework/Errors.html +328 -0
- data/docs/api/Brut/Framework/FussyTypeEnforcement.html +392 -0
- data/docs/api/Brut/Framework/MCP.html +861 -0
- data/docs/api/Brut/Framework/ProjectEnvironment.html +648 -0
- data/docs/api/Brut/Framework.html +129 -0
- data/docs/api/Brut/FrontEnd/AssetPathResolver.html +317 -0
- data/docs/api/Brut/FrontEnd/Component/Helpers.html +326 -0
- data/docs/api/Brut/FrontEnd/Component.html +365 -0
- data/docs/api/Brut/FrontEnd/Components/ConstraintViolations.html +470 -0
- data/docs/api/Brut/FrontEnd/Components/FormTag.html +518 -0
- data/docs/api/Brut/FrontEnd/Components/I18nTranslations.html +317 -0
- data/docs/api/Brut/FrontEnd/Components/Input.html +195 -0
- data/docs/api/Brut/FrontEnd/Components/Inputs/CsrfToken.html +339 -0
- data/docs/api/Brut/FrontEnd/Components/Inputs/InputTag.html +660 -0
- data/docs/api/Brut/FrontEnd/Components/Inputs/RadioButton.html +417 -0
- data/docs/api/Brut/FrontEnd/Components/Inputs/SelectTagWithOptions.html +918 -0
- data/docs/api/Brut/FrontEnd/Components/Inputs/TextareaTag.html +651 -0
- data/docs/api/Brut/FrontEnd/Components/Inputs.html +125 -0
- data/docs/api/Brut/FrontEnd/Components/LocaleDetection.html +367 -0
- data/docs/api/Brut/FrontEnd/Components/PageIdentifier.html +336 -0
- data/docs/api/Brut/FrontEnd/Components/TimeTag.html +655 -0
- data/docs/api/Brut/FrontEnd/Components/Traceparent.html +352 -0
- data/docs/api/Brut/FrontEnd/Components.html +135 -0
- data/docs/api/Brut/FrontEnd/Download.html +467 -0
- data/docs/api/Brut/FrontEnd/Flash.html +1150 -0
- data/docs/api/Brut/FrontEnd/Form.html +1157 -0
- data/docs/api/Brut/FrontEnd/Forms/ConstraintViolation.html +634 -0
- data/docs/api/Brut/FrontEnd/Forms/Input.html +615 -0
- data/docs/api/Brut/FrontEnd/Forms/InputDeclarations.html +547 -0
- data/docs/api/Brut/FrontEnd/Forms/InputDefinition.html +1318 -0
- data/docs/api/Brut/FrontEnd/Forms/RadioButtonGroupInput.html +609 -0
- data/docs/api/Brut/FrontEnd/Forms/RadioButtonGroupInputDefinition.html +587 -0
- data/docs/api/Brut/FrontEnd/Forms/SelectInput.html +613 -0
- data/docs/api/Brut/FrontEnd/Forms/SelectInputDefinition.html +582 -0
- data/docs/api/Brut/FrontEnd/Forms/ValidityState.html +609 -0
- data/docs/api/Brut/FrontEnd/Forms.html +127 -0
- data/docs/api/Brut/FrontEnd/GenericResponse.html +377 -0
- data/docs/api/Brut/FrontEnd/Handler.html +442 -0
- data/docs/api/Brut/FrontEnd/Handlers/CspReportingHandler.html +318 -0
- data/docs/api/Brut/FrontEnd/Handlers/InstrumentationHandler/TraceParent.html +336 -0
- data/docs/api/Brut/FrontEnd/Handlers/InstrumentationHandler.html +399 -0
- data/docs/api/Brut/FrontEnd/Handlers/LocaleDetectionHandler.html +354 -0
- data/docs/api/Brut/FrontEnd/Handlers/MissingHandler/Form.html +151 -0
- data/docs/api/Brut/FrontEnd/Handlers/MissingHandler.html +315 -0
- data/docs/api/Brut/FrontEnd/Handlers.html +125 -0
- data/docs/api/Brut/FrontEnd/HandlingResults.html +339 -0
- data/docs/api/Brut/FrontEnd/HttpMethod.html +661 -0
- data/docs/api/Brut/FrontEnd/HttpStatus.html +496 -0
- data/docs/api/Brut/FrontEnd/InlineSvgLocator.html +284 -0
- data/docs/api/Brut/FrontEnd/Layout.html +318 -0
- data/docs/api/Brut/FrontEnd/Middleware.html +135 -0
- data/docs/api/Brut/FrontEnd/Middlewares/AnnotateBrutOwnedPaths.html +288 -0
- data/docs/api/Brut/FrontEnd/Middlewares/Favicon.html +292 -0
- data/docs/api/Brut/FrontEnd/Middlewares/OpenTelemetrySpan.html +324 -0
- data/docs/api/Brut/FrontEnd/Middlewares/ReloadApp.html +372 -0
- data/docs/api/Brut/FrontEnd/Middlewares.html +125 -0
- data/docs/api/Brut/FrontEnd/Page.html +773 -0
- data/docs/api/Brut/FrontEnd/Pages/MissingPage.html +797 -0
- data/docs/api/Brut/FrontEnd/Pages.html +125 -0
- data/docs/api/Brut/FrontEnd/RequestContext.html +1312 -0
- data/docs/api/Brut/FrontEnd/RouteHook.html +424 -0
- data/docs/api/Brut/FrontEnd/RouteHooks/AgeFlash.html +242 -0
- data/docs/api/Brut/FrontEnd/RouteHooks/CSPNoInlineScripts.html +249 -0
- data/docs/api/Brut/FrontEnd/RouteHooks/CSPNoInlineStylesOrScripts/ReportOnly.html +264 -0
- data/docs/api/Brut/FrontEnd/RouteHooks/CSPNoInlineStylesOrScripts.html +261 -0
- data/docs/api/Brut/FrontEnd/RouteHooks/LocaleDetection.html +284 -0
- data/docs/api/Brut/FrontEnd/RouteHooks/SetupRequestContext.html +252 -0
- data/docs/api/Brut/FrontEnd/RouteHooks.html +115 -0
- data/docs/api/Brut/FrontEnd/Routing/FormHandlerRoute.html +227 -0
- data/docs/api/Brut/FrontEnd/Routing/FormRoute.html +305 -0
- data/docs/api/Brut/FrontEnd/Routing/MissingForm.html +324 -0
- data/docs/api/Brut/FrontEnd/Routing/MissingHandler.html +319 -0
- data/docs/api/Brut/FrontEnd/Routing/MissingPage.html +315 -0
- data/docs/api/Brut/FrontEnd/Routing/MissingPath.html +315 -0
- data/docs/api/Brut/FrontEnd/Routing/PageRoute.html +327 -0
- data/docs/api/Brut/FrontEnd/Routing/Route.html +761 -0
- data/docs/api/Brut/FrontEnd/Routing.html +927 -0
- data/docs/api/Brut/FrontEnd/Session.html +1195 -0
- data/docs/api/Brut/FrontEnd.html +134 -0
- data/docs/api/Brut/I18n/BaseMethods.html +931 -0
- data/docs/api/Brut/I18n/ForBackEnd.html +302 -0
- data/docs/api/Brut/I18n/ForCLI.html +302 -0
- data/docs/api/Brut/I18n/ForHTML.html +296 -0
- data/docs/api/Brut/I18n/HTTPAcceptLanguage/AlwaysEnglish.html +316 -0
- data/docs/api/Brut/I18n/HTTPAcceptLanguage.html +930 -0
- data/docs/api/Brut/I18n.html +127 -0
- data/docs/api/Brut/Instrumentation/LoggerSpanExporter.html +435 -0
- data/docs/api/Brut/Instrumentation/OpenTelemetry/NormalizedAttributes.html +286 -0
- data/docs/api/Brut/Instrumentation/OpenTelemetry/Span.html +302 -0
- data/docs/api/Brut/Instrumentation/OpenTelemetry.html +864 -0
- data/docs/api/Brut/Instrumentation.html +126 -0
- data/docs/api/Brut/SinatraHelpers/ClassMethods.html +532 -0
- data/docs/api/Brut/SinatraHelpers.html +281 -0
- data/docs/api/Brut/SpecSupport/ClockSupport.html +383 -0
- data/docs/api/Brut/SpecSupport/ComponentSupport.html +502 -0
- data/docs/api/Brut/SpecSupport/E2ETestServer.html +503 -0
- data/docs/api/Brut/SpecSupport/E2eSupport.html +142 -0
- data/docs/api/Brut/SpecSupport/EnhancedNode.html +403 -0
- data/docs/api/Brut/SpecSupport/FlashSupport.html +278 -0
- data/docs/api/Brut/SpecSupport/GeneralSupport/ClassMethods.html +401 -0
- data/docs/api/Brut/SpecSupport/GeneralSupport.html +195 -0
- data/docs/api/Brut/SpecSupport/HandlerSupport.html +160 -0
- data/docs/api/Brut/SpecSupport/Matchers/HaveConstraintViolation.html +553 -0
- data/docs/api/Brut/SpecSupport/Matchers/HaveHTMLAttribute.html +439 -0
- data/docs/api/Brut/SpecSupport/Matchers.html +125 -0
- data/docs/api/Brut/SpecSupport/RSpecSetup/OptionalSidekiqSupport.html +335 -0
- data/docs/api/Brut/SpecSupport/RSpecSetup.html +602 -0
- data/docs/api/Brut/SpecSupport/SessionSupport.html +196 -0
- data/docs/api/Brut/SpecSupport.html +129 -0
- data/docs/api/Brut.html +225 -0
- data/docs/api/Clock.html +603 -0
- data/docs/api/RichString.html +968 -0
- data/docs/api/SemanticLogger/Appender/Async.html +219 -0
- data/docs/api/Sequel/Extensions/BrutInstrumentation.html +115 -0
- data/docs/api/Sequel/Extensions/BrutMigrations.html +533 -0
- data/docs/api/Sequel/Extensions.html +117 -0
- data/docs/api/Sequel/Plugins/CreatedAt/InstanceMethods.html +105 -0
- data/docs/api/Sequel/Plugins/CreatedAt.html +125 -0
- data/docs/api/Sequel/Plugins/ExternalId/ClassMethods.html +207 -0
- data/docs/api/Sequel/Plugins/ExternalId/InstanceMethods.html +186 -0
- data/docs/api/Sequel/Plugins/ExternalId.html +218 -0
- data/docs/api/Sequel/Plugins/FindBang/ClassMethods.html +202 -0
- data/docs/api/Sequel/Plugins/FindBang.html +125 -0
- data/docs/api/Sequel/Plugins.html +117 -0
- data/docs/api/Sequel.html +117 -0
- data/docs/api/_index.html +1553 -0
- data/docs/api/class_list.html +54 -0
- data/docs/api/css/common.css +1 -0
- data/docs/api/css/full_list.css +58 -0
- data/docs/api/css/style.css +503 -0
- data/docs/api/file.README.html +127 -0
- data/docs/api/file_list.html +59 -0
- data/docs/api/frames.html +22 -0
- data/docs/api/index.html +127 -0
- data/docs/api/js/app.js +344 -0
- data/docs/api/js/full_list.js +242 -0
- data/docs/api/js/jquery.js +4 -0
- data/docs/api/method_list.html +3998 -0
- data/docs/api/top-level-namespace.html +112 -0
- data/docs/assets/ai.md.tZrjP9im.js +1 -0
- data/docs/assets/ai.md.tZrjP9im.lean.js +1 -0
- data/docs/assets/app.D_yaTITQ.js +1 -0
- data/docs/assets/assets.md.D3wunzLx.js +19 -0
- data/docs/assets/assets.md.D3wunzLx.lean.js +1 -0
- data/docs/assets/brut-js.md.o2DAO2s2.js +12 -0
- data/docs/assets/brut-js.md.o2DAO2s2.lean.js +1 -0
- data/docs/assets/business-logic.md.BY4hGy0m.js +1 -0
- data/docs/assets/business-logic.md.BY4hGy0m.lean.js +1 -0
- data/docs/assets/chunks/@localSearchIndexroot.BsN5i0Fi.js +1 -0
- data/docs/assets/chunks/VPLocalSearchBox.B2-ZzyTY.js +8 -0
- data/docs/assets/chunks/framework.1L-BeKqY.js +18 -0
- data/docs/assets/chunks/theme.CfGFVRvE.js +2 -0
- data/docs/assets/cli.md.RmeA2b0i.js +127 -0
- data/docs/assets/cli.md.RmeA2b0i.lean.js +1 -0
- data/docs/assets/components.md.eCttGlN-.js +104 -0
- data/docs/assets/components.md.eCttGlN-.lean.js +1 -0
- data/docs/assets/configuration.md.BRriU0cL.js +78 -0
- data/docs/assets/configuration.md.BRriU0cL.lean.js +1 -0
- data/docs/assets/css.md.DJgj2clw.js +21 -0
- data/docs/assets/css.md.DJgj2clw.lean.js +1 -0
- data/docs/assets/custom-element-tests.md.BrYJQEl3.js +69 -0
- data/docs/assets/custom-element-tests.md.BrYJQEl3.lean.js +1 -0
- data/docs/assets/database-access.md.C7l-Vuvb.js +63 -0
- data/docs/assets/database-access.md.C7l-Vuvb.lean.js +1 -0
- data/docs/assets/database-schema.md.BUjR0VS1.js +63 -0
- data/docs/assets/database-schema.md.BUjR0VS1.lean.js +1 -0
- data/docs/assets/deployment.md.Dbka4OTr.js +1 -0
- data/docs/assets/deployment.md.Dbka4OTr.lean.js +1 -0
- data/docs/assets/dev-env-overview.Gj7NWM8-.png +0 -0
- data/docs/assets/dev-env-protocol.DysDAtnz.png +0 -0
- data/docs/assets/dev-environment.md.BNc8AYiK.js +11 -0
- data/docs/assets/dev-environment.md.BNc8AYiK.lean.js +1 -0
- data/docs/assets/doc-conventions.md.DCfRXXi-.js +1 -0
- data/docs/assets/doc-conventions.md.DCfRXXi-.lean.js +1 -0
- data/docs/assets/end-to-end-tests.md.yfQHC0b5.js +26 -0
- data/docs/assets/end-to-end-tests.md.yfQHC0b5.lean.js +1 -0
- data/docs/assets/flash-and-session.md.BXY8RvT0.js +93 -0
- data/docs/assets/flash-and-session.md.BXY8RvT0.lean.js +1 -0
- data/docs/assets/forms.md.CBTYQ_Cz.js +379 -0
- data/docs/assets/forms.md.CBTYQ_Cz.lean.js +1 -0
- data/docs/assets/getting-started.md.Bz2s1Vjb.js +2 -0
- data/docs/assets/getting-started.md.Bz2s1Vjb.lean.js +1 -0
- data/docs/assets/handlers.md.089DVD3v.js +69 -0
- data/docs/assets/handlers.md.089DVD3v.lean.js +1 -0
- data/docs/assets/hooks.md.C4-moMny.js +80 -0
- data/docs/assets/hooks.md.C4-moMny.lean.js +1 -0
- data/docs/assets/i18n.md.Do9i1qWl.js +23 -0
- data/docs/assets/i18n.md.Do9i1qWl.lean.js +1 -0
- data/docs/assets/index.md.B28EwVpq.js +1 -0
- data/docs/assets/index.md.B28EwVpq.lean.js +1 -0
- data/docs/assets/instrumentation.md.CL6ax7nT.js +35 -0
- data/docs/assets/instrumentation.md.CL6ax7nT.lean.js +1 -0
- data/docs/assets/javascript.md.GWbhRS51.js +31 -0
- data/docs/assets/javascript.md.GWbhRS51.lean.js +1 -0
- data/docs/assets/jobs.md.S-2amAYp.js +1 -0
- data/docs/assets/jobs.md.S-2amAYp.lean.js +1 -0
- data/docs/assets/keyword-injection.md.Dt2tKREs.js +25 -0
- data/docs/assets/keyword-injection.md.Dt2tKREs.lean.js +1 -0
- data/docs/assets/markdown-examples.md.CCFEQO44.js +33 -0
- data/docs/assets/markdown-examples.md.CCFEQO44.lean.js +1 -0
- data/docs/assets/middleware.md.Czz_UlJN.js +20 -0
- data/docs/assets/middleware.md.Czz_UlJN.lean.js +1 -0
- data/docs/assets/not-released.md.BBy28McC.js +1 -0
- data/docs/assets/not-released.md.BBy28McC.lean.js +1 -0
- data/docs/assets/overview.Da81cB9R.png +0 -0
- data/docs/assets/overview.md.CDalkuxV.js +133 -0
- data/docs/assets/overview.md.CDalkuxV.lean.js +1 -0
- data/docs/assets/pages.md.BE3kfOc5.js +122 -0
- data/docs/assets/pages.md.BE3kfOc5.lean.js +1 -0
- data/docs/assets/routes.md.BMM7peut.js +29 -0
- data/docs/assets/routes.md.BMM7peut.lean.js +1 -0
- data/docs/assets/security.md.C668yXCi.js +1 -0
- data/docs/assets/security.md.C668yXCi.lean.js +1 -0
- data/docs/assets/seed-data.md.BvFZlqIk.js +14 -0
- data/docs/assets/seed-data.md.BvFZlqIk.lean.js +1 -0
- data/docs/assets/spa.qejUdp-5.png +0 -0
- data/docs/assets/space-time-continuum.md.KPUIKysQ.js +1 -0
- data/docs/assets/space-time-continuum.md.KPUIKysQ.lean.js +1 -0
- data/docs/assets/style.D73IYGCX.css +1 -0
- data/docs/assets/tutorial.md.BnoGjrdK.js +1 -0
- data/docs/assets/tutorial.md.BnoGjrdK.lean.js +1 -0
- data/docs/assets/unit-tests.md.DUGrnLj5.js +13 -0
- data/docs/assets/unit-tests.md.DUGrnLj5.lean.js +1 -0
- data/docs/assets/workspace-protocol.C0gXsoDb.png +0 -0
- data/docs/assets.html +42 -0
- data/docs/brut-css/brut.css +1 -0
- data/docs/brut-css/brut.max.css +22372 -0
- data/docs/brut-css/classes/appearances.html +783 -0
- data/docs/brut-css/classes/background-colors.html +3529 -0
- data/docs/brut-css/classes/border-colors.html +3529 -0
- data/docs/brut-css/classes/borders.html +2293 -0
- data/docs/brut-css/classes/dimensions.html +2581 -0
- data/docs/brut-css/classes/flex.html +917 -0
- data/docs/brut-css/classes/foreground-colors.html +3261 -0
- data/docs/brut-css/classes/junk-drawer.html +431 -0
- data/docs/brut-css/classes/layout.html +668 -0
- data/docs/brut-css/classes/lists.html +331 -0
- data/docs/brut-css/classes/positioning.html +1751 -0
- data/docs/brut-css/classes/spacings.html +2633 -0
- data/docs/brut-css/classes/typography.html +2206 -0
- data/docs/brut-css/customization/advanced-configuration.html +204 -0
- data/docs/brut-css/customization/breakpoints.html +227 -0
- data/docs/brut-css/customization/design-system.html +197 -0
- data/docs/brut-css/customization/pseudo-classes.html +228 -0
- data/docs/brut-css/docs.css +98 -0
- data/docs/brut-css/getting-started/core-concepts.html +234 -0
- data/docs/brut-css/getting-started/installation.html +190 -0
- data/docs/brut-css/getting-started/overview.html +210 -0
- data/docs/brut-css/getting-started/simple-example.html +285 -0
- data/docs/brut-css/index.html +193 -0
- data/docs/brut-css/prism-twilight.min.css +1 -0
- data/docs/brut-css/properties/colors.html +1548 -0
- data/docs/brut-css/properties/spacings.html +614 -0
- data/docs/brut-css/properties/typography.html +777 -0
- data/docs/brut-js/api/AjaxSubmit.html +374 -0
- data/docs/brut-js/api/AjaxSubmit.js.html +435 -0
- data/docs/brut-js/api/Autosubmit.html +192 -0
- data/docs/brut-js/api/Autosubmit.js.html +114 -0
- data/docs/brut-js/api/BaseCustomElement.html +1091 -0
- data/docs/brut-js/api/BaseCustomElement.js.html +312 -0
- data/docs/brut-js/api/BrutCustomElements.html +172 -0
- data/docs/brut-js/api/BufferedLogger.html +173 -0
- data/docs/brut-js/api/ConfirmSubmit.html +278 -0
- data/docs/brut-js/api/ConfirmSubmit.js.html +167 -0
- data/docs/brut-js/api/ConfirmationDialog.html +425 -0
- data/docs/brut-js/api/ConfirmationDialog.js.html +194 -0
- data/docs/brut-js/api/ConstraintViolationMessage.html +448 -0
- data/docs/brut-js/api/ConstraintViolationMessage.js.html +176 -0
- data/docs/brut-js/api/ConstraintViolationMessages.html +590 -0
- data/docs/brut-js/api/ConstraintViolationMessages.js.html +149 -0
- data/docs/brut-js/api/CopyToClipboard.html +345 -0
- data/docs/brut-js/api/CopyToClipboard.js.html +147 -0
- data/docs/brut-js/api/Form.html +294 -0
- data/docs/brut-js/api/Form.js.html +202 -0
- data/docs/brut-js/api/I18nTranslation.html +409 -0
- data/docs/brut-js/api/I18nTranslation.js.html +112 -0
- data/docs/brut-js/api/LocaleDetection.html +312 -0
- data/docs/brut-js/api/LocaleDetection.js.html +168 -0
- data/docs/brut-js/api/Logger.html +702 -0
- data/docs/brut-js/api/Logger.js.html +141 -0
- data/docs/brut-js/api/Message.html +238 -0
- data/docs/brut-js/api/Message.js.html +107 -0
- data/docs/brut-js/api/PrefixedLogger.html +369 -0
- data/docs/brut-js/api/RichString.html +1049 -0
- data/docs/brut-js/api/RichString.js.html +164 -0
- data/docs/brut-js/api/Tabs.html +295 -0
- data/docs/brut-js/api/Tabs.js.html +219 -0
- data/docs/brut-js/api/Tracing.html +277 -0
- data/docs/brut-js/api/Tracing.js.html +298 -0
- data/docs/brut-js/api/external-CustomElementRegistry.html +140 -0
- data/docs/brut-js/api/external-Performance.html +138 -0
- data/docs/brut-js/api/external-Promise.html +138 -0
- data/docs/brut-js/api/external-ValidityState.html +138 -0
- data/docs/brut-js/api/external-Window.html +233 -0
- data/docs/brut-js/api/external-fetch.html +138 -0
- data/docs/brut-js/api/global.html +400 -0
- data/docs/brut-js/api/index.html +168 -0
- data/docs/brut-js/api/index.js.html +181 -0
- data/docs/brut-js/api/module-testing.html +383 -0
- data/docs/brut-js/api/scripts/linenumber.js +25 -0
- data/docs/brut-js/api/scripts/prettify/Apache-License-2.0.txt +202 -0
- data/docs/brut-js/api/scripts/prettify/lang-css.js +2 -0
- data/docs/brut-js/api/scripts/prettify/prettify.js +28 -0
- data/docs/brut-js/api/styles/jsdoc-default.css +327 -0
- data/docs/brut-js/api/styles/prettify-jsdoc.css +111 -0
- data/docs/brut-js/api/styles/prettify-tomorrow.css +132 -0
- data/docs/brut-js/api/testing.AssetMetadata.html +172 -0
- data/docs/brut-js/api/testing.AssetMetadataLoader.html +171 -0
- data/docs/brut-js/api/testing.CustomElementTest.html +679 -0
- data/docs/brut-js/api/testing.DOMCreator.html +171 -0
- data/docs/brut-js/api/testing_AssetMetadata.js.html +86 -0
- data/docs/brut-js/api/testing_AssetMetadataLoader.js.html +76 -0
- data/docs/brut-js/api/testing_CustomElementTest.js.html +286 -0
- data/docs/brut-js/api/testing_DOMCreator.js.html +96 -0
- data/docs/brut-js/api/testing_index.js.html +99 -0
- data/docs/brut-js.html +35 -0
- data/docs/business-logic.html +24 -0
- data/docs/cli.html +150 -0
- data/docs/components.html +127 -0
- data/docs/configuration.html +101 -0
- data/docs/css.html +44 -0
- data/docs/custom-element-tests.html +92 -0
- data/docs/database-access.html +86 -0
- data/docs/database-schema.html +86 -0
- data/docs/deployment.html +24 -0
- data/docs/dev-environment.html +34 -0
- data/docs/doc-conventions.html +24 -0
- data/docs/end-to-end-tests.html +49 -0
- data/docs/flash-and-session.html +116 -0
- data/docs/forms.html +402 -0
- data/docs/getting-started.html +25 -0
- data/docs/handlers.html +92 -0
- data/docs/hashmap.json +1 -0
- data/docs/hooks.html +103 -0
- data/docs/i18n.html +46 -0
- data/docs/images/logo-300.png +0 -0
- data/docs/images/logo.png +0 -0
- data/docs/index.html +24 -0
- data/docs/instrumentation.html +58 -0
- data/docs/javascript.html +54 -0
- data/docs/jobs.html +24 -0
- data/docs/keyword-injection.html +48 -0
- data/docs/markdown-examples.html +56 -0
- data/docs/middleware.html +43 -0
- data/docs/not-released.html +24 -0
- data/docs/overview.html +156 -0
- data/docs/pages.html +145 -0
- data/docs/routes.html +52 -0
- data/docs/security.html +24 -0
- data/docs/seed-data.html +37 -0
- data/docs/space-time-continuum.html +24 -0
- data/docs/tutorial.html +24 -0
- data/docs/unit-tests.html +36 -0
- data/docs/vp-icons.css +1 -0
- data/lib/brut/back_end/seed_data.rb +19 -2
- data/lib/brut/back_end/sidekiq/middlewares/server.rb +2 -1
- data/lib/brut/back_end/sidekiq/middlewares.rb +2 -1
- data/lib/brut/back_end/sidekiq.rb +2 -1
- data/lib/brut/back_end/validator.rb +5 -1
- data/lib/brut/back_end.rb +4 -2
- data/lib/brut/cli/app_runner.rb +1 -1
- data/lib/brut/cli/apps/test.rb +5 -0
- data/lib/brut/cli.rb +4 -3
- data/lib/brut/factory_bot.rb +0 -5
- data/lib/brut/framework/app.rb +70 -5
- data/lib/brut/framework/config.rb +5 -3
- data/lib/brut/framework/container.rb +3 -2
- data/lib/brut/framework/errors.rb +12 -4
- data/lib/brut/framework/mcp.rb +58 -1
- data/lib/brut/framework/project_environment.rb +6 -2
- data/lib/brut/framework.rb +1 -1
- data/lib/brut/front_end/component.rb +69 -71
- data/lib/brut/front_end/components/constraint_violations.rb +1 -4
- data/lib/brut/front_end/components/form_tag.rb +1 -1
- data/lib/brut/front_end/components/input.rb +3 -3
- data/lib/brut/front_end/components/inputs/csrf_token.rb +1 -1
- data/lib/brut/front_end/components/inputs/{text_field.rb → input_tag.rb} +7 -9
- data/lib/brut/front_end/components/inputs/radio_button.rb +1 -1
- data/lib/brut/front_end/components/inputs/select_tag_with_options.rb +187 -0
- data/lib/brut/front_end/components/inputs/{textarea.rb → textarea_tag.rb} +2 -2
- data/lib/brut/front_end/components/time_tag.rb +2 -1
- data/lib/brut/front_end/form.rb +4 -4
- data/lib/brut/front_end/forms/input.rb +2 -1
- data/lib/brut/front_end/forms/input_definition.rb +5 -2
- data/lib/brut/front_end/forms/radio_button_group_input.rb +2 -1
- data/lib/brut/front_end/forms/radio_button_group_input_definition.rb +2 -2
- data/lib/brut/front_end/forms/select_input.rb +2 -4
- data/lib/brut/front_end/forms/select_input_definition.rb +2 -2
- data/lib/brut/front_end/handler.rb +28 -26
- data/lib/brut/front_end/handlers/csp_reporting_handler.rb +5 -2
- data/lib/brut/front_end/handlers/instrumentation_handler.rb +8 -4
- data/lib/brut/front_end/handlers/locale_detection_handler.rb +9 -5
- data/lib/brut/front_end/handlers/missing_handler.rb +5 -2
- data/lib/brut/front_end/layout.rb +16 -0
- data/lib/brut/front_end/page.rb +52 -29
- data/lib/brut/front_end/request_context.rb +3 -2
- data/lib/brut/front_end/routing.rb +5 -1
- data/lib/brut/front_end.rb +4 -13
- data/lib/brut/i18n/base_methods.rb +167 -79
- data/lib/brut/i18n/for_back_end.rb +4 -0
- data/lib/brut/i18n/for_cli.rb +4 -0
- data/lib/brut/i18n/for_html.rb +32 -4
- data/lib/brut/i18n/http_accept_language.rb +47 -0
- data/lib/brut/instrumentation/open_telemetry.rb +36 -1
- data/lib/brut/instrumentation.rb +3 -5
- data/lib/brut/sinatra_helpers.rb +11 -3
- data/lib/brut/spec_support/component_support.rb +30 -16
- data/lib/brut/spec_support/e2e_support.rb +1 -1
- data/lib/brut/spec_support/e2e_test_server.rb +3 -0
- data/lib/brut/spec_support/general_support.rb +3 -0
- data/lib/brut/spec_support/handler_support.rb +6 -1
- data/lib/brut/spec_support/matcher.rb +1 -0
- data/lib/brut/spec_support/matchers/be_page_for.rb +1 -0
- data/lib/brut/spec_support/matchers/have_html_attribute.rb +1 -0
- data/lib/brut/spec_support/matchers/have_i18n_string.rb +2 -5
- data/lib/brut/spec_support/matchers/have_link_to.rb +1 -0
- data/lib/brut/spec_support/matchers/have_redirected_to.rb +1 -0
- data/lib/brut/spec_support/matchers/have_rendered.rb +1 -0
- data/lib/brut/spec_support/matchers/have_returned_rack_response.rb +44 -0
- data/lib/brut/spec_support.rb +1 -1
- data/lib/brut/version.rb +1 -1
- data/lib/brut.rb +5 -4
- data/lib/sequel/extensions/brut_migrations.rb +1 -1
- metadata +648 -13
- data/doc-src/architecture.md +0 -102
- data/doc-src/assets.md +0 -98
- data/doc-src/forms.md +0 -214
- data/doc-src/handlers.md +0 -83
- data/doc-src/javascript.md +0 -265
- data/doc-src/pages.md +0 -210
- data/doc-src/route-hooks.md +0 -59
- data/lib/brut/front_end/components/inputs/select.rb +0 -117
data/doc-src/javascript.md
DELETED
@@ -1,265 +0,0 @@
|
|
1
|
-
# JavaScript and Front End Behavior
|
2
|
-
|
3
|
-
Brut does not prevent you from using any front-end framework you like. You can certainly install React and reference it in
|
4
|
-
`app/src/front_end/javascript/index.js`. However, Brut would like to humbly request that you not do this.
|
5
|
-
|
6
|
-
Brut is based around server-generated HTML and the web platform. This means that Brut would like you to use custom elements for any
|
7
|
-
client-side behavior you need, and to do so with progressive enhancement.
|
8
|
-
|
9
|
-
To that end, Brut includes BrutJS, which is a set of custom elements and ancillary JavaScript you can use to build client-side
|
10
|
-
behavior.
|
11
|
-
|
12
|
-
By default, your `index.js` will look like this:
|
13
|
-
|
14
|
-
import { BrutCustomElements } from "brut-js"
|
15
|
-
import Example from "./Example"
|
16
|
-
|
17
|
-
document.addEventListener("DOMContentLoaded", () => {
|
18
|
-
BrutCustomElements.define()
|
19
|
-
Example.define()
|
20
|
-
})
|
21
|
-
|
22
|
-
`BrutCustomElements` and `BrutCustomElements.define()` will set up the custom elements bundled with Brut. `Example` shows you how to
|
23
|
-
build your own custom elements.
|
24
|
-
|
25
|
-
## Some Useful Brut Elements
|
26
|
-
|
27
|
-
Please refer to BrutJS's documentation for everything that is included, but here are a few highlights that you will find usefule.
|
28
|
-
|
29
|
-
### Client Side Form Support
|
30
|
-
|
31
|
-
{file:doc-src/forms.md Forms} outlines Brut's server-side form support. Brut provides custom elements to allow you to unify client
|
32
|
-
and server side constraint validations, and make the process a bit easier to manage with CSS.
|
33
|
-
|
34
|
-
First, surrounding a form with a `<brut-form>` will place `data-submitted` onto the `<form>` *only* when the user attempts to submit the
|
35
|
-
form. You can use this in your CSS to prevent showing error messages before a user has submitted.
|
36
|
-
|
37
|
-
Second, you can use `<brut-cv-messages>` and `<brut-cv>` to control the error messages that are shown when the browser detects
|
38
|
-
constraint violations. This works with `<brut-i18n-translation>` to show translated strings.
|
39
|
-
|
40
|
-
Consider this ERB
|
41
|
-
|
42
|
-
<label>
|
43
|
-
<%=
|
44
|
-
component(
|
45
|
-
Brut::FrontEnd::Components::Inputs::TextField.for_form_input(form:, input_name: :name)
|
46
|
-
)
|
47
|
-
%>
|
48
|
-
<span>Name</span>
|
49
|
-
<%= constraint_violations(form:,input_name: :name) %>
|
50
|
-
</label>
|
51
|
-
|
52
|
-
{Brut::FrontEnd::Component::Helpers#constraint_violations} will render the built-in {Brut::FrontEnd::Components::ConstraintViolations}
|
53
|
-
component. Along with `for_form_input`, the following HTML will be generated:
|
54
|
-
|
55
|
-
<label>
|
56
|
-
<input type="text" name="name" required>
|
57
|
-
<span>Name</span>
|
58
|
-
<brut-cv-messages input-name="name">
|
59
|
-
</brut-cv-messages>
|
60
|
-
</label>
|
61
|
-
|
62
|
-
When any element of the form causes a validity event to be fired, `<brut-form>` will locate the appropriate `<brut-cv-messages>` and
|
63
|
-
insert the appropriate `<brut-cv>` elements. Suppose the user submitted this form. Since the `name` input is required, the form
|
64
|
-
submission wouldn't happen, and the resulting HTML would look like so:
|
65
|
-
|
66
|
-
<label>
|
67
|
-
<input type="text" name="name" required>
|
68
|
-
<span>Name</span>
|
69
|
-
<brut-cv-messages input-name="name">
|
70
|
-
<brut-cv input-name="name" key="valueMissing"></brut-cv>
|
71
|
-
</brut-cv-messages>
|
72
|
-
</label>
|
73
|
-
|
74
|
-
Now, assuming your layout used `<brut-i18n-translation>` custom elements, for example like so:
|
75
|
-
|
76
|
-
<brut-i18n-translation key="general.cv.fe.valueMissing">%{field} is required</brut-i18n-translation>
|
77
|
-
|
78
|
-
The `<brut-cv>` custom element will find this and replace its `textContent`, resulting in the following HTML:
|
79
|
-
|
80
|
-
<label>
|
81
|
-
<input type="text" name="name" required>
|
82
|
-
<span>Name</span>
|
83
|
-
<brut-cv-messages input-name="name">
|
84
|
-
<brut-cv input-name="name" key="valueMissing">
|
85
|
-
This field is required
|
86
|
-
</brut-cv>
|
87
|
-
</brut-cv-messages>
|
88
|
-
</label>
|
89
|
-
|
90
|
-
You can now use CSS to style client-side validations *and* control the content shown to the user. If there are server-side constraint
|
91
|
-
violations, The `ConstraintViolations` component would render them (as well as server-generated translations), for example if a name
|
92
|
-
was given, but it's taken already, `ConstraintViolations` would render this HTML:
|
93
|
-
|
94
|
-
<label>
|
95
|
-
<input type="text" name="name" required value="foo">
|
96
|
-
<span>Name</span>
|
97
|
-
<brut-cv-messages input-name="name">
|
98
|
-
<brut-cv input-name="name" server-side>
|
99
|
-
This value has been taken
|
100
|
-
</brut-cv>
|
101
|
-
</brut-cv-messages>
|
102
|
-
</label>
|
103
|
-
|
104
|
-
### Confirming Dangerous Actions
|
105
|
-
|
106
|
-
Often, you want to use JavaScript to confirm the submission of a form whose action is considered dangerous to the user or hard to undo.
|
107
|
-
This can be achieved with `<brut-confirm-submit>`
|
108
|
-
|
109
|
-
<form>
|
110
|
-
<input type="text" name="name" required>
|
111
|
-
<brut-confirm-submit message="This will delete the app">
|
112
|
-
<button>Delete App</button>
|
113
|
-
</brut-confirm-submit>
|
114
|
-
</form>
|
115
|
-
|
116
|
-
By default, this will use the browser's built-in `window.confirm`, however you can also use a `<dialog>` element as well.
|
117
|
-
|
118
|
-
If the generated HTML includes a `<dialog>`, you can surround it with `<brut-confirmation-dialog>` to indicate it should be used for
|
119
|
-
confirmation. The `<dialog>` should have an `<h1>` where the message will be placed and two buttons, one with `value="ok"` and one
|
120
|
-
with `value="cancel"`.
|
121
|
-
|
122
|
-
|
123
|
-
<brut-confirmation-dialog>
|
124
|
-
<dialog>
|
125
|
-
<h1></h1>
|
126
|
-
<button value="ok">Do It!</button>
|
127
|
-
<button value="cancel">Nevermind</button>
|
128
|
-
</dialog>
|
129
|
-
</brut-confirmation-dialog>
|
130
|
-
|
131
|
-
When the `<brut-confirm-submit>`'s `<button>` is clicked, this `<dialog>` is shown with the message inserted. If the user hits the
|
132
|
-
button with `value` of `"ok"`, the form submission goes through. Otherwise, it doesn't. The dialog is then hidden.
|
133
|
-
|
134
|
-
### Ajax Form Submission
|
135
|
-
|
136
|
-
To submit a form via Ajax, you can use `<brut-ajax-submit>` around the `<button>` that should submit the form with Ajax. This element
|
137
|
-
attempts to provide a fault-tolerant user experience and will set various attributes on itself to allow you to change styling during
|
138
|
-
the various phases of the request.
|
139
|
-
|
140
|
-
If the submission works, it will fire a `brut:submitok` event that your custom code can receive and do whatever makes the most sense
|
141
|
-
in that case.
|
142
|
-
|
143
|
-
If the submission fails with a 422, your server should return a series of `<brut-cv>` custom elements. If it does, this will be
|
144
|
-
inserted into the correct `<brut-cv-messages>` elements to dynamically create error messages. The element will then fire a
|
145
|
-
`brut:submitinvalid` event you can catch and handle to do something custom.
|
146
|
-
|
147
|
-
If the submission times out or fails in some other way, Brut will submit the form the old-fashioned way.
|
148
|
-
|
149
|
-
### Client-Side, Accessible Tabs
|
150
|
-
|
151
|
-
Many UIs involve a set of tabs that switch between different views. While HTML has no built-in support for this, Brut's `<brut-tabs>`
|
152
|
-
custom element can captialize on the various ARIA roles required to design a tabbed interface and provide all the JavaScript behavior
|
153
|
-
necessary. See that element's documentation for an extended example.
|
154
|
-
|
155
|
-
## Building Your Own Custom Elements
|
156
|
-
|
157
|
-
Because custom elements are part of the web platform, Brut encourages you to use them to add client-side behavior. As a
|
158
|
-
demonstration of this working, there is an `Example` element set up when you created your app. Assuming your app's prefix was `cc`
|
159
|
-
when you created it, the `Example` element works like so:
|
160
|
-
|
161
|
-
<cc-example transform="upper">
|
162
|
-
Here is some text
|
163
|
-
</cc-example>
|
164
|
-
|
165
|
-
When the browser renders this—assuming JavaScript is enabled—it will render the following:
|
166
|
-
|
167
|
-
<cc-example transform="upper">
|
168
|
-
HERE IS SOME TEXT
|
169
|
-
</cc-example>
|
170
|
-
|
171
|
-
You wouldn't want to do this, but this simple element demonstrates both how to make your own and that custom elements in your app are
|
172
|
-
properly configured.
|
173
|
-
|
174
|
-
This element is very close to a vanilla custom element, but it extends `BaseCustomElement`, which is provided by BrutJS, which includes
|
175
|
-
z few quality-of-life improvements. Let's walk through the code.
|
176
|
-
|
177
|
-
First, we extends `BaseCustomElement` (which extends `HTMLElement`) and define a static attribute, `tagName` that will be the
|
178
|
-
element's tag name you can use in your HTML:
|
179
|
-
|
180
|
-
import { BaseCustomElement } from "brut-js"
|
181
|
-
|
182
|
-
class Example extends BaseCustomElement {
|
183
|
-
static tagName = "cc-example"
|
184
|
-
|
185
|
-
Next, we'll define the attributes of our element using `observedAttributes`, which is part of the custom element spec (and not
|
186
|
-
specific to BrutJS):
|
187
|
-
|
188
|
-
static observedAttributes = [
|
189
|
-
"transform",
|
190
|
-
"show-warnings",
|
191
|
-
]
|
192
|
-
|
193
|
-
The `show-warnings` attribute, if placed on the element's HTML, configures `this.logger` from `BaseCustomElement` to allow output of debug messages. This allows you to easily debug your element's behavior in development, but remove them from production. We'll see that in a bit.
|
194
|
-
|
195
|
-
Next, we'll set a private attribute to hold a default value for the `transform` HTML attribute. It can be named anything:
|
196
|
-
|
197
|
-
#transform = "upper"
|
198
|
-
|
199
|
-
Now, we want to know when `transform` changes. Normally, you'd implement `attributeChangedCallback` and check its `name` parameter.
|
200
|
-
`BaseCustomElement` allows you to do this more directly by creating a `xxxChangedCallback` method for each attribute in
|
201
|
-
`observedAttributes` that you want to know about. For `transform`, that means implementing `transformChangedCallback`:
|
202
|
-
|
203
|
-
transformChangedCallback({newValue}) {
|
204
|
-
this.#transform = newValue
|
205
|
-
}
|
206
|
-
|
207
|
-
Next, we implement the bulk of the element's behavior. Because there are many lifecycle events that may require modifying the
|
208
|
-
element, `BaseCustomElement` consolidates all of those events and calls the method `update()`. `update()` should be idempotent
|
209
|
-
and should examine the element's state (as well as the document's, if necessary) and update the element however it makes sense.
|
210
|
-
|
211
|
-
For this example, we want to grab the content, examine the value for `transform` and change the content:
|
212
|
-
|
213
|
-
update() {
|
214
|
-
const content = this.textContent
|
215
|
-
if (this.#transform == "upper") {
|
216
|
-
this.textContent = content.toLocaleUpperCase()
|
217
|
-
}
|
218
|
-
else if (this.#transform == "lower") {
|
219
|
-
this.textContent = content.toLocaleLowerCase()
|
220
|
-
}
|
221
|
-
else {
|
222
|
-
this.logger.info("We only support upper or lower, but got %s",this.#transform)
|
223
|
-
}
|
224
|
-
}
|
225
|
-
|
226
|
-
Notice the last line where we call `this.logger.info`. If `show-warnings` is omitted from the HTML, this message isn't shown anywhere. If `show-warnings` *is* present, this message will show up in the console. This can be useful for understanding why your element isn't working.
|
227
|
-
|
228
|
-
Lastly, we export the class:
|
229
|
-
|
230
|
-
}
|
231
|
-
export default Example
|
232
|
-
|
233
|
-
By virtue of having extended `BaseCustomElement`, the static `define()` method will set it up as a custom element with the browser.
|
234
|
-
|
235
|
-
## Testing Custom Elements
|
236
|
-
|
237
|
-
Sometimes, system tests are sufficient to ensure your custom element code is working. If not, Brut (via BrutJS) provides a way to
|
238
|
-
test your element in isolation.
|
239
|
-
|
240
|
-
These tests use JSDom which, while not perfect, allows the tests to run reasonably quickly. Each test begins with `withHTML` to
|
241
|
-
define the markup that is being tested. This is followed by `test` which accepts a function that performs the test.
|
242
|
-
|
243
|
-
Rather than have a DSL to provide access to your element's state, you can use the browser's API (as provided by JSDom) to check
|
244
|
-
whatever it is you need to check:
|
245
|
-
|
246
|
-
import { withHTML } from "./SpecHelper.js"
|
247
|
-
|
248
|
-
describe("<cc-example>", () => {
|
249
|
-
withHTML(`
|
250
|
-
<cc-example>This is some Text</cc-example>
|
251
|
-
`).test("upper case by default", ({document,assert}) => {
|
252
|
-
const element = document.querySelector("cc-example")
|
253
|
-
assert.equal(element.textContent,"THIS IS SOME TEXT")
|
254
|
-
})
|
255
|
-
withHTML(`
|
256
|
-
<cc-example transform="lower">This is some Text</cc-example>
|
257
|
-
`).test("lower case when asked", ({document,assert}) => {
|
258
|
-
const element = document.querySelector("cc-example")
|
259
|
-
assert.equal(element.textContent,"this is some text")
|
260
|
-
})
|
261
|
-
})
|
262
|
-
|
263
|
-
Note that `test`'s second argument—the function that performs the test—is called with objects you can use to perform the test. In this
|
264
|
-
case, the `document` is passed in as-is the `assert` method.
|
265
|
-
|
data/doc-src/pages.md
DELETED
@@ -1,210 +0,0 @@
|
|
1
|
-
# Pages and Components
|
2
|
-
|
3
|
-
A website is made up of pages. Even a web *app* has pages. Thus, the most basic way to create dynamic behavior with Brut is the
|
4
|
-
*page*. In Brut, a page is a URL, a subclass of {Brut::FrontEnd::Page}, and an ERB template. The template is rendered in the context
|
5
|
-
of an instance of the class whenever the URL is requested.
|
6
|
-
|
7
|
-
## Overview of Rendering Pages
|
8
|
-
|
9
|
-
In your `app.rb`, you first declare a page using the {Brut::SinatraHelpers::ClassMethods#page} method (inside the block given to {Brut::Framework::App.routes}):
|
10
|
-
|
11
|
-
class App < Brut::Framework::App
|
12
|
-
|
13
|
-
routes do
|
14
|
-
|
15
|
-
page "/sign_in"
|
16
|
-
end
|
17
|
-
end
|
18
|
-
|
19
|
-
The name of the route must start with a `/` and the rest of the route determines the name of the page. In the example above, Brut
|
20
|
-
will expect to find a class named `SignInPage`, which should be located in `app/src/front_end/pages`. You can create it with
|
21
|
-
`bin/scaffold`:
|
22
|
-
|
23
|
-
> bin/scaffold page SignInPage
|
24
|
-
|
25
|
-
The page class must subclass {Brut::FrontEnd::Page}. You write a constructor to accept whatever your page needs to assemble the data
|
26
|
-
needed to render it.
|
27
|
-
|
28
|
-
The HTML is generated based on an ERB file located in `app/src/front_end/pages`. In this example, that's
|
29
|
-
`app/src/front_end/pages/sign_in_page.html.erb`. That ERB is executed with an instance of the page class as context. That means you
|
30
|
-
can references any methods or ivars.
|
31
|
-
|
32
|
-
## Creating a Page Class
|
33
|
-
|
34
|
-
{Brut::FrontEnd::Page#render} is called by Brut and expects to receive HTML, which it will send as the body of the response. `render`
|
35
|
-
manages the ERB rendering, so the bulk of your page's logic will be in other methods. The constructor is where any data you need is
|
36
|
-
provided.
|
37
|
-
|
38
|
-
A page's `initialize` method must accept only keyword arguments and those keywords are used by Brut to determine what data needs to be
|
39
|
-
passed in. This technique, called *{file:doc-src/keyword-injection.md Keyword Injection}*, is used in several other places in Brut.
|
40
|
-
|
41
|
-
When the page's URL is requested, an instance of your page class is created, passing in the values needed. Beyond that, however, your page class is a normal Ruby class. You can save data to instance variables, implement attributes or other methods. Basically, whatever logic your page needs to render will exist in the page class.
|
42
|
-
|
43
|
-
## Implementing Your ERB
|
44
|
-
|
45
|
-
ERB is part of the Ruby standard library that allows the creation of dynamic templates. Brut's ERB is close to this implementation, but has a few additional features that make working with HTML a bit safer.
|
46
|
-
|
47
|
-
### Your Page is the Only Context
|
48
|
-
|
49
|
-
The instance of your page class is the only context available to the ERB. There is no set of global helpers that are injected, nor
|
50
|
-
are there any modules added when the page is rendered. Anything you need to do in your ERB must be available to the page class you've
|
51
|
-
created.
|
52
|
-
|
53
|
-
The reason for this is to keep things simple. If your page's HTML needs access to a lot of stuff, your page class will be the source
|
54
|
-
of truth as to where that stuff comes from. You will always be able to figure out where methods are defined by looking at the page
|
55
|
-
class or any of its ancestors. You are free to `include` as many modules as you like, or dump lots of methods into your `AppPage`
|
56
|
-
base class. But you don't have to.
|
57
|
-
|
58
|
-
### All Strings are HTML-Escaped
|
59
|
-
|
60
|
-
Any instance of a `String` is HTML-escaped by Brut before being inserted into the HTML being generated by the ERB. This is, generally, what you want to have happen. If you have a string that you believe is safe and does not need to be escaped, you can prevent HTML-escaping in one of two ways:
|
61
|
-
|
62
|
-
* Call {Brut::FrontEnd::Component#html_safe!} on the string
|
63
|
-
|
64
|
-
<%= html_safe!(get_value) %>
|
65
|
-
|
66
|
-
* Wrap the string in a {Brut::FrontEnd::Templates::HTMLSafeString}.
|
67
|
-
|
68
|
-
def get_value
|
69
|
-
Brut::FrontEnd::Templates::HTMLSafeString.from_string(some_value)
|
70
|
-
end
|
71
|
-
|
72
|
-
This is what `html_safe!` does and this is what Brut does internally. Brut doesn't monkey-patch `String`. A `String` is always
|
73
|
-
considered unsafe, and an `HTMLSafeString` is always considered safe.
|
74
|
-
|
75
|
-
Both of these methods are verbose, but this is on purpose. You generally don't want un-escaped HTML going into your HTML, but if you
|
76
|
-
do, you may find it better to create a component (see below), or use {Brut::FrontEnd::Component::Helpers#html_tag} to generate markup.
|
77
|
-
|
78
|
-
### Pages Have Layouts
|
79
|
-
|
80
|
-
The page's ERB is rendered in the context of a *layout*. A Layout works similar to how it works in Rails. It's intended to hold HTML
|
81
|
-
needed by every page of your app. A minimal layout might look like so:
|
82
|
-
|
83
|
-
<!DOCTYPE html>
|
84
|
-
<html>
|
85
|
-
<head>
|
86
|
-
<meta charset="utf-8">
|
87
|
-
<%= component(Brut::FrontEnd::Components::PageIdentifier.new(self.page_name)) %>
|
88
|
-
<link rel="preload" as="style" href="<%= asset_path('/css/styles.css') %>">
|
89
|
-
<link rel="stylesheet" href="<%= asset_path('/css/styles.css') %>">
|
90
|
-
<script defer src="<%= asset_path('/js/app.js') %>"></script>
|
91
|
-
</head>
|
92
|
-
<body>
|
93
|
-
<%= yield %>
|
94
|
-
</body>
|
95
|
-
</html>
|
96
|
-
|
97
|
-
The layout is rendered in the context of the page class, so every page must provide whatever features are needed by the layout. This
|
98
|
-
is usually done by adding globally-used functions to `AppPage`, which is the base class of all your app's pages.
|
99
|
-
|
100
|
-
A page can use another layout by overriding {Brut::FrontEnd::Page#layout}. The string returned must match a file in
|
101
|
-
`app/src/front_end/layouts/`.
|
102
|
-
|
103
|
-
### There Are No Partials
|
104
|
-
|
105
|
-
Rails partials are not part of ERB, and Brut does not include this feature. Instead, you would use *Components*.
|
106
|
-
|
107
|
-
## Decompose and Re-Use with Components
|
108
|
-
|
109
|
-
*Components* in Brut are very similar to the View Components library, though somewhat simpler. A component is a class and an ERB
|
110
|
-
template. That template is rendered in the context of an instance of the class. That class is created the same way a page is and has
|
111
|
-
a `render` method that works just like a page's.
|
112
|
-
|
113
|
-
This is all because {Brut::FrontEnd::Page} extends {Brut::FrontEnd::Component}. A page adds the concept of a layout, but generally a
|
114
|
-
page and a component are the same thing.
|
115
|
-
|
116
|
-
### Using Components
|
117
|
-
|
118
|
-
The main difference from your perspective is that generally *you* create instances of components. This means that your page must have
|
119
|
-
access to any data a component needs. When you've created your component instance, use {Brut::FrontEnd::Component#component} to
|
120
|
-
render it:
|
121
|
-
|
122
|
-
<%= component(Button.new(type: :danger, label: "Cancel Subscription")) %>
|
123
|
-
|
124
|
-
### Components with Templates
|
125
|
-
|
126
|
-
The most common way to use a component is with an ERB template. It is expected to be in `app/src/front_end/components`. For the
|
127
|
-
hypothetical `Button` component above, Brut would expect `app/src/front_end/components/button.html.erb` to exist as a template.
|
128
|
-
|
129
|
-
Just like a page, the component's ERB is rendered in the context of the component only.
|
130
|
-
|
131
|
-
Sometimes, components are simple enough that you don't need HTML.
|
132
|
-
|
133
|
-
### Components That Render Themselves
|
134
|
-
|
135
|
-
While overriding `render` in a page is generally discouraged, {Brut::FrontEnd::Component#render} can be overridden if you want to
|
136
|
-
generate HTML yourself. The best way to do that is with {Brut::FrontEnd::Component::Helpers#html_tag}, though you can always return a
|
137
|
-
`String` or {Brut::FrontEnd::Templates::HTMLSafeString} that you've created yourself. Just remeber that all `String` instances will be
|
138
|
-
HTML-escaped.
|
139
|
-
|
140
|
-
As an example, here is how you might have a Markdown component that renders Markdown as HTML:
|
141
|
-
|
142
|
-
class MarkdownComponent < AppComponent
|
143
|
-
def initialize(markdown:)
|
144
|
-
@markdown = markdown
|
145
|
-
@renderer = Redcarpet::Markdown.new(
|
146
|
-
Redcarpet::Render::HTML.new(
|
147
|
-
filter_html: true,
|
148
|
-
no_images: true,
|
149
|
-
no_styles: true,
|
150
|
-
safe_links_only: true,
|
151
|
-
),
|
152
|
-
fenced_code_blocks: true,
|
153
|
-
autolink: true,
|
154
|
-
quote: true,
|
155
|
-
)
|
156
|
-
end
|
157
|
-
|
158
|
-
def render
|
159
|
-
html_safe!(@renderer.render(@markdown.to_s))
|
160
|
-
end
|
161
|
-
end
|
162
|
-
|
163
|
-
|
164
|
-
### Global Components
|
165
|
-
|
166
|
-
While a component is just a class with an initializer, sometimes you need a component that is generally useful on any page, but that
|
167
|
-
you don't want to initialize. For example, if your component needs access to the flash, but your page does not, you don't want your
|
168
|
-
page to require a flash just to pass to the component. In that case, a *global component* can be used and Brut will instantiate it.
|
169
|
-
|
170
|
-
{Brut::FrontEnd::Component#component} can be given a class, and Brut will use keyword injection to create it. Consider a generic
|
171
|
-
flash component:
|
172
|
-
|
173
|
-
class GlobalFlash < AppComponent
|
174
|
-
|
175
|
-
attr_reader :flash
|
176
|
-
|
177
|
-
def initialize(flash:)
|
178
|
-
@flash = flash
|
179
|
-
end
|
180
|
-
end
|
181
|
-
|
182
|
-
You can use this anywhere like so:
|
183
|
-
|
184
|
-
<%= component(GlobalFlash) %>
|
185
|
-
|
186
|
-
Because the flash is availble from the {Brut::FrontEnd::RequestContext}, Brut can create this component when needed.
|
187
|
-
|
188
|
-
## Testing Pages and Components
|
189
|
-
|
190
|
-
Pages and Components are classes with a constructor you create and a well-defined primary method called `render`. This means you can
|
191
|
-
test them conventionally, since they can be directly created in your tests.
|
192
|
-
|
193
|
-
That said, you likely want to test the generated HTML and not the methods of the class. All tests of a page or component have the
|
194
|
-
methods in {Brut::SpecSupport::ComponentSupport} available. Of particular interest is
|
195
|
-
{Brut::SpecSupport::ComponentSupport#render_and_parse}.
|
196
|
-
|
197
|
-
This method accepts an instance of a page or component, parses the resulting HTML with Nokogiri, and returns a
|
198
|
-
{Brut::SpecSupport::EnhancedNode}, which wraps a `Nokogiri::XML::Node` or `Nokogiri::XML::Element`, depending on what was parsed. You
|
199
|
-
can then use Nokogiri's API locate elements and assert on them.
|
200
|
-
|
201
|
-
To keep close to the web platform, it's recommended to use CSS selectors either via {Brut::SpecSupport::EnhancedNode#e} or {Brut::SpecSupport::EnhancedNode#e!} (both of which wrap Nokogiri's `css` method).
|
202
|
-
|
203
|
-
Instead of creating another API for accessing HTML content, the Nokogiri API should be used directly. You can create custom matchers
|
204
|
-
as needed for common assertions. Brut includes a few that you will find useful:
|
205
|
-
|
206
|
-
* `have_link_to`
|
207
|
-
* `have_html_attribute`
|
208
|
-
* `have_i18n_string`
|
209
|
-
|
210
|
-
|
data/doc-src/route-hooks.md
DELETED
@@ -1,59 +0,0 @@
|
|
1
|
-
# Route and Page Hooks
|
2
|
-
|
3
|
-
Route and page hooks allow you to perform logic or redirect the visitor before a page is rendered or action handled.
|
4
|
-
|
5
|
-
## Route Hooks
|
6
|
-
|
7
|
-
Route hooks are objects that are run before or after a request has been handled. They are useful for setting up cross-cutting code
|
8
|
-
that you don't want to have inside a page or handler.
|
9
|
-
|
10
|
-
To use one, call either {Brut::Framework::App.before} or {Brut::Framework::App.after}, passing it the *name* of a class to use as the
|
11
|
-
hook (i.e. a `String`).
|
12
|
-
|
13
|
-
Then, implement that class, extending {Brut::FrontEnd::RouteHook}, and provide either {Brut::FrontEnd::RouteHook#before} or {Brut::FrontEnd::RouteHook#after}. As discussed in {file:doc-src/keyword-injection.md Keyword Injection}, your hook can be passed some managed values to allow it to work.
|
14
|
-
|
15
|
-
In general, a hook will allow the request to continue or not, but using one of the following methods as the return value:
|
16
|
-
|
17
|
-
* {Brut::FrontEnd::HandlingResults#redirect_to} to redirect the user instead of rendering the page or handling the request.
|
18
|
-
* {Brut::FrontEnd::HandlingResults#http_status} to return an HTTP status instead of rendering the page or handling the request.
|
19
|
-
* {Brut::FrontEnd::RouteHook#continue} to proceed with the request.
|
20
|
-
|
21
|
-
## Page Hooks
|
22
|
-
|
23
|
-
Sometimes, the behavior you want to manage before a page is rendered is specific to a page and not cross-cutting. Because a page
|
24
|
-
exepcts to render HTML, you cannot easily put such code in your page class.
|
25
|
-
|
26
|
-
If you implement {Brut::FrontEnd::Page#before_render}, you can skip page rendering entirely and redirect the user or send an error. A
|
27
|
-
good example of this would be a set of admin pages where the logged-in site visitor must possess some roles in order to see the page.
|
28
|
-
|
29
|
-
A page hook expects one of these return values:
|
30
|
-
|
31
|
-
* `URI` - redirect the visitor instead of rendering the page.
|
32
|
-
* {Brut::FrontEnd::HttpStatus} - Send the browser this status code instead of rendering the page.
|
33
|
-
* Anything else - render the page as normal
|
34
|
-
|
35
|
-
Thus, the lifecycle of a page is:
|
36
|
-
|
37
|
-
1. "Before" Route Hooks
|
38
|
-
2. Page Initializer, injected as described in {file:doc-src/keyword-injection.md}
|
39
|
-
3. Page's `before_render`, called with no arguments.
|
40
|
-
4. Page's ERB generates HTML
|
41
|
-
5. "After" Route Hooks
|
42
|
-
|
43
|
-
## Handler Hooks
|
44
|
-
|
45
|
-
Like page hooks, handler hooks are called before handling logic. Implement `before_handle`. It's arguments must be a subset of the
|
46
|
-
arguments passed to `handle`. Thus, any value needed by `before_handle` must be declared as a keyword argument to `handle` as well.
|
47
|
-
|
48
|
-
If `before_handle` returns `nil`, `handle` is then called. Otherwise, `handle` is skipped and the return value of `before_handle` is
|
49
|
-
interpreted as the return value of `handle`. See {Brut::FrontEnd::Handler#handle}.
|
50
|
-
|
51
|
-
This makes the lifecycle of a handler as such:
|
52
|
-
|
53
|
-
1. "Before" Route Hooks
|
54
|
-
2. Handler Initializer, called with no argument.
|
55
|
-
3. Handler's `handle!`, injected with arguments as described in {file:doc-src/keyword-injections.md}
|
56
|
-
1. `handle!` calls `before_handle`, passing the arguments in.
|
57
|
-
2. `handle!` calls `handle`, passing the arguments in.
|
58
|
-
4. "After" Route Hooks
|
59
|
-
|
@@ -1,117 +0,0 @@
|
|
1
|
-
# Renders an HTML `<select>`.
|
2
|
-
class Brut::FrontEnd::Components::Inputs::Select < Brut::FrontEnd::Components::Input
|
3
|
-
# Creates the appropriate select input for the given {Brut::FrontEnd::Form} and input name.
|
4
|
-
# Generally, you want to use this method over the initializer.
|
5
|
-
#
|
6
|
-
# @param [Brut::FrontEnd::Form} form The form that is being rendered. This method will consult this class to understand the requirements on this select so its HTML is generated correctly.
|
7
|
-
# @param [String] input_name the name of the input, which should be a member of `form`
|
8
|
-
# @param [Array<Object>] options An array of objects represented what is being selected. These can be any object and are ideally whatever domain object or data type you want on the backend to represent this selection.
|
9
|
-
# @param [Object] selected_value The currently-selected value for the select. Can be `nil` if nothing is selected.
|
10
|
-
# @param [Symbol|String] value_attribute the name of an attribute or no-parameter method that can be called on objects inside `options` to get the value to use in the select input. This should be unique amongst the options, and is usually an id.
|
11
|
-
# @param [Symbol|String] option_text_attribute the name of an attribute or no-parameter method that can be called on objects inside `options` to get the actual text of the option shown to the user. This should probably allow for I18n.
|
12
|
-
# @param [Integer] index if this input is part of an array, this is the index into that array. This is used to get the input's value.
|
13
|
-
# @param [Hash] html_attributes any additional HTML attributes to include on the `<select>` element.
|
14
|
-
# @param [false|true|Hash] include_blank configure how and if to include a blank element in the select. If this is false, there will be no blank element. If it's `true`, there will be one with no value nor text. If this is a `Hash` it must contain a `value:` key and `text_content:` key to be used as the `value` attribute and option text content, respectively.
|
15
|
-
def self.for_form_input(form:,
|
16
|
-
input_name:,
|
17
|
-
options:,
|
18
|
-
selected_value:,
|
19
|
-
include_blank: false,
|
20
|
-
value_attribute:,
|
21
|
-
option_text_attribute:,
|
22
|
-
index: nil,
|
23
|
-
html_attributes: {})
|
24
|
-
html_attributes = html_attributes.map { |key,value| [ key.to_sym, value ] }.to_h
|
25
|
-
default_html_attributes = {}
|
26
|
-
index ||= 0
|
27
|
-
input = form.input(input_name, index:)
|
28
|
-
default_html_attributes[:required] = input.required
|
29
|
-
if !form.new? && !input.valid?
|
30
|
-
default_html_attributes["data-invalid"] = true
|
31
|
-
input.validity_state.each do |constraint,violated|
|
32
|
-
if violated
|
33
|
-
default_html_attributes["data-#{constraint}"] = true
|
34
|
-
end
|
35
|
-
end
|
36
|
-
end
|
37
|
-
name = if input.array?
|
38
|
-
"#{input.name}[]"
|
39
|
-
else
|
40
|
-
input.name
|
41
|
-
end
|
42
|
-
Brut::FrontEnd::Components::Inputs::Select.new(
|
43
|
-
name: name,
|
44
|
-
options:,
|
45
|
-
selected_value:,
|
46
|
-
value_attribute:,
|
47
|
-
option_text_attribute:,
|
48
|
-
include_blank:,
|
49
|
-
html_attributes: default_html_attributes.merge(html_attributes)
|
50
|
-
)
|
51
|
-
end
|
52
|
-
# Create the element. See {.for_form_input} for documentation on these parameters.
|
53
|
-
def initialize(name:,
|
54
|
-
options:,
|
55
|
-
include_blank: false,
|
56
|
-
selected_value:,
|
57
|
-
value_attribute:,
|
58
|
-
option_text_attribute:,
|
59
|
-
html_attributes:)
|
60
|
-
@options = options
|
61
|
-
@include_blank = IncludeBlank.from_param(include_blank)
|
62
|
-
@selected_value = selected_value
|
63
|
-
@value_attribute = value_attribute
|
64
|
-
@option_text_attribute = option_text_attribute
|
65
|
-
@html_attributes = html_attributes
|
66
|
-
|
67
|
-
@html_attributes[:name] = name
|
68
|
-
end
|
69
|
-
|
70
|
-
def view_template
|
71
|
-
select(**@html_attributes) {
|
72
|
-
if @include_blank
|
73
|
-
option(**@include_blank.option_attributes) {
|
74
|
-
@include_blank.text_content
|
75
|
-
}
|
76
|
-
end
|
77
|
-
options = @options.each do |option|
|
78
|
-
value = option.send(@value_attribute)
|
79
|
-
option_attributes = { value: value }
|
80
|
-
if value == @selected_value
|
81
|
-
option_attributes[:selected] = true
|
82
|
-
end
|
83
|
-
option(**option_attributes) {
|
84
|
-
option.send(@option_text_attribute)
|
85
|
-
}
|
86
|
-
end
|
87
|
-
}
|
88
|
-
end
|
89
|
-
private
|
90
|
-
|
91
|
-
# @!visibility private
|
92
|
-
class IncludeBlank
|
93
|
-
attr_reader :text_content, :option_attributes
|
94
|
-
def self.from_param(include_blank)
|
95
|
-
if !include_blank
|
96
|
-
return nil
|
97
|
-
else
|
98
|
-
self.new(include_blank)
|
99
|
-
end
|
100
|
-
end
|
101
|
-
def initialize(include_blank)
|
102
|
-
if include_blank == true
|
103
|
-
@text_content = ""
|
104
|
-
@option_attributes = {}
|
105
|
-
elsif include_blank.kind_of?(Hash)
|
106
|
-
if include_blank.key?(:value) && include_blank.key?(:text_content)
|
107
|
-
@text_content = include_blank[:text_content]
|
108
|
-
@option_attributes = { value: include_blank[:value] }
|
109
|
-
else
|
110
|
-
raise ArgumentError, "when include_blank: is a Hash, it must include both :value and :text_content as keys. Got: #{include_blank.keys.join(", ")}"
|
111
|
-
end
|
112
|
-
else
|
113
|
-
raise ArgumentError,"include_blank: was a #{include_blank.class}. It should be true, false, nil, or a Hash"
|
114
|
-
end
|
115
|
-
end
|
116
|
-
end
|
117
|
-
end
|