brut 0.12.1 → 0.14.0
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/CHANGELOG.md +28 -0
- data/Gemfile.lock +1 -1
- data/brut-css/package-lock.json +57 -106
- data/brut-css/package.json +1 -1
- data/brut-js/package-lock.json +61 -331
- data/brut-js/package.json +2 -2
- data/brut-js/specs/AjaxSubmit.spec.js +48 -0
- data/brut-js/src/AjaxSubmit.js +77 -18
- data/brutrb.com/.vitepress/config.mjs +0 -1
- data/brutrb.com/layouts.md +70 -12
- data/brutrb.com/package-lock.json +428 -381
- data/brutrb.com/recipes/authentication.md +6 -6
- data/docs/404.html +2 -2
- data/docs/adrs.html +3 -3
- data/docs/ai.html +3 -3
- data/docs/api/Brut/BackEnd/SeedData.html +1 -1
- data/docs/api/Brut/BackEnd/Sidekiq/Middlewares/Server/FlushSpans.html +1 -1
- data/docs/api/Brut/BackEnd/Sidekiq/Middlewares/Server.html +1 -1
- data/docs/api/Brut/BackEnd/Sidekiq/Middlewares.html +1 -1
- data/docs/api/Brut/BackEnd/Sidekiq.html +1 -1
- data/docs/api/Brut/BackEnd/Validators/FormValidator.html +1 -1
- data/docs/api/Brut/BackEnd/Validators.html +1 -1
- data/docs/api/Brut/BackEnd.html +1 -1
- data/docs/api/Brut/CLI/App.html +1 -1
- data/docs/api/Brut/CLI/AppRunner.html +1 -1
- data/docs/api/Brut/CLI/Apps/BuildAssets/All.html +1 -1
- data/docs/api/Brut/CLI/Apps/BuildAssets/CSS.html +1 -1
- data/docs/api/Brut/CLI/Apps/BuildAssets/Images.html +1 -1
- data/docs/api/Brut/CLI/Apps/BuildAssets/JS.html +1 -1
- data/docs/api/Brut/CLI/Apps/BuildAssets.html +1 -1
- data/docs/api/Brut/CLI/Apps/DB/Create.html +1 -1
- data/docs/api/Brut/CLI/Apps/DB/Drop.html +1 -1
- data/docs/api/Brut/CLI/Apps/DB/Migrate.html +1 -1
- data/docs/api/Brut/CLI/Apps/DB/NewMigration.html +1 -1
- data/docs/api/Brut/CLI/Apps/DB/Rebuild.html +1 -1
- data/docs/api/Brut/CLI/Apps/DB/Seed.html +1 -1
- data/docs/api/Brut/CLI/Apps/DB/Status.html +1 -1
- data/docs/api/Brut/CLI/Apps/DB.html +1 -1
- data/docs/api/Brut/CLI/Apps/DeployBase/GitChecks.html +1 -1
- data/docs/api/Brut/CLI/Apps/DeployBase.html +1 -1
- data/docs/api/Brut/CLI/Apps/HerokuContainerBasedDeploy/Deploy.html +1 -1
- data/docs/api/Brut/CLI/Apps/HerokuContainerBasedDeploy.html +1 -1
- data/docs/api/Brut/CLI/Apps/Scaffold/Action/Route.html +1 -1
- data/docs/api/Brut/CLI/Apps/Scaffold/Action.html +1 -1
- data/docs/api/Brut/CLI/Apps/Scaffold/Component.html +1 -1
- data/docs/api/Brut/CLI/Apps/Scaffold/CustomElementTest.html +1 -1
- data/docs/api/Brut/CLI/Apps/Scaffold/DbModel.html +1 -1
- data/docs/api/Brut/CLI/Apps/Scaffold/E2ETest.html +1 -1
- data/docs/api/Brut/CLI/Apps/Scaffold/Form.html +1 -1
- data/docs/api/Brut/CLI/Apps/Scaffold/Page/Route.html +1 -1
- data/docs/api/Brut/CLI/Apps/Scaffold/Page.html +1 -1
- data/docs/api/Brut/CLI/Apps/Scaffold/RoutesEditor.html +1 -1
- data/docs/api/Brut/CLI/Apps/Scaffold/Test.html +1 -1
- data/docs/api/Brut/CLI/Apps/Scaffold.html +1 -1
- data/docs/api/Brut/CLI/Apps/Test/Audit.html +1 -1
- data/docs/api/Brut/CLI/Apps/Test/E2e.html +1 -1
- data/docs/api/Brut/CLI/Apps/Test/JS.html +1 -1
- data/docs/api/Brut/CLI/Apps/Test/Run.html +1 -1
- data/docs/api/Brut/CLI/Apps/Test.html +1 -1
- data/docs/api/Brut/CLI/Apps.html +1 -1
- data/docs/api/Brut/CLI/Command.html +1 -1
- data/docs/api/Brut/CLI/Error.html +1 -1
- data/docs/api/Brut/CLI/ExecutionResults/Result.html +1 -1
- data/docs/api/Brut/CLI/ExecutionResults.html +1 -1
- data/docs/api/Brut/CLI/Executor.html +1 -1
- data/docs/api/Brut/CLI/InvalidOption.html +1 -1
- data/docs/api/Brut/CLI/Options.html +1 -1
- data/docs/api/Brut/CLI/Output.html +1 -1
- data/docs/api/Brut/CLI/SystemExecError.html +1 -1
- data/docs/api/Brut/CLI.html +1 -1
- data/docs/api/Brut/FactoryBot.html +1 -1
- data/docs/api/Brut/Framework/App.html +1 -1
- data/docs/api/Brut/Framework/Config.html +1 -1
- data/docs/api/Brut/Framework/Container.html +1 -1
- data/docs/api/Brut/Framework/Error.html +1 -1
- data/docs/api/Brut/Framework/Errors/AbstractMethod.html +1 -1
- data/docs/api/Brut/Framework/Errors/Bug.html +1 -1
- data/docs/api/Brut/Framework/Errors/MissingConfiguration.html +1 -1
- data/docs/api/Brut/Framework/Errors/MissingParameter.html +1 -1
- data/docs/api/Brut/Framework/Errors/NoClassForPath.html +1 -1
- data/docs/api/Brut/Framework/Errors/NotFound.html +1 -1
- data/docs/api/Brut/Framework/Errors/NotImplemented.html +1 -1
- data/docs/api/Brut/Framework/Errors.html +1 -1
- data/docs/api/Brut/Framework/FussyTypeEnforcement.html +1 -1
- data/docs/api/Brut/Framework/MCP.html +1 -1
- data/docs/api/Brut/Framework/ProjectEnvironment.html +1 -1
- data/docs/api/Brut/Framework.html +1 -1
- data/docs/api/Brut/FrontEnd/AssetPathResolver.html +1 -1
- data/docs/api/Brut/FrontEnd/Component/Helpers.html +1 -1
- data/docs/api/Brut/FrontEnd/Component.html +1 -1
- data/docs/api/Brut/FrontEnd/Components/ConstraintViolations.html +1 -1
- data/docs/api/Brut/FrontEnd/Components/FormTag.html +1 -1
- data/docs/api/Brut/FrontEnd/Components/I18nTranslations.html +1 -1
- data/docs/api/Brut/FrontEnd/Components/Input.html +1 -1
- data/docs/api/Brut/FrontEnd/Components/Inputs/ButtonTag.html +1 -1
- data/docs/api/Brut/FrontEnd/Components/Inputs/CsrfToken.html +1 -1
- data/docs/api/Brut/FrontEnd/Components/Inputs/InputTag.html +1 -1
- data/docs/api/Brut/FrontEnd/Components/Inputs/RadioButton.html +1 -1
- data/docs/api/Brut/FrontEnd/Components/Inputs/SelectTagWithOptions.html +1 -1
- data/docs/api/Brut/FrontEnd/Components/Inputs/TextareaTag.html +1 -1
- data/docs/api/Brut/FrontEnd/Components/Inputs.html +1 -1
- data/docs/api/Brut/FrontEnd/Components/LocaleDetection.html +1 -1
- data/docs/api/Brut/FrontEnd/Components/PageIdentifier.html +1 -1
- data/docs/api/Brut/FrontEnd/Components/TimeTag.html +1 -1
- data/docs/api/Brut/FrontEnd/Components/Traceparent.html +1 -1
- data/docs/api/Brut/FrontEnd/Components.html +1 -1
- data/docs/api/Brut/FrontEnd/CsrfProtector.html +1 -1
- data/docs/api/Brut/FrontEnd/Download.html +1 -1
- data/docs/api/Brut/FrontEnd/Flash.html +1 -1
- data/docs/api/Brut/FrontEnd/Form.html +1 -1
- data/docs/api/Brut/FrontEnd/Forms/Button.html +1 -1
- data/docs/api/Brut/FrontEnd/Forms/ButtonInputDefinition.html +1 -1
- data/docs/api/Brut/FrontEnd/Forms/ConstraintViolation.html +1 -1
- data/docs/api/Brut/FrontEnd/Forms/Input/Color.html +1 -1
- data/docs/api/Brut/FrontEnd/Forms/Input/TimeOfDay.html +1 -1
- data/docs/api/Brut/FrontEnd/Forms/Input.html +1 -1
- data/docs/api/Brut/FrontEnd/Forms/InputDeclarations.html +1 -1
- data/docs/api/Brut/FrontEnd/Forms/InputDefinition.html +1 -1
- data/docs/api/Brut/FrontEnd/Forms/RadioButtonGroupInput.html +1 -1
- data/docs/api/Brut/FrontEnd/Forms/RadioButtonGroupInputDefinition.html +1 -1
- data/docs/api/Brut/FrontEnd/Forms/SelectInput.html +1 -1
- data/docs/api/Brut/FrontEnd/Forms/SelectInputDefinition.html +1 -1
- data/docs/api/Brut/FrontEnd/Forms/ValidityState.html +1 -1
- data/docs/api/Brut/FrontEnd/Forms.html +1 -1
- data/docs/api/Brut/FrontEnd/GenericResponse.html +1 -1
- data/docs/api/Brut/FrontEnd/Handler.html +1 -1
- data/docs/api/Brut/FrontEnd/Handlers/CspReportingHandler.html +1 -1
- data/docs/api/Brut/FrontEnd/Handlers/InstrumentationHandler/TraceParent.html +1 -1
- data/docs/api/Brut/FrontEnd/Handlers/InstrumentationHandler.html +1 -1
- data/docs/api/Brut/FrontEnd/Handlers/LocaleDetectionHandler.html +1 -1
- data/docs/api/Brut/FrontEnd/Handlers/MissingHandler/Form.html +1 -1
- data/docs/api/Brut/FrontEnd/Handlers/MissingHandler.html +1 -1
- data/docs/api/Brut/FrontEnd/Handlers.html +1 -1
- data/docs/api/Brut/FrontEnd/HandlingResults.html +1 -1
- data/docs/api/Brut/FrontEnd/HttpMethod.html +1 -1
- data/docs/api/Brut/FrontEnd/HttpStatus.html +1 -1
- data/docs/api/Brut/FrontEnd/InlineSvgLocator.html +1 -1
- data/docs/api/Brut/FrontEnd/Layout.html +1 -1
- data/docs/api/Brut/FrontEnd/Middleware.html +1 -1
- data/docs/api/Brut/FrontEnd/Middlewares/AnnotateBrutOwnedPaths.html +1 -1
- data/docs/api/Brut/FrontEnd/Middlewares/Favicon.html +1 -1
- data/docs/api/Brut/FrontEnd/Middlewares/OpenTelemetrySpan.html +1 -1
- data/docs/api/Brut/FrontEnd/Middlewares/ReloadApp.html +1 -1
- data/docs/api/Brut/FrontEnd/Middlewares.html +1 -1
- data/docs/api/Brut/FrontEnd/Page.html +1 -1
- data/docs/api/Brut/FrontEnd/Pages/MissingPage.html +1 -1
- data/docs/api/Brut/FrontEnd/Pages.html +1 -1
- data/docs/api/Brut/FrontEnd/RequestContext.html +1 -1
- data/docs/api/Brut/FrontEnd/RouteHook.html +1 -1
- data/docs/api/Brut/FrontEnd/RouteHooks/AgeFlash.html +1 -1
- data/docs/api/Brut/FrontEnd/RouteHooks/CSPNoInlineScripts.html +1 -1
- data/docs/api/Brut/FrontEnd/RouteHooks/CSPNoInlineStylesOrScripts/ReportOnly.html +1 -1
- data/docs/api/Brut/FrontEnd/RouteHooks/CSPNoInlineStylesOrScripts.html +1 -1
- data/docs/api/Brut/FrontEnd/RouteHooks/LocaleDetection.html +1 -1
- data/docs/api/Brut/FrontEnd/RouteHooks/SetupRequestContext.html +1 -1
- data/docs/api/Brut/FrontEnd/RouteHooks.html +1 -1
- data/docs/api/Brut/FrontEnd/Routing/FormHandlerRoute.html +1 -1
- data/docs/api/Brut/FrontEnd/Routing/FormRoute.html +1 -1
- data/docs/api/Brut/FrontEnd/Routing/MissingForm.html +1 -1
- data/docs/api/Brut/FrontEnd/Routing/MissingHandler.html +1 -1
- data/docs/api/Brut/FrontEnd/Routing/MissingPage.html +1 -1
- data/docs/api/Brut/FrontEnd/Routing/MissingPath.html +1 -1
- data/docs/api/Brut/FrontEnd/Routing/PageRoute.html +1 -1
- data/docs/api/Brut/FrontEnd/Routing/Route.html +1 -1
- data/docs/api/Brut/FrontEnd/Routing.html +1 -1
- data/docs/api/Brut/FrontEnd/Session.html +1 -1
- data/docs/api/Brut/FrontEnd.html +1 -1
- data/docs/api/Brut/I18n/BaseMethods.html +1 -1
- data/docs/api/Brut/I18n/ForBackEnd.html +1 -1
- data/docs/api/Brut/I18n/ForCLI.html +1 -1
- data/docs/api/Brut/I18n/ForHTML.html +1 -1
- data/docs/api/Brut/I18n/HTTPAcceptLanguage/AlwaysEnglish.html +1 -1
- data/docs/api/Brut/I18n/HTTPAcceptLanguage.html +1 -1
- data/docs/api/Brut/I18n.html +1 -1
- data/docs/api/Brut/Instrumentation/LoggerSpanExporter.html +1 -1
- data/docs/api/Brut/Instrumentation/Methods/ClassMethods.html +1 -1
- data/docs/api/Brut/Instrumentation/Methods.html +1 -1
- data/docs/api/Brut/Instrumentation/OpenTelemetry/NormalizedAttributes.html +1 -1
- data/docs/api/Brut/Instrumentation/OpenTelemetry/Span.html +1 -1
- data/docs/api/Brut/Instrumentation/OpenTelemetry.html +1 -1
- data/docs/api/Brut/Instrumentation.html +1 -1
- data/docs/api/Brut/RubocopConfig.html +1 -1
- data/docs/api/Brut/SinatraHelpers/ClassMethods.html +1 -1
- data/docs/api/Brut/SinatraHelpers.html +1 -1
- data/docs/api/Brut/SpecSupport/ClockSupport.html +1 -1
- data/docs/api/Brut/SpecSupport/ComponentSupport.html +1 -1
- data/docs/api/Brut/SpecSupport/E2ETestServer.html +1 -1
- data/docs/api/Brut/SpecSupport/E2eSupport.html +1 -1
- data/docs/api/Brut/SpecSupport/EnhancedNode.html +1 -1
- data/docs/api/Brut/SpecSupport/FlashSupport.html +1 -1
- data/docs/api/Brut/SpecSupport/GeneralSupport/ClassMethods.html +1 -1
- data/docs/api/Brut/SpecSupport/GeneralSupport.html +1 -1
- data/docs/api/Brut/SpecSupport/HandlerSupport.html +1 -1
- data/docs/api/Brut/SpecSupport/Matchers/BeABug.html +1 -1
- data/docs/api/Brut/SpecSupport/Matchers/BePageFor.html +1 -1
- data/docs/api/Brut/SpecSupport/Matchers/BeRoutingFor.html +1 -1
- data/docs/api/Brut/SpecSupport/Matchers/HaveConstraintViolation.html +1 -1
- data/docs/api/Brut/SpecSupport/Matchers/HaveGenerated.html +1 -1
- data/docs/api/Brut/SpecSupport/Matchers/HaveHTMLAttribute.html +1 -1
- data/docs/api/Brut/SpecSupport/Matchers/HaveI18nString.html +1 -1
- data/docs/api/Brut/SpecSupport/Matchers/HaveLinkTo.html +1 -1
- data/docs/api/Brut/SpecSupport/Matchers/HaveRedirectedTo.html +1 -1
- data/docs/api/Brut/SpecSupport/Matchers/HaveReturnedHttpStatus.html +1 -1
- data/docs/api/Brut/SpecSupport/Matchers/HaveReturnedRackResponse.html +1 -1
- data/docs/api/Brut/SpecSupport/Matchers.html +1 -1
- data/docs/api/Brut/SpecSupport/RSpecSetup/OptionalSidekiqSupport.html +1 -1
- data/docs/api/Brut/SpecSupport/RSpecSetup.html +1 -1
- data/docs/api/Brut/SpecSupport/SessionSupport.html +1 -1
- data/docs/api/Brut/SpecSupport.html +1 -1
- data/docs/api/Brut.html +1 -1
- data/docs/api/Clock.html +1 -1
- data/docs/api/ModuleName.html +1 -1
- data/docs/api/RichString.html +1 -1
- data/docs/api/SemanticLogger/Appender/Async.html +1 -1
- data/docs/api/Sequel/Extensions/BrutInstrumentation.html +1 -1
- data/docs/api/Sequel/Extensions/BrutMigrations.html +1 -1
- data/docs/api/Sequel/Extensions.html +1 -1
- data/docs/api/Sequel/Plugins/CreatedAt/InstanceMethods.html +1 -1
- data/docs/api/Sequel/Plugins/CreatedAt.html +1 -1
- data/docs/api/Sequel/Plugins/ExternalId/ClassMethods.html +1 -1
- data/docs/api/Sequel/Plugins/ExternalId/InstanceMethods.html +1 -1
- data/docs/api/Sequel/Plugins/ExternalId.html +1 -1
- data/docs/api/Sequel/Plugins/FindBang/ClassMethods.html +1 -1
- data/docs/api/Sequel/Plugins/FindBang.html +1 -1
- data/docs/api/Sequel/Plugins.html +1 -1
- data/docs/api/Sequel.html +1 -1
- data/docs/api/_index.html +1 -1
- data/docs/api/file.README.html +1 -1
- data/docs/api/index.html +1 -1
- data/docs/api/top-level-namespace.html +1 -1
- data/docs/assets/{app.CGHFI-MA.js → app.BDtsVxyd.js} +1 -1
- data/docs/assets/chunks/@localSearchIndexroot.BWVzhs5N.js +1 -0
- data/docs/assets/chunks/{VPLocalSearchBox.-c-Kihwo.js → VPLocalSearchBox.DCJk5nAW.js} +1 -1
- data/docs/assets/chunks/{theme.DpqrgXG8.js → theme.DZKmijwi.js} +2 -2
- data/docs/assets/{components.md.X4h_TEG-.js → components.md.rMhQ0WdZ.js} +3 -3
- data/docs/assets/{configuration.md.CPCyUo5A.js → configuration.md.BK42Yjp_.js} +1 -1
- data/docs/assets/{forms.md.BwmfAZ6Z.js → forms.md.v9qIbmUM.js} +2 -2
- data/docs/assets/{forms.md.BwmfAZ6Z.lean.js → forms.md.v9qIbmUM.lean.js} +1 -1
- data/docs/assets/{getting-started.md.mPgbkbBv.js → getting-started.md.DTOl4c2g.js} +2 -2
- data/docs/assets/{recipes_authentication.md.BAISoxmN.js → recipes_authentication.md.nwO6F7Ou.js} +6 -6
- data/docs/assets/{tutorials_02-dialog.md.DOonTBrE.js → tutorials_02-dialog.md.CPNK1SC_.js} +2 -2
- data/docs/assets/{tutorials_02-dialog.md.DOonTBrE.lean.js → tutorials_02-dialog.md.CPNK1SC_.lean.js} +1 -1
- data/docs/assets.html +3 -3
- data/docs/brut-js/api/AjaxSubmit.html +29 -10
- data/docs/brut-js/api/AjaxSubmit.js.html +78 -19
- data/docs/brut-js/api/Autosubmit.html +1 -1
- data/docs/brut-js/api/Autosubmit.js.html +1 -1
- data/docs/brut-js/api/BaseCustomElement.html +1 -1
- data/docs/brut-js/api/BaseCustomElement.js.html +1 -1
- data/docs/brut-js/api/BrutCustomElements.html +1 -1
- data/docs/brut-js/api/BufferedLogger.html +1 -1
- data/docs/brut-js/api/ConfirmSubmit.html +1 -1
- data/docs/brut-js/api/ConfirmSubmit.js.html +1 -1
- data/docs/brut-js/api/ConfirmationDialog.html +1 -1
- data/docs/brut-js/api/ConfirmationDialog.js.html +1 -1
- data/docs/brut-js/api/ConstraintViolationMessage.html +1 -1
- data/docs/brut-js/api/ConstraintViolationMessage.js.html +1 -1
- data/docs/brut-js/api/ConstraintViolationMessages.html +1 -1
- data/docs/brut-js/api/ConstraintViolationMessages.js.html +1 -1
- data/docs/brut-js/api/CopyToClipboard.html +1 -1
- data/docs/brut-js/api/CopyToClipboard.js.html +1 -1
- data/docs/brut-js/api/Form.html +1 -1
- data/docs/brut-js/api/Form.js.html +1 -1
- data/docs/brut-js/api/I18nTranslation.html +1 -1
- data/docs/brut-js/api/I18nTranslation.js.html +1 -1
- data/docs/brut-js/api/LocaleDetection.html +1 -1
- data/docs/brut-js/api/LocaleDetection.js.html +1 -1
- data/docs/brut-js/api/Logger.html +1 -1
- data/docs/brut-js/api/Logger.js.html +1 -1
- data/docs/brut-js/api/Message.html +1 -1
- data/docs/brut-js/api/Message.js.html +1 -1
- data/docs/brut-js/api/PrefixedLogger.html +1 -1
- data/docs/brut-js/api/RichString.html +1 -1
- data/docs/brut-js/api/RichString.js.html +1 -1
- data/docs/brut-js/api/Tabs.html +1 -1
- data/docs/brut-js/api/Tabs.js.html +1 -1
- data/docs/brut-js/api/Tracing.html +1 -1
- data/docs/brut-js/api/Tracing.js.html +1 -1
- data/docs/brut-js/api/external-CustomElementRegistry.html +1 -1
- data/docs/brut-js/api/external-Performance.html +1 -1
- data/docs/brut-js/api/external-Promise.html +1 -1
- data/docs/brut-js/api/external-ValidityState.html +1 -1
- data/docs/brut-js/api/external-Window.html +1 -1
- data/docs/brut-js/api/external-fetch.html +1 -1
- data/docs/brut-js/api/global.html +1 -1
- data/docs/brut-js/api/index.html +1 -1
- data/docs/brut-js/api/index.js.html +1 -1
- data/docs/brut-js/api/module-testing.html +1 -1
- data/docs/brut-js/api/testing.AssetMetadata.html +1 -1
- data/docs/brut-js/api/testing.AssetMetadataLoader.html +1 -1
- data/docs/brut-js/api/testing.CustomElementTest.html +1 -1
- data/docs/brut-js/api/testing.DOMCreator.html +1 -1
- data/docs/brut-js/api/testing_AssetMetadata.js.html +1 -1
- data/docs/brut-js/api/testing_AssetMetadataLoader.js.html +1 -1
- data/docs/brut-js/api/testing_CustomElementTest.js.html +1 -1
- data/docs/brut-js/api/testing_DOMCreator.js.html +1 -1
- data/docs/brut-js/api/testing_index.js.html +1 -1
- data/docs/brut-js.html +3 -3
- data/docs/business-logic.html +3 -3
- data/docs/cli.html +3 -3
- data/docs/components.html +7 -7
- data/docs/configuration.html +5 -5
- data/docs/css.html +3 -3
- data/docs/custom-element-tests.html +3 -3
- data/docs/database-access.html +3 -3
- data/docs/database-schema.html +3 -3
- data/docs/deployment.html +3 -3
- data/docs/dev-environment.html +3 -3
- data/docs/dir-structure.html +3 -3
- data/docs/doc-conventions.html +3 -3
- data/docs/end-to-end-tests.html +3 -3
- data/docs/features.html +3 -3
- data/docs/flash-and-session.html +3 -3
- data/docs/form-constraints.html +3 -3
- data/docs/forms.html +5 -5
- data/docs/getting-started.html +6 -6
- data/docs/handlers.html +3 -3
- data/docs/hashmap.json +1 -1
- data/docs/hooks.html +3 -3
- data/docs/i18n.html +3 -3
- data/docs/index.html +3 -3
- data/docs/instrumentation.html +3 -3
- data/docs/javascript.html +3 -3
- data/docs/jobs.html +3 -3
- data/docs/keyword-injection.html +3 -3
- data/docs/layouts.html +3 -3
- data/docs/lsp.html +3 -3
- data/docs/markdown-examples.html +3 -3
- data/docs/middleware.html +3 -3
- data/docs/overview.html +3 -3
- data/docs/pages.html +3 -3
- data/docs/recipes/alternate-layouts.html +3 -3
- data/docs/recipes/authentication.html +10 -10
- data/docs/recipes/blank-layouts.html +3 -3
- data/docs/recipes/custom-flash.html +3 -3
- data/docs/recipes/form-errors.html +3 -3
- data/docs/recipes/indexed-forms.html +3 -3
- data/docs/recipes/migrations.html +3 -3
- data/docs/recipes/text-field-component.html +3 -3
- data/docs/roadmap.html +3 -3
- data/docs/routes.html +3 -3
- data/docs/security.html +3 -3
- data/docs/seed-data.html +3 -3
- data/docs/space-time-continuum.html +3 -3
- data/docs/tutorial.html +3 -3
- data/docs/tutorials/01-intro.html +3 -3
- data/docs/tutorials/02-dialog.html +5 -5
- data/docs/unit-tests.html +3 -3
- data/docs/why.html +3 -3
- data/lib/brut/front_end/components/page_identifier.rb +7 -4
- data/lib/brut/front_end/layout.rb +11 -0
- data/lib/brut/front_end/page.rb +1 -1
- data/lib/brut/version.rb +1 -1
- data/mkbrut/Gemfile.lock +1 -1
- data/mkbrut/lib/mkbrut/version.rb +1 -1
- data/mkbrut/templates/Base/app/src/front_end/layouts/blank_layout.rb +11 -0
- data/mkbrut/templates/Base/app/src/front_end/layouts/default_layout.rb.erb +3 -6
- metadata +18 -18
- data/brutrb.com/recipes/blank-layouts.md +0 -22
- data/docs/assets/chunks/@localSearchIndexroot.DcMsFVrO.js +0 -1
- /data/docs/assets/{components.md.X4h_TEG-.lean.js → components.md.rMhQ0WdZ.lean.js} +0 -0
- /data/docs/assets/{configuration.md.CPCyUo5A.lean.js → configuration.md.BK42Yjp_.lean.js} +0 -0
- /data/docs/assets/{getting-started.md.mPgbkbBv.lean.js → getting-started.md.DTOl4c2g.lean.js} +0 -0
- /data/docs/assets/{recipes_authentication.md.BAISoxmN.lean.js → recipes_authentication.md.nwO6F7Ou.lean.js} +0 -0
data/brut-js/src/AjaxSubmit.js
CHANGED
@@ -9,7 +9,8 @@ import ConstraintViolationMessage from "./ConstraintViolationMessage"
|
|
9
9
|
*
|
10
10
|
* 1. When the button is clicked, the form's validity is checked. If it's not valid, nothing happens.
|
11
11
|
* 2. If the form is valid, this element will be given the `requesting` attribute.
|
12
|
-
* 3. The request will be initiated, set to abort after `request-timeout` ms
|
12
|
+
* 3. The request will be initiated, set to abort after `request-timeout` ms as well
|
13
|
+
* as to abort on an externally-provided AbortSignal (see below).
|
13
14
|
* The data submitted will be the contents of `new FormData(form)`, along with the
|
14
15
|
* name/value of the button that was clicked, if it has a name and value.
|
15
16
|
* 4. If the request returns OK:
|
@@ -28,8 +29,7 @@ import ConstraintViolationMessage from "./ConstraintViolationMessage"
|
|
28
29
|
* - otherwise, the operation is aborted.
|
29
30
|
* 7. If fetch throws an error, the operation is aborted.
|
30
31
|
*
|
31
|
-
*
|
32
|
-
* `log-request-errors` to introspect this process.
|
32
|
+
* ## 422 Responses
|
33
33
|
*
|
34
34
|
* For a 422 response (where `no-server-side-error-parsing` is *not* set),
|
35
35
|
* this element assumes the response is `text/html` and contains one or more `<brut-cv>`
|
@@ -56,6 +56,20 @@ import ConstraintViolationMessage from "./ConstraintViolationMessage"
|
|
56
56
|
* - the response body will be `text/html`
|
57
57
|
* - the response body will contain one or more `<brut-cv>` elements
|
58
58
|
*
|
59
|
+
* ## Aborting the `fetch` Request
|
60
|
+
*
|
61
|
+
* By default, the call to `fetch` will be aborted after the value of `request-timeout` ms (default is 5,000).
|
62
|
+
* When this happens, the **form is submitted through the browser** without Ajax. This is currently
|
63
|
+
* not configurable.
|
64
|
+
*
|
65
|
+
* You *can* set an additional `AbortSignal` by setting the `abortSignal` property. When this is done, then
|
66
|
+
* either a timeout or your custom abort will abort the request and **submit the form through the browser**.
|
67
|
+
*
|
68
|
+
* For your custom abort signal, you can prevent browser submission by providing a reason
|
69
|
+
* with the value `AjaxSubmit.doNotSubmitThroughBrowser`. This can be useful when you are debouncing
|
70
|
+
* requests. See the examples. You will also find it useful to set `log-request-errors` on the element
|
71
|
+
* so you can see what is happening.
|
72
|
+
*
|
59
73
|
* @property {boolean} no-server-side-error-parsing - if set, the response body for a 422 will not be parsed and inserted into the DOM. Instead, the body will be part of the detail of the `brut:submitinvalid` event.
|
60
74
|
* @property {number} request-timeout - number of ms that the entire operation is expected to complete within. Default is 5000
|
61
75
|
* @property {number} submitted-lifetime - number of ms that "submitted" should remain on the element after the form has completed. Default is 2000
|
@@ -67,23 +81,29 @@ import ConstraintViolationMessage from "./ConstraintViolationMessage"
|
|
67
81
|
* @fires brut:submitok Fired when the AJAX request initated by this returns OK and all processing has completed. The detail will include the *parsed document* of the HTML returned in the response.
|
68
82
|
* @fires brut:submitinvalid Fired when the AJAX request initated by this returns a 422 and all logic around managing the reponse has completed. The detail will be null unless `no-server-side-error-parsing` is set, in which case it will be the parsed document of the HTML returned in the response.
|
69
83
|
*
|
70
|
-
* @example
|
84
|
+
* @example <caption>Typical use</caption>
|
71
85
|
* <form action="/widgets" method="post">
|
72
86
|
* <input type="text" name="name">
|
73
87
|
*
|
74
88
|
* <brut-ajax-submit>
|
75
89
|
* <button name="button" value="save">Save</button>
|
76
|
-
*
|
90
|
+
* </brut-ajax-submit>
|
77
91
|
* <brut-ajax-submit>
|
78
92
|
* <button name="button" value="analyze">Analyze</button>
|
79
|
-
*
|
93
|
+
* </brut-ajax-submit>
|
80
94
|
* </form>
|
81
|
-
*
|
82
95
|
* <!-- When "Save" is clicked, "name" will have the value from the text field,
|
83
96
|
* and "button" will have the value "save".
|
84
97
|
* When "Analyze" is clicked, "name" will have the value from the text
|
85
98
|
* field, and "button" will have the value "analyze". -->
|
86
99
|
*
|
100
|
+
* @example <caption>Using a custom abort signal</caption>
|
101
|
+
* const ajaxSubmit = document.querySelector("brut-ajax-submit")
|
102
|
+
* const controller = new AbortController()
|
103
|
+
* ajaxSubmit.abortSignal = controller.signal
|
104
|
+
* // later, when you want to abort the request
|
105
|
+
* controller.abort(AjaxSubmit.doNotSubmitThroughBrowser)
|
106
|
+
*
|
87
107
|
* @customelement brut-ajax-submit
|
88
108
|
*/
|
89
109
|
class AjaxSubmit extends BaseCustomElement {
|
@@ -99,12 +119,28 @@ class AjaxSubmit extends BaseCustomElement {
|
|
99
119
|
"no-server-side-error-parsing",
|
100
120
|
]
|
101
121
|
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
122
|
+
/* Use this when calling abort() to indicate that the form
|
123
|
+
* should be sub submitted through the browser.
|
124
|
+
*/
|
125
|
+
static doNotSubmitThroughBrowser = "doNotSubmitThroughBrowser"
|
126
|
+
|
127
|
+
#requestErrorLogger = () => {}
|
128
|
+
#formSubmitDelay = 0
|
129
|
+
#submittedLifetime = 2000
|
130
|
+
#requestTimeout = 5000
|
131
|
+
#maxRetryAttempts = 25
|
107
132
|
#serverSideErrorParsing = true
|
133
|
+
#abortSignal = null
|
134
|
+
|
135
|
+
/* Set an additional abort signal to be used when
|
136
|
+
* the form is submitted via Ajax. This allows you to
|
137
|
+
* control the fetch request beyond the built-in timeout.
|
138
|
+
*
|
139
|
+
* @param {AbortSignal} value the AbortSignal to add to the fetch request.
|
140
|
+
*/
|
141
|
+
set abortSignal(value) {
|
142
|
+
this.#abortSignal = value
|
143
|
+
}
|
108
144
|
|
109
145
|
constructor() {
|
110
146
|
super()
|
@@ -205,18 +241,36 @@ class AjaxSubmit extends BaseCustomElement {
|
|
205
241
|
}
|
206
242
|
const urlSearchParams = new URLSearchParams(formData)
|
207
243
|
|
208
|
-
const
|
244
|
+
const signals = [
|
245
|
+
AbortSignal.timeout(this.#requestTimeout),
|
246
|
+
]
|
247
|
+
if (this.#abortSignal) {
|
248
|
+
signals.push(this.#abortSignal)
|
249
|
+
}
|
250
|
+
const abortSignals = AbortSignal.any(signals)
|
251
|
+
|
252
|
+
let url = form.action
|
253
|
+
let body = null
|
254
|
+
|
255
|
+
if (form.method.toLowerCase() == "get") {
|
256
|
+
const sep = url.includes("?") ? "&" : "?"
|
257
|
+
url += sep + urlSearchParams.toString()
|
258
|
+
}
|
259
|
+
else {
|
260
|
+
body = urlSearchParams
|
261
|
+
}
|
209
262
|
|
210
263
|
const request = new Request(
|
211
|
-
|
264
|
+
url,
|
212
265
|
{
|
213
266
|
headers: headers,
|
214
267
|
method: form.method,
|
215
|
-
body:
|
216
|
-
signal:
|
268
|
+
body: body,
|
269
|
+
signal: abortSignals,
|
217
270
|
}
|
218
271
|
)
|
219
272
|
|
273
|
+
|
220
274
|
if (numAttempts > this.#maxRetryAttempts) {
|
221
275
|
this.#requestErrorLogger("%d attempts. Giving up",numAttempts)
|
222
276
|
this.#submitFormThroughBrowser(form)
|
@@ -273,8 +327,13 @@ class AjaxSubmit extends BaseCustomElement {
|
|
273
327
|
}
|
274
328
|
}
|
275
329
|
}).catch( (error) => {
|
276
|
-
|
277
|
-
|
330
|
+
if (error == AjaxSubmit.doNotSubmitThroughBrowser) {
|
331
|
+
this.#requestErrorLogger("Error indicates we should not submit through browser: %o",error)
|
332
|
+
}
|
333
|
+
else {
|
334
|
+
this.#requestErrorLogger("Got %o, which cannot be retried",error)
|
335
|
+
this.#submitFormThroughBrowser(form)
|
336
|
+
}
|
278
337
|
})
|
279
338
|
}
|
280
339
|
|
@@ -133,7 +133,6 @@ export default defineConfig({
|
|
133
133
|
{ text: "Styling Form Errors", link: "/recipes/form-errors" },
|
134
134
|
{ text: "Authentication", link: "/recipes/authentication" },
|
135
135
|
{ text: "Alternate Layouts", link: "/recipes/alternate-layouts" },
|
136
|
-
{ text: "Blank Layouts", link: "/recipes/blank-layouts" },
|
137
136
|
{ text: "Custom Flash Class", link: "/recipes/custom-flash" },
|
138
137
|
{ text: "Indexed Form Elements", link: "/recipes/indexed-forms" },
|
139
138
|
{ text: "Text Field Component", link: "/recipes/text-field-component" },
|
data/brutrb.com/layouts.md
CHANGED
@@ -12,14 +12,16 @@ Your app should include `app/src/front_end/layouts/default_layout.rb`. The name
|
|
12
12
|
A layout is a Phlex component that's expected to have a single call to `yield` in
|
13
13
|
its `view_template` method.
|
14
14
|
|
15
|
+
### Default Layout and Common Layout Needs
|
16
|
+
|
15
17
|
Here is the `DefaultLayout` provided to new Brut apps:
|
16
18
|
|
17
19
|
```ruby {33}
|
18
20
|
class DefaultLayout < Brut::FrontEnd::Layout
|
19
21
|
include Brut::FrontEnd::Components
|
20
22
|
|
21
|
-
def initialize(
|
22
|
-
@
|
23
|
+
def initialize(page:)
|
24
|
+
@page = page
|
23
25
|
end
|
24
26
|
|
25
27
|
def view_template
|
@@ -34,7 +36,7 @@ class DefaultLayout < Brut::FrontEnd::Layout
|
|
34
36
|
link(rel: "stylesheet", href: asset_path("/css/styles.css"))
|
35
37
|
script(defer: true, src: asset_path("/js/app.js"))
|
36
38
|
title { app_name }
|
37
|
-
PageIdentifier(
|
39
|
+
PageIdentifier(page)
|
38
40
|
I18nTranslations("cv.cs")
|
39
41
|
I18nTranslations("cv.this_field")
|
40
42
|
Traceparent()
|
@@ -46,7 +48,7 @@ class DefaultLayout < Brut::FrontEnd::Layout
|
|
46
48
|
end
|
47
49
|
body do
|
48
50
|
brut_tracing url: "/__brut/instrumentation", show_warnings: true
|
49
|
-
main class:
|
51
|
+
main class: page.page_name do
|
50
52
|
yield
|
51
53
|
end
|
52
54
|
end
|
@@ -65,8 +67,69 @@ included by default are important for other features of Brut:
|
|
65
67
|
| `Brut::FrontEnd::Components::Traceparent` | Includes the OpenTelemetry *traceparent* on the page so that client-side telemetry is reported back to the server. See `<brut-tracing>` and [observability](/instrumentation) |
|
66
68
|
| `<brut-tracing>` / `brut_tracing` | Custom element that collects the client-side telemetry and sends it back to the server. See [observability](/instrumentation) |
|
67
69
|
|
68
|
-
|
69
|
-
|
70
|
+
### Adding Logic/Dynamic Behavior to Layouts
|
71
|
+
|
72
|
+
Often, your pages will need to make slight tweaks to the layout that don't apply to all pages. For example, you may wish for a certain page to refresh on a schedule and you want to do that with a [meta refresh](https://en.wikipedia.org/wiki/Meta_refresh), which must appear in the `<head>` of the page.
|
73
|
+
|
74
|
+
Unlike Rails, which uses named blocks to render optional or dynamic content, Brut allows you to use methods and normal Ruby-based flow logic. Since your layouts have access to the page they are laying out, you can use your pages' APIs to do whatever it is you need.
|
75
|
+
|
76
|
+
Taking the meta refresh example, suppose your `AppPage` defines a method, `auto_refresh_seconds` that, if non-`nil` means your page should automatically reload itself after that many seconds. By default, you don't refresh, so it returns `nil`:
|
77
|
+
|
78
|
+
```ruby
|
79
|
+
class AppPage < Brut::FrontEnd::page
|
80
|
+
|
81
|
+
# ...
|
82
|
+
|
83
|
+
def auto_refresh_seconds = nil
|
84
|
+
|
85
|
+
# ...
|
86
|
+
end
|
87
|
+
```
|
88
|
+
|
89
|
+
Your layout can refrence this API, since it's just a method on a class:
|
90
|
+
|
91
|
+
```ruby
|
92
|
+
class DefaultLayout < Brut::FrontEnd::Layout
|
93
|
+
|
94
|
+
# ...
|
95
|
+
|
96
|
+
def view_template
|
97
|
+
doctype
|
98
|
+
html(lang: "en") do
|
99
|
+
head do
|
100
|
+
if page.auto_refresh_seconds
|
101
|
+
meta(http_equiv: safe("refresh"), content: page.auto_refresh_seconds)
|
102
|
+
end
|
103
|
+
|
104
|
+
# ...
|
105
|
+
end
|
106
|
+
# ...
|
107
|
+
end
|
108
|
+
```
|
109
|
+
|
110
|
+
Since your pages are a class hierarchy, you can override `auto_refresh_seconds` in any page, and that page will automatically refresh itself:
|
111
|
+
|
112
|
+
```ruby
|
113
|
+
class DashboardPage < AppPage
|
114
|
+
|
115
|
+
def auto_refresh_seconds = 60 * 60
|
116
|
+
|
117
|
+
# ...
|
118
|
+
|
119
|
+
end
|
120
|
+
```
|
121
|
+
|
122
|
+
### Alternate Layouts
|
123
|
+
|
124
|
+
If you used `mkbrut`, you should have access to a `BlankLayout` that is useful for allowing a page to respond to Ajax requests:
|
125
|
+
|
126
|
+
```ruby
|
127
|
+
class SomePage < AppPage
|
128
|
+
def layout = "blank"
|
129
|
+
end
|
130
|
+
```
|
131
|
+
|
132
|
+
See [creating alternate layouts](/recipes/alternate-layouts) for more information on creating alternate layouts based on your needs.
|
70
133
|
|
71
134
|
## Testing
|
72
135
|
|
@@ -87,12 +150,7 @@ be [global components](/components#global-components)
|
|
87
150
|
> Technical Notes are for deeper understanding and debugging. While we will try to keep them up-to-date with changes to Brut's
|
88
151
|
> internals, the source code is always more correct.
|
89
152
|
|
90
|
-
_Last Updated
|
153
|
+
_Last Updated Sep 9, 2025_
|
91
154
|
|
92
155
|
Layouts work due to the implementation of the method `view_template` in `Brut::FrontEnd::Page`. This is why a page class must provide `page_template` instead.
|
93
156
|
|
94
|
-
While you could override `view_template` in your page to provide a "blank layout",
|
95
|
-
this is discouraged, as the use of `view_template` should be considered a
|
96
|
-
private implementation detail.
|
97
|
-
|
98
|
-
|