brut 0.0.28 → 0.1.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/.gitignore +6 -0
- data/.projections.json +10 -0
- data/.rspec +3 -0
- data/Dockerfile.dx +32 -14
- data/Gemfile.lock +1 -1
- data/README.md +23 -2
- data/assets/Logo-Square.pxd +0 -0
- data/assets/LogoPylon.pxd +0 -0
- data/assets/LogoStop.pxd +0 -0
- data/assets/LogoTall.pxd +0 -0
- data/assets/MetroLogo.graffle +0 -0
- data/assets/SocialImage.png +0 -0
- data/assets/SocialImage.pxd +0 -0
- data/bin/docs +24 -2
- data/bin/rspec +27 -0
- data/bin/setup +3 -3
- data/brutrb.com/.vitepress/config.mjs +45 -8
- data/brutrb.com/.vitepress/theme/custom.css +7 -0
- data/brutrb.com/.vitepress/theme/style.css +29 -17
- data/brutrb.com/ai.md +10 -15
- data/brutrb.com/assets.md +2 -9
- data/brutrb.com/brut-js.md +12 -2
- data/brutrb.com/cli.md +9 -13
- data/brutrb.com/components.md +118 -96
- data/brutrb.com/configuration.md +3 -4
- data/brutrb.com/css.md +2 -2
- data/brutrb.com/custom-element-tests.md +3 -4
- data/brutrb.com/database-access.md +1 -1
- data/brutrb.com/database-schema.md +29 -41
- data/brutrb.com/deployment.md +123 -45
- data/brutrb.com/dev-environment.md +7 -7
- data/brutrb.com/dir-structure.md +120 -0
- data/brutrb.com/doc-conventions.md +18 -15
- data/brutrb.com/dx +1 -0
- data/brutrb.com/end-to-end-tests.md +12 -10
- data/brutrb.com/features.md +373 -0
- data/brutrb.com/flash-and-session.md +115 -131
- data/brutrb.com/form-constraints.md +266 -0
- data/brutrb.com/forms.md +140 -765
- data/brutrb.com/getting-started.md +10 -11
- data/brutrb.com/handlers.md +119 -95
- data/brutrb.com/hooks.md +18 -20
- data/brutrb.com/i18n.md +6 -4
- data/brutrb.com/images/LogoPylon.png +0 -0
- data/brutrb.com/images/LogoSquare.png +0 -0
- data/brutrb.com/images/LogoStop.png +0 -0
- data/brutrb.com/images/LogoTall.png +0 -0
- data/brutrb.com/images/OverviewMetro.graffle +0 -0
- data/brutrb.com/images/OverviewMetro.png +0 -0
- data/brutrb.com/index.md +4 -3
- data/brutrb.com/instrumentation.md +7 -10
- data/brutrb.com/javascript.md +14 -14
- data/brutrb.com/keyword-injection.md +72 -114
- data/brutrb.com/layouts.md +20 -52
- data/brutrb.com/lsp.md +1 -1
- data/brutrb.com/overview.md +35 -377
- data/brutrb.com/pages.md +119 -207
- data/brutrb.com/public/SocialImage.png +0 -0
- data/brutrb.com/public/favicon.ico +0 -0
- data/brutrb.com/recipes/alternate-layouts.md +32 -0
- data/brutrb.com/recipes/authentication.md +315 -6
- data/brutrb.com/recipes/blank-layouts.md +22 -0
- data/brutrb.com/recipes/custom-flash.md +51 -0
- data/brutrb.com/recipes/indexed-forms.md +149 -0
- data/brutrb.com/recipes/text-field-component.md +182 -0
- data/brutrb.com/routes.md +56 -82
- data/brutrb.com/security.md +0 -3
- data/brutrb.com/space-time-continuum.md +8 -12
- data/brutrb.com/tutorial.md +1 -1
- data/brutrb.com/why.md +19 -0
- data/docker-compose.dx.yml +5 -2
- data/docs/404.html +8 -3
- data/docs/SocialImage.png +0 -0
- data/docs/ai.html +11 -6
- 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/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/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/Download.html +1 -1
- data/docs/api/Brut/FrontEnd/Flash.html +1 -1
- data/docs/api/Brut/FrontEnd/Form.html +9 -11
- data/docs/api/Brut/FrontEnd/Forms/ConstraintViolation.html +1 -1
- data/docs/api/Brut/FrontEnd/Forms/Input/Color.html +201 -0
- data/docs/api/Brut/FrontEnd/Forms/Input/TimeOfDay.html +535 -0
- data/docs/api/Brut/FrontEnd/Forms/Input.html +983 -35
- data/docs/api/Brut/FrontEnd/Forms/InputDeclarations.html +1 -1
- data/docs/api/Brut/FrontEnd/Forms/InputDefinition.html +29 -19
- data/docs/api/Brut/FrontEnd/Forms/RadioButtonGroupInput.html +141 -20
- data/docs/api/Brut/FrontEnd/Forms/RadioButtonGroupInputDefinition.html +1 -1
- data/docs/api/Brut/FrontEnd/Forms/SelectInput.html +141 -20
- 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/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/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/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 +15 -1
- data/docs/api/class_list.html +1 -1
- data/docs/api/css/full_list.css +2 -1
- data/docs/api/css/style.css +14 -13
- data/docs/api/file.README.html +22 -3
- data/docs/api/index.html +22 -3
- data/docs/api/method_list.html +435 -275
- data/docs/api/top-level-namespace.html +1 -1
- data/docs/assets/LogoStop.Gb3tDhL1.png +0 -0
- data/docs/assets/OverviewMetro.DUS-5fUZ.png +0 -0
- data/docs/assets/{ai.md._6HCDL6d.js → ai.md.Cy9GWnER.js} +1 -1
- data/docs/assets/ai.md.Cy9GWnER.lean.js +1 -0
- data/docs/assets/{app.BX81XO4N.js → app.ClaS47Ru.js} +1 -1
- data/docs/assets/{assets.md.D3wunzLx.js → assets.md.7C3HWkga.js} +3 -3
- data/docs/assets/{assets.md.D3wunzLx.lean.js → assets.md.7C3HWkga.lean.js} +1 -1
- data/docs/assets/{brut-js.md.o2DAO2s2.js → brut-js.md.B4GYxQVw.js} +1 -1
- data/docs/assets/{brut-js.md.o2DAO2s2.lean.js → brut-js.md.B4GYxQVw.lean.js} +1 -1
- data/docs/assets/chunks/@localSearchIndexroot.Biqy1A4t.js +1 -0
- data/docs/assets/chunks/{VPLocalSearchBox.gABXcTWp.js → VPLocalSearchBox.DtgDfde2.js} +1 -1
- data/docs/assets/chunks/{theme.DwUXXAL3.js → theme.B45bvibT.js} +2 -2
- data/docs/assets/{cli.md.RmeA2b0i.js → cli.md.CjsktgFz.js} +15 -20
- data/docs/assets/components.md.DatoNgFo.js +96 -0
- data/docs/assets/{components.md.CRUMdRoN.lean.js → components.md.DatoNgFo.lean.js} +1 -1
- data/docs/assets/{configuration.md.BGHl8oRC.js → configuration.md.DeyhpqEx.js} +3 -3
- data/docs/assets/{css.md.DJgj2clw.js → css.md.CltvJqAa.js} +3 -3
- data/docs/assets/{custom-element-tests.md.BrYJQEl3.js → custom-element-tests.md.B_rbta32.js} +3 -3
- data/docs/assets/{database-access.md.C7l-Vuvb.js → database-access.md.gnluu54N.js} +1 -1
- data/docs/assets/{database-schema.md.BUjR0VS1.js → database-schema.md.CSYk6E6v.js} +6 -6
- data/docs/assets/{database-schema.md.BUjR0VS1.lean.js → database-schema.md.CSYk6E6v.lean.js} +1 -1
- data/docs/assets/deployment.md.BLseERGV.js +48 -0
- data/docs/assets/deployment.md.BLseERGV.lean.js +1 -0
- data/docs/assets/dev-environment.md.BroAOLhF.js +11 -0
- data/docs/assets/dir-structure.md.CWir1pic.js +46 -0
- data/docs/assets/dir-structure.md.CWir1pic.lean.js +1 -0
- data/docs/assets/doc-conventions.md.BzmSrTEW.js +1 -0
- data/docs/assets/doc-conventions.md.BzmSrTEW.lean.js +1 -0
- data/docs/assets/{end-to-end-tests.md.yfQHC0b5.js → end-to-end-tests.md.DzqRpZ43.js} +5 -3
- data/docs/assets/end-to-end-tests.md.DzqRpZ43.lean.js +1 -0
- data/docs/assets/features.md.DPFXsy0z.js +154 -0
- data/docs/assets/features.md.DPFXsy0z.lean.js +1 -0
- data/docs/assets/flash-and-session.md.nPvUpnUx.js +79 -0
- data/docs/assets/{flash-and-session.md.BXY8RvT0.lean.js → flash-and-session.md.nPvUpnUx.lean.js} +1 -1
- data/docs/assets/form-constraints.md.x5tNpTTI.js +90 -0
- data/docs/assets/form-constraints.md.x5tNpTTI.lean.js +1 -0
- data/docs/assets/forms.md.C2Dizvzq.js +64 -0
- data/docs/assets/forms.md.C2Dizvzq.lean.js +1 -0
- data/docs/assets/{getting-started.md.Ciz82L0m.js → getting-started.md.C93e0odB.js} +5 -5
- data/docs/assets/{getting-started.md.Ciz82L0m.lean.js → getting-started.md.C93e0odB.lean.js} +1 -1
- data/docs/assets/handlers.md.Chyri6KA.js +54 -0
- data/docs/assets/handlers.md.Chyri6KA.lean.js +1 -0
- data/docs/assets/{hooks.md.C4-moMny.js → hooks.md.Jmb5VOLA.js} +4 -4
- data/docs/assets/{hooks.md.C4-moMny.lean.js → hooks.md.Jmb5VOLA.lean.js} +1 -1
- data/docs/assets/{i18n.md.Do9i1qWl.js → i18n.md.xQhiGo1G.js} +2 -2
- data/docs/assets/{i18n.md.Do9i1qWl.lean.js → i18n.md.xQhiGo1G.lean.js} +1 -1
- data/docs/assets/index.md.CAMqGBJE.js +1 -0
- data/docs/assets/index.md.CAMqGBJE.lean.js +1 -0
- data/docs/assets/{instrumentation.md.a9Pjps4P.js → instrumentation.md.BgcaGVYH.js} +2 -2
- data/docs/assets/{instrumentation.md.a9Pjps4P.lean.js → instrumentation.md.BgcaGVYH.lean.js} +1 -1
- data/docs/assets/{javascript.md.GWbhRS51.js → javascript.md.DzrMxUmI.js} +7 -7
- data/docs/assets/{javascript.md.GWbhRS51.lean.js → javascript.md.DzrMxUmI.lean.js} +1 -1
- data/docs/assets/keyword-injection.md.95Zgh2eN.js +21 -0
- data/docs/assets/{keyword-injection.md.Dt2tKREs.lean.js → keyword-injection.md.95Zgh2eN.lean.js} +1 -1
- data/docs/assets/{layouts.md.cPnh3NId.js → layouts.md.CJGDFY-m.js} +2 -15
- data/docs/assets/layouts.md.CJGDFY-m.lean.js +1 -0
- data/docs/assets/{lsp.md.Bsu-f6VU.js → lsp.md.Dn1rIiW0.js} +1 -1
- data/docs/assets/{lsp.md.Bsu-f6VU.lean.js → lsp.md.Dn1rIiW0.lean.js} +1 -1
- data/docs/assets/overview.md.Bdq4qt3L.js +1 -0
- data/docs/assets/overview.md.Bdq4qt3L.lean.js +1 -0
- data/docs/assets/pages.md.B7Hc-i6H.js +45 -0
- data/docs/assets/pages.md.B7Hc-i6H.lean.js +1 -0
- data/docs/assets/recipes_alternate-layouts.md.BwEytl59.js +22 -0
- data/docs/assets/recipes_alternate-layouts.md.BwEytl59.lean.js +1 -0
- data/docs/assets/recipes_authentication.md.Dzvi_g69.js +156 -0
- data/docs/assets/recipes_authentication.md.Dzvi_g69.lean.js +1 -0
- data/docs/assets/recipes_blank-layouts.md.fyAUJyJR.js +15 -0
- data/docs/assets/recipes_blank-layouts.md.fyAUJyJR.lean.js +1 -0
- data/docs/assets/recipes_custom-flash.md.CrQbI5eH.js +26 -0
- data/docs/assets/recipes_custom-flash.md.CrQbI5eH.lean.js +1 -0
- data/docs/assets/recipes_indexed-forms.md.CstYyOSo.js +74 -0
- data/docs/assets/recipes_indexed-forms.md.CstYyOSo.lean.js +1 -0
- data/docs/assets/recipes_text-field-component.md.H4wLAK0Z.js +101 -0
- data/docs/assets/recipes_text-field-component.md.H4wLAK0Z.lean.js +1 -0
- data/docs/assets/routes.md.B8kfUPHU.js +21 -0
- data/docs/assets/{routes.md.BMM7peut.lean.js → routes.md.B8kfUPHU.lean.js} +1 -1
- data/docs/assets/{security.md.C668yXCi.js → security.md.C0G_AZR-.js} +1 -1
- data/docs/assets/{security.md.C668yXCi.lean.js → security.md.C0G_AZR-.lean.js} +1 -1
- data/docs/assets/space-time-continuum.md.xl44xDos.js +1 -0
- data/docs/assets/{space-time-continuum.md.KPUIKysQ.lean.js → space-time-continuum.md.xl44xDos.lean.js} +1 -1
- data/docs/assets/{style.D73IYGCX.css → style.prAgp4yQ.css} +1 -1
- data/docs/assets/tutorial.md.a4a0eVOy.js +1 -0
- data/docs/assets/tutorial.md.a4a0eVOy.lean.js +1 -0
- data/docs/assets/why.md.C-hk5xgJ.js +1 -0
- data/docs/assets/why.md.C-hk5xgJ.lean.js +1 -0
- data/docs/assets.html +12 -7
- data/docs/brut-js/api/AjaxSubmit.html +1 -1
- data/docs/brut-js/api/AjaxSubmit.js.html +1 -1
- 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 +12 -7
- data/docs/business-logic.html +10 -5
- data/docs/cli.html +26 -26
- data/docs/components.html +61 -64
- data/docs/configuration.html +13 -8
- data/docs/css.html +14 -9
- data/docs/custom-element-tests.html +14 -9
- data/docs/database-access.html +12 -7
- data/docs/database-schema.html +15 -10
- data/docs/deployment.html +58 -6
- data/docs/dev-environment.html +12 -7
- data/docs/dir-structure.html +74 -0
- data/docs/doc-conventions.html +11 -6
- data/docs/end-to-end-tests.html +15 -8
- data/docs/favicon.ico +0 -0
- data/docs/features.html +182 -0
- data/docs/flash-and-session.html +73 -82
- data/docs/form-constraints.html +118 -0
- data/docs/forms.html +57 -367
- data/docs/getting-started.html +15 -10
- data/docs/handlers.html +51 -61
- data/docs/hashmap.json +1 -1
- data/docs/hooks.html +14 -9
- data/docs/i18n.html +12 -7
- data/docs/index.html +11 -6
- data/docs/instrumentation.html +12 -7
- data/docs/javascript.html +17 -12
- data/docs/jobs.html +10 -5
- data/docs/keyword-injection.html +22 -21
- data/docs/layouts.html +12 -20
- data/docs/lsp.html +11 -6
- data/docs/markdown-examples.html +10 -5
- data/docs/middleware.html +10 -5
- data/docs/not-released.html +10 -5
- data/docs/overview.html +11 -138
- data/docs/pages.html +49 -121
- data/docs/recipes/alternate-layouts.html +50 -0
- data/docs/recipes/authentication.html +166 -6
- data/docs/recipes/blank-layouts.html +43 -0
- data/docs/recipes/custom-flash.html +54 -0
- data/docs/recipes/indexed-forms.html +102 -0
- data/docs/recipes/text-field-component.html +129 -0
- data/docs/routes.html +16 -19
- data/docs/security.html +11 -6
- data/docs/seed-data.html +10 -5
- data/docs/space-time-continuum.html +11 -6
- data/docs/tutorial.html +11 -6
- data/docs/unit-tests.html +10 -5
- data/docs/why.html +29 -0
- data/dx/bash_customizations +7 -0
- data/dx/build +13 -2
- data/dx/docker-compose.env +1 -1
- data/dx/exec +25 -8
- data/lib/brut/front_end/form.rb +8 -8
- data/lib/brut/front_end/forms/input.rb +253 -20
- data/lib/brut/front_end/forms/input_definition.rb +15 -12
- data/lib/brut/front_end/forms/radio_button_group_input.rb +8 -1
- data/lib/brut/front_end/forms/select_input.rb +8 -1
- data/lib/brut/front_end.rb +1 -0
- data/lib/brut/version.rb +1 -1
- data/specs/brut/front_end/forms/input.spec.rb +978 -0
- data/specs/brut/front_end/forms/radio_button_group_input.spec.rb +54 -0
- data/specs/brut/front_end/forms/select_input.spec.rb +54 -0
- data/specs/spec_helper.rb +27 -0
- data/specs/support/matchers/have_constraint_violation.rb +23 -0
- data/specs/support/matchers.rb +5 -0
- data/specs/support.rb +3 -0
- metadata +141 -77
- data/brutrb.com/public/images/logo-300.png +0 -0
- data/brutrb.com/public/images/logo.png +0 -0
- data/docs/assets/ai.md._6HCDL6d.lean.js +0 -1
- data/docs/assets/chunks/@localSearchIndexroot.CoYzciVi.js +0 -1
- data/docs/assets/components.md.CRUMdRoN.js +0 -104
- data/docs/assets/deployment.md.Dbka4OTr.js +0 -1
- data/docs/assets/deployment.md.Dbka4OTr.lean.js +0 -1
- data/docs/assets/dev-environment.md.GZv6xvi9.js +0 -11
- data/docs/assets/doc-conventions.md.-kN3Xo5C.js +0 -1
- data/docs/assets/doc-conventions.md.-kN3Xo5C.lean.js +0 -1
- data/docs/assets/end-to-end-tests.md.yfQHC0b5.lean.js +0 -1
- data/docs/assets/flash-and-session.md.BXY8RvT0.js +0 -93
- data/docs/assets/forms.md.B-koVgyw.js +0 -379
- data/docs/assets/forms.md.B-koVgyw.lean.js +0 -1
- data/docs/assets/handlers.md.089DVD3v.js +0 -69
- data/docs/assets/handlers.md.089DVD3v.lean.js +0 -1
- data/docs/assets/index.md.B28EwVpq.js +0 -1
- data/docs/assets/index.md.B28EwVpq.lean.js +0 -1
- data/docs/assets/keyword-injection.md.Dt2tKREs.js +0 -25
- data/docs/assets/layouts.md.cPnh3NId.lean.js +0 -1
- data/docs/assets/overview.Da81cB9R.png +0 -0
- data/docs/assets/overview.md.C5wlBcR5.js +0 -133
- data/docs/assets/overview.md.C5wlBcR5.lean.js +0 -1
- data/docs/assets/pages.md.BE3kfOc5.js +0 -122
- data/docs/assets/pages.md.BE3kfOc5.lean.js +0 -1
- data/docs/assets/recipes_authentication.md.CAsXf7hk.js +0 -1
- data/docs/assets/recipes_authentication.md.CAsXf7hk.lean.js +0 -1
- data/docs/assets/routes.md.BMM7peut.js +0 -29
- data/docs/assets/space-time-continuum.md.KPUIKysQ.js +0 -1
- data/docs/assets/tutorial.md.BnoGjrdK.js +0 -1
- data/docs/assets/tutorial.md.BnoGjrdK.lean.js +0 -1
- data/docs/images/logo-300.png +0 -0
- data/docs/images/logo.png +0 -0
- /data/docs/assets/{cli.md.RmeA2b0i.lean.js → cli.md.CjsktgFz.lean.js} +0 -0
- /data/docs/assets/{configuration.md.BGHl8oRC.lean.js → configuration.md.DeyhpqEx.lean.js} +0 -0
- /data/docs/assets/{css.md.DJgj2clw.lean.js → css.md.CltvJqAa.lean.js} +0 -0
- /data/docs/assets/{custom-element-tests.md.BrYJQEl3.lean.js → custom-element-tests.md.B_rbta32.lean.js} +0 -0
- /data/docs/assets/{database-access.md.C7l-Vuvb.lean.js → database-access.md.gnluu54N.lean.js} +0 -0
- /data/docs/assets/{dev-environment.md.GZv6xvi9.lean.js → dev-environment.md.BroAOLhF.lean.js} +0 -0
@@ -1,12 +1,54 @@
|
|
1
|
+
require "uri"
|
2
|
+
|
1
3
|
# An Input is a stateful object representing a specific input and its value
|
2
4
|
# during the course of a form submission process. In particular, it wraps a value
|
3
|
-
# and a {Brut::FrontEnd::Forms::ValidityState}. These are mutable, whereas
|
5
|
+
# and a {Brut::FrontEnd::Forms::ValidityState}. These are mutable, whereas
|
6
|
+
# the wrapped {Brut::FrontEnd::Forms::InputDefinition} is not.
|
7
|
+
#
|
8
|
+
# Of note, this class also initiates the server-execution of the
|
9
|
+
# client-side validations. When a form is posted, and you ask the form
|
10
|
+
# to validate itself, this class examines the value for the inputs
|
11
|
+
# and determines if a client-side violation should've happened in the browser.
|
12
|
+
#
|
13
|
+
# It does its best to mimic the browser's behavior, however there will be
|
14
|
+
# some subtle differences.
|
15
|
+
#
|
16
|
+
# An important considering is that everything is a string. The browser wants
|
17
|
+
# strings and its internal API is string-based. So is this. That said,
|
18
|
+
# this will convert strings into richer types in order to perform constraint
|
19
|
+
# analysis.
|
20
|
+
#
|
21
|
+
# Where possible, standard library classes are used, but in some cases -
|
22
|
+
# colors and times - there is no standard library class.
|
23
|
+
#
|
24
|
+
# See {#typed_value}.
|
4
25
|
class Brut::FrontEnd::Forms::Input
|
5
26
|
|
6
27
|
extend Forwardable
|
7
28
|
|
8
|
-
# @return [String] the input's value
|
29
|
+
# @return [String] the input's value. **DO NOTE** this returns a string. This is
|
30
|
+
# because HTML stores these values as Strings. When using a checkbox, in
|
31
|
+
# particular, the value will *not* be a boolean. You cannot do `if
|
32
|
+
# input.value` and expect that work. See {#typed_value} instead.
|
9
33
|
attr_reader :value
|
34
|
+
|
35
|
+
# @return [Date|Time|BigDecimal|String|true|false|nil] the value of the input,
|
36
|
+
# as coerced to the type used to evaluate constraints.
|
37
|
+
# Returns `nil` if the value could not be coerced (which likely means
|
38
|
+
# there is or will be a constraint violation), **or**
|
39
|
+
# if it is a blank String.
|
40
|
+
#
|
41
|
+
# Of note, `type=color` will return a String here, but that String
|
42
|
+
# should be parseable into a hex code, i.e. #XXXXXX, where each "X"
|
43
|
+
# is 0-9 or a-f. Further, `type=time` will also return a string
|
44
|
+
# in the format HH:MM or HH:MM:SS, where the time is in 24 hour
|
45
|
+
# time. Lastly, `type=datetime-local` will return a `Time`, even
|
46
|
+
# though the control allows the visitor to choose an invalid time.
|
47
|
+
# If there is no `valueMissing` constraint violation, but this
|
48
|
+
# attribute returns `nil`, {#value} *will* return the string sent by
|
49
|
+
# the browser, even if it's not a real timestamp.
|
50
|
+
attr_reader :typed_value
|
51
|
+
|
10
52
|
# @return [Brut::FrontEnd::Forms::ValidityState] Validity state that captures the current constraint violations, if any
|
11
53
|
attr_reader :validity_state
|
12
54
|
|
@@ -20,6 +62,46 @@ class Brut::FrontEnd::Forms::Input
|
|
20
62
|
self.value=(value)
|
21
63
|
end
|
22
64
|
|
65
|
+
# @!method max
|
66
|
+
# @return [String|nil] the value from the `input_definition` given to the
|
67
|
+
# initializer
|
68
|
+
|
69
|
+
# @!method maxlength
|
70
|
+
# @return [String|nil] the value from the `input_definition` given to the
|
71
|
+
# initializer
|
72
|
+
|
73
|
+
# @!method min
|
74
|
+
# @return [String|nil] the value from the `input_definition` given to the
|
75
|
+
# initializer
|
76
|
+
|
77
|
+
# @!method minlength
|
78
|
+
# @return [String|nil] the value from the `input_definition` given to the
|
79
|
+
# initializer
|
80
|
+
|
81
|
+
# @!method name
|
82
|
+
# @return [String|nil] the value from the `input_definition` given to the
|
83
|
+
# initializer
|
84
|
+
|
85
|
+
# @!method pattern
|
86
|
+
# @return [String|nil] the value from the `input_definition` given to the
|
87
|
+
# initializer
|
88
|
+
|
89
|
+
# @!method required
|
90
|
+
# @return [String|nil] the value from the `input_definition` given to the
|
91
|
+
# initializer
|
92
|
+
|
93
|
+
# @!method step
|
94
|
+
# @return [String|nil] the value from the `input_definition` given to the
|
95
|
+
# initializer
|
96
|
+
|
97
|
+
# @!method type
|
98
|
+
# @return [String|nil] the value from the `input_definition` given to the
|
99
|
+
# initializer
|
100
|
+
|
101
|
+
# @!method array?
|
102
|
+
# @return [String|nil] the value from the `input_definition` given to the
|
103
|
+
# initializer
|
104
|
+
|
23
105
|
def_delegators :"@input_definition", :max,
|
24
106
|
:maxlength,
|
25
107
|
:min,
|
@@ -37,14 +119,52 @@ class Brut::FrontEnd::Forms::Input
|
|
37
119
|
#
|
38
120
|
# When this method completes, the value of {#validity_state} could change.
|
39
121
|
#
|
40
|
-
# @param [String] new_value the value for the input.
|
122
|
+
# @param [String] new_value the value for the input. Any non-String value will
|
123
|
+
# be coerced to a String via `to_s`.
|
41
124
|
def value=(new_value)
|
42
|
-
|
43
|
-
|
125
|
+
new_value = new_value.to_s
|
126
|
+
@typed_value = case self.type
|
127
|
+
when "number"
|
128
|
+
BigDecimal(new_value, exception: false)
|
129
|
+
when "range"
|
130
|
+
BigDecimal(new_value, exception: false)
|
131
|
+
when "checkbox"
|
132
|
+
new_value == "true"
|
133
|
+
when "color"
|
134
|
+
Color.from_hex_string(new_value)&.to_s
|
135
|
+
when "date"
|
136
|
+
begin
|
137
|
+
Date.parse(new_value)
|
138
|
+
rescue
|
139
|
+
nil
|
140
|
+
end
|
141
|
+
when "time"
|
142
|
+
TimeOfDay.from_string(new_value)
|
143
|
+
when "datetime-local"
|
144
|
+
begin
|
145
|
+
Time.parse(new_value)
|
146
|
+
rescue
|
147
|
+
nil
|
148
|
+
end
|
149
|
+
when "url"
|
150
|
+
begin
|
151
|
+
URI(new_value)
|
152
|
+
rescue
|
153
|
+
nil
|
154
|
+
end
|
155
|
+
else
|
156
|
+
new_value.strip == "" ? nil : new_value
|
157
|
+
end
|
158
|
+
|
159
|
+
if type == "hidden" || type == "file"
|
160
|
+
@value = new_value
|
161
|
+
return
|
44
162
|
end
|
45
|
-
|
163
|
+
|
164
|
+
value_missing = new_value.strip == "" || @typed_value.nil?
|
165
|
+
|
46
166
|
missing = if self.required
|
47
|
-
value_missing
|
167
|
+
value_missing || @typed_value == false
|
48
168
|
else
|
49
169
|
false
|
50
170
|
end
|
@@ -60,38 +180,81 @@ class Brut::FrontEnd::Forms::Input
|
|
60
180
|
false
|
61
181
|
end
|
62
182
|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
183
|
+
range_overflow = if self.max && !value_missing
|
184
|
+
if type == "date"
|
185
|
+
max_date = Date.parse(self.max)
|
186
|
+
@typed_value > max_date
|
187
|
+
elsif type == "datetime-local"
|
188
|
+
max_date = Time.parse(self.max)
|
189
|
+
@typed_value > max_date
|
190
|
+
elsif type == "time"
|
191
|
+
max_time = TimeOfDay.from_string(self.max)
|
192
|
+
@typed_value > max_time
|
193
|
+
else
|
194
|
+
new_value.to_i > self.max
|
195
|
+
end
|
67
196
|
else
|
68
197
|
false
|
69
198
|
end
|
70
199
|
|
71
|
-
range_underflow = if self.min && !value_missing
|
72
|
-
|
200
|
+
range_underflow = if self.min && !value_missing
|
201
|
+
if type == "date"
|
202
|
+
min_date = Date.parse(self.min)
|
203
|
+
@typed_value < min_date
|
204
|
+
elsif type == "datetime-local"
|
205
|
+
min_date = Time.parse(self.min)
|
206
|
+
@typed_value < min_date
|
207
|
+
elsif type == "time"
|
208
|
+
min_time = TimeOfDay.from_string(self.min)
|
209
|
+
@typed_value < min_time
|
210
|
+
else
|
211
|
+
new_value.to_i < self.min
|
212
|
+
end
|
73
213
|
else
|
74
214
|
false
|
75
215
|
end
|
76
216
|
|
77
|
-
pattern_mismatch = if self.pattern && !value_missing
|
217
|
+
pattern_mismatch = if self.pattern && !value_missing
|
78
218
|
!new_value.match?(Regexp.new(self.pattern))
|
79
|
-
|
80
|
-
|
219
|
+
elsif type == "url" && !value_missing
|
220
|
+
@typed_value.nil? || !@typed_value.absolute
|
81
221
|
end
|
82
|
-
step_mismatch =
|
222
|
+
step_mismatch = if self.step && !value_missing
|
223
|
+
step_big_decimal = BigDecimal(self.step.to_s)
|
224
|
+
if type == "date"
|
225
|
+
basis_date = self.min ? Date.parse(self.min) : Date.parse("1970-01-01")
|
226
|
+
num_days = (@typed_value - basis_date)
|
227
|
+
num_days % step_big_decimal != 0
|
228
|
+
elsif type == "datetime-local"
|
229
|
+
basis_time = self.min ? Time.parse(self.min) : Time.parse("1970-01-01T00:00:00")
|
230
|
+
num_seconds = (@typed_value - basis_time)
|
231
|
+
num_seconds % step_big_decimal != 0
|
232
|
+
elsif type == "time"
|
233
|
+
basis_time = self.min ? TimeOfDay.from_string(self.min) : TimeOfDay.new(hour: 0, minute: 0, second: 0)
|
234
|
+
num_seconds = (@typed_value - basis_time)
|
235
|
+
num_seconds % step_big_decimal != 0
|
236
|
+
else
|
237
|
+
@typed_value % step_big_decimal != 0
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
241
|
+
if type == "range" || type == "color"
|
242
|
+
missing = false
|
243
|
+
elsif type == "time"
|
244
|
+
@typed_value = @typed_value&.to_s
|
245
|
+
end
|
83
246
|
|
84
247
|
@validity_state = Brut::FrontEnd::Forms::ValidityState.new(
|
85
248
|
valueMissing: missing,
|
86
249
|
tooShort: too_short,
|
87
|
-
tooLong:
|
250
|
+
tooLong: too_long,
|
88
251
|
rangeOverflow: range_overflow,
|
89
252
|
rangeUnderflow: range_underflow,
|
90
253
|
patternMismatch: pattern_mismatch,
|
91
254
|
stepMismatch: step_mismatch,
|
92
|
-
typeMismatch:
|
255
|
+
typeMismatch: false,
|
93
256
|
)
|
94
|
-
@value = new_value
|
257
|
+
@value = value_missing ? nil : new_value
|
95
258
|
end
|
96
259
|
|
97
260
|
# Set a server-side constraint violation on this input. This is essentially arbitrary, but note
|
@@ -105,4 +268,74 @@ class Brut::FrontEnd::Forms::Input
|
|
105
268
|
|
106
269
|
# @return [true|false] true if the underlying {#validity_state} has no constraint violations
|
107
270
|
def valid? = @validity_state.valid?
|
271
|
+
|
272
|
+
class Color
|
273
|
+
def self.from_hex_string(string)
|
274
|
+
string = string.to_s
|
275
|
+
if string.match?(/\A#[0-9a-fA-F]{6}\z/)
|
276
|
+
string.downcase
|
277
|
+
else
|
278
|
+
nil
|
279
|
+
end
|
280
|
+
end
|
281
|
+
end
|
282
|
+
|
283
|
+
class TimeOfDay
|
284
|
+
include Comparable
|
285
|
+
def self.from_string(string)
|
286
|
+
match_data = string.match(/\A([01]?[0-9]|2[0-3]):([0-5][0-9])(:([0-5][0-9]))?\z/)
|
287
|
+
if match_data
|
288
|
+
hour = match_data[1].to_i
|
289
|
+
minute = match_data[2].to_i
|
290
|
+
second = match_data[4] ? match_data[4].to_i : nil
|
291
|
+
self.new(hour:, minute:, second:)
|
292
|
+
else
|
293
|
+
nil
|
294
|
+
end
|
295
|
+
end
|
296
|
+
|
297
|
+
def initialize(hour:, minute:, second:)
|
298
|
+
@hour = in_range!(hour, 0, 23, "hour")
|
299
|
+
@minute = in_range!(minute, 0, 59, "minute")
|
300
|
+
@second = second.nil? ? nil : in_range!(second, 0, 59, "second")
|
301
|
+
end
|
302
|
+
|
303
|
+
def to_s
|
304
|
+
if @second.nil?
|
305
|
+
sprintf("%02d:%02d", @hour, @minute)
|
306
|
+
else
|
307
|
+
sprintf("%02d:%02d:%02d", @hour, @minute, @second)
|
308
|
+
end
|
309
|
+
end
|
310
|
+
|
311
|
+
def to_seconds
|
312
|
+
(@second || 0) +
|
313
|
+
(@minute * 60) +
|
314
|
+
(@hour * 3600)
|
315
|
+
end
|
316
|
+
|
317
|
+
def -(other)
|
318
|
+
if other.kind_of?(self.class)
|
319
|
+
self.to_seconds - other.to_seconds
|
320
|
+
else
|
321
|
+
raise ArgumentError, "Cannot subtract #{other.class} from TimeOfDay"
|
322
|
+
end
|
323
|
+
end
|
324
|
+
|
325
|
+
def <=>(other)
|
326
|
+
return -1 if !other.kind_of?(self.class)
|
327
|
+
self.to_seconds <=> other.to_seconds
|
328
|
+
end
|
329
|
+
|
330
|
+
private
|
331
|
+
|
332
|
+
def in_range!(value, min, max, name)
|
333
|
+
if value < min || value > max
|
334
|
+
raise ArgumentError, "Invalid #{name} '#{value}' (must be between #{min} and #{max})"
|
335
|
+
end
|
336
|
+
value
|
337
|
+
rescue ArgumentError => e
|
338
|
+
raise ArgumentError, "Invalid TimeOfDay: #{e.message}"
|
339
|
+
end
|
340
|
+
end
|
108
341
|
end
|
@@ -22,12 +22,11 @@ class Brut::FrontEnd::Forms::InputDefinition
|
|
22
22
|
INPUT_TYPES_TO_CLASS = {
|
23
23
|
"checkbox" => String,
|
24
24
|
"color" => String,
|
25
|
-
"date" =>
|
26
|
-
"datetime-local" =>
|
25
|
+
"date" => Date,
|
26
|
+
"datetime-local" => Time,
|
27
27
|
"email" => String,
|
28
28
|
"file" => String,
|
29
29
|
"hidden" => String,
|
30
|
-
"month" => String,
|
31
30
|
"number" => Numeric,
|
32
31
|
"password" => String,
|
33
32
|
"radio" => String,
|
@@ -35,9 +34,8 @@ class Brut::FrontEnd::Forms::InputDefinition
|
|
35
34
|
"search" => String,
|
36
35
|
"tel" => String,
|
37
36
|
"text" => String,
|
38
|
-
"time" =>
|
37
|
+
"time" => Time, # XXX
|
39
38
|
"url" => String,
|
40
|
-
"week" => String,
|
41
39
|
}
|
42
40
|
|
43
41
|
# Create an InputDefinition. This should very closely mirror
|
@@ -65,7 +63,7 @@ class Brut::FrontEnd::Forms::InputDefinition
|
|
65
63
|
min: nil,
|
66
64
|
minlength: nil,
|
67
65
|
name: nil,
|
68
|
-
pattern:
|
66
|
+
pattern: :based_on_type,
|
69
67
|
required: :based_on_type,
|
70
68
|
step: nil,
|
71
69
|
type: nil,
|
@@ -89,20 +87,25 @@ class Brut::FrontEnd::Forms::InputDefinition
|
|
89
87
|
required = type != "checkbox"
|
90
88
|
end
|
91
89
|
|
92
|
-
@max =
|
90
|
+
@max = max
|
93
91
|
@maxlength = type!( maxlength , Numeric , "maxlength")
|
94
|
-
@min =
|
92
|
+
@min = min
|
95
93
|
@minlength = type!( minlength , Numeric , "minlength")
|
96
94
|
@name = type!( name , String , "name", required: true)
|
97
|
-
@pattern = type!( pattern , String , "pattern")
|
98
95
|
@required = type!( required , [true, false] , "required", required: true)
|
99
96
|
@step = type!( step , Numeric , "step")
|
100
97
|
@type = type!( type , INPUT_TYPES_TO_CLASS.keys , "type", required: true)
|
101
98
|
@array = type!( array , [true, false] , "array", required: true)
|
102
99
|
|
103
|
-
|
104
|
-
|
105
|
-
|
100
|
+
@pattern = if pattern == :based_on_type
|
101
|
+
if type == "email"
|
102
|
+
/^[^@]+@[^@]+\.[^@]+$/.source
|
103
|
+
else
|
104
|
+
nil
|
105
|
+
end
|
106
|
+
else
|
107
|
+
type!( pattern, String, "pattern" )
|
108
|
+
end
|
106
109
|
end
|
107
110
|
|
108
111
|
def array? = @array
|
@@ -4,6 +4,10 @@ class Brut::FrontEnd::Forms::RadioButtonGroupInput
|
|
4
4
|
|
5
5
|
# (see Brut::FrontEnd::Forms::Input#value)
|
6
6
|
attr_reader :value
|
7
|
+
|
8
|
+
# (see Brut::FrontEnd::Forms::Input#typed_value)
|
9
|
+
attr_reader :typed_value
|
10
|
+
|
7
11
|
# (see Brut::FrontEnd::Forms::Input#validity_state)
|
8
12
|
attr_reader :validity_state
|
9
13
|
|
@@ -24,7 +28,10 @@ class Brut::FrontEnd::Forms::RadioButtonGroupInput
|
|
24
28
|
|
25
29
|
# (see Brut::FrontEnd::Forms::Input#value=)
|
26
30
|
def value=(new_value)
|
27
|
-
|
31
|
+
new_value = new_value.to_s
|
32
|
+
@typed_value = new_value.strip == "" ? nil : new_value
|
33
|
+
|
34
|
+
value_missing = @typed_value.nil?
|
28
35
|
missing = if self.required
|
29
36
|
value_missing
|
30
37
|
else
|
@@ -5,6 +5,10 @@ class Brut::FrontEnd::Forms::SelectInput
|
|
5
5
|
|
6
6
|
# (see Brut::FrontEnd::Forms::Input#value)
|
7
7
|
attr_reader :value
|
8
|
+
|
9
|
+
# (see Brut::FrontEnd::Forms::Input#typed_value)
|
10
|
+
attr_reader :typed_value
|
11
|
+
|
8
12
|
# (see Brut::FrontEnd::Forms::Input#validity_state)
|
9
13
|
attr_reader :validity_state
|
10
14
|
|
@@ -22,7 +26,10 @@ class Brut::FrontEnd::Forms::SelectInput
|
|
22
26
|
|
23
27
|
# (see Brut::FrontEnd::Forms::Input#value=)
|
24
28
|
def value=(new_value)
|
25
|
-
|
29
|
+
new_value = new_value.to_s
|
30
|
+
@typed_value = new_value.strip == "" ? nil : new_value
|
31
|
+
|
32
|
+
value_missing = @typed_value.nil?
|
26
33
|
missing = if self.required
|
27
34
|
value_missing
|
28
35
|
else
|
data/lib/brut/front_end.rb
CHANGED
@@ -12,6 +12,7 @@ module Brut::FrontEnd
|
|
12
12
|
autoload(:Download, "brut/front_end/download")
|
13
13
|
autoload(:Flash, "brut/front_end/flash")
|
14
14
|
autoload(:Form, "brut/front_end/form")
|
15
|
+
autoload(:Forms, "brut/front_end/form")
|
15
16
|
autoload(:GenericResponse, "brut/front_end/generic_response")
|
16
17
|
autoload(:Handler, "brut/front_end/handler")
|
17
18
|
autoload(:Handlers, "brut/front_end/handler")
|
data/lib/brut/version.rb
CHANGED