tina4ruby 0.4.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.
Files changed (74) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +80 -0
  3. data/LICENSE.txt +21 -0
  4. data/README.md +768 -0
  5. data/exe/tina4 +4 -0
  6. data/lib/tina4/api.rb +152 -0
  7. data/lib/tina4/auth.rb +139 -0
  8. data/lib/tina4/cli.rb +349 -0
  9. data/lib/tina4/crud.rb +124 -0
  10. data/lib/tina4/database.rb +135 -0
  11. data/lib/tina4/database_result.rb +89 -0
  12. data/lib/tina4/debug.rb +83 -0
  13. data/lib/tina4/dev.rb +15 -0
  14. data/lib/tina4/dev_reload.rb +68 -0
  15. data/lib/tina4/drivers/firebird_driver.rb +94 -0
  16. data/lib/tina4/drivers/mssql_driver.rb +112 -0
  17. data/lib/tina4/drivers/mysql_driver.rb +90 -0
  18. data/lib/tina4/drivers/postgres_driver.rb +99 -0
  19. data/lib/tina4/drivers/sqlite_driver.rb +85 -0
  20. data/lib/tina4/env.rb +55 -0
  21. data/lib/tina4/field_types.rb +84 -0
  22. data/lib/tina4/graphql.rb +837 -0
  23. data/lib/tina4/localization.rb +100 -0
  24. data/lib/tina4/middleware.rb +59 -0
  25. data/lib/tina4/migration.rb +124 -0
  26. data/lib/tina4/orm.rb +168 -0
  27. data/lib/tina4/public/css/tina4.css +2286 -0
  28. data/lib/tina4/public/css/tina4.min.css +2 -0
  29. data/lib/tina4/public/js/tina4.js +134 -0
  30. data/lib/tina4/public/js/tina4helper.js +387 -0
  31. data/lib/tina4/queue.rb +117 -0
  32. data/lib/tina4/queue_backends/kafka_backend.rb +80 -0
  33. data/lib/tina4/queue_backends/lite_backend.rb +79 -0
  34. data/lib/tina4/queue_backends/rabbitmq_backend.rb +73 -0
  35. data/lib/tina4/rack_app.rb +150 -0
  36. data/lib/tina4/request.rb +158 -0
  37. data/lib/tina4/response.rb +172 -0
  38. data/lib/tina4/router.rb +148 -0
  39. data/lib/tina4/scss/tina4css/_alerts.scss +34 -0
  40. data/lib/tina4/scss/tina4css/_badges.scss +22 -0
  41. data/lib/tina4/scss/tina4css/_buttons.scss +69 -0
  42. data/lib/tina4/scss/tina4css/_cards.scss +49 -0
  43. data/lib/tina4/scss/tina4css/_forms.scss +156 -0
  44. data/lib/tina4/scss/tina4css/_grid.scss +81 -0
  45. data/lib/tina4/scss/tina4css/_modals.scss +84 -0
  46. data/lib/tina4/scss/tina4css/_nav.scss +149 -0
  47. data/lib/tina4/scss/tina4css/_reset.scss +94 -0
  48. data/lib/tina4/scss/tina4css/_tables.scss +54 -0
  49. data/lib/tina4/scss/tina4css/_typography.scss +55 -0
  50. data/lib/tina4/scss/tina4css/_utilities.scss +197 -0
  51. data/lib/tina4/scss/tina4css/_variables.scss +117 -0
  52. data/lib/tina4/scss/tina4css/base.scss +1 -0
  53. data/lib/tina4/scss/tina4css/colors.scss +48 -0
  54. data/lib/tina4/scss/tina4css/tina4.scss +17 -0
  55. data/lib/tina4/scss_compiler.rb +131 -0
  56. data/lib/tina4/seeder.rb +529 -0
  57. data/lib/tina4/session.rb +145 -0
  58. data/lib/tina4/session_handlers/file_handler.rb +55 -0
  59. data/lib/tina4/session_handlers/mongo_handler.rb +49 -0
  60. data/lib/tina4/session_handlers/redis_handler.rb +43 -0
  61. data/lib/tina4/swagger.rb +123 -0
  62. data/lib/tina4/template.rb +478 -0
  63. data/lib/tina4/templates/base.twig +26 -0
  64. data/lib/tina4/templates/errors/403.twig +22 -0
  65. data/lib/tina4/templates/errors/404.twig +22 -0
  66. data/lib/tina4/templates/errors/500.twig +22 -0
  67. data/lib/tina4/testing.rb +213 -0
  68. data/lib/tina4/version.rb +5 -0
  69. data/lib/tina4/webserver.rb +101 -0
  70. data/lib/tina4/websocket.rb +167 -0
  71. data/lib/tina4/wsdl.rb +164 -0
  72. data/lib/tina4.rb +259 -0
  73. data/lib/tina4ruby.rb +4 -0
  74. metadata +324 -0
@@ -0,0 +1,2 @@
1
+ /* Tina4 CSS v2.0.0 | MIT | tina4stack/tina4-css */
2
+ *,*::before,*::after{box-sizing:border-box}html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;tab-size:4;scroll-behavior:smooth}body{margin:0;font-family:system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;font-size:1rem;font-weight:400;line-height:1.5;color:#212529;background-color:#fff;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:0.5rem;font-weight:500;line-height:1.25}p{margin-top:0;margin-bottom:1rem}ol,ul{padding-left:2rem;margin-top:0;margin-bottom:1rem}a{color:#4a90d9;text-decoration:none}a:hover{color:#256ab1;text-decoration:underline}img,svg{max-width:100%;height:auto;vertical-align:middle}table{border-collapse:collapse;caption-side:bottom}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,select{text-transform:none}button,[type="button"],[type="reset"],[type="submit"]{-webkit-appearance:button;cursor:pointer}hr{margin:1rem 0;color:inherit;border:0;border-top:1px solid rgba(0,0,0,0.1);opacity:0.25}pre,code,kbd,samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:1em}pre{margin-top:0;margin-bottom:1rem;overflow:auto}[hidden]{display:none !important}h1{font-size:2.25rem}h2{font-size:1.875rem}h3{font-size:1.5rem}h4{font-size:1.25rem}h5{font-size:1.125rem}h6{font-size:1rem}small,.small{font-size:0.875em}mark,.mark{padding:0.2em;background-color:rgba(255,193,7,0.3)}blockquote{margin:0 0 1rem;padding:0.5rem 1rem;border-left:0.25rem solid rgba(0,0,0,0.1);font-size:1.125rem}code{font-size:0.875em;color:#dc3545;word-wrap:break-word}pre{display:block;padding:1rem;font-size:0.875em;color:#212529;background-color:#f8f9fa;border-radius:.25rem}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;list-style:none}.list-inline-item{display:inline-block}.list-inline-item:not(:last-child){margin-right:0.5rem}.container,.container-fluid{width:100%;padding-right:calc($grid-gutter / 2);padding-left:calc($grid-gutter / 2);margin-right:auto;margin-left:auto}@media (min-width: 576px){.container{max-width:540px}}@media (min-width: 768px){.container{max-width:720px}}@media (min-width: 992px){.container{max-width:960px}}@media (min-width: 1200px){.container{max-width:1140px}}@media (min-width: 1400px){.container{max-width:1320px}}.row{display:flex;flex-wrap:wrap;margin-right:calc($grid-gutter / -2);margin-left:calc($grid-gutter / -2)}.row>*{flex-shrink:0;width:100%;max-width:100%;padding-right:calc($grid-gutter / 2);padding-left:calc($grid-gutter / 2)}.col{flex:1 0 0%}.col-1{flex:0 0 auto;width:8.33333%}.col-2{flex:0 0 auto;width:16.66667%}.col-3{flex:0 0 auto;width:25%}.col-4{flex:0 0 auto;width:33.33333%}.col-5{flex:0 0 auto;width:41.66667%}.col-6{flex:0 0 auto;width:50%}.col-7{flex:0 0 auto;width:58.33333%}.col-8{flex:0 0 auto;width:66.66667%}.col-9{flex:0 0 auto;width:75%}.col-10{flex:0 0 auto;width:83.33333%}.col-11{flex:0 0 auto;width:91.66667%}.col-12{flex:0 0 auto;width:100%}.offset-1{margin-left:8.33333%}.offset-2{margin-left:16.66667%}.offset-3{margin-left:25%}.offset-4{margin-left:33.33333%}.offset-0{margin-left:0}@media (min-width: 576px){.col-sm{flex:1 0 0%}.col-sm-1{flex:0 0 auto;width:8.33333%}.col-sm-2{flex:0 0 auto;width:16.66667%}.col-sm-3{flex:0 0 auto;width:25%}.col-sm-4{flex:0 0 auto;width:33.33333%}.col-sm-5{flex:0 0 auto;width:41.66667%}.col-sm-6{flex:0 0 auto;width:50%}.col-sm-7{flex:0 0 auto;width:58.33333%}.col-sm-8{flex:0 0 auto;width:66.66667%}.col-sm-9{flex:0 0 auto;width:75%}.col-sm-10{flex:0 0 auto;width:83.33333%}.col-sm-11{flex:0 0 auto;width:91.66667%}.col-sm-12{flex:0 0 auto;width:100%}.offset-sm-0{margin-left:0}}@media (min-width: 768px){.col-md{flex:1 0 0%}.col-md-1{flex:0 0 auto;width:8.33333%}.col-md-2{flex:0 0 auto;width:16.66667%}.col-md-3{flex:0 0 auto;width:25%}.col-md-4{flex:0 0 auto;width:33.33333%}.col-md-5{flex:0 0 auto;width:41.66667%}.col-md-6{flex:0 0 auto;width:50%}.col-md-7{flex:0 0 auto;width:58.33333%}.col-md-8{flex:0 0 auto;width:66.66667%}.col-md-9{flex:0 0 auto;width:75%}.col-md-10{flex:0 0 auto;width:83.33333%}.col-md-11{flex:0 0 auto;width:91.66667%}.col-md-12{flex:0 0 auto;width:100%}.offset-md-0{margin-left:0}}@media (min-width: 992px){.col-lg{flex:1 0 0%}.col-lg-1{flex:0 0 auto;width:8.33333%}.col-lg-2{flex:0 0 auto;width:16.66667%}.col-lg-3{flex:0 0 auto;width:25%}.col-lg-4{flex:0 0 auto;width:33.33333%}.col-lg-5{flex:0 0 auto;width:41.66667%}.col-lg-6{flex:0 0 auto;width:50%}.col-lg-7{flex:0 0 auto;width:58.33333%}.col-lg-8{flex:0 0 auto;width:66.66667%}.col-lg-9{flex:0 0 auto;width:75%}.col-lg-10{flex:0 0 auto;width:83.33333%}.col-lg-11{flex:0 0 auto;width:91.66667%}.col-lg-12{flex:0 0 auto;width:100%}.offset-lg-0{margin-left:0}}@media (min-width: 1200px){.col-xl{flex:1 0 0%}.col-xl-1{flex:0 0 auto;width:8.33333%}.col-xl-2{flex:0 0 auto;width:16.66667%}.col-xl-3{flex:0 0 auto;width:25%}.col-xl-4{flex:0 0 auto;width:33.33333%}.col-xl-5{flex:0 0 auto;width:41.66667%}.col-xl-6{flex:0 0 auto;width:50%}.col-xl-7{flex:0 0 auto;width:58.33333%}.col-xl-8{flex:0 0 auto;width:66.66667%}.col-xl-9{flex:0 0 auto;width:75%}.col-xl-10{flex:0 0 auto;width:83.33333%}.col-xl-11{flex:0 0 auto;width:91.66667%}.col-xl-12{flex:0 0 auto;width:100%}.offset-xl-0{margin-left:0}}.justify-start{justify-content:flex-start}.justify-center{justify-content:center}.justify-end{justify-content:flex-end}.justify-between{justify-content:space-between}.justify-around{justify-content:space-around}.align-start{align-items:flex-start}.align-center{align-items:center}.align-end{align-items:flex-end}.btn{display:inline-block;font-weight:400;line-height:1.5;color:#212529;text-align:center;vertical-align:middle;cursor:pointer;user-select:none;background-color:transparent;border:1px solid transparent;padding:0.375rem 0.75rem;font-size:1rem;border-radius:.25rem;transition:color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;text-decoration:none}.btn:disabled,.btn.disabled{pointer-events:none;opacity:0.65}.btn-primary{color:#fff;background-color:#4a90d9;border-color:#4a90d9}.btn-primary:hover{background-color:#2b7bcf;border-color:#2a76c6}.btn-secondary{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-secondary:hover{background-color:#596167;border-color:#545b62}.btn-success{color:#fff;background-color:#28a745;border-color:#28a745}.btn-success:hover{background-color:#208637;border-color:#1e7e34}.btn-danger{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-danger:hover{background-color:#c62232;border-color:#bd2130}.btn-warning{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-warning:hover{background-color:#dda600;border-color:#d39e00}.btn-info{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-info:hover{background-color:#128294;border-color:#117a8b}.btn-light{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-light:hover{background-color:#e0e5e9;border-color:#dae0e5}.btn-dark{color:#fff;background-color:#212529;border-color:#212529}.btn-dark:hover{background-color:#0f1112;border-color:#0a0c0d}.btn-outline-primary{color:#4a90d9;border-color:#4a90d9;background-color:transparent}.btn-outline-primary:hover{color:#fff;background-color:#4a90d9}.btn-outline-secondary{color:#6c757d;border-color:#6c757d;background-color:transparent}.btn-outline-secondary:hover{color:#fff;background-color:#6c757d}.btn-outline-success{color:#28a745;border-color:#28a745;background-color:transparent}.btn-outline-success:hover{color:#fff;background-color:#28a745}.btn-outline-danger{color:#dc3545;border-color:#dc3545;background-color:transparent}.btn-outline-danger:hover{color:#fff;background-color:#dc3545}.btn-outline-warning{color:#ffc107;border-color:#ffc107;background-color:transparent}.btn-outline-warning:hover{color:#212529;background-color:#ffc107}.btn-outline-info{color:#17a2b8;border-color:#17a2b8;background-color:transparent}.btn-outline-info:hover{color:#fff;background-color:#17a2b8}.btn-outline-light{color:#f8f9fa;border-color:#f8f9fa;background-color:transparent}.btn-outline-light:hover{color:#212529;background-color:#f8f9fa}.btn-outline-dark{color:#212529;border-color:#212529;background-color:transparent}.btn-outline-dark:hover{color:#fff;background-color:#212529}.btn-sm{padding:0.25rem 0.5rem;font-size:.875rem;border-radius:.25rem}.btn-lg{padding:0.5rem 1rem;font-size:1.125rem;border-radius:.5rem}.btn-block{display:block;width:100%}.form-group{margin-bottom:1rem}.form-label{display:inline-block;margin-bottom:0.5rem;font-weight:700}.form-control{display:block;width:100%;padding:0.375rem 0.75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#212529;background-color:#fff;background-clip:padding-box;border:1px solid #ced4da;border-radius:.25rem;transition:border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;appearance:none}.form-control:focus{color:#212529;background-color:#fff;border-color:#b3d1ef;outline:0;box-shadow:0 0 0 0.2rem rgba(74,144,217,0.25)}.form-control::placeholder{color:#6c757d;opacity:1}.form-control:disabled{background-color:#f8f9fa;opacity:1}textarea.form-control{min-height:calc(1.5em + 0.75rem + 2px)}.form-select{display:block;width:100%;padding:0.375rem 2.25rem 0.375rem 0.75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#212529;background-color:#fff;border:1px solid #ced4da;border-radius:.25rem;transition:border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;appearance:none}.form-select:focus{border-color:#b3d1ef;outline:0;box-shadow:0 0 0 0.2rem rgba(74,144,217,0.25)}.form-check{display:block;min-height:1.5rem;padding-left:1.5em;margin-bottom:0.125rem}.form-check-input{float:left;width:1em;height:1em;margin-left:-1.5em;margin-top:0.25em;appearance:none;background-color:#fff;border:1px solid rgba(0,0,0,0.25)}.form-check-input[type="checkbox"]{border-radius:0.25em}.form-check-input[type="radio"]{border-radius:50%}.form-check-input:checked{background-color:#4a90d9;border-color:#4a90d9}.form-check-input:focus{border-color:#b3d1ef;outline:0;box-shadow:0 0 0 0.2rem rgba(74,144,217,0.25)}.form-check-label{cursor:pointer}.is-valid{border-color:#28a745 !important}.is-valid:focus{box-shadow:0 0 0 0.2rem rgba(40,167,69,0.25) !important}.is-invalid{border-color:#dc3545 !important}.is-invalid:focus{box-shadow:0 0 0 0.2rem rgba(220,53,69,0.25) !important}.valid-feedback,.invalid-feedback{display:none;width:100%;margin-top:0.25rem;font-size:0.875em}.valid-feedback{color:#28a745}.invalid-feedback{color:#dc3545}.is-valid~.valid-feedback,.is-invalid~.invalid-feedback{display:block}.input-group{position:relative;display:flex;flex-wrap:wrap;align-items:stretch;width:100%}.input-group>.form-control,.input-group>.form-select{position:relative;flex:1 1 auto;width:1%;min-width:0}.input-group-text{display:flex;align-items:center;padding:0.375rem 0.75rem;font-size:1rem;line-height:1.5;color:#212529;text-align:center;white-space:nowrap;background-color:#f8f9fa;border:1px solid #ced4da;border-radius:.25rem}.card{position:relative;display:flex;flex-direction:column;min-width:0;word-wrap:break-word;background-color:#fff;background-clip:border-box;border:1px solid rgba(0,0,0,0.125);border-radius:.5rem}.card-header{padding:0.75rem 1rem;margin-bottom:0;background-color:rgba(0,0,0,0.03);border-bottom:1px solid rgba(0,0,0,0.125)}.card-header:first-child{border-radius:calc($border-radius-lg - 1px) calc($border-radius-lg - 1px) 0 0}.card-body{flex:1 1 auto;padding:1rem}.card-footer{padding:0.75rem 1rem;background-color:rgba(0,0,0,0.03);border-top:1px solid rgba(0,0,0,0.125)}.card-footer:last-child{border-radius:0 0 calc($border-radius-lg - 1px) calc($border-radius-lg - 1px)}.card-title{margin-bottom:0.5rem;font-weight:700}.card-text:last-child{margin-bottom:0}.card-img-top{width:100%;border-top-left-radius:calc($border-radius-lg - 1px);border-top-right-radius:calc($border-radius-lg - 1px)}.nav{display:flex;flex-wrap:wrap;padding-left:0;margin-bottom:0;list-style:none}.nav-link{display:block;padding:0.5rem 1rem;color:#4a90d9;text-decoration:none;transition:color 0.15s ease-in-out, background-color 0.15s ease-in-out}.nav-link:hover,.nav-link:focus{color:#256ab1}.nav-link.disabled{color:#6c757d;pointer-events:none;cursor:default}.nav-link.active{color:#4a90d9;font-weight:700}.navbar{position:relative;display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;padding:0.5rem 1rem}.navbar-brand{padding-top:0.3125rem;padding-bottom:0.3125rem;margin-right:1rem;font-size:1.25rem;font-weight:700;color:inherit;text-decoration:none;white-space:nowrap}.navbar-brand:hover{color:inherit;opacity:0.8}.navbar-nav{display:flex;flex-direction:column;padding-left:0;margin-bottom:0;list-style:none}.navbar-nav .nav-link{padding-right:0.5rem;padding-left:0.5rem}.navbar-toggler{padding:0.25rem 0.75rem;font-size:1.125rem;line-height:1;background-color:transparent;border:1px solid rgba(0,0,0,0.1);border-radius:.25rem;cursor:pointer}.navbar-toggler:focus{outline:0;box-shadow:0 0 0 0.2rem rgba(74,144,217,0.25)}.navbar-collapse{flex-basis:100%;flex-grow:1;align-items:center;display:none}.navbar-collapse.show{display:flex}@media (min-width: 576px){.navbar-expand-sm .navbar-nav{flex-direction:row}.navbar-expand-sm .navbar-collapse{display:flex;flex-basis:auto}.navbar-expand-sm .navbar-toggler{display:none}}@media (min-width: 768px){.navbar-expand-md .navbar-nav{flex-direction:row}.navbar-expand-md .navbar-collapse{display:flex;flex-basis:auto}.navbar-expand-md .navbar-toggler{display:none}}@media (min-width: 992px){.navbar-expand-lg .navbar-nav{flex-direction:row}.navbar-expand-lg .navbar-collapse{display:flex;flex-basis:auto}.navbar-expand-lg .navbar-toggler{display:none}}@media (min-width: 1200px){.navbar-expand-xl .navbar-nav{flex-direction:row}.navbar-expand-xl .navbar-collapse{display:flex;flex-basis:auto}.navbar-expand-xl .navbar-toggler{display:none}}@media (min-width: 1400px){.navbar-expand-xxl .navbar-nav{flex-direction:row}.navbar-expand-xxl .navbar-collapse{display:flex;flex-basis:auto}.navbar-expand-xxl .navbar-toggler{display:none}}@media (min-width: 992px){.navbar-nav{flex-direction:row}.navbar-collapse{display:flex;flex-basis:auto}.navbar-toggler{display:none}}.navbar-dark{color:#fff;background-color:#212529}.navbar-dark .navbar-brand{color:#fff}.navbar-dark .nav-link{color:rgba(255,255,255,0.75)}.navbar-dark .nav-link:hover,.navbar-dark .nav-link.active{color:#fff}.navbar-light{background-color:#f8f9fa}.navbar-light .nav-link{color:rgba(0,0,0,0.55)}.navbar-light .nav-link:hover,.navbar-light .nav-link.active{color:rgba(0,0,0,0.9)}.breadcrumb{display:flex;flex-wrap:wrap;padding:0.5rem 1rem;margin-bottom:1rem;list-style:none;background-color:#f8f9fa;border-radius:.25rem}.breadcrumb-item+.breadcrumb-item::before{display:inline-block;padding-right:0.5rem;padding-left:0.5rem;color:#6c757d;content:"/"}.breadcrumb-item.active{color:#6c757d}.modal{position:fixed;top:0;left:0;z-index:1050;display:none;width:100%;height:100%;overflow-x:hidden;overflow-y:auto;outline:0}.modal.show{display:block}.modal-dialog{position:relative;width:auto;margin:1.75rem auto;max-width:500px}.modal-sm{max-width:300px}.modal-lg{max-width:800px}.modal-content{position:relative;display:flex;flex-direction:column;width:100%;pointer-events:auto;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,0.2);border-radius:.5rem;box-shadow:0 0.5rem 1rem rgba(0,0,0,0.15);outline:0}.modal-header{display:flex;flex-shrink:0;align-items:center;justify-content:space-between;padding:1rem;border-bottom:1px solid rgba(0,0,0,0.1)}.modal-title{margin-bottom:0;font-weight:700}.modal-body{position:relative;flex:1 1 auto;padding:1rem}.modal-footer{display:flex;flex-shrink:0;flex-wrap:wrap;align-items:center;justify-content:flex-end;padding:0.75rem;border-top:1px solid rgba(0,0,0,0.1);gap:0.5rem}.modal-backdrop{position:fixed;top:0;left:0;z-index:1040;width:100vw;height:100vh;background-color:rgba(0,0,0,0.5);display:none}.modal-backdrop.show{display:block}.alert{position:relative;padding:0.75rem 1rem;margin-bottom:1rem;border:1px solid transparent;border-radius:.25rem}.alert-primary{color:#1c5187;background-color:#deeaf8;border-color:#b3d1ef}.alert-secondary{color:#313539;background-color:#caced1;border-color:#afb5ba}.alert-success{color:#0f401b;background-color:#9be7ac;border-color:#71dd8a}.alert-danger{color:#7c151f;background-color:#f6cdd1;border-color:#efa2a9}.alert-warning{color:#876500;background-color:#ffeeba;border-color:#ffe187}.alert-info{color:#093e47;background-color:#90e4f1;border-color:#63d9ec}.alert-light{color:#aeb9c5;background-color:#fff;border-color:#fff}.alert-dark{color:#000;background-color:#717e8c;border-color:#5a6570}.alert-dismissible{padding-right:3rem}.alert-dismissible .btn-close{position:absolute;top:0;right:0;padding:0.9375rem 1rem;background:transparent;border:0;cursor:pointer;color:inherit;opacity:0.5}.alert-dismissible .btn-close:hover{opacity:0.75}.table{width:100%;margin-bottom:1rem;vertical-align:top;border-color:rgba(0,0,0,0.1)}.table>:not(caption)>*>*{padding:0.5rem;border-bottom-width:1px;border-bottom-style:solid;border-bottom-color:inherit}.table>thead{vertical-align:bottom;border-bottom:2px solid currentColor}.table>tbody>tr:last-child>*{border-bottom-color:transparent}.table-sm>:not(caption)>*>*{padding:0.25rem}.table-bordered{border:1px solid rgba(0,0,0,0.1)}.table-bordered>:not(caption)>*>*{border-width:1px;border-style:solid;border-color:inherit}.table-striped>tbody>tr:nth-of-type(odd)>*{background-color:rgba(0,0,0,0.02)}.table-hover>tbody>tr:hover>*{background-color:rgba(0,0,0,0.04)}.table-responsive{overflow-x:auto;-webkit-overflow-scrolling:touch}.badge{display:inline-block;padding:0.25em 0.65em;font-size:0.75em;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:50rem}.badge-primary{color:#fff;background-color:#4a90d9}.badge-secondary{color:#fff;background-color:#6c757d}.badge-success{color:#fff;background-color:#28a745}.badge-danger{color:#fff;background-color:#dc3545}.badge-warning{color:#212529;background-color:#ffc107}.badge-info{color:#fff;background-color:#17a2b8}.badge-light{color:#212529;background-color:#f8f9fa}.badge-dark{color:#fff;background-color:#212529}.d-none{display:none !important}.d-block{display:block !important}.d-flex{display:flex !important}.d-inline{display:inline !important}.d-inline-block{display:inline-block !important}.d-grid{display:grid !important}@media (min-width: 576px){.d-sm-none{display:none !important}.d-sm-block{display:block !important}.d-sm-flex{display:flex !important}.d-sm-inline{display:inline !important}.d-sm-inline-block{display:inline-block !important}}@media (min-width: 768px){.d-md-none{display:none !important}.d-md-block{display:block !important}.d-md-flex{display:flex !important}.d-md-inline{display:inline !important}.d-md-inline-block{display:inline-block !important}}@media (min-width: 992px){.d-lg-none{display:none !important}.d-lg-block{display:block !important}.d-lg-flex{display:flex !important}.d-lg-inline{display:inline !important}.d-lg-inline-block{display:inline-block !important}}.flex-row{flex-direction:row !important}.flex-column{flex-direction:column !important}.flex-wrap{flex-wrap:wrap !important}.flex-nowrap{flex-wrap:nowrap !important}.flex-grow-0{flex-grow:0 !important}.flex-grow-1{flex-grow:1 !important}.justify-content-start{justify-content:flex-start !important}.justify-content-end{justify-content:flex-end !important}.justify-content-center{justify-content:center !important}.justify-content-between{justify-content:space-between !important}.justify-content-around{justify-content:space-around !important}.align-items-start{align-items:flex-start !important}.align-items-end{align-items:flex-end !important}.align-items-center{align-items:center !important}.gap-0{gap:0 !important}.gap-1{gap:.25rem !important}.gap-2{gap:.5rem !important}.gap-3{gap:1rem !important}.gap-4{gap:1.5rem !important}.gap-5{gap:3rem !important}.m-0{margin:0 !important}.m-1{margin:.25rem !important}.m-2{margin:.5rem !important}.m-3{margin:1rem !important}.m-4{margin:1.5rem !important}.m-5{margin:3rem !important}.mt-0{margin-top:0 !important}.mt-1{margin-top:.25rem !important}.mt-2{margin-top:.5rem !important}.mt-3{margin-top:1rem !important}.mt-4{margin-top:1.5rem !important}.mt-5{margin-top:3rem !important}.mb-0{margin-bottom:0 !important}.mb-1{margin-bottom:.25rem !important}.mb-2{margin-bottom:.5rem !important}.mb-3{margin-bottom:1rem !important}.mb-4{margin-bottom:1.5rem !important}.mb-5{margin-bottom:3rem !important}.ms-0{margin-left:0 !important}.ms-1{margin-left:.25rem !important}.ms-2{margin-left:.5rem !important}.ms-3{margin-left:1rem !important}.ms-4{margin-left:1.5rem !important}.ms-5{margin-left:3rem !important}.me-0{margin-right:0 !important}.me-1{margin-right:.25rem !important}.me-2{margin-right:.5rem !important}.me-3{margin-right:1rem !important}.me-4{margin-right:1.5rem !important}.me-5{margin-right:3rem !important}.p-0{padding:0 !important}.p-1{padding:.25rem !important}.p-2{padding:.5rem !important}.p-3{padding:1rem !important}.p-4{padding:1.5rem !important}.p-5{padding:3rem !important}.pt-0{padding-top:0 !important}.pt-1{padding-top:.25rem !important}.pt-2{padding-top:.5rem !important}.pt-3{padding-top:1rem !important}.pt-4{padding-top:1.5rem !important}.pt-5{padding-top:3rem !important}.pb-0{padding-bottom:0 !important}.pb-1{padding-bottom:.25rem !important}.pb-2{padding-bottom:.5rem !important}.pb-3{padding-bottom:1rem !important}.pb-4{padding-bottom:1.5rem !important}.pb-5{padding-bottom:3rem !important}.ps-0{padding-left:0 !important}.ps-1{padding-left:.25rem !important}.ps-2{padding-left:.5rem !important}.ps-3{padding-left:1rem !important}.ps-4{padding-left:1.5rem !important}.ps-5{padding-left:3rem !important}.pe-0{padding-right:0 !important}.pe-1{padding-right:.25rem !important}.pe-2{padding-right:.5rem !important}.pe-3{padding-right:1rem !important}.pe-4{padding-right:1.5rem !important}.pe-5{padding-right:3rem !important}.mx-auto{margin-left:auto !important;margin-right:auto !important}.text-start{text-align:left !important}.text-center{text-align:center !important}.text-end{text-align:right !important}.text-uppercase{text-transform:uppercase !important}.text-capitalize{text-transform:capitalize !important}.text-nowrap{white-space:nowrap !important}.text-truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fw-bold{font-weight:700 !important}.fw-normal{font-weight:400 !important}.fw-light{font-weight:300 !important}.fs-1{font-size:2.25rem !important}.fs-2{font-size:1.875rem !important}.fs-3{font-size:1.5rem !important}.fs-4{font-size:1.25rem !important}.fs-5{font-size:1rem !important}.fs-6{font-size:.875rem !important}.text-primary{color:#4a90d9 !important}.text-secondary{color:#6c757d !important}.text-success{color:#28a745 !important}.text-danger{color:#dc3545 !important}.text-warning{color:#ffc107 !important}.text-info{color:#17a2b8 !important}.text-light{color:#f8f9fa !important}.text-dark{color:#212529 !important}.text-muted{color:#6c757d !important}.text-white{color:#fff !important}.bg-primary{background-color:#4a90d9 !important}.bg-secondary{background-color:#6c757d !important}.bg-success{background-color:#28a745 !important}.bg-danger{background-color:#dc3545 !important}.bg-warning{background-color:#ffc107 !important}.bg-info{background-color:#17a2b8 !important}.bg-light{background-color:#f8f9fa !important}.bg-dark{background-color:#212529 !important}.bg-white{background-color:#fff !important}.border{border:1px solid rgba(0,0,0,0.1) !important}.border-0{border:0 !important}.rounded{border-radius:.25rem !important}.rounded-0{border-radius:0 !important}.rounded-circle{border-radius:50% !important}.rounded-pill{border-radius:50rem !important}.w-25{width:25% !important}.h-25{height:25% !important}.w-50{width:50% !important}.h-50{height:50% !important}.w-75{width:75% !important}.h-75{height:75% !important}.w-100{width:100% !important}.h-100{height:100% !important}.mw-100{max-width:100% !important}.mh-100{max-height:100% !important}.position-relative{position:relative !important}.position-absolute{position:absolute !important}.position-fixed{position:fixed !important}.position-sticky{position:sticky !important}.position-static{position:static !important}.visible{visibility:visible !important}.invisible{visibility:hidden !important}.overflow-hidden{overflow:hidden !important}.overflow-auto{overflow:auto !important}.shadow{box-shadow:0 0.5rem 1rem rgba(0,0,0,0.15) !important}.shadow-sm{box-shadow:0 0.125rem 0.25rem rgba(0,0,0,0.075) !important}.shadow-lg{box-shadow:0 1rem 3rem rgba(0,0,0,0.175) !important}.shadow-none{box-shadow:none !important}.collapse{display:none}.collapse.show{display:block}.fade{opacity:0;transition:opacity 0.15s linear}.fade.show{opacity:1}.clearfix::after{display:block;clear:both;content:""}.cursor-pointer{cursor:pointer !important}.btn-close{box-sizing:content-box;width:1em;height:1em;padding:0.25em;color:#000;background:transparent;border:0;opacity:0.5;cursor:pointer;font-size:1.25rem;line-height:1}.btn-close:hover{opacity:0.75}.btn-close:focus{opacity:1;outline:0;box-shadow:0 0 0 0.2rem rgba(74,144,217,0.25)}.img-fluid{max-width:100%;height:auto}.img-thumbnail{padding:0.25rem;background-color:#fff;border:1px solid rgba(0,0,0,0.1);border-radius:.25rem;max-width:100%;height:auto}.border-top{border-top:1px solid rgba(0,0,0,0.1) !important}.border-bottom{border-bottom:1px solid rgba(0,0,0,0.1) !important}.align-middle{vertical-align:middle !important}.align-top{vertical-align:top !important}.align-bottom{vertical-align:bottom !important}.float-start{float:left !important}.float-end{float:right !important}.float-none{float:none !important}.visually-hidden{position:absolute !important;width:1px !important;height:1px !important;padding:0 !important;margin:-1px !important;overflow:hidden !important;clip:rect(0, 0, 0, 0) !important;white-space:nowrap !important;border:0 !important}
@@ -0,0 +1,134 @@
1
+ /**
2
+ * Tina4 CSS — Lightweight JS components
3
+ * Replaces Bootstrap's JavaScript for: modals, alerts, navbar toggler
4
+ * ~3KB unminified, zero dependencies
5
+ */
6
+ (function () {
7
+ "use strict";
8
+
9
+ // ── Modals ──────────────────────────────────────────────────
10
+ // Usage: <button data-t4-toggle="modal" data-t4-target="#myModal">Open</button>
11
+ // <button data-t4-dismiss="modal">Close</button>
12
+ // Also supports Bootstrap syntax: data-bs-toggle, data-bs-target, data-bs-dismiss
13
+
14
+ function getModalEl(selector) {
15
+ if (!selector) return null;
16
+ return document.querySelector(selector);
17
+ }
18
+
19
+ function openModal(modal) {
20
+ if (!modal) return;
21
+ // Create backdrop if not exists
22
+ var backdrop = modal._t4Backdrop;
23
+ if (!backdrop) {
24
+ backdrop = document.createElement("div");
25
+ backdrop.className = "modal-backdrop";
26
+ document.body.appendChild(backdrop);
27
+ modal._t4Backdrop = backdrop;
28
+ backdrop.addEventListener("click", function () {
29
+ closeModal(modal);
30
+ });
31
+ }
32
+ modal.style.display = "block";
33
+ backdrop.style.display = "block";
34
+ // Force reflow then add .show for transition
35
+ void modal.offsetHeight;
36
+ modal.classList.add("show");
37
+ backdrop.classList.add("show");
38
+ document.body.style.overflow = "hidden";
39
+ // Focus trap — focus first focusable element
40
+ var focusable = modal.querySelector("input, select, textarea, button, [tabindex]");
41
+ if (focusable) focusable.focus();
42
+ }
43
+
44
+ function closeModal(modal) {
45
+ if (!modal) return;
46
+ modal.classList.remove("show");
47
+ var backdrop = modal._t4Backdrop;
48
+ if (backdrop) backdrop.classList.remove("show");
49
+ // Wait for transition
50
+ setTimeout(function () {
51
+ modal.style.display = "none";
52
+ if (backdrop) backdrop.style.display = "none";
53
+ document.body.style.overflow = "";
54
+ }, 150);
55
+ }
56
+
57
+ // Delegated click handler for modal triggers
58
+ document.addEventListener("click", function (e) {
59
+ var trigger = e.target.closest("[data-t4-toggle='modal'], [data-bs-toggle='modal']");
60
+ if (trigger) {
61
+ e.preventDefault();
62
+ var target = trigger.getAttribute("data-t4-target") || trigger.getAttribute("data-bs-target") || trigger.getAttribute("href");
63
+ var modal = getModalEl(target);
64
+ if (modal) openModal(modal);
65
+ return;
66
+ }
67
+
68
+ // Dismiss button
69
+ var dismiss = e.target.closest("[data-t4-dismiss='modal'], [data-bs-dismiss='modal'], .btn-close");
70
+ if (dismiss) {
71
+ var modal = dismiss.closest(".modal");
72
+ if (modal) closeModal(modal);
73
+ return;
74
+ }
75
+ });
76
+
77
+ // ESC key closes top modal
78
+ document.addEventListener("keydown", function (e) {
79
+ if (e.key === "Escape") {
80
+ var modals = document.querySelectorAll(".modal.show");
81
+ if (modals.length > 0) closeModal(modals[modals.length - 1]);
82
+ }
83
+ });
84
+
85
+ // ── Alerts (dismissible) ────────────────────────────────────
86
+ // Usage: <div class="alert alert-danger alert-dismissible">
87
+ // Message <button class="btn-close" data-t4-dismiss="alert">&times;</button>
88
+ // </div>
89
+
90
+ document.addEventListener("click", function (e) {
91
+ var dismiss = e.target.closest("[data-t4-dismiss='alert'], [data-bs-dismiss='alert']");
92
+ if (dismiss) {
93
+ var alert = dismiss.closest(".alert");
94
+ if (alert) {
95
+ alert.style.opacity = "0";
96
+ setTimeout(function () { alert.remove(); }, 150);
97
+ }
98
+ }
99
+ });
100
+
101
+ // ── Navbar toggler ──────────────────────────────────────────
102
+ // Usage: <button class="navbar-toggler" data-t4-toggle="collapse" data-t4-target="#navContent">
103
+ // &#9776;
104
+ // </button>
105
+ // <div class="navbar-collapse collapse" id="navContent">...</div>
106
+
107
+ document.addEventListener("click", function (e) {
108
+ var toggler = e.target.closest("[data-t4-toggle='collapse'], [data-bs-toggle='collapse']");
109
+ if (toggler) {
110
+ e.preventDefault();
111
+ var target = toggler.getAttribute("data-t4-target") || toggler.getAttribute("data-bs-target") || toggler.getAttribute("href");
112
+ var el = document.querySelector(target);
113
+ if (el) {
114
+ el.classList.toggle("show");
115
+ }
116
+ }
117
+ });
118
+
119
+ // ── Programmatic API ────────────────────────────────────────
120
+ // window.tina4.modal.open("#myModal")
121
+ // window.tina4.modal.close("#myModal")
122
+
123
+ window.tina4 = window.tina4 || {};
124
+ window.tina4.modal = {
125
+ open: function (selector) {
126
+ var modal = typeof selector === "string" ? document.querySelector(selector) : selector;
127
+ openModal(modal);
128
+ },
129
+ close: function (selector) {
130
+ var modal = typeof selector === "string" ? document.querySelector(selector) : selector;
131
+ closeModal(modal);
132
+ }
133
+ };
134
+ })();
@@ -0,0 +1,387 @@
1
+ var formToken = null;
2
+
3
+ /**
4
+ * Sends an http request
5
+ * @param url
6
+ * @param request
7
+ * @param method
8
+ * @param callback
9
+ */
10
+ function sendRequest(url, request, method, callback) {
11
+ // Default values
12
+ if (url === undefined) url = "";
13
+ if (request === undefined) request = null;
14
+ if (method === undefined) method = 'GET';
15
+
16
+ const xhr = new XMLHttpRequest();
17
+ xhr.open(method, url, true);
18
+
19
+ // Add authorization header if token exists
20
+ if (formToken !== null) {
21
+ xhr.setRequestHeader('Authorization', 'Bearer ' + formToken);
22
+ }
23
+
24
+ // ────────────────────────────────────────────────
25
+ // Content-Type logic – only set when appropriate
26
+ // ────────────────────────────────────────────────
27
+ let isFormData = request instanceof FormData;
28
+
29
+ if (method.toUpperCase() === 'POST' || method.toUpperCase() === 'PUT' || method.toUpperCase() === 'PATCH') {
30
+ if (isFormData) {
31
+ //DO not touch this
32
+ } else if (typeof request === 'object' && request !== null) {
33
+ //Becomes a JSON String
34
+ request = JSON.stringify(request);
35
+ xhr.setRequestHeader('Content-Type', 'application/json; charset=UTF-8');
36
+ } else if (typeof request === 'string') {
37
+ // Already a string – assume JSON or let server decide
38
+ // You can set charset=UTF-8 if you're sure it's JSON/text
39
+ xhr.setRequestHeader('Content-Type', 'text/plain; charset=UTF-8');
40
+ }
41
+ }
42
+
43
+ // ────────────────────────────────────────────────
44
+ // Response handling
45
+ // ────────────────────────────────────────────────
46
+ xhr.onload = function () {
47
+ let content = xhr.response;
48
+
49
+ // Update token if server sent a fresh one
50
+ const freshToken = xhr.getResponseHeader('FreshToken');
51
+ if (freshToken && freshToken !== '') {
52
+ formToken = freshToken;
53
+ }
54
+
55
+ try {
56
+ content = JSON.parse(content);
57
+ } catch (e) {
58
+ // Not JSON → keep as raw string/text
59
+ }
60
+
61
+ if (typeof callback === 'function') {
62
+ callback(content, xhr.status, xhr);
63
+ }
64
+ };
65
+
66
+ // Optional: handle errors
67
+ xhr.onerror = function () {
68
+ if (typeof callback === 'function') {
69
+ callback(null, xhr.status, xhr);
70
+ }
71
+ };
72
+
73
+ // Send the body (or null)
74
+ xhr.send(request);
75
+ }
76
+
77
+ /**
78
+ * Gets form data based on a form Id
79
+ * @param formId
80
+ * @returns {FormData}
81
+ */
82
+ function getFormData(formId) {
83
+ let data = new FormData();
84
+ let elements = document.querySelectorAll("#" + formId + " select, #" + formId + " input, #" + formId + " textarea");
85
+ for (let ie = 0; ie < elements.length; ie++ )
86
+ {
87
+ let element = elements[ie];
88
+ //refresh the token
89
+ if (element.name === 'formToken' && formToken !== null) {
90
+ element.value = formToken;
91
+ }
92
+ if (element.name) {
93
+ if (element.type === 'file') {
94
+ for (let i = 0; i < element.files.length; i++) {
95
+ let fileData = element.files[i];
96
+ let elementName = element.name;
97
+ if (fileData !== undefined) {
98
+ if (element.files.length > 1 && !elementName.includes('[')) {
99
+ elementName = elementName + '[]';
100
+ }
101
+ data.append(elementName, fileData, fileData.name);
102
+ }
103
+ }
104
+ } else if (element.type === 'checkbox' || element.type === 'radio') {
105
+ if (element.checked) {
106
+ data.append(element.name, element.value)
107
+ } else {
108
+ if (element.type !== 'radio') {
109
+ data.append(element.name, "0")
110
+ }
111
+ }
112
+ } else {
113
+ if (element.value === '') {
114
+ element.value = null;
115
+ }
116
+ data.append(element.name, element.value);
117
+ }
118
+ }
119
+ }
120
+ return data;
121
+ }
122
+
123
+ /**
124
+ * Handles the data returned from a request
125
+ * @param data
126
+ * @param targetElement
127
+ */
128
+ function handleHtmlData(data, targetElement) {
129
+ //Strip out the scripts
130
+ if (data === "") return '';
131
+ const parser = new DOMParser();
132
+ const htmlData = parser.parseFromString(data.includes !== undefined && data.includes('<html>') ? data : '<body>'+data+'</body></html>', 'text/html');
133
+ const body = htmlData.querySelector('body');
134
+ const scripts = body.querySelectorAll('script');
135
+ // remove the script tags
136
+ body.querySelectorAll('script').forEach(script => script.remove());
137
+
138
+ if (targetElement !== null) {
139
+ if (body.children.length > 0) {
140
+ document.getElementById(targetElement).replaceChildren(...body.children);
141
+ } else {
142
+ document.getElementById(targetElement).replaceChildren(body.innerHTML);
143
+ }
144
+ if (scripts) {
145
+ scripts.forEach(script => {
146
+ const newScript = document.createElement("script");
147
+ newScript.type = 'text/javascript';
148
+ newScript.async = true;
149
+ newScript.textContent = script.innerText;
150
+ document.getElementById(targetElement).append(newScript);
151
+ });
152
+ }
153
+ } else {
154
+ if (scripts) {
155
+ scripts.forEach(script => {
156
+ const newScript = document.createElement("script");
157
+ newScript.type = 'text/javascript';
158
+ newScript.async = true;
159
+ newScript.textContent = script.innerText;
160
+ document.body.append(newScript);
161
+ console.log(newScript);
162
+ });
163
+ }
164
+
165
+ return body.innerHTML;
166
+ }
167
+
168
+ return '';
169
+ }
170
+
171
+ /**
172
+ * Loads a page to a target html element
173
+ * @param loadURL
174
+ * @param targetElement
175
+ * @param callback
176
+ * @callback
177
+ */
178
+ function loadPage(loadURL, targetElement, callback = null) {
179
+ if (targetElement === undefined) targetElement = 'content';
180
+ sendRequest(loadURL, null, "GET", function(data) {
181
+ let processedHTML = '';
182
+ if (document.getElementById(targetElement) !== null) {
183
+ processedHTML = handleHtmlData(data, targetElement);
184
+ } else {
185
+ if (callback) {
186
+ callback(data);
187
+ } else {
188
+ console.log('TINA4 - define targetElement or callback for loadPage', data);
189
+ }
190
+ return;
191
+ }
192
+
193
+ if (callback) {
194
+ callback(processedHTML, data);
195
+ }
196
+ });
197
+ }
198
+
199
+ /**
200
+ * Shows a form from a URL in a target html element
201
+ * @param action
202
+ * @param loadURL
203
+ * @param targetElement
204
+ * @param callback
205
+ */
206
+ function showForm(action, loadURL, targetElement, callback = null) {
207
+ if (targetElement === undefined) targetElement = 'form';
208
+
209
+ if (action === 'create') action = 'GET';
210
+ if (action === 'edit') action = 'GET';
211
+ if (action === 'delete') action = 'DELETE';
212
+
213
+ sendRequest(loadURL, null, action, function(data) {
214
+ let processedHTML = '';
215
+ if (data.message !== undefined) {
216
+ processedHTML = handleHtmlData ((data.message), targetElement);
217
+ } else {
218
+ if (document.getElementById(targetElement) !== null) {
219
+ processedHTML = handleHtmlData (data, targetElement);
220
+ } else {
221
+ if (callback) {
222
+ callback(data);
223
+ } else {
224
+ console.log('TINA4 - define targetElement or callback for showForm', data);
225
+ }
226
+ return;
227
+ }
228
+ }
229
+
230
+ if (callback) {
231
+ callback(processedHTML);
232
+ }
233
+ });
234
+ }
235
+
236
+ /**
237
+ * Post URL posts data to a specific url
238
+ * @param url
239
+ * @param data
240
+ * @param targetElement
241
+ * @param callback
242
+ */
243
+ function postUrl(url, data, targetElement, callback= null) {
244
+ sendRequest(url, data, 'POST', function(data) {
245
+ let processedHTML = '';
246
+ if (data.message !== undefined) {
247
+ processedHTML = handleHtmlData ((data.message), targetElement);
248
+ } else {
249
+ if (document.getElementById(targetElement) !== null) {
250
+ processedHTML = handleHtmlData (data, targetElement);
251
+ } else {
252
+ if (callback) {
253
+ callback(data);
254
+ } else {
255
+ console.log('TINA4 - define targetElement or callback for postUrl', data);
256
+ }
257
+ return;
258
+ }
259
+ }
260
+
261
+ if (callback) {
262
+ callback(processedHTML,data)
263
+ }
264
+ });
265
+ }
266
+
267
+ /**
268
+ * Saves a form to a POST end point
269
+ * @param formId
270
+ * @param targetURL
271
+ * @param targetElement
272
+ * @param callback - optional
273
+ */
274
+ function saveForm(formId, targetURL, targetElement, callback = null) {
275
+ if (targetElement === undefined) targetElement = 'message';
276
+ //compile a data model
277
+ let data = getFormData(formId);
278
+
279
+ postUrl(targetURL, data, targetElement, callback);
280
+ }
281
+
282
+ /**
283
+ * Alias of saveForm
284
+ * @param formId
285
+ * @param targetURL
286
+ * @param targetElement
287
+ * @param callback
288
+ */
289
+ function postForm(formId, targetURL, targetElement, callback = null){
290
+ saveForm(formId, targetURL, targetElement, callback)
291
+ }
292
+
293
+ /**
294
+ * Alias of saveForm
295
+ * @param formId
296
+ * @param targetURL
297
+ * @param targetElement
298
+ * @param callback
299
+ */
300
+ function submitForm(formId, targetURL, targetElement, callback = null){
301
+ saveForm(formId, targetURL, targetElement, callback)
302
+ }
303
+
304
+ /**
305
+ * Shows a message
306
+ * @param message
307
+ */
308
+ function showMessage(message) {
309
+ document.getElementById('message').innerHTML = '<div class="alert alert-info alert-dismissible fade show"><strong>Info</strong> ' + message + '<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button></div>';
310
+ }
311
+
312
+ /**
313
+ * Set cookie
314
+ * @param name
315
+ * @param value
316
+ * @param days
317
+ */
318
+ function setCookie(name, value, days) {
319
+ let expires = "";
320
+ if (days) {
321
+ let date = new Date();
322
+ date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
323
+ expires = "; expires=" + date.toUTCString();
324
+ }
325
+ document.cookie = name + "=" + (value || "") + expires + "; path=/";
326
+ }
327
+
328
+ /**
329
+ * Get cookie
330
+ * @param name
331
+ * @returns {null|string}
332
+ */
333
+ function getCookie(name) {
334
+ let nameEQ = name + "=";
335
+ let ca = document.cookie.split(';');
336
+ for (let i = 0; i < ca.length; i++) {
337
+ var c = ca[i];
338
+ while (c.charAt(0) == ' ') c = c.substring(1, c.length);
339
+ if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length);
340
+ }
341
+ return null;
342
+ }
343
+
344
+ //https://stackoverflow.com/questions/4068373/center-a-popup-window-on-screen
345
+ const popupCenter = ({url, title, w, h}) => {
346
+ // Fixes dual-screen position Most browsers Firefox
347
+ const dualScreenLeft = window.screenLeft !== undefined ? window.screenLeft : window.screenX;
348
+ const dualScreenTop = window.screenTop !== undefined ? window.screenTop : window.screenY;
349
+
350
+ const width = window.innerWidth ? window.innerWidth : document.documentElement.clientWidth ? document.documentElement.clientWidth : screen.width;
351
+ const height = window.innerHeight ? window.innerHeight : document.documentElement.clientHeight ? document.documentElement.clientHeight : screen.height;
352
+
353
+ const systemZoom = width / window.screen.availWidth;
354
+ const left = (width - w) / 2 / systemZoom + dualScreenLeft
355
+ const top = (height - h) / 2 / systemZoom + dualScreenTop
356
+ const newWindow = window.open(url, title,
357
+ `
358
+ directories=no,toolbar=no,location=no,status=no,menubar=no,scrollbars=no,resizable=no,
359
+ width=${w / systemZoom},
360
+ height=${h / systemZoom},
361
+ top=${top},
362
+ left=${left}
363
+ `
364
+ )
365
+
366
+ if (window.focus) newWindow.focus();
367
+ return newWindow;
368
+ }
369
+
370
+ /**
371
+ * Opens a popup window
372
+ * @param pdfReportPath
373
+ */
374
+ function openReport(pdfReportPath){
375
+ if (pdfReportPath.indexOf("No data available") < 0){
376
+ open(pdfReportPath, "content", "target=_blank, toolbar=no, scrollbars=yes, resizable=yes, width=800, height=600, top=0, left=0");
377
+ }
378
+ else {
379
+ window.alert("Sorry , unable to print a report according to your selection!");
380
+ }
381
+ }
382
+
383
+ function getRoute(loadURL, callback) {
384
+ sendRequest(loadURL, null, 'GET', function(data) {
385
+ callback(handleHtmlData (data, null));
386
+ });
387
+ }